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}