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