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_ternary();
826        self.leave_nesting();
827        result
828    }
829
830    fn parse_ternary(&mut self) -> Result<ParsedExpr, ParseError> {
831        let condition = self.parse_or()?;
832        if !matches!(self.peek_kind(), TokenKind::Question) {
833            return Ok(condition);
834        }
835        self.bump();
836        let then_expr = self.parse_expr()?;
837        self.expect_exact(TokenKind::Colon, "`:`")?;
838        let else_expr = self.parse_expr()?;
839        let span = Span {
840            start: condition.span.start,
841            end: else_expr.span.end,
842        };
843        Ok(ParsedExpr::node(
844            Expr::If {
845                condition: Box::new(condition.expr.clone()),
846                then_block: Box::new(then_expr.expr.clone()),
847                else_block: Box::new(else_expr.expr.clone()),
848            },
849            span,
850            [(0, condition), (1, then_expr), (2, else_expr)],
851        ))
852    }
853
854    fn parse_or(&mut self) -> Result<ParsedExpr, ParseError> {
855        let mut expr = self.parse_and()?;
856        while matches!(self.peek_kind(), TokenKind::Or | TokenKind::OrOr) {
857            self.bump();
858            let right = self.parse_and()?;
859            let span = Span {
860                start: expr.span.start,
861                end: right.span.end,
862            };
863            expr = ParsedExpr::node(
864                Expr::Binary {
865                    left: Box::new(expr.expr.clone()),
866                    op: BinaryOp::Or,
867                    right: Box::new(right.expr.clone()),
868                },
869                span,
870                [(0, expr), (1, right)],
871            );
872        }
873        Ok(expr)
874    }
875
876    fn parse_and(&mut self) -> Result<ParsedExpr, ParseError> {
877        let mut expr = self.parse_compare()?;
878        while matches!(self.peek_kind(), TokenKind::And | TokenKind::AndAnd) {
879            self.bump();
880            let right = self.parse_compare()?;
881            let span = Span {
882                start: expr.span.start,
883                end: right.span.end,
884            };
885            expr = ParsedExpr::node(
886                Expr::Binary {
887                    left: Box::new(expr.expr.clone()),
888                    op: BinaryOp::And,
889                    right: Box::new(right.expr.clone()),
890                },
891                span,
892                [(0, expr), (1, right)],
893            );
894        }
895        Ok(expr)
896    }
897
898    fn parse_compare(&mut self) -> Result<ParsedExpr, ParseError> {
899        let mut expr = self.parse_add()?;
900        loop {
901            let op = match self.peek_kind() {
902                TokenKind::DoubleEqual => BinaryOp::Equal,
903                TokenKind::BangEqual => BinaryOp::NotEqual,
904                TokenKind::Less => BinaryOp::Less,
905                TokenKind::LessEqual => BinaryOp::LessEqual,
906                TokenKind::Greater => BinaryOp::Greater,
907                TokenKind::GreaterEqual => BinaryOp::GreaterEqual,
908                _ => break,
909            };
910            self.bump();
911            let right = self.parse_add()?;
912            let span = Span {
913                start: expr.span.start,
914                end: right.span.end,
915            };
916            expr = ParsedExpr::node(
917                Expr::Binary {
918                    left: Box::new(expr.expr.clone()),
919                    op,
920                    right: Box::new(right.expr.clone()),
921                },
922                span,
923                [(0, expr), (1, right)],
924            );
925        }
926        Ok(expr)
927    }
928
929    fn parse_add(&mut self) -> Result<ParsedExpr, ParseError> {
930        let mut expr = self.parse_mul()?;
931        loop {
932            let op = match self.peek_kind() {
933                TokenKind::Plus => BinaryOp::Add,
934                TokenKind::Minus => BinaryOp::Subtract,
935                _ => break,
936            };
937            self.bump();
938            let right = self.parse_mul()?;
939            let span = Span {
940                start: expr.span.start,
941                end: right.span.end,
942            };
943            expr = ParsedExpr::node(
944                Expr::Binary {
945                    left: Box::new(expr.expr.clone()),
946                    op,
947                    right: Box::new(right.expr.clone()),
948                },
949                span,
950                [(0, expr), (1, right)],
951            );
952        }
953        Ok(expr)
954    }
955
956    fn parse_mul(&mut self) -> Result<ParsedExpr, ParseError> {
957        let mut expr = self.parse_unary()?;
958        loop {
959            let op = match self.peek_kind() {
960                TokenKind::Star => BinaryOp::Multiply,
961                TokenKind::Slash => BinaryOp::Divide,
962                TokenKind::Percent => BinaryOp::Modulo,
963                _ => break,
964            };
965            self.bump();
966            let right = self.parse_unary()?;
967            let span = Span {
968                start: expr.span.start,
969                end: right.span.end,
970            };
971            expr = ParsedExpr::node(
972                Expr::Binary {
973                    left: Box::new(expr.expr.clone()),
974                    op,
975                    right: Box::new(right.expr.clone()),
976                },
977                span,
978                [(0, expr), (1, right)],
979            );
980        }
981        Ok(expr)
982    }
983
984    fn parse_unary(&mut self) -> Result<ParsedExpr, ParseError> {
985        match self.peek_kind() {
986            TokenKind::Minus => {
987                let start = self.bump().span.start;
988                let expr = self.parse_unary()?;
989                Ok(ParsedExpr::node(
990                    Expr::Unary {
991                        op: UnaryOp::Negate,
992                        expr: Box::new(expr.expr.clone()),
993                    },
994                    Span {
995                        start,
996                        end: expr.span.end,
997                    },
998                    [(0, expr)],
999                ))
1000            }
1001            TokenKind::Not => {
1002                let start = self.bump().span.start;
1003                let expr = self.parse_unary()?;
1004                Ok(ParsedExpr::node(
1005                    Expr::Unary {
1006                        op: UnaryOp::Not,
1007                        expr: Box::new(expr.expr.clone()),
1008                    },
1009                    Span {
1010                        start,
1011                        end: expr.span.end,
1012                    },
1013                    [(0, expr)],
1014                ))
1015            }
1016            TokenKind::Bang => {
1017                let start = self.bump().span.start;
1018                let expr = self.parse_unary()?;
1019                Ok(ParsedExpr::node(
1020                    Expr::Unary {
1021                        op: UnaryOp::Not,
1022                        expr: Box::new(expr.expr.clone()),
1023                    },
1024                    Span {
1025                        start,
1026                        end: expr.span.end,
1027                    },
1028                    [(0, expr)],
1029                ))
1030            }
1031            TokenKind::Await => {
1032                let start = self.bump().span.start;
1033                let expr = self.parse_unary()?;
1034                Ok(ParsedExpr::node(
1035                    Expr::Await(Box::new(expr.expr.clone())),
1036                    Span {
1037                        start,
1038                        end: expr.span.end,
1039                    },
1040                    [(0, expr)],
1041                ))
1042            }
1043            _ => self.parse_postfix(),
1044        }
1045    }
1046
1047    fn parse_postfix(&mut self) -> Result<ParsedExpr, ParseError> {
1048        let mut expr = self.parse_primary()?;
1049        loop {
1050            match self.peek_kind() {
1051                TokenKind::Dot => {
1052                    self.bump();
1053                    let field = self.expect_key_name()?;
1054                    if matches!(self.peek_kind(), TokenKind::LParen) {
1055                        let args = self.parse_call_arguments()?;
1056                        let span = self.span_from(expr.span.start);
1057                        let mut children = Vec::with_capacity(args.len() + 1);
1058                        children.push((0, expr));
1059                        let arg_exprs = args.iter().map(|arg| arg.expr.clone()).collect();
1060                        children.extend(
1061                            args.into_iter()
1062                                .enumerate()
1063                                .map(|(index, arg)| ((index + 1) as u32, arg)),
1064                        );
1065                        let receiver = children[0].1.expr.clone();
1066                        expr = ParsedExpr::node(
1067                            Expr::ReceiverCall {
1068                                receiver: Box::new(receiver),
1069                                operation: field,
1070                                args: arg_exprs,
1071                            },
1072                            span,
1073                            children,
1074                        );
1075                    } else {
1076                        let span = self.span_from(expr.span.start);
1077                        expr = ParsedExpr::node(
1078                            Expr::Field {
1079                                target: Box::new(expr.expr.clone()),
1080                                field,
1081                            },
1082                            span,
1083                            [(0, expr)],
1084                        );
1085                    }
1086                }
1087                TokenKind::LBracket => {
1088                    self.bump();
1089                    let index = self.parse_expr()?;
1090                    self.expect_exact(TokenKind::RBracket, "`]`")?;
1091                    let span = self.span_from(expr.span.start);
1092                    expr = ParsedExpr::node(
1093                        Expr::Index {
1094                            target: Box::new(expr.expr.clone()),
1095                            index: Box::new(index.expr.clone()),
1096                        },
1097                        span,
1098                        [(0, expr), (1, index)],
1099                    );
1100                }
1101                TokenKind::Question if !self.question_starts_ternary() => {
1102                    self.bump();
1103                    let span = self.span_from(expr.span.start);
1104                    expr = ParsedExpr::node(
1105                        Expr::ResultUnwrap(Box::new(expr.expr.clone())),
1106                        span,
1107                        [(0, expr)],
1108                    );
1109                }
1110                _ => break,
1111            }
1112        }
1113        Ok(expr)
1114    }
1115
1116    fn parse_primary(&mut self) -> Result<ParsedExpr, ParseError> {
1117        match self.peek_kind() {
1118            TokenKind::Null => {
1119                let span = self.bump().span;
1120                Ok(ParsedExpr::leaf(Expr::Null, span))
1121            }
1122            TokenKind::True => {
1123                let span = self.bump().span;
1124                Ok(ParsedExpr::leaf(Expr::Bool(true), span))
1125            }
1126            TokenKind::False => {
1127                let span = self.bump().span;
1128                Ok(ParsedExpr::leaf(Expr::Bool(false), span))
1129            }
1130            TokenKind::Number(value) => {
1131                let value = *value;
1132                let span = self.bump().span;
1133                Ok(ParsedExpr::leaf(Expr::Number(value), span))
1134            }
1135            TokenKind::String(value) => {
1136                let value = value.clone();
1137                let span = self.bump().span;
1138                Ok(ParsedExpr::leaf(Expr::String(value), span))
1139            }
1140            TokenKind::Ident(name) => {
1141                let token_span = self.peek().span;
1142                if name == "parallel"
1143                    && self
1144                        .tokens
1145                        .get(self.index + 1)
1146                        .is_some_and(|token| matches!(token.kind, TokenKind::LBrace))
1147                {
1148                    return Err(ParseError::Unexpected {
1149                        found: "`parallel`".to_string(),
1150                        span: self.peek().span,
1151                    });
1152                }
1153                let name = name.clone();
1154                self.bump();
1155                if name == "sleep" {
1156                    return self.parse_sleep_expr(token_span.start);
1157                }
1158                if name == "start" && matches!(self.peek_kind(), TokenKind::Call) {
1159                    return Err(ParseError::Unexpected {
1160                        found: "`start call`".to_string(),
1161                        span: self.tokens[self.index.saturating_sub(1)].span,
1162                    });
1163                }
1164                if name == "start"
1165                    && (matches!(self.peek_kind(), TokenKind::Ident(_))
1166                        || matches!(self.peek_kind(), TokenKind::LBrace)
1167                        || self.paren_group_followed_by_lbrace())
1168                {
1169                    return self.parse_process_start_expr(token_span.start);
1170                }
1171                if name == "Type" && matches!(self.peek_kind(), TokenKind::LBrace) {
1172                    let ty = self.parse_type_object()?;
1173                    return Ok(ParsedExpr::leaf(
1174                        Expr::TypeLiteral(Box::new(ty)),
1175                        self.span_from(token_span.start),
1176                    ));
1177                }
1178                if matches!(self.peek_kind(), TokenKind::LParen) {
1179                    let args = self.parse_call_arguments()?;
1180                    if name == "wait_signal" {
1181                        if args.len() != 1 {
1182                            return Err(ParseError::Expected {
1183                                expected: "one signal name argument",
1184                                found: format!("{} arguments", args.len()),
1185                                span: self.tokens[self.index.saturating_sub(1)].span,
1186                            });
1187                        }
1188                        return Ok(ParsedExpr::leaf(
1189                            Expr::WaitSignal {
1190                                name: static_signal_name_arg(&args[0].expr, "wait_signal")?,
1191                            },
1192                            self.span_from(token_span.start),
1193                        ));
1194                    }
1195                    if name == "signal_run" {
1196                        if args.len() != 3 {
1197                            return Err(ParseError::Expected {
1198                                expected: "run handle, signal name, and payload arguments",
1199                                found: format!("{} arguments", args.len()),
1200                                span: self.tokens[self.index.saturating_sub(1)].span,
1201                            });
1202                        }
1203                        let run = args[0].expr.clone();
1204                        let payload = args[2].expr.clone();
1205                        return Ok(ParsedExpr::node(
1206                            Expr::SignalRun {
1207                                run: Box::new(run),
1208                                name: static_signal_name_arg(&args[1].expr, "signal_run")?,
1209                                payload: Box::new(payload),
1210                            },
1211                            self.span_from(token_span.start),
1212                            [(0, args[0].clone()), (1, args[2].clone())],
1213                        ));
1214                    }
1215                    let arg_exprs = args.iter().map(|arg| arg.expr.clone()).collect();
1216                    Ok(ParsedExpr::node(
1217                        Expr::BuiltinCall {
1218                            name,
1219                            args: arg_exprs,
1220                        },
1221                        self.span_from(token_span.start),
1222                        args.into_iter()
1223                            .enumerate()
1224                            .map(|(index, arg)| (index as u32, arg)),
1225                    ))
1226                } else {
1227                    Ok(ParsedExpr::leaf(Expr::Variable(name), token_span))
1228                }
1229            }
1230            TokenKind::LParen => {
1231                self.bump();
1232                let expr = self.parse_expr()?;
1233                self.expect_exact(TokenKind::RParen, "`)`")?;
1234                Ok(expr)
1235            }
1236            TokenKind::LBracket => self.parse_list(),
1237            TokenKind::LBrace => self.parse_record(),
1238            TokenKind::Call => Err(ParseError::Unexpected {
1239                found: "`call`".to_string(),
1240                span: self.peek().span,
1241            }),
1242            _ => Err(self.unexpected()),
1243        }
1244    }
1245
1246    fn parse_list(&mut self) -> Result<ParsedExpr, ParseError> {
1247        let start = self.peek().span.start;
1248        self.expect_exact(TokenKind::LBracket, "`[`")?;
1249        if matches!(self.peek_kind(), TokenKind::RBracket) {
1250            self.expect_exact(TokenKind::RBracket, "`]`")?;
1251            return Ok(ParsedExpr::node(
1252                Expr::List(Vec::new()),
1253                self.span_from(start),
1254                [],
1255            ));
1256        }
1257
1258        let first = self.parse_expr()?;
1259        if matches!(self.peek_kind(), TokenKind::For) {
1260            let parsed_clauses = self.parse_list_comprehension_clauses()?;
1261            self.expect_exact(TokenKind::RBracket, "`]`")?;
1262            let clauses = parsed_clauses
1263                .iter()
1264                .map(|parsed| parsed.clause.clone())
1265                .collect::<Vec<_>>();
1266            let mut children = parsed_clauses
1267                .into_iter()
1268                .enumerate()
1269                .map(|(index, parsed)| (index as u32, parsed.expr_span))
1270                .collect::<Vec<_>>();
1271            children.push((children.len() as u32, first.clone()));
1272            return Ok(ParsedExpr::node(
1273                Expr::ListComprehension {
1274                    element: Box::new(first.expr.clone()),
1275                    clauses,
1276                },
1277                self.span_from(start),
1278                children,
1279            ));
1280        }
1281
1282        let mut items = Vec::new();
1283        let mut children = Vec::new();
1284        children.push((0, first));
1285        items.push(children.last().expect("item").1.expr.clone());
1286        if matches!(self.peek_kind(), TokenKind::Comma) {
1287            self.bump();
1288        } else {
1289            self.expect_exact(TokenKind::RBracket, "`]`")?;
1290            return Ok(ParsedExpr::node(
1291                Expr::List(items),
1292                self.span_from(start),
1293                children,
1294            ));
1295        }
1296        while !matches!(self.peek_kind(), TokenKind::RBracket) {
1297            let item = self.parse_expr()?;
1298            children.push((items.len() as u32, item));
1299            items.push(children.last().expect("item").1.expr.clone());
1300            if matches!(self.peek_kind(), TokenKind::Comma) {
1301                self.bump();
1302                continue;
1303            }
1304            break;
1305        }
1306        self.expect_exact(TokenKind::RBracket, "`]`")?;
1307        Ok(ParsedExpr::node(
1308            Expr::List(items),
1309            self.span_from(start),
1310            children,
1311        ))
1312    }
1313
1314    fn parse_list_comprehension_clauses(
1315        &mut self,
1316    ) -> Result<Vec<ParsedListComprehensionClause>, ParseError> {
1317        let mut clauses = Vec::new();
1318        while matches!(self.peek_kind(), TokenKind::For) {
1319            self.bump();
1320            let binding = self.expect_ident()?;
1321            self.expect_exact(TokenKind::In, "`in`")?;
1322            let iterable = self.parse_expr()?;
1323            clauses.push(ParsedListComprehensionClause {
1324                clause: ListComprehensionClause::For {
1325                    binding,
1326                    iterable: iterable.expr.clone(),
1327                },
1328                expr_span: iterable,
1329            });
1330            while matches!(self.peek_kind(), TokenKind::If) {
1331                self.bump();
1332                let condition = self.parse_expr()?;
1333                clauses.push(ParsedListComprehensionClause {
1334                    clause: ListComprehensionClause::If {
1335                        condition: condition.expr.clone(),
1336                    },
1337                    expr_span: condition,
1338                });
1339            }
1340        }
1341        Ok(clauses)
1342    }
1343
1344    fn parse_record(&mut self) -> Result<ParsedExpr, ParseError> {
1345        let start = self.peek().span.start;
1346        self.expect_exact(TokenKind::LBrace, "`{`")?;
1347        let entries = self.parse_record_entries()?;
1348        self.expect_exact(TokenKind::RBrace, "`}`")?;
1349        let expr_entries = entries
1350            .iter()
1351            .map(|(key, value)| (key.clone(), value.expr.clone()))
1352            .collect();
1353        Ok(ParsedExpr::node(
1354            Expr::Record(expr_entries),
1355            self.span_from(start),
1356            entries
1357                .into_iter()
1358                .enumerate()
1359                .map(|(index, (_, value))| (index as u32, value)),
1360        ))
1361    }
1362
1363    fn parse_record_entries(&mut self) -> Result<Vec<(AstString, ParsedExpr)>, ParseError> {
1364        let mut entries = Vec::new();
1365        while !matches!(self.peek_kind(), TokenKind::RBrace) {
1366            let key = self.expect_key_name()?;
1367            self.expect_exact(TokenKind::Colon, "`:`")?;
1368            let value = self.parse_expr()?;
1369            entries.push((key, value));
1370            if matches!(self.peek_kind(), TokenKind::Comma) {
1371                self.bump();
1372                continue;
1373            }
1374            break;
1375        }
1376        Ok(entries)
1377    }
1378
1379    fn parse_call_arguments(&mut self) -> Result<Vec<ParsedExpr>, ParseError> {
1380        self.expect_exact(TokenKind::LParen, "`(`")?;
1381        let mut args = Vec::new();
1382        if !matches!(self.peek_kind(), TokenKind::RParen) {
1383            loop {
1384                args.push(self.parse_expr()?);
1385                if matches!(self.peek_kind(), TokenKind::Comma) {
1386                    self.bump();
1387                    if matches!(self.peek_kind(), TokenKind::RParen) {
1388                        break;
1389                    }
1390                    continue;
1391                }
1392                break;
1393            }
1394        }
1395        self.expect_exact(TokenKind::RParen, "`)`")?;
1396        Ok(args)
1397    }
1398
1399    fn parse_named_arguments(&mut self) -> Result<Vec<(AstString, ParsedExpr)>, ParseError> {
1400        let mut entries = Vec::new();
1401        while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
1402            let key = self.expect_key_name()?;
1403            self.expect_exact(TokenKind::Colon, "`:`")?;
1404            let value = self.parse_expr()?;
1405            entries.push((key, value));
1406            if matches!(self.peek_kind(), TokenKind::Comma) {
1407                self.bump();
1408                continue;
1409            }
1410            break;
1411        }
1412        Ok(entries)
1413    }
1414
1415    fn parse_process_start_expr(&mut self, start: usize) -> Result<ParsedExpr, ParseError> {
1416        if matches!(self.peek_kind(), TokenKind::LBrace) || self.paren_group_followed_by_lbrace() {
1417            return Err(ParseError::Unexpected {
1418                found: "inline `start` process body".to_string(),
1419                span: self.peek().span,
1420            });
1421        }
1422        let process = self.expect_ident()?;
1423        self.expect_exact(TokenKind::LParen, "`(`")?;
1424        let args = self.parse_named_arguments()?;
1425        self.expect_exact(TokenKind::RParen, "`)`")?;
1426        let expr_args = args
1427            .iter()
1428            .map(|(name, value)| (name.clone(), value.expr.clone()))
1429            .collect();
1430        Ok(ParsedExpr::node(
1431            Expr::StartProcess(ProcessStartExpr {
1432                process,
1433                args: expr_args,
1434            }),
1435            self.span_from(start),
1436            args.into_iter()
1437                .enumerate()
1438                .map(|(index, (_, value))| (index as u32, value)),
1439        ))
1440    }
1441
1442    fn parse_sleep_expr(&mut self, start: usize) -> Result<ParsedExpr, ParseError> {
1443        if matches!(self.peek_kind(), TokenKind::For) {
1444            self.bump();
1445            let expr = self.parse_expr()?;
1446            return Ok(ParsedExpr::node(
1447                Expr::SleepFor(Box::new(expr.expr.clone())),
1448                Span {
1449                    start,
1450                    end: expr.span.end,
1451                },
1452                [(0, expr)],
1453            ));
1454        }
1455        if self.peek_contextual("until") {
1456            self.bump();
1457            let expr = self.parse_expr()?;
1458            return Ok(ParsedExpr::node(
1459                Expr::SleepUntil(Box::new(expr.expr.clone())),
1460                Span {
1461                    start,
1462                    end: expr.span.end,
1463                },
1464                [(0, expr)],
1465            ));
1466        }
1467        Err(ParseError::Expected {
1468            expected: "`for` or `until`",
1469            found: render_kind(self.peek_kind()),
1470            span: self.peek().span,
1471        })
1472    }
1473
1474    fn parse_type_object(&mut self) -> Result<TypeExpr, ParseError> {
1475        self.expect_exact(TokenKind::LBrace, "`{`")?;
1476        self.parse_type_object_body_after_lbrace()
1477    }
1478
1479    fn parse_type_object_body(&mut self) -> Result<TypeExpr, ParseError> {
1480        self.expect_exact(TokenKind::LBrace, "`{`")?;
1481        self.parse_type_object_body_after_lbrace()
1482    }
1483
1484    fn parse_type_object_body_after_lbrace(&mut self) -> Result<TypeExpr, ParseError> {
1485        let mut fields = Vec::new();
1486        let mut seen = std::collections::HashSet::new();
1487        while !matches!(self.peek_kind(), TokenKind::RBrace) {
1488            let name_token_span = self.peek().span;
1489            let name = self.expect_key_name()?;
1490            if !seen.insert(name.clone()) {
1491                return Err(ParseError::Expected {
1492                    expected: "unique field name",
1493                    found: format!("duplicate field `{name}`"),
1494                    span: name_token_span,
1495                });
1496            }
1497            self.expect_exact(TokenKind::Colon, "`:`")?;
1498            let ty = self.parse_type_expr()?;
1499            let optional = if matches!(self.peek_kind(), TokenKind::Question) {
1500                self.bump();
1501                true
1502            } else {
1503                false
1504            };
1505            fields.push(TypeField { name, ty, optional });
1506            if matches!(self.peek_kind(), TokenKind::Comma) {
1507                self.bump();
1508                continue;
1509            }
1510            break;
1511        }
1512        self.expect_exact(TokenKind::RBrace, "`}`")?;
1513        Ok(TypeExpr::Object(fields))
1514    }
1515
1516    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
1517        let first = self.parse_type_term()?;
1518        if !matches!(self.peek_kind(), TokenKind::Pipe) {
1519            return Ok(first);
1520        }
1521        // Union: `str | null`, `int | str | null`, etc. `|` has lower
1522        // precedence than any other type constructor — once we see it
1523        // at top level, keep parsing `| <term>` until the run ends.
1524        let mut variants = vec![first];
1525        while matches!(self.peek_kind(), TokenKind::Pipe) {
1526            self.bump();
1527            variants.push(self.parse_type_term()?);
1528        }
1529        Ok(TypeExpr::Union(variants))
1530    }
1531
1532    fn parse_type_term(&mut self) -> Result<TypeExpr, ParseError> {
1533        let token = self.peek().clone();
1534        match token.kind {
1535            TokenKind::Null => {
1536                self.bump();
1537                Ok(TypeExpr::Null)
1538            }
1539            TokenKind::String(value) => {
1540                self.bump();
1541                Ok(TypeExpr::Enum(vec![value]))
1542            }
1543            // A bare `{` in type position is the classic "forgot the
1544            // `Type` keyword" mistake (`foo: { ok: bool }` instead of
1545            // `foo: Type { ok: bool }`). Surface a targeted diagnostic
1546            // rather than the generic "expected type expression" shrug.
1547            TokenKind::LBrace => self.parse_type_object_body(),
1548            TokenKind::Ident(_name) => {
1549                let name = self.parse_type_name()?;
1550                match name.as_str() {
1551                    "str" | "string" => Ok(TypeExpr::Str),
1552                    "int" | "integer" => Ok(TypeExpr::Int),
1553                    "float" | "number" => Ok(TypeExpr::Float),
1554                    "bool" | "boolean" => Ok(TypeExpr::Bool),
1555                    "dict" | "object" => Ok(TypeExpr::Dict),
1556                    "any" => Ok(TypeExpr::Any),
1557                    "enum" => {
1558                        self.expect_exact(TokenKind::LBracket, "`[`")?;
1559                        let mut values = Vec::new();
1560                        if !matches!(self.peek_kind(), TokenKind::RBracket) {
1561                            loop {
1562                                let value = self.expect_string_literal()?;
1563                                values.push(value);
1564                                if matches!(self.peek_kind(), TokenKind::Comma) {
1565                                    self.bump();
1566                                    continue;
1567                                }
1568                                break;
1569                            }
1570                        }
1571                        if values.is_empty() {
1572                            return Err(ParseError::Expected {
1573                                expected: "at least one enum string literal",
1574                                found: "empty enum".to_string(),
1575                                span: token.span,
1576                            });
1577                        }
1578                        self.expect_exact(TokenKind::RBracket, "`]`")?;
1579                        Ok(TypeExpr::Enum(values))
1580                    }
1581                    "list" => {
1582                        self.expect_exact(TokenKind::LBracket, "`[`")?;
1583                        let inner = self.parse_type_expr()?;
1584                        self.expect_exact(TokenKind::RBracket, "`]`")?;
1585                        Ok(TypeExpr::List(Box::new(inner)))
1586                    }
1587                    "Process" => {
1588                        self.expect_exact(TokenKind::Less, "`<`")?;
1589                        let input = self.parse_type_expr()?;
1590                        self.expect_exact(TokenKind::Comma, "`,`")?;
1591                        let output = self.parse_type_expr()?;
1592                        self.expect_exact(TokenKind::Greater, "`>`")?;
1593                        Ok(TypeExpr::Process {
1594                            input: Box::new(input),
1595                            output: Box::new(output),
1596                            input_count: 1,
1597                        })
1598                    }
1599                    "TriggerHandle" => {
1600                        self.expect_exact(TokenKind::Less, "`<`")?;
1601                        let event = self.parse_type_expr()?;
1602                        self.expect_exact(TokenKind::Greater, "`>`")?;
1603                        Ok(TypeExpr::TriggerHandle(Box::new(event)))
1604                    }
1605                    "Type" => self.parse_type_object(),
1606                    _ => Ok(TypeExpr::Ref(name)),
1607                }
1608            }
1609            _ => Err(ParseError::Expected {
1610                expected: "type expression",
1611                found: render_kind(&token.kind),
1612                span: token.span,
1613            }),
1614        }
1615    }
1616
1617    fn expect_string_literal(&mut self) -> Result<AstString, ParseError> {
1618        let token = self.bump().clone();
1619        match token.kind {
1620            TokenKind::String(value) => Ok(value),
1621            other => Err(ParseError::Expected {
1622                expected: "string literal",
1623                found: render_kind(&other),
1624                span: token.span,
1625            }),
1626        }
1627    }
1628
1629    fn expect_ident(&mut self) -> Result<AstString, ParseError> {
1630        let token = self.bump();
1631        match &token.kind {
1632            TokenKind::Ident(name) => Ok(name.clone()),
1633            other => Err(ParseError::Expected {
1634                expected: "identifier",
1635                found: render_kind(other),
1636                span: token.span,
1637            }),
1638        }
1639    }
1640
1641    fn parse_type_name(&mut self) -> Result<AstString, ParseError> {
1642        let mut path = vec![self.expect_ident()?];
1643        while matches!(self.peek_kind(), TokenKind::Dot) {
1644            self.bump();
1645            path.push(self.expect_ident()?);
1646        }
1647        Ok(path
1648            .iter()
1649            .map(AstString::as_str)
1650            .collect::<Vec<_>>()
1651            .join(".")
1652            .into())
1653    }
1654
1655    fn expect_key_name(&mut self) -> Result<AstString, ParseError> {
1656        let token = self.bump();
1657        match &token.kind {
1658            TokenKind::Ident(name) | TokenKind::String(name) => Ok(name.clone()),
1659            other => keyword_key_name(other)
1660                .map(Into::into)
1661                .ok_or_else(|| ParseError::Expected {
1662                    expected: "identifier, string key, or keyword key",
1663                    found: render_kind(other),
1664                    span: token.span,
1665                }),
1666        }
1667    }
1668
1669    fn expect_exact(
1670        &mut self,
1671        expected_kind: TokenKind,
1672        expected: &'static str,
1673    ) -> Result<(), ParseError> {
1674        let token = self.bump();
1675        if std::mem::discriminant(&token.kind) == std::mem::discriminant(&expected_kind) {
1676            Ok(())
1677        } else {
1678            Err(ParseError::Expected {
1679                expected,
1680                found: render_kind(&token.kind),
1681                span: token.span,
1682            })
1683        }
1684    }
1685
1686    fn unexpected(&mut self) -> ParseError {
1687        let token = self.peek();
1688        ParseError::Unexpected {
1689            found: render_kind(&token.kind),
1690            span: token.span,
1691        }
1692    }
1693
1694    fn peek_assignment_target(&self) -> bool {
1695        if !matches!(self.peek_kind(), TokenKind::Ident(_)) {
1696            return false;
1697        }
1698
1699        let mut index = self.index + 1;
1700        loop {
1701            match self.tokens.get(index).map(|token| &token.kind) {
1702                Some(TokenKind::Dot) => {
1703                    if !self
1704                        .tokens
1705                        .get(index + 1)
1706                        .is_some_and(|token| token_can_be_key(&token.kind))
1707                    {
1708                        return false;
1709                    }
1710                    index += 2;
1711                }
1712                Some(TokenKind::LBracket) => {
1713                    let Some(after_index) = self.skip_bracketed_index(index) else {
1714                        return false;
1715                    };
1716                    index = after_index;
1717                }
1718                Some(TokenKind::Equal) => return true,
1719                _ => return false,
1720            }
1721        }
1722    }
1723
1724    fn skip_bracketed_index(&self, start: usize) -> Option<usize> {
1725        debug_assert!(matches!(
1726            self.tokens.get(start).map(|token| &token.kind),
1727            Some(TokenKind::LBracket)
1728        ));
1729        let mut parens = 0usize;
1730        let mut brackets = 1usize;
1731        let mut braces = 0usize;
1732        for (offset, token) in self.tokens.iter().enumerate().skip(start + 1) {
1733            match &token.kind {
1734                TokenKind::LParen => parens += 1,
1735                TokenKind::RParen => parens = parens.checked_sub(1)?,
1736                TokenKind::LBracket => brackets += 1,
1737                TokenKind::RBracket => {
1738                    brackets = brackets.checked_sub(1)?;
1739                    if brackets == 0 && parens == 0 && braces == 0 {
1740                        return Some(offset + 1);
1741                    }
1742                }
1743                TokenKind::LBrace => braces += 1,
1744                TokenKind::RBrace => braces = braces.checked_sub(1)?,
1745                TokenKind::Eof => return None,
1746                _ => {}
1747            }
1748        }
1749        None
1750    }
1751
1752    fn question_starts_ternary(&self) -> bool {
1753        debug_assert!(matches!(self.peek_kind(), TokenKind::Question));
1754        let Some(next) = self.tokens.get(self.index + 1) else {
1755            return false;
1756        };
1757        if !token_can_start_expr(&next.kind) {
1758            return false;
1759        }
1760
1761        let mut parens = 0usize;
1762        let mut brackets = 0usize;
1763        let mut braces = 0usize;
1764        for token in self.tokens.iter().skip(self.index + 1) {
1765            match &token.kind {
1766                TokenKind::Colon if parens == 0 && brackets == 0 && braces == 0 => return true,
1767                TokenKind::Equal if parens == 0 && brackets == 0 && braces == 0 => return false,
1768                TokenKind::Comma | TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace
1769                    if parens == 0 && brackets == 0 && braces == 0 =>
1770                {
1771                    return false;
1772                }
1773                TokenKind::Eof => return false,
1774                TokenKind::LParen => parens += 1,
1775                TokenKind::RParen => {
1776                    if parens == 0 {
1777                        return false;
1778                    }
1779                    parens -= 1;
1780                }
1781                TokenKind::LBracket => brackets += 1,
1782                TokenKind::RBracket => {
1783                    if brackets == 0 {
1784                        return false;
1785                    }
1786                    brackets -= 1;
1787                }
1788                TokenKind::LBrace => braces += 1,
1789                TokenKind::RBrace => {
1790                    if braces == 0 {
1791                        return false;
1792                    }
1793                    braces -= 1;
1794                }
1795                _ => {}
1796            }
1797        }
1798        false
1799    }
1800
1801    fn paren_group_followed_by_lbrace(&self) -> bool {
1802        if !matches!(self.peek_kind(), TokenKind::LParen) {
1803            return false;
1804        }
1805        let mut depth = 0usize;
1806        for (index, token) in self.tokens.iter().enumerate().skip(self.index) {
1807            match &token.kind {
1808                TokenKind::LParen => depth += 1,
1809                TokenKind::RParen => {
1810                    depth = depth.saturating_sub(1);
1811                    if depth == 0 {
1812                        return self
1813                            .tokens
1814                            .get(index + 1)
1815                            .is_some_and(|next| matches!(next.kind, TokenKind::LBrace));
1816                    }
1817                }
1818                TokenKind::Eof => return false,
1819                _ => {}
1820            }
1821        }
1822        false
1823    }
1824
1825    fn peek_contextual(&self, keyword: &str) -> bool {
1826        matches!(self.peek_kind(), TokenKind::Ident(name) if name.as_str() == keyword)
1827    }
1828
1829    fn expect_contextual(&mut self, keyword: &'static str) -> Result<(), ParseError> {
1830        let token = self.bump();
1831        match &token.kind {
1832            TokenKind::Ident(name) if name.as_str() == keyword => Ok(()),
1833            other => Err(ParseError::Expected {
1834                expected: keyword,
1835                found: render_kind(other),
1836                span: token.span,
1837            }),
1838        }
1839    }
1840
1841    fn at_eof(&self) -> bool {
1842        matches!(self.peek_kind(), TokenKind::Eof)
1843    }
1844
1845    fn peek_kind(&self) -> &TokenKind {
1846        &self.tokens[self.index].kind
1847    }
1848
1849    fn peek(&self) -> &Token {
1850        &self.tokens[self.index]
1851    }
1852
1853    fn bump(&mut self) -> &Token {
1854        let token = &self.tokens[self.index];
1855        self.index += 1;
1856        token
1857    }
1858}
1859
1860fn static_signal_name_arg(expr: &Expr, call: &'static str) -> Result<AstString, ParseError> {
1861    if let Expr::String(name) = expr {
1862        return Ok(name.clone());
1863    }
1864    Err(ParseError::Unexpected {
1865        found: format!("non-literal signal name in `{call}`"),
1866        span: Span { start: 0, end: 0 },
1867    })
1868}
1869
1870fn token_can_be_key(kind: &TokenKind) -> bool {
1871    matches!(kind, TokenKind::Ident(_) | TokenKind::String(_)) || keyword_key_name(kind).is_some()
1872}
1873
1874fn keyword_key_name(kind: &TokenKind) -> Option<&'static str> {
1875    Some(match kind {
1876        TokenKind::If => "if",
1877        TokenKind::Else => "else",
1878        TokenKind::For => "for",
1879        TokenKind::In => "in",
1880        TokenKind::Await => "await",
1881        TokenKind::Cancel => "cancel",
1882        TokenKind::Submit => "submit",
1883        TokenKind::Print => "print",
1884        TokenKind::Call => "call",
1885        TokenKind::Ident(name) if matches!(name.as_str(), "yield" | "wake" | "finish" | "fail") => {
1886            return Some(match name.as_str() {
1887                "yield" => "yield",
1888                "wake" => "wake",
1889                "finish" => "finish",
1890                "fail" => "fail",
1891                _ => unreachable!(),
1892            });
1893        }
1894        TokenKind::And => "and",
1895        TokenKind::Or => "or",
1896        TokenKind::Not => "not",
1897        TokenKind::True => "true",
1898        TokenKind::False => "false",
1899        TokenKind::Null => "null",
1900        _ => return None,
1901    })
1902}
1903
1904fn token_can_start_expr(kind: &TokenKind) -> bool {
1905    matches!(
1906        kind,
1907        TokenKind::Null
1908            | TokenKind::True
1909            | TokenKind::False
1910            | TokenKind::Number(_)
1911            | TokenKind::String(_)
1912            | TokenKind::Ident(_)
1913            | TokenKind::LParen
1914            | TokenKind::LBracket
1915            | TokenKind::LBrace
1916            | TokenKind::Await
1917            | TokenKind::Minus
1918            | TokenKind::Bang
1919            | TokenKind::Not
1920    )
1921}
1922
1923fn render_kind(kind: &TokenKind) -> String {
1924    match kind {
1925        TokenKind::Ident(name) => format!("identifier `{name}`"),
1926        TokenKind::String(value) => format!("string {:?}", value),
1927        TokenKind::Number(value) => format!("number {value}"),
1928        TokenKind::LBrace => "`{`".to_string(),
1929        TokenKind::RBrace => "`}`".to_string(),
1930        TokenKind::LParen => "`(`".to_string(),
1931        TokenKind::RParen => "`)`".to_string(),
1932        TokenKind::LBracket => "`[`".to_string(),
1933        TokenKind::RBracket => "`]`".to_string(),
1934        TokenKind::Comma => "`,`".to_string(),
1935        TokenKind::Colon => "`:`".to_string(),
1936        TokenKind::At => "`@`".to_string(),
1937        TokenKind::Question => "`?`".to_string(),
1938        TokenKind::Dot => "`.`".to_string(),
1939        TokenKind::Bang => "`!`".to_string(),
1940        TokenKind::Equal => "`=`".to_string(),
1941        TokenKind::DoubleEqual => "`==`".to_string(),
1942        TokenKind::BangEqual => "`!=`".to_string(),
1943        TokenKind::AndAnd => "`&&`".to_string(),
1944        TokenKind::OrOr => "`||`".to_string(),
1945        TokenKind::Pipe => "`|`".to_string(),
1946        TokenKind::Less => "`<`".to_string(),
1947        TokenKind::LessEqual => "`<=`".to_string(),
1948        TokenKind::Greater => "`>`".to_string(),
1949        TokenKind::GreaterEqual => "`>=`".to_string(),
1950        TokenKind::Plus => "`+`".to_string(),
1951        TokenKind::Minus => "`-`".to_string(),
1952        TokenKind::Star => "`*`".to_string(),
1953        TokenKind::Slash => "`/`".to_string(),
1954        TokenKind::Percent => "`%`".to_string(),
1955        TokenKind::If => "`if`".to_string(),
1956        TokenKind::Else => "`else`".to_string(),
1957        TokenKind::For => "`for`".to_string(),
1958        TokenKind::In => "`in`".to_string(),
1959        TokenKind::Await => "`await`".to_string(),
1960        TokenKind::Cancel => "`cancel`".to_string(),
1961        TokenKind::Submit => "`submit`".to_string(),
1962        TokenKind::Print => "`print`".to_string(),
1963        TokenKind::Call => "`call`".to_string(),
1964        TokenKind::And => "`and`".to_string(),
1965        TokenKind::Or => "`or`".to_string(),
1966        TokenKind::Not => "`not`".to_string(),
1967        TokenKind::True => "`true`".to_string(),
1968        TokenKind::False => "`false`".to_string(),
1969        TokenKind::Null => "`null`".to_string(),
1970        TokenKind::Eof => "end of input".to_string(),
1971    }
1972}
1973
1974include!("parser/tests.rs");