brink_syntax/parser/mod.rs
1mod choice;
2mod content;
3mod declaration;
4mod divert;
5mod expression;
6mod gather;
7mod inline;
8mod knot;
9mod logic;
10mod story;
11mod tag;
12
13use crate::SyntaxKind::{self, COLON, EOF, ERROR, IDENT, L_BRACE, NEWLINE, PIPE, R_BRACE};
14use crate::lexer;
15use rowan::GreenNode;
16
17/// Result of parsing an Ink source file.
18pub struct Parse {
19 green: GreenNode,
20 errors: Vec<ParseError>,
21}
22
23impl Parse {
24 /// The root green node of the lossless CST.
25 #[must_use]
26 pub fn green(&self) -> &GreenNode {
27 &self.green
28 }
29
30 /// The root syntax node (typed wrapper around the green tree).
31 #[must_use]
32 pub fn syntax(&self) -> crate::SyntaxNode {
33 crate::SyntaxNode::new_root(self.green.clone())
34 }
35
36 /// Parse errors encountered.
37 #[must_use]
38 pub fn errors(&self) -> &[ParseError] {
39 &self.errors
40 }
41}
42
43/// A parse error with a message.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct ParseError {
46 pub message: String,
47}
48
49/// Parse an Ink source string into a lossless CST.
50#[must_use]
51pub fn parse(source: &str) -> Parse {
52 let raw_tokens = lexer::lex(source);
53 let mut p = Parser::new(&raw_tokens);
54 story::source_file(&mut p);
55 let green = p.builder.finish();
56 Parse {
57 green,
58 errors: p.errors,
59 }
60}
61
62/// Parse with a shared [`rowan::NodeCache`] for green-node interning.
63///
64/// Re-parsing the same source through the same cache produces structurally
65/// identical subtrees that share the same `Arc` allocation, enabling O(1)
66/// pointer-equality checks via `GreenNode::eq`.
67pub fn parse_with_cache(source: &str, cache: &mut rowan::NodeCache) -> Parse {
68 let raw_tokens = lexer::lex(source);
69 let mut p = Parser::with_cache(&raw_tokens, cache);
70 story::source_file(&mut p);
71 let green = p.builder.finish();
72 Parse {
73 green,
74 errors: p.errors,
75 }
76}
77
78// ── Parser internals ────────────────────────────────────────────────
79
80/// Maximum nesting depth for recursive grammar rules (inline logic, expressions,
81/// parenthesized groups). Prevents stack overflow and superlinear parse time on
82/// pathological input. 256 matches Rust's default `recursion_limit`.
83const MAX_DEPTH: u32 = 256;
84
85/// The parser. Holds a token stream and a `GreenNodeBuilder`.
86pub(crate) struct Parser<'t, 'c> {
87 tokens: &'t [(SyntaxKind, &'t str)],
88 pos: usize,
89 depth: u32,
90 /// Pre-computed scan results for each `{` token. Indexed by raw token
91 /// position. For positions that are not `L_BRACE`, the value is meaningless.
92 /// For `L_BRACE` positions, stores `PIPE`, `COLON`, or `EOF` indicating
93 /// which delimiter appears first at depth-0 inside that brace pair.
94 brace_scan: Vec<SyntaxKind>,
95 /// Pre-computed non-trivia token indices. `non_trivia[k]` is the raw
96 /// token index of the k-th non-trivia token. Enables O(1) `nth(n)`.
97 non_trivia: Vec<usize>,
98 builder: rowan::GreenNodeBuilder<'c>,
99 errors: Vec<ParseError>,
100}
101
102impl<'t> Parser<'t, 'static> {
103 fn new(tokens: &'t [(SyntaxKind, &'t str)]) -> Self {
104 let brace_scan = Self::build_brace_scan(tokens);
105 let non_trivia = Self::build_non_trivia(tokens);
106 Self {
107 tokens,
108 pos: 0,
109 depth: 0,
110 brace_scan,
111 non_trivia,
112 builder: rowan::GreenNodeBuilder::new(),
113 errors: Vec::new(),
114 }
115 }
116}
117
118impl<'t, 'c> Parser<'t, 'c> {
119 fn with_cache(tokens: &'t [(SyntaxKind, &'t str)], cache: &'c mut rowan::NodeCache) -> Self {
120 let brace_scan = Self::build_brace_scan(tokens);
121 let non_trivia = Self::build_non_trivia(tokens);
122 Self {
123 tokens,
124 pos: 0,
125 depth: 0,
126 brace_scan,
127 non_trivia,
128 builder: rowan::GreenNodeBuilder::with_cache(cache),
129 errors: Vec::new(),
130 }
131 }
132
133 /// O(n) pre-pass: collect the raw indices of all non-trivia tokens.
134 /// Enables O(1) `nth(n)` lookup during parsing.
135 fn build_non_trivia(tokens: &[(SyntaxKind, &str)]) -> Vec<usize> {
136 tokens
137 .iter()
138 .enumerate()
139 .filter(|(_, (k, _))| !k.is_trivia())
140 .map(|(i, _)| i)
141 .collect()
142 }
143
144 /// O(n) pre-pass: for each `L_BRACE`, classify the brace pair as `COLON`
145 /// (conditional), `PIPE` (sequence), or `EOF` (bare expression).
146 ///
147 /// Classification rules (`||`-aware):
148 /// 1. If a **single** `|` (not part of `||`) appears at depth-0 →
149 /// sequence (`PIPE`), regardless of any COLON.
150 /// 2. Else if `COLON` appears at depth-0 → conditional (`COLON`).
151 /// 3. Else if `||` appears (no single `|`, no COLON) → sequence (`PIPE`),
152 /// since `||` without a conditional colon means two separators.
153 /// 4. Neither → bare expression (`EOF`).
154 ///
155 /// Examples:
156 /// - `{a|b:c}` — single `|` → sequence (rule 1)
157 /// - `{x || y: body}` — no single `|`, has COLON → conditional (rule 2)
158 /// - `{a||b}` — `||` only, no COLON → sequence (rule 3)
159 /// - `{x}` — neither → bare expression (rule 4)
160 fn build_brace_scan(tokens: &[(SyntaxKind, &str)]) -> Vec<SyntaxKind> {
161 // Stack entries track what we've seen at depth-0 inside each brace pair.
162 // `single_pipe_before_colon` is the key signal: a lone `|` that appears
163 // before any `:` means this brace pair is a sequence, not a conditional
164 // (the `|` is a separator, not part of a conditional body like `{x: a|b}`).
165 struct Entry {
166 brace_pos: usize,
167 has_colon: bool,
168 has_pipe: bool,
169 single_pipe_before_colon: bool,
170 }
171
172 fn classify(e: &Entry) -> SyntaxKind {
173 if e.single_pipe_before_colon {
174 PIPE // rule 1: single `|` before `:` → sequence
175 } else if e.has_colon {
176 COLON // rule 2: colon (with only `||` or no pipe before it) → conditional
177 } else if e.has_pipe {
178 PIPE // rule 3: `||` without colon → sequence separators
179 } else {
180 EOF // rule 4: bare expression
181 }
182 }
183
184 let n = tokens.len();
185 let mut result = vec![EOF; n];
186
187 // Precompute: for each token position, the next non-trivia token index.
188 let next_nt = {
189 let mut v = vec![n; n];
190 let mut last = n;
191 for i in (0..n).rev() {
192 v[i] = last;
193 if !tokens[i].0.is_trivia() {
194 last = i;
195 }
196 }
197 v
198 };
199
200 let mut stack: Vec<Entry> = Vec::new();
201 let mut prev_nt = EOF;
202
203 for (i, &(kind, _)) in tokens.iter().enumerate() {
204 if kind.is_trivia() {
205 continue;
206 }
207 match kind {
208 L_BRACE => {
209 stack.push(Entry {
210 brace_pos: i,
211 has_colon: false,
212 has_pipe: false,
213 single_pipe_before_colon: false,
214 });
215 prev_nt = L_BRACE;
216 }
217 R_BRACE => {
218 if let Some(entry) = stack.pop() {
219 result[entry.brace_pos] = classify(&entry);
220 }
221 prev_nt = R_BRACE;
222 }
223 COLON => {
224 if let Some(e) = stack.last_mut() {
225 e.has_colon = true;
226 }
227 prev_nt = COLON;
228 }
229 PIPE => {
230 if let Some(e) = stack.last_mut() {
231 e.has_pipe = true;
232 // Determine if this is a single `|` (not part of `||`).
233 let next_is_pipe = next_nt[i] < n && tokens[next_nt[i]].0 == PIPE;
234 let prev_is_pipe = prev_nt == PIPE;
235 let is_single = !next_is_pipe && !prev_is_pipe;
236 // Only matters if we haven't seen COLON yet.
237 if is_single && !e.has_colon {
238 e.single_pipe_before_colon = true;
239 }
240 }
241 prev_nt = PIPE;
242 }
243 NEWLINE => {
244 while let Some(entry) = stack.pop() {
245 result[entry.brace_pos] = classify(&entry);
246 }
247 prev_nt = NEWLINE;
248 }
249 _ => {
250 prev_nt = kind;
251 }
252 }
253 }
254
255 for entry in stack {
256 result[entry.brace_pos] = classify(&entry);
257 }
258
259 result
260 }
261
262 /// Returns `true` if the nesting depth limit has been reached.
263 fn at_depth_limit(&self) -> bool {
264 self.depth >= MAX_DEPTH
265 }
266
267 /// Look up the pre-computed scan result for a `{` token at the given raw
268 /// position. Returns `PIPE`, `COLON`, or `EOF`.
269 fn brace_scan_at(&self, raw_pos: usize) -> SyntaxKind {
270 self.brace_scan.get(raw_pos).copied().unwrap_or(EOF)
271 }
272
273 // ── Lookahead ───────────────────────────────────────────────
274
275 /// The kind of the current token (or `EOF` if past the end).
276 fn current(&self) -> SyntaxKind {
277 self.nth(0)
278 }
279
280 /// Lookahead by `n` tokens, skipping trivia (WHITESPACE, comments).
281 /// `nth(0)` returns the current non-trivia token.
282 ///
283 /// Uses the pre-computed `non_trivia` index for O(log n + 1) lookup
284 /// (binary search to find our position, then constant-time indexing).
285 fn nth(&self, n: usize) -> SyntaxKind {
286 // Find the first non-trivia index >= self.pos via binary search.
287 let start = self.non_trivia.partition_point(|&idx| idx < self.pos);
288 let target = start + n;
289 if target < self.non_trivia.len() {
290 self.tokens[self.non_trivia[target]].0
291 } else {
292 EOF
293 }
294 }
295
296 /// Lookahead by `n` tokens WITHOUT skipping trivia.
297 fn nth_raw(&self, n: usize) -> SyntaxKind {
298 self.tokens.get(self.pos + n).map_or(EOF, |&(k, _)| k)
299 }
300
301 /// Returns `true` if the current non-trivia token matches `kind`.
302 fn at(&self, kind: SyntaxKind) -> bool {
303 self.current() == kind
304 }
305
306 /// Returns `true` if we're at end-of-file.
307 fn at_eof(&self) -> bool {
308 self.pos >= self.tokens.len()
309 }
310
311 /// Current position in the token stream (for loop-progress checks).
312 fn pos(&self) -> usize {
313 self.pos
314 }
315
316 // ── Consumption ─────────────────────────────────────────────
317
318 /// Emit the current token to the builder and advance.
319 fn bump(&mut self) {
320 if self.pos < self.tokens.len() {
321 let (kind, text) = self.tokens[self.pos];
322 self.builder.token(rowan::SyntaxKind(kind as u16), text);
323 self.pos += 1;
324 }
325 }
326
327 /// Bump the current token, asserting its kind matches `kind`.
328 fn bump_assert(&mut self, kind: SyntaxKind) {
329 debug_assert_eq!(self.nth_raw(0), kind);
330 self.bump();
331 }
332
333 /// If the current non-trivia token matches `kind`, eat trivia then bump it.
334 /// Returns `true` if consumed.
335 fn eat(&mut self, kind: SyntaxKind) -> bool {
336 if self.current() == kind {
337 self.skip_ws();
338 self.bump();
339 true
340 } else {
341 false
342 }
343 }
344
345 /// Expect the current non-trivia token to be `kind`. If it is, eat trivia
346 /// and bump. Otherwise, emit an error.
347 fn expect(&mut self, kind: SyntaxKind) {
348 if !self.eat(kind) {
349 self.error(format!("expected {kind:?}"));
350 }
351 }
352
353 /// Returns `true` if the current non-trivia token is `IDENT` or a keyword.
354 ///
355 /// Ink keywords are contextual — they may appear as identifiers in some
356 /// positions (e.g. list member names like `or`, `and`, `not`).
357 fn at_ident_or_keyword(&self) -> bool {
358 self.current() == IDENT || self.current().is_keyword()
359 }
360
361 /// If the current non-trivia token is `IDENT` or a keyword, eat trivia
362 /// then bump it. Returns `true` if consumed.
363 fn eat_ident_or_keyword(&mut self) -> bool {
364 if self.at_ident_or_keyword() {
365 self.skip_ws();
366 self.bump();
367 true
368 } else {
369 false
370 }
371 }
372
373 /// Expect the current non-trivia token to be `IDENT` or a keyword.
374 /// If not, emit an error.
375 fn expect_ident_or_keyword(&mut self) {
376 if !self.eat_ident_or_keyword() {
377 self.error("expected IDENT".into());
378 }
379 }
380
381 /// Consume all trivia (`WHITESPACE`, `LINE_COMMENT`, `BLOCK_COMMENT`).
382 fn skip_ws(&mut self) {
383 while self.pos < self.tokens.len() && self.tokens[self.pos].0.is_trivia() {
384 self.bump();
385 }
386 }
387
388 // ── Nodes ───────────────────────────────────────────────────
389
390 /// Start a new CST node.
391 fn start_node(&mut self, kind: SyntaxKind) {
392 self.builder.start_node(rowan::SyntaxKind(kind as u16));
393 }
394
395 /// Start a new CST node at a previously saved checkpoint.
396 fn start_node_at(&mut self, checkpoint: rowan::Checkpoint, kind: SyntaxKind) {
397 self.builder
398 .start_node_at(checkpoint, rowan::SyntaxKind(kind as u16));
399 }
400
401 /// Finish the current CST node.
402 fn finish_node(&mut self) {
403 self.builder.finish_node();
404 }
405
406 /// Save the current position as a checkpoint for `start_node_at`.
407 fn checkpoint(&self) -> rowan::Checkpoint {
408 self.builder.checkpoint()
409 }
410
411 // ── Errors ──────────────────────────────────────────────────
412
413 /// Record a parse error at the current position.
414 fn error(&mut self, message: String) {
415 self.errors.push(ParseError { message });
416 }
417
418 /// Wrap the current token in an `ERROR` node and advance.
419 ///
420 /// Used by grammar rules that need to recover from unexpected tokens
421 /// without losing the rest of the input.
422 fn error_recover(&mut self, message: &str) {
423 self.error(message.to_owned());
424 self.start_node(ERROR);
425 self.bump();
426 self.finish_node();
427 }
428}
429
430#[cfg(test)]
431mod tests;