styx_parse/events.rs
1//! Event types for the Styx event-based parser.
2
3use std::borrow::Cow;
4
5use styx_tokenizer::Span;
6
7/// An event emitted by the parser, with its source location.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Event<'src> {
10 /// The source location associated with this event.
11 pub span: Span,
12 /// The kind of event.
13 pub kind: EventKind<'src>,
14}
15
16/// The kind of event emitted by the parser.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum EventKind<'src> {
19 // Document boundaries
20 /// Start of document.
21 DocumentStart,
22 /// End of document.
23 DocumentEnd,
24
25 // Objects
26 /// Start of an object `{ ... }`.
27 ObjectStart,
28 /// End of an object.
29 ObjectEnd,
30
31 // Sequences
32 /// Start of a sequence `( ... )`.
33 SequenceStart,
34 /// End of a sequence.
35 SequenceEnd,
36
37 // Entry structure (within objects)
38 /// Start of an entry (key-value pair).
39 EntryStart,
40 /// A key in an entry.
41 ///
42 /// Keys can be scalars or unit, optionally tagged.
43 /// Objects, sequences, and heredocs are not allowed as keys.
44 Key {
45 /// Tag name if this key is tagged (without @).
46 tag: Option<&'src str>,
47 /// Scalar payload after escape processing. None means unit.
48 payload: Option<Cow<'src, str>>,
49 /// Kind of scalar used for the key. Only meaningful if payload is Some.
50 kind: ScalarKind,
51 },
52 /// End of an entry.
53 EntryEnd,
54
55 // Values
56 /// A scalar value.
57 Scalar {
58 /// Value after escape processing.
59 value: Cow<'src, str>,
60 /// Kind of scalar.
61 kind: ScalarKind,
62 },
63 /// Unit value `@`.
64 Unit,
65
66 // Tags
67 /// Start of a tag `@name`.
68 TagStart {
69 /// Tag name (without @).
70 name: &'src str,
71 },
72 /// End of a tag.
73 TagEnd,
74
75 // Comments
76 /// Line comment `// ...`.
77 Comment {
78 /// Comment text (including //).
79 text: &'src str,
80 },
81 /// Doc comment `/// ...`.
82 DocComment {
83 /// Doc comment lines (without `/// ` prefix).
84 lines: Vec<&'src str>,
85 },
86
87 // Errors
88 /// Parse error.
89 Error {
90 /// Kind of error.
91 kind: ParseErrorKind,
92 },
93}
94
95/// Kind of scalar.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[cfg_attr(feature = "facet", derive(facet::Facet))]
98#[repr(u8)]
99pub enum ScalarKind {
100 /// Bare (unquoted) scalar.
101 Bare,
102 /// Quoted string `"..."`.
103 Quoted,
104 /// Raw string `r#"..."#`.
105 Raw,
106 /// Heredoc `<<DELIM...DELIM`.
107 Heredoc,
108}
109
110/// Parse error kinds.
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum ParseErrorKind {
113 /// Unexpected token.
114 UnexpectedToken,
115 /// Unclosed object (missing `}`).
116 UnclosedObject,
117 /// Unclosed sequence (missing `)`).
118 UnclosedSequence,
119 /// Invalid escape sequence in quoted string.
120 InvalidEscape(String),
121 /// Expected a key.
122 ExpectedKey,
123 /// Expected a value.
124 ExpectedValue,
125 /// Unexpected end of input.
126 UnexpectedEof,
127 /// Duplicate key in object. Contains the span of the first occurrence.
128 // parser[impl entry.key-equality]
129 DuplicateKey { original: Span },
130 /// Invalid tag name (must match pattern).
131 InvalidTagName,
132 /// Invalid key (e.g., heredoc used as key).
133 InvalidKey,
134 /// Dangling doc comment (not followed by entry).
135 DanglingDocComment,
136 /// Too many atoms in entry (expected at most 2: key and value).
137 // parser[impl entry.toomany]
138 TooManyAtoms,
139 /// Attempted to reopen a path that was closed when a sibling appeared.
140 // parser[impl entry.path.reopen]
141 ReopenedPath {
142 /// The closed path that was attempted to be reopened.
143 closed_path: Vec<String>,
144 },
145 /// Attempted to nest into a path that has a terminal value (scalar/sequence/tag/unit).
146 NestIntoTerminal {
147 /// The path that has a terminal value.
148 terminal_path: Vec<String>,
149 },
150 /// Comma in sequence (sequences are whitespace-separated, not comma-separated).
151 CommaInSequence,
152 /// Missing whitespace between bare scalar and `{` or `(`.
153 // parser[impl entry.whitespace]
154 MissingWhitespaceBeforeBlock,
155 /// Trailing content after explicit root object.
156 TrailingContent,
157}
158
159impl std::fmt::Display for ParseErrorKind {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 match self {
162 ParseErrorKind::UnexpectedToken => write!(f, "unexpected token"),
163 ParseErrorKind::UnclosedObject => write!(f, "unclosed object (missing `}}`)"),
164 ParseErrorKind::UnclosedSequence => write!(f, "unclosed sequence (missing `)`)"),
165 ParseErrorKind::InvalidEscape(seq) => write!(f, "invalid escape sequence: {}", seq),
166 ParseErrorKind::ExpectedKey => write!(f, "expected a key"),
167 ParseErrorKind::ExpectedValue => write!(f, "expected a value"),
168 ParseErrorKind::UnexpectedEof => write!(f, "unexpected end of input"),
169 ParseErrorKind::DuplicateKey { .. } => write!(f, "duplicate key"),
170 ParseErrorKind::InvalidTagName => write!(f, "invalid tag name"),
171 ParseErrorKind::InvalidKey => write!(f, "invalid key"),
172 ParseErrorKind::DanglingDocComment => {
173 write!(f, "doc comment not followed by an entry")
174 }
175 ParseErrorKind::TooManyAtoms => {
176 write!(f, "unexpected atom after value (entry has too many atoms)")
177 }
178 ParseErrorKind::ReopenedPath { closed_path } => {
179 write!(
180 f,
181 "cannot reopen path `{}` after sibling appeared",
182 closed_path.join(".")
183 )
184 }
185 ParseErrorKind::NestIntoTerminal { terminal_path } => {
186 write!(
187 f,
188 "cannot nest into `{}` which has a terminal value",
189 terminal_path.join(".")
190 )
191 }
192 ParseErrorKind::CommaInSequence => {
193 write!(
194 f,
195 "unexpected `,` in sequence (sequences are whitespace-separated, not comma-separated)"
196 )
197 }
198 ParseErrorKind::MissingWhitespaceBeforeBlock => {
199 write!(
200 f,
201 "missing whitespace before `{{` or `(` (required after bare scalar to distinguish from tag syntax like `@tag{{}}`)"
202 )
203 }
204 ParseErrorKind::TrailingContent => {
205 write!(f, "trailing content after explicit root object")
206 }
207 }
208 }
209}