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