Skip to main content

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}