Skip to main content

lashlang/
parser.rs

1use crate::ast::{
2    AssignPathStep, AssignTarget, AstString, BinaryOp, Declaration, Expr, ExpressionSourceSpan,
3    LabelMetadata, ListComprehensionClause, ProcessDecl, ProcessParam, ProcessSignalDecl,
4    ProcessStartExpr, Program, TypeDecl, TypeExpr, TypeField, UnaryOp,
5};
6use crate::lexer::{LexError, Span, Token, TokenKind, lex};
7use thiserror::Error;
8
9#[derive(Debug, Error, PartialEq)]
10pub enum ParseError {
11    #[error(transparent)]
12    Lex(#[from] LexError),
13    #[error("expected {expected}, found {found}")]
14    Expected {
15        expected: &'static str,
16        found: String,
17        span: Span,
18    },
19    #[error("unexpected {found}")]
20    Unexpected { found: String, span: Span },
21    #[error("`{keyword}` can only be used inside a loop")]
22    LoopControlOutsideLoop { keyword: &'static str, span: Span },
23    #[error("`{keyword}` can only be used inside a `process` body")]
24    SessionProcessAdminOutsideBlock { keyword: &'static str, span: Span },
25    #[error("`{keyword}` can't be used inside a `process` body")]
26    ForegroundControlInsideProcess { keyword: &'static str, span: Span },
27    #[error(
28        "declarative trigger syntax has been removed; construct a source value and call the trigger registry register operation"
29    )]
30    DeclarativeTriggerRemoved { span: Span },
31    #[error("invalid @label annotation: {message}")]
32    InvalidLabelAnnotation { message: String, span: Span },
33    #[error(
34        "@label can annotate statements or process declarations, but not other declarations or another @label"
35    )]
36    InvalidLabelTarget { span: Span },
37    #[error("expression nesting too deep (limit {limit}); flatten the program")]
38    NestingTooDeep { limit: usize, span: Span },
39}
40
41impl ParseError {
42    pub fn span(&self) -> Option<Span> {
43        match self {
44            Self::Lex(_) => None,
45            Self::Expected { span, .. }
46            | Self::Unexpected { span, .. }
47            | Self::LoopControlOutsideLoop { span, .. }
48            | Self::SessionProcessAdminOutsideBlock { span, .. }
49            | Self::ForegroundControlInsideProcess { span, .. }
50            | Self::DeclarativeTriggerRemoved { span }
51            | Self::InvalidLabelAnnotation { span, .. }
52            | Self::InvalidLabelTarget { span }
53            | Self::NestingTooDeep { span, .. } => Some(*span),
54        }
55    }
56
57    pub fn offset(&self) -> usize {
58        match self {
59            Self::Lex(err) => err.offset(),
60            Self::Expected { span, .. }
61            | Self::Unexpected { span, .. }
62            | Self::LoopControlOutsideLoop { span, .. }
63            | Self::SessionProcessAdminOutsideBlock { span, .. }
64            | Self::ForegroundControlInsideProcess { span, .. }
65            | Self::DeclarativeTriggerRemoved { span }
66            | Self::InvalidLabelAnnotation { span, .. }
67            | Self::InvalidLabelTarget { span }
68            | Self::NestingTooDeep { span, .. } => span.start,
69        }
70    }
71}
72
73pub fn parse(source: &str) -> Result<Program, ParseError> {
74    let tokens = lex(source)?;
75    Parser {
76        tokens,
77        index: 0,
78        loop_depth: 0,
79        process_depth: 0,
80        nesting_depth: 0,
81    }
82    .parse_program()
83}
84
85/// Maximum syntactic nesting depth (nested expressions *and* nested blocks).
86/// Bounds recursive-descent stack growth so adversarial model-emitted source
87/// (deeply nested brackets or `if`/`for` bodies) returns a `ParseError` instead
88/// of overflowing the native stack and aborting the host.
89///
90/// The deepest chain is expression nesting: each level descends the full
91/// precedence ladder (`parse_expr` -> ternary -> or -> and -> compare -> add ->
92/// mul -> unary -> postfix -> primary -> grouping -> `parse_expr`), roughly a
93/// dozen native frames carrying the large parsed-expression/span bundle.
94/// Empirically ~40 levels parse comfortably on a 2 MiB thread stack while
95/// leaving headroom for debug-test frames, so the limit is kept under that
96/// cliff. Block nesting (`parse_block` ->
97/// `parse_statement_expr` -> `parse_if`/`parse_for`/`parse_while` -> `parse_block`) is a
98/// shallower per-level chain and shares the same budget, so any mix of the two
99/// stays bounded. Real generated programs nest only a handful deep, so this is
100/// ample headroom; capping here also bounds every downstream AST walker
101/// (validate, lower, compile, eval), since the tree can never be deeper than
102/// the parser allowed.
103const MAX_NESTING_DEPTH: usize = 40;
104
105struct Parser {
106    tokens: Vec<Token>,
107    index: usize,
108    loop_depth: usize,
109    process_depth: usize,
110    nesting_depth: usize,
111}
112
113#[derive(Clone)]
114struct ParsedExpr {
115    expr: Expr,
116    span: Span,
117    source_spans: Vec<ExpressionSourceSpan>,
118}
119
120impl ParsedExpr {
121    fn leaf(expr: Expr, span: Span) -> Self {
122        Self {
123            expr,
124            span,
125            source_spans: vec![ExpressionSourceSpan {
126                path: Vec::new(),
127                span,
128            }],
129        }
130    }
131
132    fn node(expr: Expr, span: Span, children: impl IntoIterator<Item = (u32, ParsedExpr)>) -> Self {
133        let mut source_spans = vec![ExpressionSourceSpan {
134            path: Vec::new(),
135            span,
136        }];
137        for (child_index, child) in children {
138            source_spans.extend(child.source_spans.into_iter().map(|mut source_span| {
139                source_span.path.insert(0, child_index);
140                source_span
141            }));
142        }
143        Self {
144            expr,
145            span,
146            source_spans,
147        }
148    }
149
150    fn into_expr(self) -> Expr {
151        self.expr
152    }
153}
154
155struct ParsedAssignTarget {
156    target: AssignTarget,
157    index_spans: Vec<ParsedExpr>,
158}
159
160struct ParsedListComprehensionClause {
161    clause: ListComprehensionClause,
162    expr_span: ParsedExpr,
163}
164
165fn push_root_expression(
166    expressions: &mut Vec<Expr>,
167    source_spans: &mut Vec<ExpressionSourceSpan>,
168    parsed: ParsedExpr,
169) {
170    let root = expressions.len() as u32;
171    let ParsedExpr {
172        expr,
173        source_spans: parsed_source_spans,
174        ..
175    } = parsed;
176    source_spans.extend(parsed_source_spans.into_iter().map(|mut source_span| {
177        source_span.path.insert(0, root);
178        source_span
179    }));
180    expressions.push(expr);
181}
182
183impl Parser {
184    fn parse_program(&mut self) -> Result<Program, ParseError> {
185        let capacity = (self.tokens.len() / 20).max(1);
186        let mut declarations = Vec::new();
187        let mut declaration_spans = Vec::new();
188        let mut expressions = Vec::with_capacity(capacity);
189        let mut expression_spans = Vec::with_capacity(capacity);
190        let mut expression_source_spans = Vec::new();
191        while !self.at_eof() {
192            if matches!(self.peek_kind(), TokenKind::At) {
193                let start = self.peek().span.start;
194                let label = self.parse_label_annotation()?;
195                if self.peek_contextual("process") && !self.peek_assignment_target() {
196                    declarations.push(Declaration::Process(self.parse_process_decl(Some(label))?));
197                    declaration_spans.push(self.span_from(start));
198                    continue;
199                }
200                if self.peek_contextual("type")
201                    || self.peek_contextual("trigger")
202                    || matches!(self.peek_kind(), TokenKind::At)
203                {
204                    return Err(ParseError::InvalidLabelTarget {
205                        span: self.peek().span,
206                    });
207                }
208                let inner = self.parse_statement_expr()?;
209                let end = self
210                    .tokens
211                    .get(self.index.saturating_sub(1))
212                    .map(|token| token.span.end)
213                    .unwrap_or(start);
214                let span = Span { start, end };
215                let expr = ParsedExpr::node(
216                    Expr::LabelAnnotated {
217                        label,
218                        expr: Box::new(inner.expr.clone()),
219                    },
220                    span,
221                    [(0, inner)],
222                );
223                expression_spans.push(span);
224                push_root_expression(&mut expressions, &mut expression_source_spans, expr);
225                continue;
226            }
227            if self.peek_contextual("type") && !self.peek_assignment_target() {
228                let start = self.peek().span.start;
229                declarations.push(Declaration::Type(self.parse_type_decl()?));
230                declaration_spans.push(self.span_from(start));
231                continue;
232            }
233            if self.peek_contextual("process") && !self.peek_assignment_target() {
234                let start = self.peek().span.start;
235                declarations.push(Declaration::Process(self.parse_process_decl(None)?));
236                declaration_spans.push(self.span_from(start));
237                continue;
238            }
239            if self.peek_contextual("trigger") && !self.peek_assignment_target() {
240                return Err(ParseError::DeclarativeTriggerRemoved {
241                    span: self.peek().span,
242                });
243            }
244            let start = self.peek().span.start;
245            let expr = self.parse_statement_expr()?;
246            let end = self
247                .tokens
248                .get(self.index.saturating_sub(1))
249                .map(|token| token.span.end)
250                .unwrap_or(start);
251            let span = Span { start, end };
252            expression_spans.push(span);
253            push_root_expression(&mut expressions, &mut expression_source_spans, expr);
254        }
255        Ok(Program::module_with_spans(
256            declarations,
257            declaration_spans,
258            expressions,
259            expression_spans,
260            expression_source_spans,
261        ))
262    }
263
264    fn span_from(&self, start: usize) -> Span {
265        let end = self
266            .tokens
267            .get(self.index.saturating_sub(1))
268            .map(|token| token.span.end)
269            .unwrap_or(start);
270        Span { start, end }
271    }
272
273    fn parse_type_decl(&mut self) -> Result<TypeDecl, ParseError> {
274        self.expect_contextual("type")?;
275        let name = self.expect_ident()?;
276        self.expect_exact(TokenKind::Equal, "`=`")?;
277        let ty = if matches!(self.peek_kind(), TokenKind::LBrace) {
278            self.parse_type_object_body()?
279        } else {
280            self.parse_type_expr()?
281        };
282        Ok(TypeDecl { name, ty })
283    }
284
285    fn parse_process_decl(
286        &mut self,
287        label: Option<LabelMetadata>,
288    ) -> Result<ProcessDecl, ParseError> {
289        self.expect_contextual("process")?;
290        let name = self.expect_ident()?;
291        self.expect_exact(TokenKind::LParen, "`(`")?;
292        let mut params = Vec::new();
293        while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
294            let param_name = self.expect_ident()?;
295            self.expect_exact(TokenKind::Colon, "`:`")?;
296            let ty = self.parse_type_expr()?;
297            params.push(ProcessParam {
298                name: param_name,
299                ty,
300            });
301            if matches!(self.peek_kind(), TokenKind::Comma) {
302                self.bump();
303                continue;
304            }
305            break;
306        }
307        self.expect_exact(TokenKind::RParen, "`)`")?;
308        let signals = if self.peek_contextual("signals") && !self.peek_assignment_target() {
309            self.parse_process_signal_decls()?
310        } else {
311            Vec::new()
312        };
313        let return_ty = if matches!(self.peek_kind(), TokenKind::Minus)
314            && self
315                .tokens
316                .get(self.index + 1)
317                .is_some_and(|token| matches!(token.kind, TokenKind::Greater))
318        {
319            self.bump();
320            self.bump();
321            Some(self.parse_type_expr()?)
322        } else {
323            None
324        };
325        self.process_depth += 1;
326        let body = self.parse_block()?;
327        self.process_depth -= 1;
328        Ok(ProcessDecl {
329            name,
330            params,
331            signals,
332            return_ty,
333            label,
334            body: body.into_expr(),
335        })
336    }
337
338    fn parse_process_signal_decls(&mut self) -> Result<Vec<ProcessSignalDecl>, ParseError> {
339        self.expect_contextual("signals")?;
340        self.expect_exact(TokenKind::LBrace, "`{`")?;
341        let mut signals = Vec::new();
342        while !matches!(self.peek_kind(), TokenKind::RBrace | TokenKind::Eof) {
343            let name = self.expect_ident()?;
344            self.expect_exact(TokenKind::Colon, "`:`")?;
345            let ty = self.parse_type_expr()?;
346            signals.push(ProcessSignalDecl { name, ty });
347            if matches!(self.peek_kind(), TokenKind::Comma) {
348                self.bump();
349                if matches!(self.peek_kind(), TokenKind::RBrace) {
350                    break;
351                }
352                continue;
353            }
354            break;
355        }
356        self.expect_exact(TokenKind::RBrace, "`}`")?;
357        Ok(signals)
358    }
359
360    fn parse_statement_expr(&mut self) -> Result<ParsedExpr, ParseError> {
361        match self.peek_kind() {
362            TokenKind::If => self.parse_if(),
363            TokenKind::For => self.parse_for(),
364            TokenKind::Submit => self.parse_submit(),
365            TokenKind::Cancel => self.parse_cancel(),
366            TokenKind::Print => self.parse_print(),
367            TokenKind::Call => Err(ParseError::Unexpected {
368                found: "`call`".to_string(),
369                span: self.peek().span,
370            }),
371            TokenKind::Ident(name) if name == "let" && !self.peek_assignment_target() => {
372                self.parse_let_assign()
373            }
374            TokenKind::Ident(name)
375                if matches!(name.as_str(), "yield" | "wake" | "finish" | "fail")
376                    && !self.peek_assignment_target() =>
377            {
378                self.parse_processes()
379            }
380            TokenKind::Ident(name) if name == "break" && !self.peek_assignment_target() => {
381                self.parse_loop_control("break")
382            }
383            TokenKind::Ident(name) if name == "continue" && !self.peek_assignment_target() => {
384                self.parse_loop_control("continue")
385            }
386            TokenKind::Ident(name) if name == "while" && !self.peek_assignment_target() => {
387                self.parse_while()
388            }
389            TokenKind::Ident(_) if self.peek_assignment_target() => self.parse_assign(),
390            _ => self.parse_expr(),
391        }
392    }
393
394    fn parse_let_assign(&mut self) -> Result<ParsedExpr, ParseError> {
395        self.bump();
396        self.parse_assign()
397    }
398
399    fn parse_assign(&mut self) -> Result<ParsedExpr, ParseError> {
400        let start = self.peek().span.start;
401        let target = self.parse_assignment_target()?;
402        self.expect_exact(TokenKind::Equal, "`=`")?;
403        let expr = self.parse_expr()?;
404        let value_child_index = target.index_spans.len() as u32;
405        let span = Span {
406            start,
407            end: expr.span.end,
408        };
409        let mut children = target
410            .index_spans
411            .into_iter()
412            .enumerate()
413            .map(|(index, expr)| (index as u32, expr))
414            .collect::<Vec<_>>();
415        children.push((value_child_index, expr));
416        let value_expr = children
417            .last()
418            .expect("assignment value child")
419            .1
420            .expr
421            .clone();
422        Ok(ParsedExpr::node(
423            Expr::Assign {
424                target: target.target,
425                expr: Box::new(value_expr),
426            },
427            span,
428            children,
429        ))
430    }
431
432    fn parse_assignment_target(&mut self) -> Result<ParsedAssignTarget, ParseError> {
433        let root = self.expect_ident()?;
434        let mut steps = Vec::new();
435        let mut index_spans = Vec::new();
436        loop {
437            match self.peek_kind() {
438                TokenKind::Dot => {
439                    self.bump();
440                    steps.push(AssignPathStep::Field(self.expect_key_name()?));
441                }
442                TokenKind::LBracket => {
443                    self.bump();
444                    let index = self.parse_expr()?;
445                    self.expect_exact(TokenKind::RBracket, "`]`")?;
446                    steps.push(AssignPathStep::Index(index.expr.clone()));
447                    index_spans.push(index);
448                }
449                _ => break,
450            }
451        }
452        Ok(ParsedAssignTarget {
453            target: AssignTarget { root, steps },
454            index_spans,
455        })
456    }
457
458    fn parse_if(&mut self) -> Result<ParsedExpr, ParseError> {
459        let start = self.bump().span.start;
460        let condition = self.parse_expr()?;
461        let then_block = self.parse_block()?;
462        let else_block = if matches!(self.peek_kind(), TokenKind::Else) {
463            self.bump();
464            if matches!(self.peek_kind(), TokenKind::If) {
465                self.parse_if()?
466            } else {
467                self.parse_block()?
468            }
469        } else {
470            ParsedExpr::leaf(
471                Expr::Block(Vec::new()),
472                Span {
473                    start: then_block.span.end,
474                    end: then_block.span.end,
475                },
476            )
477        };
478        let span = Span {
479            start,
480            end: else_block.span.end,
481        };
482        Ok(ParsedExpr::node(
483            Expr::If {
484                condition: Box::new(condition.expr.clone()),
485                then_block: Box::new(then_block.expr.clone()),
486                else_block: Box::new(else_block.expr.clone()),
487            },
488            span,
489            [(0, condition), (1, then_block), (2, else_block)],
490        ))
491    }
492
493    fn parse_for(&mut self) -> Result<ParsedExpr, ParseError> {
494        let start = self.bump().span.start;
495        let binding = self.expect_ident()?;
496        self.expect_exact(TokenKind::In, "`in`")?;
497        let iterable = self.parse_expr()?;
498        self.loop_depth += 1;
499        let body = self.parse_block()?;
500        self.loop_depth -= 1;
501        let span = Span {
502            start,
503            end: body.span.end,
504        };
505        Ok(ParsedExpr::node(
506            Expr::For {
507                binding,
508                iterable: Box::new(iterable.expr.clone()),
509                body: Box::new(body.expr.clone()),
510            },
511            span,
512            [(0, iterable), (1, body)],
513        ))
514    }
515
516    fn parse_while(&mut self) -> Result<ParsedExpr, ParseError> {
517        let start = self.bump().span.start;
518        let condition = self.parse_expr()?;
519        self.loop_depth += 1;
520        let body = self.parse_block()?;
521        self.loop_depth -= 1;
522        let span = Span {
523            start,
524            end: body.span.end,
525        };
526        Ok(ParsedExpr::node(
527            Expr::While {
528                condition: Box::new(condition.expr.clone()),
529                body: Box::new(body.expr.clone()),
530            },
531            span,
532            [(0, condition), (1, body)],
533        ))
534    }
535
536    fn parse_loop_control(&mut self, keyword: &'static str) -> Result<ParsedExpr, ParseError> {
537        let span = self.bump().span;
538        if self.loop_depth == 0 {
539            return Err(ParseError::LoopControlOutsideLoop { keyword, span });
540        }
541        let expr = match keyword {
542            "break" => Expr::Break,
543            "continue" => Expr::Continue,
544            _ => unreachable!("unknown loop control keyword"),
545        };
546        Ok(ParsedExpr::leaf(expr, span))
547    }
548
549    fn parse_submit(&mut self) -> Result<ParsedExpr, ParseError> {
550        let span = self.bump().span;
551        if self.process_depth > 0 {
552            return Err(ParseError::ForegroundControlInsideProcess {
553                keyword: "submit",
554                span,
555            });
556        }
557        let expr = if matches!(self.peek_kind(), TokenKind::RBrace | TokenKind::Eof) {
558            None
559        } else {
560            Some(self.parse_expr()?)
561        };
562        let end = expr.as_ref().map(|expr| expr.span.end).unwrap_or(span.end);
563        let span = Span {
564            start: span.start,
565            end,
566        };
567        let value_expr = expr.as_ref().map(|expr| Box::new(expr.expr.clone()));
568        Ok(match expr {
569            Some(expr) => ParsedExpr::node(Expr::Submit(value_expr), span, [(0, expr)]),
570            None => ParsedExpr::leaf(Expr::Submit(None), span),
571        })
572    }
573
574    fn parse_print(&mut self) -> Result<ParsedExpr, ParseError> {
575        let span = self.bump().span;
576        if self.process_depth > 0 {
577            return Err(ParseError::ForegroundControlInsideProcess {
578                keyword: "print",
579                span,
580            });
581        }
582        let expr = self.parse_expr()?;
583        let span = Span {
584            start: span.start,
585            end: expr.span.end,
586        };
587        Ok(ParsedExpr::node(
588            Expr::Print(Box::new(expr.expr.clone())),
589            span,
590            [(0, expr)],
591        ))
592    }
593
594    fn parse_processes(&mut self) -> Result<ParsedExpr, ParseError> {
595        let token = self.bump().clone();
596        let TokenKind::Ident(keyword) = token.kind else {
597            unreachable!("process admins are contextual identifiers");
598        };
599        let keyword_static = match keyword.as_str() {
600            "yield" => "yield",
601            "wake" => "wake",
602            "finish" => "finish",
603            "fail" => "fail",
604            _ => unreachable!("unknown process admin keyword"),
605        };
606        if self.process_depth == 0 {
607            return Err(ParseError::SessionProcessAdminOutsideBlock {
608                keyword: keyword_static,
609                span: token.span,
610            });
611        }
612        match keyword_static {
613            "yield" => {
614                let expr = self.parse_expr()?;
615                let span = Span {
616                    start: token.span.start,
617                    end: expr.span.end,
618                };
619                Ok(ParsedExpr::node(
620                    Expr::Yield(Box::new(expr.expr.clone())),
621                    span,
622                    [(0, expr)],
623                ))
624            }
625            "wake" => {
626                let expr = self.parse_expr()?;
627                let span = Span {
628                    start: token.span.start,
629                    end: expr.span.end,
630                };
631                Ok(ParsedExpr::node(
632                    Expr::Wake(Box::new(expr.expr.clone())),
633                    span,
634                    [(0, expr)],
635                ))
636            }
637            "finish" => {
638                let expr = if matches!(self.peek_kind(), TokenKind::RBrace | TokenKind::Eof) {
639                    None
640                } else {
641                    Some(self.parse_expr()?)
642                };
643                let end = expr
644                    .as_ref()
645                    .map(|expr| expr.span.end)
646                    .unwrap_or(token.span.end);
647                let span = Span {
648                    start: token.span.start,
649                    end,
650                };
651                let value_expr = expr.as_ref().map(|expr| Box::new(expr.expr.clone()));
652                Ok(match expr {
653                    Some(expr) => ParsedExpr::node(Expr::Finish(value_expr), span, [(0, expr)]),
654                    None => ParsedExpr::leaf(Expr::Finish(None), span),
655                })
656            }
657            "fail" => {
658                let expr = self.parse_expr()?;
659                let span = Span {
660                    start: token.span.start,
661                    end: expr.span.end,
662                };
663                Ok(ParsedExpr::node(
664                    Expr::Fail(Box::new(expr.expr.clone())),
665                    span,
666                    [(0, expr)],
667                ))
668            }
669            _ => unreachable!("unknown process admin keyword"),
670        }
671    }
672
673    fn parse_cancel(&mut self) -> Result<ParsedExpr, ParseError> {
674        let start = self.bump().span.start;
675        let expr = self.parse_expr()?;
676        let span = Span {
677            start,
678            end: expr.span.end,
679        };
680        Ok(ParsedExpr::node(
681            Expr::Cancel(Box::new(expr.expr.clone())),
682            span,
683            [(0, expr)],
684        ))
685    }
686
687    /// Account for entering one more level of syntactic nesting, rejecting
688    /// input that would recurse deep enough to overflow the native stack.
689    /// Pair every successful call with [`Parser::leave_nesting`].
690    fn enter_nesting(&mut self) -> Result<(), ParseError> {
691        if self.nesting_depth >= MAX_NESTING_DEPTH {
692            return Err(ParseError::NestingTooDeep {
693                limit: MAX_NESTING_DEPTH,
694                span: self.peek().span,
695            });
696        }
697        self.nesting_depth += 1;
698        Ok(())
699    }
700
701    fn leave_nesting(&mut self) {
702        self.nesting_depth -= 1;
703    }
704
705    fn parse_block(&mut self) -> Result<ParsedExpr, ParseError> {
706        // Nested blocks (`if`/`for` bodies, bare braces) recurse through here
707        // without passing through `parse_expr`, so they need their own guard.
708        self.enter_nesting()?;
709        let result = self.parse_block_inner();
710        self.leave_nesting();
711        result
712    }
713
714    fn parse_block_inner(&mut self) -> Result<ParsedExpr, ParseError> {
715        let start = self.peek().span.start;
716        self.expect_exact(TokenKind::LBrace, "`{`")?;
717        let mut expressions = Vec::new();
718        let mut children = Vec::new();
719        while !matches!(self.peek_kind(), TokenKind::RBrace | TokenKind::Eof) {
720            let expr = if matches!(self.peek_kind(), TokenKind::At) {
721                self.parse_annotated_statement()?
722            } else {
723                self.parse_statement_expr()?
724            };
725            let child_index = expressions.len() as u32;
726            expressions.push(expr.expr.clone());
727            children.push((child_index, expr));
728        }
729        self.expect_exact(TokenKind::RBrace, "`}`")?;
730        Ok(ParsedExpr::node(
731            Expr::Block(expressions),
732            self.span_from(start),
733            children,
734        ))
735    }
736
737    fn parse_annotated_statement(&mut self) -> Result<ParsedExpr, ParseError> {
738        let start = self.peek().span.start;
739        let label = self.parse_label_annotation()?;
740        if matches!(self.peek_kind(), TokenKind::At)
741            || self.peek_contextual("type")
742            || self.peek_contextual("process")
743            || self.peek_contextual("trigger")
744        {
745            return Err(ParseError::InvalidLabelTarget {
746                span: self.peek().span,
747            });
748        }
749        let expr = self.parse_statement_expr()?;
750        let span = Span {
751            start,
752            end: expr.span.end,
753        };
754        Ok(ParsedExpr::node(
755            Expr::LabelAnnotated {
756                label,
757                expr: Box::new(expr.expr.clone()),
758            },
759            span,
760            [(0, expr)],
761        ))
762    }
763
764    fn parse_label_annotation(&mut self) -> Result<LabelMetadata, ParseError> {
765        let span = self.peek().span;
766        self.expect_exact(TokenKind::At, "`@`")?;
767        self.expect_contextual("label")?;
768        self.expect_exact(TokenKind::LParen, "`(`")?;
769
770        let mut title = None;
771        let mut description = None;
772        while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
773            let key_span = self.peek().span;
774            let key = self.expect_ident()?;
775            self.expect_exact(TokenKind::Colon, "`:`")?;
776            let value = self.expect_string_literal()?;
777            match key.as_str() {
778                "title" => {
779                    if title.replace(value).is_some() {
780                        return Err(ParseError::InvalidLabelAnnotation {
781                            message: "duplicate `title` field".to_string(),
782                            span: key_span,
783                        });
784                    }
785                }
786                "description" => {
787                    if description.replace(value).is_some() {
788                        return Err(ParseError::InvalidLabelAnnotation {
789                            message: "duplicate `description` field".to_string(),
790                            span: key_span,
791                        });
792                    }
793                }
794                _ => {
795                    return Err(ParseError::InvalidLabelAnnotation {
796                        message: format!("unknown field `{key}`"),
797                        span: key_span,
798                    });
799                }
800            }
801            if matches!(self.peek_kind(), TokenKind::Comma) {
802                self.bump();
803                if matches!(self.peek_kind(), TokenKind::RParen) {
804                    break;
805                }
806                continue;
807            }
808            break;
809        }
810        self.expect_exact(TokenKind::RParen, "`)`")?;
811        let Some(title) = title else {
812            return Err(ParseError::InvalidLabelAnnotation {
813                message: "`title` is required".to_string(),
814                span,
815            });
816        };
817        Ok(LabelMetadata { title, description })
818    }
819
820    fn parse_expr(&mut self) -> Result<ParsedExpr, ParseError> {
821        // Every nested expression (parenthesised, list/record element, index,
822        // ternary, ...) funnels through here, so one depth guard at this
823        // chokepoint bounds total recursive-descent stack growth.
824        self.enter_nesting()?;
825        let result = self.parse_tuple_expr();
826        self.leave_nesting();
827        result
828    }
829
830    fn parse_expr_no_tuple(&mut self) -> Result<ParsedExpr, ParseError> {
831        self.enter_nesting()?;
832        let result = self.parse_ternary();
833        self.leave_nesting();
834        result
835    }
836
837    fn parse_tuple_expr(&mut self) -> Result<ParsedExpr, ParseError> {
838        let first = self.parse_ternary()?;
839        if !matches!(self.peek_kind(), TokenKind::Comma) {
840            return Ok(first);
841        }
842
843        let mut items = Vec::new();
844        let mut children = Vec::new();
845        children.push((0, first));
846        items.push(children.last().expect("tuple item").1.expr.clone());
847        let mut end = children.last().expect("tuple item").1.span.end;
848
849        while matches!(self.peek_kind(), TokenKind::Comma) {
850            end = self.bump().span.end;
851            if matches!(
852                self.peek_kind(),
853                TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace | TokenKind::Eof
854            ) {
855                break;
856            }
857            let item = self.parse_ternary()?;
858            end = item.span.end;
859            children.push((items.len() as u32, item));
860            items.push(children.last().expect("tuple item").1.expr.clone());
861        }
862
863        let span = Span {
864            start: children.first().expect("tuple first").1.span.start,
865            end,
866        };
867        Ok(ParsedExpr::node(Expr::Tuple(items), span, children))
868    }
869
870    fn parse_ternary(&mut self) -> Result<ParsedExpr, ParseError> {
871        let condition = self.parse_or()?;
872        if !matches!(self.peek_kind(), TokenKind::Question) {
873            return Ok(condition);
874        }
875        self.bump();
876        let then_expr = self.parse_expr_no_tuple()?;
877        self.expect_exact(TokenKind::Colon, "`:`")?;
878        let else_expr = self.parse_expr_no_tuple()?;
879        let span = Span {
880            start: condition.span.start,
881            end: else_expr.span.end,
882        };
883        Ok(ParsedExpr::node(
884            Expr::If {
885                condition: Box::new(condition.expr.clone()),
886                then_block: Box::new(then_expr.expr.clone()),
887                else_block: Box::new(else_expr.expr.clone()),
888            },
889            span,
890            [(0, condition), (1, then_expr), (2, else_expr)],
891        ))
892    }
893
894    fn parse_or(&mut self) -> Result<ParsedExpr, ParseError> {
895        let mut expr = self.parse_and()?;
896        while matches!(self.peek_kind(), TokenKind::Or | TokenKind::OrOr) {
897            self.bump();
898            let right = self.parse_and()?;
899            let span = Span {
900                start: expr.span.start,
901                end: right.span.end,
902            };
903            expr = ParsedExpr::node(
904                Expr::Binary {
905                    left: Box::new(expr.expr.clone()),
906                    op: BinaryOp::Or,
907                    right: Box::new(right.expr.clone()),
908                },
909                span,
910                [(0, expr), (1, right)],
911            );
912        }
913        Ok(expr)
914    }
915
916    fn parse_and(&mut self) -> Result<ParsedExpr, ParseError> {
917        let mut expr = self.parse_compare()?;
918        while matches!(self.peek_kind(), TokenKind::And | TokenKind::AndAnd) {
919            self.bump();
920            let right = self.parse_compare()?;
921            let span = Span {
922                start: expr.span.start,
923                end: right.span.end,
924            };
925            expr = ParsedExpr::node(
926                Expr::Binary {
927                    left: Box::new(expr.expr.clone()),
928                    op: BinaryOp::And,
929                    right: Box::new(right.expr.clone()),
930                },
931                span,
932                [(0, expr), (1, right)],
933            );
934        }
935        Ok(expr)
936    }
937
938    fn parse_compare(&mut self) -> Result<ParsedExpr, ParseError> {
939        let mut expr = self.parse_add()?;
940        loop {
941            let op = match self.peek_kind() {
942                TokenKind::DoubleEqual => BinaryOp::Equal,
943                TokenKind::BangEqual => BinaryOp::NotEqual,
944                TokenKind::Less => BinaryOp::Less,
945                TokenKind::LessEqual => BinaryOp::LessEqual,
946                TokenKind::Greater => BinaryOp::Greater,
947                TokenKind::GreaterEqual => BinaryOp::GreaterEqual,
948                _ => break,
949            };
950            self.bump();
951            let right = self.parse_add()?;
952            let span = Span {
953                start: expr.span.start,
954                end: right.span.end,
955            };
956            expr = ParsedExpr::node(
957                Expr::Binary {
958                    left: Box::new(expr.expr.clone()),
959                    op,
960                    right: Box::new(right.expr.clone()),
961                },
962                span,
963                [(0, expr), (1, right)],
964            );
965        }
966        Ok(expr)
967    }
968
969    fn parse_add(&mut self) -> Result<ParsedExpr, ParseError> {
970        let mut expr = self.parse_mul()?;
971        loop {
972            let op = match self.peek_kind() {
973                TokenKind::Plus => BinaryOp::Add,
974                TokenKind::Minus => BinaryOp::Subtract,
975                _ => break,
976            };
977            self.bump();
978            let right = self.parse_mul()?;
979            let span = Span {
980                start: expr.span.start,
981                end: right.span.end,
982            };
983            expr = ParsedExpr::node(
984                Expr::Binary {
985                    left: Box::new(expr.expr.clone()),
986                    op,
987                    right: Box::new(right.expr.clone()),
988                },
989                span,
990                [(0, expr), (1, right)],
991            );
992        }
993        Ok(expr)
994    }
995
996    fn parse_mul(&mut self) -> Result<ParsedExpr, ParseError> {
997        let mut expr = self.parse_unary()?;
998        loop {
999            let op = match self.peek_kind() {
1000                TokenKind::Star => BinaryOp::Multiply,
1001                TokenKind::Slash => BinaryOp::Divide,
1002                TokenKind::Percent => BinaryOp::Modulo,
1003                _ => break,
1004            };
1005            self.bump();
1006            let right = self.parse_unary()?;
1007            let span = Span {
1008                start: expr.span.start,
1009                end: right.span.end,
1010            };
1011            expr = ParsedExpr::node(
1012                Expr::Binary {
1013                    left: Box::new(expr.expr.clone()),
1014                    op,
1015                    right: Box::new(right.expr.clone()),
1016                },
1017                span,
1018                [(0, expr), (1, right)],
1019            );
1020        }
1021        Ok(expr)
1022    }
1023
1024    fn parse_unary(&mut self) -> Result<ParsedExpr, ParseError> {
1025        match self.peek_kind() {
1026            TokenKind::Minus => {
1027                let start = self.bump().span.start;
1028                let expr = self.parse_unary()?;
1029                Ok(ParsedExpr::node(
1030                    Expr::Unary {
1031                        op: UnaryOp::Negate,
1032                        expr: Box::new(expr.expr.clone()),
1033                    },
1034                    Span {
1035                        start,
1036                        end: expr.span.end,
1037                    },
1038                    [(0, expr)],
1039                ))
1040            }
1041            TokenKind::Not => {
1042                let start = self.bump().span.start;
1043                let expr = self.parse_unary()?;
1044                Ok(ParsedExpr::node(
1045                    Expr::Unary {
1046                        op: UnaryOp::Not,
1047                        expr: Box::new(expr.expr.clone()),
1048                    },
1049                    Span {
1050                        start,
1051                        end: expr.span.end,
1052                    },
1053                    [(0, expr)],
1054                ))
1055            }
1056            TokenKind::Bang => {
1057                let start = self.bump().span.start;
1058                let expr = self.parse_unary()?;
1059                Ok(ParsedExpr::node(
1060                    Expr::Unary {
1061                        op: UnaryOp::Not,
1062                        expr: Box::new(expr.expr.clone()),
1063                    },
1064                    Span {
1065                        start,
1066                        end: expr.span.end,
1067                    },
1068                    [(0, expr)],
1069                ))
1070            }
1071            TokenKind::Await => {
1072                let start = self.bump().span.start;
1073                let expr = self.parse_unary()?;
1074                Ok(ParsedExpr::node(
1075                    Expr::Await(Box::new(expr.expr.clone())),
1076                    Span {
1077                        start,
1078                        end: expr.span.end,
1079                    },
1080                    [(0, expr)],
1081                ))
1082            }
1083            _ => self.parse_postfix(),
1084        }
1085    }
1086
1087    fn parse_postfix(&mut self) -> Result<ParsedExpr, ParseError> {
1088        let mut expr = self.parse_primary()?;
1089        loop {
1090            match self.peek_kind() {
1091                TokenKind::Dot => {
1092                    self.bump();
1093                    let field = self.expect_key_name()?;
1094                    if matches!(self.peek_kind(), TokenKind::LParen) {
1095                        let args = self.parse_call_arguments()?;
1096                        let span = self.span_from(expr.span.start);
1097                        let mut children = Vec::with_capacity(args.len() + 1);
1098                        children.push((0, expr));
1099                        let arg_exprs = args.iter().map(|arg| arg.expr.clone()).collect();
1100                        children.extend(
1101                            args.into_iter()
1102                                .enumerate()
1103                                .map(|(index, arg)| ((index + 1) as u32, arg)),
1104                        );
1105                        let receiver = children[0].1.expr.clone();
1106                        expr = ParsedExpr::node(
1107                            Expr::ReceiverCall {
1108                                receiver: Box::new(receiver),
1109                                operation: field,
1110                                args: arg_exprs,
1111                            },
1112                            span,
1113                            children,
1114                        );
1115                    } else {
1116                        let span = self.span_from(expr.span.start);
1117                        expr = ParsedExpr::node(
1118                            Expr::Field {
1119                                target: Box::new(expr.expr.clone()),
1120                                field,
1121                            },
1122                            span,
1123                            [(0, expr)],
1124                        );
1125                    }
1126                }
1127                TokenKind::LBracket => {
1128                    self.bump();
1129                    let index = self.parse_expr()?;
1130                    self.expect_exact(TokenKind::RBracket, "`]`")?;
1131                    let span = self.span_from(expr.span.start);
1132                    expr = ParsedExpr::node(
1133                        Expr::Index {
1134                            target: Box::new(expr.expr.clone()),
1135                            index: Box::new(index.expr.clone()),
1136                        },
1137                        span,
1138                        [(0, expr), (1, index)],
1139                    );
1140                }
1141                TokenKind::Question if !self.question_starts_ternary() => {
1142                    self.bump();
1143                    let span = self.span_from(expr.span.start);
1144                    expr = ParsedExpr::node(
1145                        Expr::ResultUnwrap(Box::new(expr.expr.clone())),
1146                        span,
1147                        [(0, expr)],
1148                    );
1149                }
1150                _ => break,
1151            }
1152        }
1153        Ok(expr)
1154    }
1155
1156    fn parse_primary(&mut self) -> Result<ParsedExpr, ParseError> {
1157        match self.peek_kind() {
1158            TokenKind::Null => {
1159                let span = self.bump().span;
1160                Ok(ParsedExpr::leaf(Expr::Null, span))
1161            }
1162            TokenKind::True => {
1163                let span = self.bump().span;
1164                Ok(ParsedExpr::leaf(Expr::Bool(true), span))
1165            }
1166            TokenKind::False => {
1167                let span = self.bump().span;
1168                Ok(ParsedExpr::leaf(Expr::Bool(false), span))
1169            }
1170            TokenKind::Number(value) => {
1171                let value = *value;
1172                let span = self.bump().span;
1173                Ok(ParsedExpr::leaf(Expr::Number(value), span))
1174            }
1175            TokenKind::String(value) => {
1176                let value = value.clone();
1177                let span = self.bump().span;
1178                Ok(ParsedExpr::leaf(Expr::String(value), span))
1179            }
1180            TokenKind::Ident(name) => {
1181                let token_span = self.peek().span;
1182                if name == "parallel"
1183                    && self
1184                        .tokens
1185                        .get(self.index + 1)
1186                        .is_some_and(|token| matches!(token.kind, TokenKind::LBrace))
1187                {
1188                    return Err(ParseError::Unexpected {
1189                        found: "`parallel`".to_string(),
1190                        span: self.peek().span,
1191                    });
1192                }
1193                let name = name.clone();
1194                self.bump();
1195                if name == "sleep" {
1196                    return self.parse_sleep_expr(token_span.start);
1197                }
1198                if name == "start" && matches!(self.peek_kind(), TokenKind::Call) {
1199                    return Err(ParseError::Unexpected {
1200                        found: "`start call`".to_string(),
1201                        span: self.tokens[self.index.saturating_sub(1)].span,
1202                    });
1203                }
1204                if name == "start"
1205                    && (matches!(self.peek_kind(), TokenKind::Ident(_))
1206                        || matches!(self.peek_kind(), TokenKind::LBrace)
1207                        || self.paren_group_followed_by_lbrace())
1208                {
1209                    return self.parse_process_start_expr(token_span.start);
1210                }
1211                if name == "Type" && matches!(self.peek_kind(), TokenKind::LBrace) {
1212                    let ty = self.parse_type_object()?;
1213                    return Ok(ParsedExpr::leaf(
1214                        Expr::TypeLiteral(Box::new(ty)),
1215                        self.span_from(token_span.start),
1216                    ));
1217                }
1218                if matches!(self.peek_kind(), TokenKind::LParen) {
1219                    let args = self.parse_call_arguments()?;
1220                    if name == "wait_signal" {
1221                        if args.len() != 1 {
1222                            return Err(ParseError::Expected {
1223                                expected: "one signal name argument",
1224                                found: format!("{} arguments", args.len()),
1225                                span: self.tokens[self.index.saturating_sub(1)].span,
1226                            });
1227                        }
1228                        return Ok(ParsedExpr::leaf(
1229                            Expr::WaitSignal {
1230                                name: static_signal_name_arg(&args[0].expr, "wait_signal")?,
1231                            },
1232                            self.span_from(token_span.start),
1233                        ));
1234                    }
1235                    if name == "signal_run" {
1236                        if args.len() != 3 {
1237                            return Err(ParseError::Expected {
1238                                expected: "run handle, signal name, and payload arguments",
1239                                found: format!("{} arguments", args.len()),
1240                                span: self.tokens[self.index.saturating_sub(1)].span,
1241                            });
1242                        }
1243                        let run = args[0].expr.clone();
1244                        let payload = args[2].expr.clone();
1245                        return Ok(ParsedExpr::node(
1246                            Expr::SignalRun {
1247                                run: Box::new(run),
1248                                name: static_signal_name_arg(&args[1].expr, "signal_run")?,
1249                                payload: Box::new(payload),
1250                            },
1251                            self.span_from(token_span.start),
1252                            [(0, args[0].clone()), (1, args[2].clone())],
1253                        ));
1254                    }
1255                    let arg_exprs = args.iter().map(|arg| arg.expr.clone()).collect();
1256                    Ok(ParsedExpr::node(
1257                        Expr::BuiltinCall {
1258                            name,
1259                            args: arg_exprs,
1260                        },
1261                        self.span_from(token_span.start),
1262                        args.into_iter()
1263                            .enumerate()
1264                            .map(|(index, arg)| (index as u32, arg)),
1265                    ))
1266                } else {
1267                    Ok(ParsedExpr::leaf(Expr::Variable(name), token_span))
1268                }
1269            }
1270            TokenKind::LParen => {
1271                let start = self.bump().span.start;
1272                if matches!(self.peek_kind(), TokenKind::RParen) {
1273                    self.bump();
1274                    return Ok(ParsedExpr::node(
1275                        Expr::Tuple(Vec::new()),
1276                        self.span_from(start),
1277                        [],
1278                    ));
1279                }
1280                let expr = self.parse_expr()?;
1281                self.expect_exact(TokenKind::RParen, "`)`")?;
1282                Ok(expr)
1283            }
1284            TokenKind::LBracket => self.parse_list(),
1285            TokenKind::LBrace => self.parse_record(),
1286            TokenKind::Call => Err(ParseError::Unexpected {
1287                found: "`call`".to_string(),
1288                span: self.peek().span,
1289            }),
1290            _ => Err(self.unexpected()),
1291        }
1292    }
1293
1294    fn parse_list(&mut self) -> Result<ParsedExpr, ParseError> {
1295        let start = self.peek().span.start;
1296        self.expect_exact(TokenKind::LBracket, "`[`")?;
1297        if matches!(self.peek_kind(), TokenKind::RBracket) {
1298            self.expect_exact(TokenKind::RBracket, "`]`")?;
1299            return Ok(ParsedExpr::node(
1300                Expr::List(Vec::new()),
1301                self.span_from(start),
1302                [],
1303            ));
1304        }
1305
1306        let first = self.parse_expr_no_tuple()?;
1307        if matches!(self.peek_kind(), TokenKind::For) {
1308            let parsed_clauses = self.parse_list_comprehension_clauses()?;
1309            self.expect_exact(TokenKind::RBracket, "`]`")?;
1310            let clauses = parsed_clauses
1311                .iter()
1312                .map(|parsed| parsed.clause.clone())
1313                .collect::<Vec<_>>();
1314            let mut children = parsed_clauses
1315                .into_iter()
1316                .enumerate()
1317                .map(|(index, parsed)| (index as u32, parsed.expr_span))
1318                .collect::<Vec<_>>();
1319            children.push((children.len() as u32, first.clone()));
1320            return Ok(ParsedExpr::node(
1321                Expr::ListComprehension {
1322                    element: Box::new(first.expr.clone()),
1323                    clauses,
1324                },
1325                self.span_from(start),
1326                children,
1327            ));
1328        }
1329
1330        let mut items = Vec::new();
1331        let mut children = Vec::new();
1332        children.push((0, first));
1333        items.push(children.last().expect("item").1.expr.clone());
1334        if matches!(self.peek_kind(), TokenKind::Comma) {
1335            self.bump();
1336        } else {
1337            self.expect_exact(TokenKind::RBracket, "`]`")?;
1338            return Ok(ParsedExpr::node(
1339                Expr::List(items),
1340                self.span_from(start),
1341                children,
1342            ));
1343        }
1344        while !matches!(self.peek_kind(), TokenKind::RBracket) {
1345            let item = self.parse_expr_no_tuple()?;
1346            children.push((items.len() as u32, item));
1347            items.push(children.last().expect("item").1.expr.clone());
1348            if matches!(self.peek_kind(), TokenKind::Comma) {
1349                self.bump();
1350                continue;
1351            }
1352            break;
1353        }
1354        self.expect_exact(TokenKind::RBracket, "`]`")?;
1355        Ok(ParsedExpr::node(
1356            Expr::List(items),
1357            self.span_from(start),
1358            children,
1359        ))
1360    }
1361
1362    fn parse_list_comprehension_clauses(
1363        &mut self,
1364    ) -> Result<Vec<ParsedListComprehensionClause>, ParseError> {
1365        let mut clauses = Vec::new();
1366        while matches!(self.peek_kind(), TokenKind::For) {
1367            self.bump();
1368            let binding = self.expect_ident()?;
1369            self.expect_exact(TokenKind::In, "`in`")?;
1370            let iterable = self.parse_expr()?;
1371            clauses.push(ParsedListComprehensionClause {
1372                clause: ListComprehensionClause::For {
1373                    binding,
1374                    iterable: iterable.expr.clone(),
1375                },
1376                expr_span: iterable,
1377            });
1378            while matches!(self.peek_kind(), TokenKind::If) {
1379                self.bump();
1380                let condition = self.parse_expr()?;
1381                clauses.push(ParsedListComprehensionClause {
1382                    clause: ListComprehensionClause::If {
1383                        condition: condition.expr.clone(),
1384                    },
1385                    expr_span: condition,
1386                });
1387            }
1388        }
1389        Ok(clauses)
1390    }
1391
1392    fn parse_record(&mut self) -> Result<ParsedExpr, ParseError> {
1393        let start = self.peek().span.start;
1394        self.expect_exact(TokenKind::LBrace, "`{`")?;
1395        let entries = self.parse_record_entries()?;
1396        self.expect_exact(TokenKind::RBrace, "`}`")?;
1397        let expr_entries = entries
1398            .iter()
1399            .map(|(key, value)| (key.clone(), value.expr.clone()))
1400            .collect();
1401        Ok(ParsedExpr::node(
1402            Expr::Record(expr_entries),
1403            self.span_from(start),
1404            entries
1405                .into_iter()
1406                .enumerate()
1407                .map(|(index, (_, value))| (index as u32, value)),
1408        ))
1409    }
1410
1411    fn parse_record_entries(&mut self) -> Result<Vec<(AstString, ParsedExpr)>, ParseError> {
1412        let mut entries = Vec::new();
1413        while !matches!(self.peek_kind(), TokenKind::RBrace) {
1414            let key = self.expect_key_name()?;
1415            self.expect_exact(TokenKind::Colon, "`:`")?;
1416            let value = self.parse_expr_no_tuple()?;
1417            entries.push((key, value));
1418            if matches!(self.peek_kind(), TokenKind::Comma) {
1419                self.bump();
1420                continue;
1421            }
1422            break;
1423        }
1424        Ok(entries)
1425    }
1426
1427    fn parse_call_arguments(&mut self) -> Result<Vec<ParsedExpr>, ParseError> {
1428        self.expect_exact(TokenKind::LParen, "`(`")?;
1429        let mut args = Vec::new();
1430        if !matches!(self.peek_kind(), TokenKind::RParen) {
1431            loop {
1432                args.push(self.parse_expr_no_tuple()?);
1433                if matches!(self.peek_kind(), TokenKind::Comma) {
1434                    self.bump();
1435                    if matches!(self.peek_kind(), TokenKind::RParen) {
1436                        break;
1437                    }
1438                    continue;
1439                }
1440                break;
1441            }
1442        }
1443        self.expect_exact(TokenKind::RParen, "`)`")?;
1444        Ok(args)
1445    }
1446
1447    fn parse_named_arguments(&mut self) -> Result<Vec<(AstString, ParsedExpr)>, ParseError> {
1448        let mut entries = Vec::new();
1449        while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
1450            let key = self.expect_key_name()?;
1451            self.expect_exact(TokenKind::Colon, "`:`")?;
1452            let value = self.parse_expr_no_tuple()?;
1453            entries.push((key, value));
1454            if matches!(self.peek_kind(), TokenKind::Comma) {
1455                self.bump();
1456                continue;
1457            }
1458            break;
1459        }
1460        Ok(entries)
1461    }
1462
1463    fn parse_process_start_expr(&mut self, start: usize) -> Result<ParsedExpr, ParseError> {
1464        if matches!(self.peek_kind(), TokenKind::LBrace) || self.paren_group_followed_by_lbrace() {
1465            return Err(ParseError::Unexpected {
1466                found: "inline `start` process body".to_string(),
1467                span: self.peek().span,
1468            });
1469        }
1470        let process = self.expect_ident()?;
1471        self.expect_exact(TokenKind::LParen, "`(`")?;
1472        let args = self.parse_named_arguments()?;
1473        self.expect_exact(TokenKind::RParen, "`)`")?;
1474        let expr_args = args
1475            .iter()
1476            .map(|(name, value)| (name.clone(), value.expr.clone()))
1477            .collect();
1478        Ok(ParsedExpr::node(
1479            Expr::StartProcess(ProcessStartExpr {
1480                process,
1481                args: expr_args,
1482            }),
1483            self.span_from(start),
1484            args.into_iter()
1485                .enumerate()
1486                .map(|(index, (_, value))| (index as u32, value)),
1487        ))
1488    }
1489
1490    fn parse_sleep_expr(&mut self, start: usize) -> Result<ParsedExpr, ParseError> {
1491        if matches!(self.peek_kind(), TokenKind::For) {
1492            self.bump();
1493            let expr = self.parse_expr()?;
1494            return Ok(ParsedExpr::node(
1495                Expr::SleepFor(Box::new(expr.expr.clone())),
1496                Span {
1497                    start,
1498                    end: expr.span.end,
1499                },
1500                [(0, expr)],
1501            ));
1502        }
1503        if self.peek_contextual("until") {
1504            self.bump();
1505            let expr = self.parse_expr()?;
1506            return Ok(ParsedExpr::node(
1507                Expr::SleepUntil(Box::new(expr.expr.clone())),
1508                Span {
1509                    start,
1510                    end: expr.span.end,
1511                },
1512                [(0, expr)],
1513            ));
1514        }
1515        Err(ParseError::Expected {
1516            expected: "`for` or `until`",
1517            found: render_kind(self.peek_kind()),
1518            span: self.peek().span,
1519        })
1520    }
1521
1522    fn parse_type_object(&mut self) -> Result<TypeExpr, ParseError> {
1523        self.expect_exact(TokenKind::LBrace, "`{`")?;
1524        self.parse_type_object_body_after_lbrace()
1525    }
1526
1527    fn parse_type_object_body(&mut self) -> Result<TypeExpr, ParseError> {
1528        self.expect_exact(TokenKind::LBrace, "`{`")?;
1529        self.parse_type_object_body_after_lbrace()
1530    }
1531
1532    fn parse_type_object_body_after_lbrace(&mut self) -> Result<TypeExpr, ParseError> {
1533        let mut fields = Vec::new();
1534        let mut seen = std::collections::HashSet::new();
1535        while !matches!(self.peek_kind(), TokenKind::RBrace) {
1536            let name_token_span = self.peek().span;
1537            let name = self.expect_key_name()?;
1538            if !seen.insert(name.clone()) {
1539                return Err(ParseError::Expected {
1540                    expected: "unique field name",
1541                    found: format!("duplicate field `{name}`"),
1542                    span: name_token_span,
1543                });
1544            }
1545            self.expect_exact(TokenKind::Colon, "`:`")?;
1546            let ty = self.parse_type_expr()?;
1547            let optional = if matches!(self.peek_kind(), TokenKind::Question) {
1548                self.bump();
1549                true
1550            } else {
1551                false
1552            };
1553            fields.push(TypeField { name, ty, optional });
1554            if matches!(self.peek_kind(), TokenKind::Comma) {
1555                self.bump();
1556                continue;
1557            }
1558            break;
1559        }
1560        self.expect_exact(TokenKind::RBrace, "`}`")?;
1561        Ok(TypeExpr::Object(fields))
1562    }
1563
1564    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
1565        let first = self.parse_type_term()?;
1566        if !matches!(self.peek_kind(), TokenKind::Pipe) {
1567            return Ok(first);
1568        }
1569        // Union: `str | null`, `int | str | null`, etc. `|` has lower
1570        // precedence than any other type constructor — once we see it
1571        // at top level, keep parsing `| <term>` until the run ends.
1572        let mut variants = vec![first];
1573        while matches!(self.peek_kind(), TokenKind::Pipe) {
1574            self.bump();
1575            variants.push(self.parse_type_term()?);
1576        }
1577        Ok(TypeExpr::Union(variants))
1578    }
1579
1580    fn parse_type_term(&mut self) -> Result<TypeExpr, ParseError> {
1581        let token = self.peek().clone();
1582        match token.kind {
1583            TokenKind::Null => {
1584                self.bump();
1585                Ok(TypeExpr::Null)
1586            }
1587            TokenKind::String(value) => {
1588                self.bump();
1589                Ok(TypeExpr::Enum(vec![value]))
1590            }
1591            // A bare `{` in type position is the classic "forgot the
1592            // `Type` keyword" mistake (`foo: { ok: bool }` instead of
1593            // `foo: Type { ok: bool }`). Surface a targeted diagnostic
1594            // rather than the generic "expected type expression" shrug.
1595            TokenKind::LBrace => self.parse_type_object_body(),
1596            TokenKind::Ident(_name) => {
1597                let name = self.parse_type_name()?;
1598                match name.as_str() {
1599                    "str" | "string" => Ok(TypeExpr::Str),
1600                    "int" | "integer" => Ok(TypeExpr::Int),
1601                    "float" | "number" => Ok(TypeExpr::Float),
1602                    "bool" | "boolean" => Ok(TypeExpr::Bool),
1603                    "dict" | "object" => Ok(TypeExpr::Dict),
1604                    "any" => Ok(TypeExpr::Any),
1605                    "enum" => {
1606                        self.expect_exact(TokenKind::LBracket, "`[`")?;
1607                        let mut values = Vec::new();
1608                        if !matches!(self.peek_kind(), TokenKind::RBracket) {
1609                            loop {
1610                                let value = self.expect_string_literal()?;
1611                                values.push(value);
1612                                if matches!(self.peek_kind(), TokenKind::Comma) {
1613                                    self.bump();
1614                                    continue;
1615                                }
1616                                break;
1617                            }
1618                        }
1619                        if values.is_empty() {
1620                            return Err(ParseError::Expected {
1621                                expected: "at least one enum string literal",
1622                                found: "empty enum".to_string(),
1623                                span: token.span,
1624                            });
1625                        }
1626                        self.expect_exact(TokenKind::RBracket, "`]`")?;
1627                        Ok(TypeExpr::Enum(values))
1628                    }
1629                    "list" => {
1630                        self.expect_exact(TokenKind::LBracket, "`[`")?;
1631                        let inner = self.parse_type_expr()?;
1632                        self.expect_exact(TokenKind::RBracket, "`]`")?;
1633                        Ok(TypeExpr::List(Box::new(inner)))
1634                    }
1635                    "Process" => {
1636                        self.expect_exact(TokenKind::Less, "`<`")?;
1637                        let input = self.parse_type_expr()?;
1638                        self.expect_exact(TokenKind::Comma, "`,`")?;
1639                        let output = self.parse_type_expr()?;
1640                        self.expect_exact(TokenKind::Greater, "`>`")?;
1641                        Ok(TypeExpr::Process {
1642                            input: Box::new(input),
1643                            output: Box::new(output),
1644                            input_count: 1,
1645                        })
1646                    }
1647                    "TriggerHandle" => {
1648                        self.expect_exact(TokenKind::Less, "`<`")?;
1649                        let event = self.parse_type_expr()?;
1650                        self.expect_exact(TokenKind::Greater, "`>`")?;
1651                        Ok(TypeExpr::TriggerHandle(Box::new(event)))
1652                    }
1653                    "Type" => self.parse_type_object(),
1654                    _ => Ok(TypeExpr::Ref(name)),
1655                }
1656            }
1657            _ => Err(ParseError::Expected {
1658                expected: "type expression",
1659                found: render_kind(&token.kind),
1660                span: token.span,
1661            }),
1662        }
1663    }
1664
1665    fn expect_string_literal(&mut self) -> Result<AstString, ParseError> {
1666        let token = self.bump().clone();
1667        match token.kind {
1668            TokenKind::String(value) => Ok(value),
1669            other => Err(ParseError::Expected {
1670                expected: "string literal",
1671                found: render_kind(&other),
1672                span: token.span,
1673            }),
1674        }
1675    }
1676
1677    fn expect_ident(&mut self) -> Result<AstString, ParseError> {
1678        let token = self.bump();
1679        match &token.kind {
1680            TokenKind::Ident(name) => Ok(name.clone()),
1681            other => Err(ParseError::Expected {
1682                expected: "identifier",
1683                found: render_kind(other),
1684                span: token.span,
1685            }),
1686        }
1687    }
1688
1689    fn parse_type_name(&mut self) -> Result<AstString, ParseError> {
1690        let mut path = vec![self.expect_ident()?];
1691        while matches!(self.peek_kind(), TokenKind::Dot) {
1692            self.bump();
1693            path.push(self.expect_ident()?);
1694        }
1695        Ok(path
1696            .iter()
1697            .map(AstString::as_str)
1698            .collect::<Vec<_>>()
1699            .join(".")
1700            .into())
1701    }
1702
1703    fn expect_key_name(&mut self) -> Result<AstString, ParseError> {
1704        let token = self.bump();
1705        match &token.kind {
1706            TokenKind::Ident(name) | TokenKind::String(name) => Ok(name.clone()),
1707            other => keyword_key_name(other)
1708                .map(Into::into)
1709                .ok_or_else(|| ParseError::Expected {
1710                    expected: "identifier, string key, or keyword key",
1711                    found: render_kind(other),
1712                    span: token.span,
1713                }),
1714        }
1715    }
1716
1717    fn expect_exact(
1718        &mut self,
1719        expected_kind: TokenKind,
1720        expected: &'static str,
1721    ) -> Result<(), ParseError> {
1722        let token = self.bump();
1723        if std::mem::discriminant(&token.kind) == std::mem::discriminant(&expected_kind) {
1724            Ok(())
1725        } else {
1726            Err(ParseError::Expected {
1727                expected,
1728                found: render_kind(&token.kind),
1729                span: token.span,
1730            })
1731        }
1732    }
1733
1734    fn unexpected(&mut self) -> ParseError {
1735        let token = self.peek();
1736        ParseError::Unexpected {
1737            found: render_kind(&token.kind),
1738            span: token.span,
1739        }
1740    }
1741
1742    fn peek_assignment_target(&self) -> bool {
1743        if !matches!(self.peek_kind(), TokenKind::Ident(_)) {
1744            return false;
1745        }
1746
1747        let mut index = self.index + 1;
1748        loop {
1749            match self.tokens.get(index).map(|token| &token.kind) {
1750                Some(TokenKind::Dot) => {
1751                    if !self
1752                        .tokens
1753                        .get(index + 1)
1754                        .is_some_and(|token| token_can_be_key(&token.kind))
1755                    {
1756                        return false;
1757                    }
1758                    index += 2;
1759                }
1760                Some(TokenKind::LBracket) => {
1761                    let Some(after_index) = self.skip_bracketed_index(index) else {
1762                        return false;
1763                    };
1764                    index = after_index;
1765                }
1766                Some(TokenKind::Equal) => return true,
1767                _ => return false,
1768            }
1769        }
1770    }
1771
1772    fn skip_bracketed_index(&self, start: usize) -> Option<usize> {
1773        debug_assert!(matches!(
1774            self.tokens.get(start).map(|token| &token.kind),
1775            Some(TokenKind::LBracket)
1776        ));
1777        let mut parens = 0usize;
1778        let mut brackets = 1usize;
1779        let mut braces = 0usize;
1780        for (offset, token) in self.tokens.iter().enumerate().skip(start + 1) {
1781            match &token.kind {
1782                TokenKind::LParen => parens += 1,
1783                TokenKind::RParen => parens = parens.checked_sub(1)?,
1784                TokenKind::LBracket => brackets += 1,
1785                TokenKind::RBracket => {
1786                    brackets = brackets.checked_sub(1)?;
1787                    if brackets == 0 && parens == 0 && braces == 0 {
1788                        return Some(offset + 1);
1789                    }
1790                }
1791                TokenKind::LBrace => braces += 1,
1792                TokenKind::RBrace => braces = braces.checked_sub(1)?,
1793                TokenKind::Eof => return None,
1794                _ => {}
1795            }
1796        }
1797        None
1798    }
1799
1800    fn question_starts_ternary(&self) -> bool {
1801        debug_assert!(matches!(self.peek_kind(), TokenKind::Question));
1802        let Some(next) = self.tokens.get(self.index + 1) else {
1803            return false;
1804        };
1805        if !token_can_start_expr(&next.kind) {
1806            return false;
1807        }
1808
1809        let mut parens = 0usize;
1810        let mut brackets = 0usize;
1811        let mut braces = 0usize;
1812        for token in self.tokens.iter().skip(self.index + 1) {
1813            match &token.kind {
1814                TokenKind::Colon if parens == 0 && brackets == 0 && braces == 0 => return true,
1815                TokenKind::Equal if parens == 0 && brackets == 0 && braces == 0 => return false,
1816                TokenKind::Comma | TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace
1817                    if parens == 0 && brackets == 0 && braces == 0 =>
1818                {
1819                    return false;
1820                }
1821                TokenKind::Eof => return false,
1822                TokenKind::LParen => parens += 1,
1823                TokenKind::RParen => {
1824                    if parens == 0 {
1825                        return false;
1826                    }
1827                    parens -= 1;
1828                }
1829                TokenKind::LBracket => brackets += 1,
1830                TokenKind::RBracket => {
1831                    if brackets == 0 {
1832                        return false;
1833                    }
1834                    brackets -= 1;
1835                }
1836                TokenKind::LBrace => braces += 1,
1837                TokenKind::RBrace => {
1838                    if braces == 0 {
1839                        return false;
1840                    }
1841                    braces -= 1;
1842                }
1843                _ => {}
1844            }
1845        }
1846        false
1847    }
1848
1849    fn paren_group_followed_by_lbrace(&self) -> bool {
1850        if !matches!(self.peek_kind(), TokenKind::LParen) {
1851            return false;
1852        }
1853        let mut depth = 0usize;
1854        for (index, token) in self.tokens.iter().enumerate().skip(self.index) {
1855            match &token.kind {
1856                TokenKind::LParen => depth += 1,
1857                TokenKind::RParen => {
1858                    depth = depth.saturating_sub(1);
1859                    if depth == 0 {
1860                        return self
1861                            .tokens
1862                            .get(index + 1)
1863                            .is_some_and(|next| matches!(next.kind, TokenKind::LBrace));
1864                    }
1865                }
1866                TokenKind::Eof => return false,
1867                _ => {}
1868            }
1869        }
1870        false
1871    }
1872
1873    fn peek_contextual(&self, keyword: &str) -> bool {
1874        matches!(self.peek_kind(), TokenKind::Ident(name) if name.as_str() == keyword)
1875    }
1876
1877    fn expect_contextual(&mut self, keyword: &'static str) -> Result<(), ParseError> {
1878        let token = self.bump();
1879        match &token.kind {
1880            TokenKind::Ident(name) if name.as_str() == keyword => Ok(()),
1881            other => Err(ParseError::Expected {
1882                expected: keyword,
1883                found: render_kind(other),
1884                span: token.span,
1885            }),
1886        }
1887    }
1888
1889    fn at_eof(&self) -> bool {
1890        matches!(self.peek_kind(), TokenKind::Eof)
1891    }
1892
1893    fn peek_kind(&self) -> &TokenKind {
1894        &self.tokens[self.index].kind
1895    }
1896
1897    fn peek(&self) -> &Token {
1898        &self.tokens[self.index]
1899    }
1900
1901    fn bump(&mut self) -> &Token {
1902        let token = &self.tokens[self.index];
1903        self.index += 1;
1904        token
1905    }
1906}
1907
1908fn static_signal_name_arg(expr: &Expr, call: &'static str) -> Result<AstString, ParseError> {
1909    if let Expr::String(name) = expr {
1910        return Ok(name.clone());
1911    }
1912    Err(ParseError::Unexpected {
1913        found: format!("non-literal signal name in `{call}`"),
1914        span: Span { start: 0, end: 0 },
1915    })
1916}
1917
1918fn token_can_be_key(kind: &TokenKind) -> bool {
1919    matches!(kind, TokenKind::Ident(_) | TokenKind::String(_)) || keyword_key_name(kind).is_some()
1920}
1921
1922fn keyword_key_name(kind: &TokenKind) -> Option<&'static str> {
1923    Some(match kind {
1924        TokenKind::If => "if",
1925        TokenKind::Else => "else",
1926        TokenKind::For => "for",
1927        TokenKind::In => "in",
1928        TokenKind::Await => "await",
1929        TokenKind::Cancel => "cancel",
1930        TokenKind::Submit => "submit",
1931        TokenKind::Print => "print",
1932        TokenKind::Call => "call",
1933        TokenKind::Ident(name) if matches!(name.as_str(), "yield" | "wake" | "finish" | "fail") => {
1934            return Some(match name.as_str() {
1935                "yield" => "yield",
1936                "wake" => "wake",
1937                "finish" => "finish",
1938                "fail" => "fail",
1939                _ => unreachable!(),
1940            });
1941        }
1942        TokenKind::And => "and",
1943        TokenKind::Or => "or",
1944        TokenKind::Not => "not",
1945        TokenKind::True => "true",
1946        TokenKind::False => "false",
1947        TokenKind::Null => "null",
1948        _ => return None,
1949    })
1950}
1951
1952fn token_can_start_expr(kind: &TokenKind) -> bool {
1953    matches!(
1954        kind,
1955        TokenKind::Null
1956            | TokenKind::True
1957            | TokenKind::False
1958            | TokenKind::Number(_)
1959            | TokenKind::String(_)
1960            | TokenKind::Ident(_)
1961            | TokenKind::LParen
1962            | TokenKind::LBracket
1963            | TokenKind::LBrace
1964            | TokenKind::Await
1965            | TokenKind::Minus
1966            | TokenKind::Bang
1967            | TokenKind::Not
1968    )
1969}
1970
1971fn render_kind(kind: &TokenKind) -> String {
1972    match kind {
1973        TokenKind::Ident(name) => format!("identifier `{name}`"),
1974        TokenKind::String(value) => format!("string {:?}", value),
1975        TokenKind::Number(value) => format!("number {value}"),
1976        TokenKind::LBrace => "`{`".to_string(),
1977        TokenKind::RBrace => "`}`".to_string(),
1978        TokenKind::LParen => "`(`".to_string(),
1979        TokenKind::RParen => "`)`".to_string(),
1980        TokenKind::LBracket => "`[`".to_string(),
1981        TokenKind::RBracket => "`]`".to_string(),
1982        TokenKind::Comma => "`,`".to_string(),
1983        TokenKind::Colon => "`:`".to_string(),
1984        TokenKind::At => "`@`".to_string(),
1985        TokenKind::Question => "`?`".to_string(),
1986        TokenKind::Dot => "`.`".to_string(),
1987        TokenKind::Bang => "`!`".to_string(),
1988        TokenKind::Equal => "`=`".to_string(),
1989        TokenKind::DoubleEqual => "`==`".to_string(),
1990        TokenKind::BangEqual => "`!=`".to_string(),
1991        TokenKind::AndAnd => "`&&`".to_string(),
1992        TokenKind::OrOr => "`||`".to_string(),
1993        TokenKind::Pipe => "`|`".to_string(),
1994        TokenKind::Less => "`<`".to_string(),
1995        TokenKind::LessEqual => "`<=`".to_string(),
1996        TokenKind::Greater => "`>`".to_string(),
1997        TokenKind::GreaterEqual => "`>=`".to_string(),
1998        TokenKind::Plus => "`+`".to_string(),
1999        TokenKind::Minus => "`-`".to_string(),
2000        TokenKind::Star => "`*`".to_string(),
2001        TokenKind::Slash => "`/`".to_string(),
2002        TokenKind::Percent => "`%`".to_string(),
2003        TokenKind::If => "`if`".to_string(),
2004        TokenKind::Else => "`else`".to_string(),
2005        TokenKind::For => "`for`".to_string(),
2006        TokenKind::In => "`in`".to_string(),
2007        TokenKind::Await => "`await`".to_string(),
2008        TokenKind::Cancel => "`cancel`".to_string(),
2009        TokenKind::Submit => "`submit`".to_string(),
2010        TokenKind::Print => "`print`".to_string(),
2011        TokenKind::Call => "`call`".to_string(),
2012        TokenKind::And => "`and`".to_string(),
2013        TokenKind::Or => "`or`".to_string(),
2014        TokenKind::Not => "`not`".to_string(),
2015        TokenKind::True => "`true`".to_string(),
2016        TokenKind::False => "`false`".to_string(),
2017        TokenKind::Null => "`null`".to_string(),
2018        TokenKind::Eof => "end of input".to_string(),
2019    }
2020}
2021
2022include!("parser/tests.rs");