Skip to main content

sage_parser/
parser.rs

1//! Parser implementation using chumsky.
2//!
3//! This module transforms a token stream into an AST.
4
5use crate::ast::{
6    AgentDecl, BeliefDecl, BinOp, Block, ClosureParam, ConstDecl, ElseBranch, EnumDecl, EventKind,
7    Expr, FieldInit, FnDecl, HandlerDecl, Literal, MapEntry, MatchArm, ModDecl, Param, Pattern,
8    Program, RecordDecl, RecordField, Stmt, StringPart, StringTemplate, UnaryOp, UseDecl, UseKind,
9};
10use chumsky::prelude::*;
11use chumsky::BoxedParser;
12use sage_lexer::{Spanned, Token};
13use sage_types::{Ident, Span, TypeExpr};
14use std::ops::Range;
15use std::sync::Arc;
16
17/// Parse error type using byte range spans.
18pub type ParseError = Simple<Token>;
19
20/// Parse a sequence of tokens into a Program AST.
21///
22/// # Errors
23///
24/// Returns parse errors if the token stream doesn't form a valid program.
25#[must_use]
26#[allow(clippy::needless_pass_by_value)] // Arc<str> is cheap to clone and idiomatic here
27pub fn parse(tokens: &[Spanned], source: Arc<str>) -> (Option<Program>, Vec<ParseError>) {
28    let len = source.len();
29
30    // Convert our Spanned tokens to (Token, Range<usize>) for chumsky
31    let token_spans: Vec<(Token, Range<usize>)> = tokens
32        .iter()
33        .map(|s| (s.token.clone(), s.start..s.end))
34        .collect();
35
36    let stream = chumsky::Stream::from_iter(len..len, token_spans.into_iter());
37
38    let (ast, errors) = program_parser(Arc::clone(&source)).parse_recovery(stream);
39
40    (ast, errors)
41}
42
43// =============================================================================
44// Top-level parsers
45// =============================================================================
46
47/// Parser for a complete program.
48#[allow(clippy::needless_pass_by_value)]
49fn program_parser(source: Arc<str>) -> impl Parser<Token, Program, Error = ParseError> {
50    let src = source.clone();
51    let src2 = source.clone();
52
53    // Top-level declarations with recovery - skip to next keyword on error
54    let top_level = mod_parser(source.clone())
55        .or(use_parser(source.clone()))
56        .or(record_parser(source.clone()))
57        .or(enum_parser(source.clone()))
58        .or(const_parser(source.clone()))
59        .or(agent_parser(source.clone()))
60        .or(fn_parser(source.clone()))
61        .recover_with(skip_then_retry_until([
62            Token::KwMod,
63            Token::KwUse,
64            Token::KwPub,
65            Token::KwRecord,
66            Token::KwEnum,
67            Token::KwConst,
68            Token::KwAgent,
69            Token::KwFn,
70            Token::KwRun,
71        ]));
72
73    let run_stmt = just(Token::KwRun)
74        .ignore_then(ident_token_parser(src.clone()))
75        .then_ignore(just(Token::Semicolon))
76        .or_not();
77
78    top_level.repeated().then(run_stmt).map_with_span(
79        move |(items, run_agent), span: Range<usize>| {
80            let mut mod_decls = Vec::new();
81            let mut use_decls = Vec::new();
82            let mut records = Vec::new();
83            let mut enums = Vec::new();
84            let mut consts = Vec::new();
85            let mut agents = Vec::new();
86            let mut functions = Vec::new();
87
88            for item in items {
89                match item {
90                    TopLevel::Mod(m) => mod_decls.push(m),
91                    TopLevel::Use(u) => use_decls.push(u),
92                    TopLevel::Record(r) => records.push(r),
93                    TopLevel::Enum(e) => enums.push(e),
94                    TopLevel::Const(c) => consts.push(c),
95                    TopLevel::Agent(a) => agents.push(a),
96                    TopLevel::Function(f) => functions.push(f),
97                }
98            }
99
100            Program {
101                mod_decls,
102                use_decls,
103                records,
104                enums,
105                consts,
106                agents,
107                functions,
108                run_agent,
109                span: make_span(&src2, span),
110            }
111        },
112    )
113}
114
115/// Helper enum for collecting top-level declarations.
116enum TopLevel {
117    Mod(ModDecl),
118    Use(UseDecl),
119    Record(RecordDecl),
120    Enum(EnumDecl),
121    Const(ConstDecl),
122    Agent(AgentDecl),
123    Function(FnDecl),
124}
125
126// =============================================================================
127// Module declaration parsers
128// =============================================================================
129
130/// Parser for a mod declaration: `mod foo` or `pub mod foo`
131#[allow(clippy::needless_pass_by_value)]
132fn mod_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
133    let src = source.clone();
134
135    just(Token::KwPub)
136        .or_not()
137        .then_ignore(just(Token::KwMod))
138        .then(ident_token_parser(src.clone()))
139        .then_ignore(just(Token::Semicolon))
140        .map_with_span(move |(is_pub, name), span: Range<usize>| {
141            TopLevel::Mod(ModDecl {
142                is_pub: is_pub.is_some(),
143                name,
144                span: make_span(&src, span),
145            })
146        })
147}
148
149/// Parser for a use declaration: `use path::to::Item` or `use path::{A, B}`
150#[allow(clippy::needless_pass_by_value)]
151fn use_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
152    let src = source.clone();
153    let src2 = source.clone();
154    let src3 = source.clone();
155    let src4 = source.clone();
156
157    // Simple use: `use a::b::C` or `use a::b::C as D`
158    let simple_use = just(Token::KwPub)
159        .or_not()
160        .then_ignore(just(Token::KwUse))
161        .then(
162            ident_token_parser(src.clone())
163                .separated_by(just(Token::ColonColon))
164                .at_least(1),
165        )
166        .then(
167            just(Token::KwAs)
168                .ignore_then(ident_token_parser(src.clone()))
169                .or_not(),
170        )
171        .then_ignore(just(Token::Semicolon))
172        .map_with_span(move |((is_pub, path), alias), span: Range<usize>| {
173            TopLevel::Use(UseDecl {
174                is_pub: is_pub.is_some(),
175                path,
176                kind: UseKind::Simple(alias),
177                span: make_span(&src, span),
178            })
179        });
180
181    // Group import item: `Name` or `Name as Alias`
182    let group_item = ident_token_parser(src2.clone()).then(
183        just(Token::KwAs)
184            .ignore_then(ident_token_parser(src2.clone()))
185            .or_not(),
186    );
187
188    // Group use: `use a::b::{C, D as E}`
189    let group_use = just(Token::KwPub)
190        .or_not()
191        .then_ignore(just(Token::KwUse))
192        .then(
193            ident_token_parser(src3.clone())
194                .then_ignore(just(Token::ColonColon))
195                .repeated()
196                .at_least(1),
197        )
198        .then(
199            group_item
200                .separated_by(just(Token::Comma))
201                .allow_trailing()
202                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
203        )
204        .then_ignore(just(Token::Semicolon))
205        .map_with_span(move |((is_pub, path), items), span: Range<usize>| {
206            TopLevel::Use(UseDecl {
207                is_pub: is_pub.is_some(),
208                path,
209                kind: UseKind::Group(items),
210                span: make_span(&src3, span),
211            })
212        });
213
214    // Glob use: `use a::b::*`
215    let glob_use = just(Token::KwPub)
216        .or_not()
217        .then_ignore(just(Token::KwUse))
218        .then(
219            ident_token_parser(src4.clone())
220                .then_ignore(just(Token::ColonColon))
221                .repeated()
222                .at_least(1),
223        )
224        .then_ignore(just(Token::Star))
225        .then_ignore(just(Token::Semicolon))
226        .map_with_span(move |(is_pub, path), span: Range<usize>| {
227            TopLevel::Use(UseDecl {
228                is_pub: is_pub.is_some(),
229                path,
230                kind: UseKind::Glob,
231                span: make_span(&src4, span),
232            })
233        });
234
235    // Try group/glob first (they need :: before { or *), then simple
236    group_use.or(glob_use).or(simple_use)
237}
238
239// =============================================================================
240// Record, Enum, Const parsers
241// =============================================================================
242
243/// Parser for a record declaration: `record Point { x: Int, y: Int }`
244#[allow(clippy::needless_pass_by_value)]
245fn record_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
246    let src = source.clone();
247    let src2 = source.clone();
248
249    // Record field: `name: Type`
250    let field = ident_token_parser(src.clone())
251        .then_ignore(just(Token::Colon))
252        .then(type_parser(src.clone()))
253        .map_with_span(move |(name, ty), span: Range<usize>| RecordField {
254            name,
255            ty,
256            span: make_span(&src, span),
257        });
258
259    just(Token::KwPub)
260        .or_not()
261        .then_ignore(just(Token::KwRecord))
262        .then(ident_token_parser(src2.clone()))
263        .then(
264            field
265                .separated_by(just(Token::Comma))
266                .allow_trailing()
267                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
268        )
269        .map_with_span(move |((is_pub, name), fields), span: Range<usize>| {
270            TopLevel::Record(RecordDecl {
271                is_pub: is_pub.is_some(),
272                name,
273                fields,
274                span: make_span(&src2, span),
275            })
276        })
277}
278
279/// Parser for an enum declaration: `enum Status { Active, Pending, Done }` or `enum Result { Ok(T), Err(E) }`
280#[allow(clippy::needless_pass_by_value)]
281fn enum_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
282    let src = source.clone();
283    let src2 = source.clone();
284    let src3 = source.clone();
285
286    // Enum variant with optional payload: `Ok(T)` or `None`
287    let variant = ident_token_parser(src.clone())
288        .then(
289            type_parser(src.clone())
290                .delimited_by(just(Token::LParen), just(Token::RParen))
291                .or_not(),
292        )
293        .map_with_span({
294            let src = src.clone();
295            move |(name, payload), span: Range<usize>| crate::ast::EnumVariant {
296                name,
297                payload,
298                span: make_span(&src, span),
299            }
300        });
301
302    just(Token::KwPub)
303        .or_not()
304        .then_ignore(just(Token::KwEnum))
305        .then(ident_token_parser(src3.clone()))
306        .then(
307            variant
308                .separated_by(just(Token::Comma))
309                .allow_trailing()
310                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
311        )
312        .map_with_span(move |((is_pub, name), variants), span: Range<usize>| {
313            TopLevel::Enum(EnumDecl {
314                is_pub: is_pub.is_some(),
315                name,
316                variants,
317                span: make_span(&src2, span),
318            })
319        })
320}
321
322/// Parser for a const declaration: `const MAX_RETRIES: Int = 3`
323#[allow(clippy::needless_pass_by_value)]
324fn const_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
325    let src = source.clone();
326    let src2 = source.clone();
327
328    just(Token::KwPub)
329        .or_not()
330        .then_ignore(just(Token::KwConst))
331        .then(ident_token_parser(src.clone()))
332        .then_ignore(just(Token::Colon))
333        .then(type_parser(src.clone()))
334        .then_ignore(just(Token::Eq))
335        .then(expr_parser(src.clone()))
336        .then_ignore(just(Token::Semicolon))
337        .map_with_span(move |(((is_pub, name), ty), value), span: Range<usize>| {
338            TopLevel::Const(ConstDecl {
339                is_pub: is_pub.is_some(),
340                name,
341                ty,
342                value,
343                span: make_span(&src2, span),
344            })
345        })
346}
347
348// =============================================================================
349// Agent parsers
350// =============================================================================
351
352/// Parser for an agent declaration.
353#[allow(clippy::needless_pass_by_value)]
354fn agent_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
355    let src = source.clone();
356    let src2 = source.clone();
357    let src3 = source.clone();
358    let src4 = source.clone();
359
360    // Agent state fields: `name: Type` (no `belief` keyword in RFC-0005)
361    // We still call them "beliefs" internally for backwards compatibility
362    let belief = ident_token_parser(src.clone())
363        .then_ignore(just(Token::Colon))
364        .then(type_parser(src.clone()))
365        .map_with_span(move |(name, ty), span: Range<usize>| BeliefDecl {
366            name,
367            ty,
368            span: make_span(&src, span),
369        });
370
371    let handler = just(Token::KwOn)
372        .ignore_then(event_kind_parser(src2.clone()))
373        .then(block_parser(src2.clone()))
374        .map_with_span(move |(event, body), span: Range<usize>| HandlerDecl {
375            event,
376            body,
377            span: make_span(&src2, span),
378        });
379
380    // Optional `receives MsgType` clause
381    let receives_clause = just(Token::KwReceives)
382        .ignore_then(type_parser(src3.clone()))
383        .or_not();
384
385    just(Token::KwPub)
386        .or_not()
387        .then_ignore(just(Token::KwAgent))
388        .then(ident_token_parser(src3.clone()))
389        .then(receives_clause)
390        .then_ignore(just(Token::LBrace))
391        .then(belief.repeated())
392        .then(handler.repeated())
393        .then_ignore(just(Token::RBrace))
394        .map_with_span(
395            move |((((is_pub, name), receives), beliefs), handlers), span: Range<usize>| {
396                TopLevel::Agent(AgentDecl {
397                    is_pub: is_pub.is_some(),
398                    name,
399                    receives,
400                    beliefs,
401                    handlers,
402                    span: make_span(&src4, span),
403                })
404            },
405        )
406}
407
408/// Parser for event kinds.
409#[allow(clippy::needless_pass_by_value)]
410fn event_kind_parser(source: Arc<str>) -> impl Parser<Token, EventKind, Error = ParseError> {
411    let src = source.clone();
412
413    let start = just(Token::KwStart).to(EventKind::Start);
414    let stop = just(Token::KwStop).to(EventKind::Stop);
415
416    let message = just(Token::KwMessage)
417        .ignore_then(just(Token::LParen))
418        .ignore_then(ident_token_parser(src.clone()))
419        .then_ignore(just(Token::Colon))
420        .then(type_parser(src.clone()))
421        .then_ignore(just(Token::RParen))
422        .map(|(param_name, param_ty)| EventKind::Message {
423            param_name,
424            param_ty,
425        });
426
427    // RFC-0007: on error(e) handler
428    let error = just(Token::KwError)
429        .ignore_then(just(Token::LParen))
430        .ignore_then(ident_token_parser(src))
431        .then_ignore(just(Token::RParen))
432        .map(|param_name| EventKind::Error { param_name });
433
434    start.or(stop).or(message).or(error)
435}
436
437// =============================================================================
438// Function parsers
439// =============================================================================
440
441/// Parser for a function declaration.
442#[allow(clippy::needless_pass_by_value)]
443fn fn_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
444    let src = source.clone();
445    let src2 = source.clone();
446    let src3 = source.clone();
447
448    let param = ident_token_parser(src.clone())
449        .then_ignore(just(Token::Colon))
450        .then(type_parser(src.clone()))
451        .map_with_span(move |(name, ty), span: Range<usize>| Param {
452            name,
453            ty,
454            span: make_span(&src, span),
455        });
456
457    let params = param
458        .separated_by(just(Token::Comma))
459        .allow_trailing()
460        .delimited_by(just(Token::LParen), just(Token::RParen));
461
462    just(Token::KwPub)
463        .or_not()
464        .then_ignore(just(Token::KwFn))
465        .then(ident_token_parser(src2.clone()))
466        .then(params)
467        .then_ignore(just(Token::Arrow))
468        .then(type_parser(src2.clone()))
469        .then(just(Token::KwFails).or_not())
470        .then(block_parser(src2))
471        .map_with_span(
472            move |(((((is_pub, name), params), return_ty), is_fallible), body),
473                  span: Range<usize>| {
474                TopLevel::Function(FnDecl {
475                    is_pub: is_pub.is_some(),
476                    name,
477                    params,
478                    return_ty,
479                    is_fallible: is_fallible.is_some(),
480                    body,
481                    span: make_span(&src3, span),
482                })
483            },
484        )
485}
486
487// =============================================================================
488// Statement parsers
489// =============================================================================
490
491/// Parser for a block of statements.
492/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
493#[allow(clippy::needless_pass_by_value)]
494fn block_parser(source: Arc<str>) -> BoxedParser<'static, Token, Block, ParseError> {
495    let src = source.clone();
496
497    recursive(move |block: Recursive<Token, Block, ParseError>| {
498        let src_inner = src.clone();
499        stmt_parser(src.clone(), block)
500            .repeated()
501            .delimited_by(just(Token::LBrace), just(Token::RBrace))
502            .recover_with(nested_delimiters(
503                Token::LBrace,
504                Token::RBrace,
505                [
506                    (Token::LParen, Token::RParen),
507                    (Token::LBracket, Token::RBracket),
508                ],
509                |_span: Range<usize>| vec![],
510            ))
511            .map_with_span(move |stmts, span: Range<usize>| Block {
512                stmts,
513                span: make_span(&src_inner, span),
514            })
515    })
516    .boxed()
517}
518
519/// Parser for statements.
520#[allow(clippy::needless_pass_by_value)]
521fn stmt_parser(
522    source: Arc<str>,
523    block: impl Parser<Token, Block, Error = ParseError> + Clone + 'static,
524) -> impl Parser<Token, Stmt, Error = ParseError> + Clone {
525    let src = source.clone();
526    let src2 = source.clone();
527    let src3 = source.clone();
528    let src4 = source.clone();
529    let src5 = source.clone();
530    let src6 = source.clone();
531    let src7 = source.clone();
532
533    // Let tuple destructuring: let (a, b) = expr;
534    let src10 = source.clone();
535    let let_tuple_stmt = just(Token::KwLet)
536        .ignore_then(
537            ident_token_parser(src10.clone())
538                .separated_by(just(Token::Comma))
539                .at_least(2)
540                .allow_trailing()
541                .delimited_by(just(Token::LParen), just(Token::RParen)),
542        )
543        .then(
544            just(Token::Colon)
545                .ignore_then(type_parser(src10.clone()))
546                .or_not(),
547        )
548        .then_ignore(just(Token::Eq))
549        .then(expr_parser(src10.clone()))
550        .then_ignore(just(Token::Semicolon))
551        .map_with_span(move |((names, ty), value), span: Range<usize>| Stmt::LetTuple {
552            names,
553            ty,
554            value,
555            span: make_span(&src10, span),
556        });
557
558    let let_stmt = just(Token::KwLet)
559        .ignore_then(ident_token_parser(src.clone()))
560        .then(
561            just(Token::Colon)
562                .ignore_then(type_parser(src.clone()))
563                .or_not(),
564        )
565        .then_ignore(just(Token::Eq))
566        .then(expr_parser(src.clone()))
567        .then_ignore(just(Token::Semicolon))
568        .map_with_span(move |((name, ty), value), span: Range<usize>| Stmt::Let {
569            name,
570            ty,
571            value,
572            span: make_span(&src, span),
573        });
574
575    let return_stmt = just(Token::KwReturn)
576        .ignore_then(expr_parser(src2.clone()).or_not())
577        .then_ignore(just(Token::Semicolon))
578        .map_with_span(move |value, span: Range<usize>| Stmt::Return {
579            value,
580            span: make_span(&src2, span),
581        });
582
583    let if_stmt = recursive(|if_stmt| {
584        let src_if = src3.clone();
585        let block_clone = block.clone();
586
587        just(Token::KwIf)
588            .ignore_then(expr_parser(src3.clone()))
589            .then(block_clone.clone())
590            .then(
591                just(Token::KwElse)
592                    .ignore_then(
593                        if_stmt
594                            .map(|s| ElseBranch::ElseIf(Box::new(s)))
595                            .or(block_clone.map(ElseBranch::Block)),
596                    )
597                    .or_not(),
598            )
599            .map_with_span(
600                move |((condition, then_block), else_block), span: Range<usize>| Stmt::If {
601                    condition,
602                    then_block,
603                    else_block,
604                    span: make_span(&src_if, span),
605                },
606            )
607    });
608
609    let for_stmt = just(Token::KwFor)
610        .ignore_then(for_pattern_parser(src4.clone()))
611        .then_ignore(just(Token::KwIn))
612        .then(expr_parser(src4.clone()))
613        .then(block.clone())
614        .map_with_span(move |((pattern, iter), body), span: Range<usize>| Stmt::For {
615            pattern,
616            iter,
617            body,
618            span: make_span(&src4, span),
619        });
620
621    let while_stmt = just(Token::KwWhile)
622        .ignore_then(expr_parser(src7.clone()))
623        .then(block.clone())
624        .map_with_span(move |(condition, body), span: Range<usize>| Stmt::While {
625            condition,
626            body,
627            span: make_span(&src7, span),
628        });
629
630    let src8 = source.clone();
631    let loop_stmt = just(Token::KwLoop)
632        .ignore_then(block.clone())
633        .map_with_span(move |body, span: Range<usize>| Stmt::Loop {
634            body,
635            span: make_span(&src8, span),
636        });
637
638    let src9 = source.clone();
639    let break_stmt = just(Token::KwBreak)
640        .then_ignore(just(Token::Semicolon))
641        .map_with_span(move |_, span: Range<usize>| Stmt::Break {
642            span: make_span(&src9, span),
643        });
644
645    let assign_stmt = ident_token_parser(src5.clone())
646        .then_ignore(just(Token::Eq))
647        .then(expr_parser(src5.clone()))
648        .then_ignore(just(Token::Semicolon))
649        .map_with_span(move |(name, value), span: Range<usize>| Stmt::Assign {
650            name,
651            value,
652            span: make_span(&src5, span),
653        });
654
655    let expr_stmt = expr_parser(src6.clone())
656        .then_ignore(just(Token::Semicolon))
657        .map_with_span(move |expr, span: Range<usize>| Stmt::Expr {
658            expr,
659            span: make_span(&src6, span),
660        });
661
662    let_tuple_stmt
663        .or(let_stmt)
664        .or(return_stmt)
665        .or(if_stmt)
666        .or(for_stmt)
667        .or(while_stmt)
668        .or(loop_stmt)
669        .or(break_stmt)
670        .or(assign_stmt)
671        .or(expr_stmt)
672}
673
674// =============================================================================
675// Expression parsers
676// =============================================================================
677
678/// Parser for expressions (with precedence climbing for binary ops).
679/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
680#[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
681fn expr_parser(source: Arc<str>) -> BoxedParser<'static, Token, Expr, ParseError> {
682    recursive(move |expr: Recursive<Token, Expr, ParseError>| {
683        let src = source.clone();
684
685        let literal = literal_parser(src.clone());
686        let var = var_parser(src.clone());
687
688        // Parenthesized expression or tuple literal
689        // (expr) is a paren, (expr, expr, ...) is a tuple
690        let paren_or_tuple = just(Token::LParen)
691            .ignore_then(
692                expr.clone()
693                    .separated_by(just(Token::Comma))
694                    .allow_trailing(),
695            )
696            .then_ignore(just(Token::RParen))
697            .map_with_span({
698                let src = src.clone();
699                move |elements, span: Range<usize>| {
700                    if elements.len() == 1 {
701                        // Single element without trailing comma = parenthesized expression
702                        Expr::Paren {
703                            inner: Box::new(elements.into_iter().next().unwrap()),
704                            span: make_span(&src, span),
705                        }
706                    } else {
707                        // Multiple elements or empty = tuple
708                        Expr::Tuple {
709                            elements,
710                            span: make_span(&src, span),
711                        }
712                    }
713                }
714            });
715
716        let list = expr
717            .clone()
718            .separated_by(just(Token::Comma))
719            .allow_trailing()
720            .delimited_by(just(Token::LBracket), just(Token::RBracket))
721            .map_with_span({
722                let src = src.clone();
723                move |elements, span: Range<usize>| Expr::List {
724                    elements,
725                    span: make_span(&src, span),
726                }
727            });
728
729        // self.field or self.method(args)
730        let self_access = just(Token::KwSelf)
731            .ignore_then(just(Token::Dot))
732            .ignore_then(ident_token_parser(src.clone()))
733            .then(
734                expr.clone()
735                    .separated_by(just(Token::Comma))
736                    .allow_trailing()
737                    .delimited_by(just(Token::LParen), just(Token::RParen))
738                    .or_not(),
739            )
740            .map_with_span({
741                let src = src.clone();
742                move |(field, args), span: Range<usize>| match args {
743                    Some(args) => Expr::SelfMethodCall {
744                        method: field,
745                        args,
746                        span: make_span(&src, span),
747                    },
748                    None => Expr::SelfField {
749                        field,
750                        span: make_span(&src, span),
751                    },
752                }
753            });
754
755        // infer("template") or infer("template" -> Type)
756        let infer_expr = just(Token::KwInfer)
757            .ignore_then(just(Token::LParen))
758            .ignore_then(string_template_parser(src.clone()))
759            .then(
760                just(Token::Arrow)
761                    .ignore_then(type_parser(src.clone()))
762                    .or_not(),
763            )
764            .then_ignore(just(Token::RParen))
765            .map_with_span({
766                let src = src.clone();
767                move |(template, result_ty), span: Range<usize>| Expr::Infer {
768                    template,
769                    result_ty,
770                    span: make_span(&src, span),
771                }
772            });
773
774        // spawn Agent { field: value, ... }
775        let spawn_field_init = ident_token_parser(src.clone())
776            .then_ignore(just(Token::Colon))
777            .then(expr.clone())
778            .map_with_span({
779                let src = src.clone();
780                move |(name, value), span: Range<usize>| FieldInit {
781                    name,
782                    value,
783                    span: make_span(&src, span),
784                }
785            });
786
787        let spawn_expr = just(Token::KwSpawn)
788            .ignore_then(ident_token_parser(src.clone()))
789            .then_ignore(just(Token::LBrace))
790            .then(
791                spawn_field_init
792                    .separated_by(just(Token::Comma))
793                    .allow_trailing(),
794            )
795            .then_ignore(just(Token::RBrace))
796            .map_with_span({
797                let src = src.clone();
798                move |(agent, fields), span: Range<usize>| Expr::Spawn {
799                    agent,
800                    fields,
801                    span: make_span(&src, span),
802                }
803            });
804
805        // await expr - we need to handle this carefully to avoid left recursion
806        let await_expr = just(Token::KwAwait)
807            .ignore_then(ident_token_parser(src.clone()).map_with_span({
808                let src = src.clone();
809                move |name, span: Range<usize>| Expr::Var {
810                    name,
811                    span: make_span(&src, span),
812                }
813            }))
814            .map_with_span({
815                let src = src.clone();
816                move |handle, span: Range<usize>| Expr::Await {
817                    handle: Box::new(handle),
818                    span: make_span(&src, span),
819                }
820            });
821
822        // send(handle, message)
823        let send_expr = just(Token::KwSend)
824            .ignore_then(just(Token::LParen))
825            .ignore_then(expr.clone())
826            .then_ignore(just(Token::Comma))
827            .then(expr.clone())
828            .then_ignore(just(Token::RParen))
829            .map_with_span({
830                let src = src.clone();
831                move |(handle, message), span: Range<usize>| Expr::Send {
832                    handle: Box::new(handle),
833                    message: Box::new(message),
834                    span: make_span(&src, span),
835                }
836            });
837
838        // emit(value)
839        let emit_expr = just(Token::KwEmit)
840            .ignore_then(just(Token::LParen))
841            .ignore_then(expr.clone())
842            .then_ignore(just(Token::RParen))
843            .map_with_span({
844                let src = src.clone();
845                move |value, span: Range<usize>| Expr::Emit {
846                    value: Box::new(value),
847                    span: make_span(&src, span),
848                }
849            });
850
851        // function call: name(args)
852        let call_expr = ident_token_parser(src.clone())
853            .then(
854                expr.clone()
855                    .separated_by(just(Token::Comma))
856                    .allow_trailing()
857                    .delimited_by(just(Token::LParen), just(Token::RParen)),
858            )
859            .map_with_span({
860                let src = src.clone();
861                move |(name, args), span: Range<usize>| Expr::Call {
862                    name,
863                    args,
864                    span: make_span(&src, span),
865                }
866            });
867
868        // Pattern for match arms
869        let pattern = pattern_parser(src.clone());
870
871        // match expression: match expr { Pattern => expr, ... }
872        let match_arm = pattern
873            .then_ignore(just(Token::FatArrow))
874            .then(expr.clone())
875            .map_with_span({
876                let src = src.clone();
877                move |(pattern, body), span: Range<usize>| MatchArm {
878                    pattern,
879                    body,
880                    span: make_span(&src, span),
881                }
882            });
883
884        let match_expr = just(Token::KwMatch)
885            .ignore_then(expr.clone())
886            .then(
887                match_arm
888                    .separated_by(just(Token::Comma))
889                    .allow_trailing()
890                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
891            )
892            .map_with_span({
893                let src = src.clone();
894                move |(scrutinee, arms), span: Range<usize>| Expr::Match {
895                    scrutinee: Box::new(scrutinee),
896                    arms,
897                    span: make_span(&src, span),
898                }
899            });
900
901        // receive() - receive message from mailbox
902        let receive_expr = just(Token::KwReceive)
903            .ignore_then(just(Token::LParen))
904            .ignore_then(just(Token::RParen))
905            .map_with_span({
906                let src = src.clone();
907                move |_, span: Range<usize>| Expr::Receive {
908                    span: make_span(&src, span),
909                }
910            });
911
912        // Record construction: RecordName { field: value, ... }
913        // This is similar to spawn but without the spawn keyword
914        // Must come before var to avoid conflict
915        let record_field_init = ident_token_parser(src.clone())
916            .then_ignore(just(Token::Colon))
917            .then(expr.clone())
918            .map_with_span({
919                let src = src.clone();
920                move |(name, value), span: Range<usize>| FieldInit {
921                    name,
922                    value,
923                    span: make_span(&src, span),
924                }
925            });
926
927        let record_construct = ident_token_parser(src.clone())
928            .then_ignore(just(Token::LBrace))
929            .then(
930                record_field_init
931                    .separated_by(just(Token::Comma))
932                    .allow_trailing(),
933            )
934            .then_ignore(just(Token::RBrace))
935            .map_with_span({
936                let src = src.clone();
937                move |(name, fields), span: Range<usize>| Expr::RecordConstruct {
938                    name,
939                    fields,
940                    span: make_span(&src, span),
941                }
942            });
943
944        // Closure parameter: `name` or `name: Type`
945        let closure_param = ident_token_parser(src.clone())
946            .then(just(Token::Colon).ignore_then(type_parser(src.clone())).or_not())
947            .map_with_span({
948                let src = src.clone();
949                move |(name, ty), span: Range<usize>| ClosureParam {
950                    name,
951                    ty,
952                    span: make_span(&src, span),
953                }
954            });
955
956        // Closure expression: |params| body
957        // Handle both `|| expr` (empty params using Or token) and `|params| expr`
958        let closure_empty = just(Token::Or)
959            .ignore_then(expr.clone())
960            .map_with_span({
961                let src = src.clone();
962                move |body, span: Range<usize>| Expr::Closure {
963                    params: vec![],
964                    body: Box::new(body),
965                    span: make_span(&src, span),
966                }
967            });
968
969        let closure_with_params = just(Token::Pipe)
970            .ignore_then(
971                closure_param
972                    .separated_by(just(Token::Comma))
973                    .allow_trailing(),
974            )
975            .then_ignore(just(Token::Pipe))
976            .then(expr.clone())
977            .map_with_span({
978                let src = src.clone();
979                move |(params, body), span: Range<usize>| Expr::Closure {
980                    params,
981                    body: Box::new(body),
982                    span: make_span(&src, span),
983                }
984            });
985
986        let closure = closure_with_params.or(closure_empty);
987
988        // Map literal: { key: value, ... } or {}
989        // This must be distinguished from record construction which has an identifier before the brace
990        let map_entry = expr
991            .clone()
992            .then_ignore(just(Token::Colon))
993            .then(expr.clone())
994            .map_with_span({
995                let src = src.clone();
996                move |(key, value), span: Range<usize>| MapEntry {
997                    key,
998                    value,
999                    span: make_span(&src, span),
1000                }
1001            });
1002
1003        let map_literal = map_entry
1004            .separated_by(just(Token::Comma))
1005            .allow_trailing()
1006            .delimited_by(just(Token::LBrace), just(Token::RBrace))
1007            .map_with_span({
1008                let src = src.clone();
1009                move |entries, span: Range<usize>| Expr::Map {
1010                    entries,
1011                    span: make_span(&src, span),
1012                }
1013            });
1014
1015        // Enum variant construction: EnumName::Variant or EnumName::Variant(payload)
1016        let variant_construct = ident_token_parser(src.clone())
1017            .then_ignore(just(Token::ColonColon))
1018            .then(ident_token_parser(src.clone()))
1019            .then(
1020                expr.clone()
1021                    .delimited_by(just(Token::LParen), just(Token::RParen))
1022                    .or_not(),
1023            )
1024            .map_with_span({
1025                let src = src.clone();
1026                move |((enum_name, variant), payload), span: Range<usize>| Expr::VariantConstruct {
1027                    enum_name,
1028                    variant,
1029                    payload: payload.map(Box::new),
1030                    span: make_span(&src, span),
1031                }
1032            });
1033
1034        // Atom: the base expression without binary ops
1035        // Box early to cut type complexity
1036        // Note: record_construct must come before call_expr and var to parse `Name { ... }` correctly
1037        // Note: receive_expr must come before call_expr to avoid being parsed as function call
1038        // Note: closure must come before other expressions to handle `|` tokens correctly
1039        // Note: map_literal must come after record_construct (record has name before brace)
1040        // Note: variant_construct must come before call_expr to parse `EnumName::Variant(...)` correctly
1041        let atom = closure
1042            .or(infer_expr)
1043            .or(spawn_expr)
1044            .or(await_expr)
1045            .or(send_expr)
1046            .or(emit_expr)
1047            .or(receive_expr)
1048            .or(match_expr)
1049            .or(self_access)
1050            .or(record_construct)
1051            .or(variant_construct)
1052            .or(call_expr)
1053            .or(map_literal)
1054            .or(list)
1055            .or(paren_or_tuple)
1056            .or(literal)
1057            .or(var)
1058            .boxed();
1059
1060        // Postfix access: expr.field or expr.0 (tuple index)
1061        // We need to distinguish between field access and tuple index
1062        enum PostfixOp {
1063            Field(Ident),
1064            TupleIndex(usize, Range<usize>),
1065        }
1066
1067        let postfix_op = just(Token::Dot).ignore_then(
1068            // Try to parse a tuple index (integer literal)
1069            filter_map({
1070                let src = src.clone();
1071                move |span: Range<usize>, token| match token {
1072                    Token::IntLit => {
1073                        let text = &src[span.start..span.end];
1074                        text.parse::<usize>()
1075                            .map(|idx| PostfixOp::TupleIndex(idx, span.clone()))
1076                            .map_err(|_| Simple::custom(span, "invalid tuple index"))
1077                    }
1078                    _ => Err(Simple::expected_input_found(
1079                        span,
1080                        vec![Some(Token::IntLit)],
1081                        Some(token),
1082                    )),
1083                }
1084            })
1085            .or(ident_token_parser(src.clone()).map(PostfixOp::Field)),
1086        );
1087
1088        let postfix = atom
1089            .then(postfix_op.repeated())
1090            .foldl({
1091                let src = src.clone();
1092                move |object, op| match op {
1093                    PostfixOp::Field(field) => {
1094                        let span = make_span(&src, object.span().start..field.span.end);
1095                        Expr::FieldAccess {
1096                            object: Box::new(object),
1097                            field,
1098                            span,
1099                        }
1100                    }
1101                    PostfixOp::TupleIndex(index, idx_span) => {
1102                        let span = make_span(&src, object.span().start..idx_span.end);
1103                        Expr::TupleIndex {
1104                            tuple: Box::new(object),
1105                            index,
1106                            span,
1107                        }
1108                    }
1109                }
1110            })
1111            .boxed();
1112
1113        // Unary expressions
1114        let unary = just(Token::Minus)
1115            .to(UnaryOp::Neg)
1116            .or(just(Token::Bang).to(UnaryOp::Not))
1117            .repeated()
1118            .then(postfix.clone())
1119            .foldr(|op, operand| {
1120                let span = operand.span().clone();
1121                Expr::Unary {
1122                    op,
1123                    operand: Box::new(operand),
1124                    span,
1125                }
1126            })
1127            .boxed();
1128
1129        // RFC-0007: try expression - propagates errors upward
1130        // try expr
1131        let try_expr = just(Token::KwTry)
1132            .ignore_then(postfix)
1133            .map_with_span({
1134                let src = src.clone();
1135                move |inner, span: Range<usize>| Expr::Try {
1136                    expr: Box::new(inner),
1137                    span: make_span(&src, span),
1138                }
1139            })
1140            .boxed();
1141
1142        // Combined unary (including try)
1143        let unary = try_expr.or(unary).boxed();
1144
1145        // Binary operators with precedence levels
1146        // Level 7: * /
1147        let mul_div_op = just(Token::Star)
1148            .to(BinOp::Mul)
1149            .or(just(Token::Slash).to(BinOp::Div));
1150
1151        let mul_div = unary
1152            .clone()
1153            .then(mul_div_op.then(unary.clone()).repeated())
1154            .foldl({
1155                let src = src.clone();
1156                move |left, (op, right)| {
1157                    let span = make_span(&src, left.span().start..right.span().end);
1158                    Expr::Binary {
1159                        op,
1160                        left: Box::new(left),
1161                        right: Box::new(right),
1162                        span,
1163                    }
1164                }
1165            })
1166            .boxed();
1167
1168        // Level 6: + -
1169        let add_sub_op = just(Token::Plus)
1170            .to(BinOp::Add)
1171            .or(just(Token::Minus).to(BinOp::Sub));
1172
1173        let add_sub = mul_div
1174            .clone()
1175            .then(add_sub_op.then(mul_div).repeated())
1176            .foldl({
1177                let src = src.clone();
1178                move |left, (op, right)| {
1179                    let span = make_span(&src, left.span().start..right.span().end);
1180                    Expr::Binary {
1181                        op,
1182                        left: Box::new(left),
1183                        right: Box::new(right),
1184                        span,
1185                    }
1186                }
1187            })
1188            .boxed();
1189
1190        // Level 5: ++
1191        let concat_op = just(Token::PlusPlus).to(BinOp::Concat);
1192
1193        let concat = add_sub
1194            .clone()
1195            .then(concat_op.then(add_sub).repeated())
1196            .foldl({
1197                let src = src.clone();
1198                move |left, (op, right)| {
1199                    let span = make_span(&src, left.span().start..right.span().end);
1200                    Expr::Binary {
1201                        op,
1202                        left: Box::new(left),
1203                        right: Box::new(right),
1204                        span,
1205                    }
1206                }
1207            })
1208            .boxed();
1209
1210        // Level 4: < > <= >=
1211        let cmp_op = choice((
1212            just(Token::Le).to(BinOp::Le),
1213            just(Token::Ge).to(BinOp::Ge),
1214            just(Token::Lt).to(BinOp::Lt),
1215            just(Token::Gt).to(BinOp::Gt),
1216        ));
1217
1218        let comparison = concat
1219            .clone()
1220            .then(cmp_op.then(concat).repeated())
1221            .foldl({
1222                let src = src.clone();
1223                move |left, (op, right)| {
1224                    let span = make_span(&src, left.span().start..right.span().end);
1225                    Expr::Binary {
1226                        op,
1227                        left: Box::new(left),
1228                        right: Box::new(right),
1229                        span,
1230                    }
1231                }
1232            })
1233            .boxed();
1234
1235        // Level 3: == !=
1236        let eq_op = just(Token::EqEq)
1237            .to(BinOp::Eq)
1238            .or(just(Token::Ne).to(BinOp::Ne));
1239
1240        let equality = comparison
1241            .clone()
1242            .then(eq_op.then(comparison).repeated())
1243            .foldl({
1244                let src = src.clone();
1245                move |left, (op, right)| {
1246                    let span = make_span(&src, left.span().start..right.span().end);
1247                    Expr::Binary {
1248                        op,
1249                        left: Box::new(left),
1250                        right: Box::new(right),
1251                        span,
1252                    }
1253                }
1254            })
1255            .boxed();
1256
1257        // Level 2: &&
1258        let and_op = just(Token::And).to(BinOp::And);
1259
1260        let and = equality
1261            .clone()
1262            .then(and_op.then(equality).repeated())
1263            .foldl({
1264                let src = src.clone();
1265                move |left, (op, right)| {
1266                    let span = make_span(&src, left.span().start..right.span().end);
1267                    Expr::Binary {
1268                        op,
1269                        left: Box::new(left),
1270                        right: Box::new(right),
1271                        span,
1272                    }
1273                }
1274            })
1275            .boxed();
1276
1277        // Level 1: ||
1278        let or_op = just(Token::Or).to(BinOp::Or);
1279
1280        let or_expr = and.clone().then(or_op.then(and).repeated()).foldl({
1281            let src = src.clone();
1282            move |left, (op, right)| {
1283                let span = make_span(&src, left.span().start..right.span().end);
1284                Expr::Binary {
1285                    op,
1286                    left: Box::new(left),
1287                    right: Box::new(right),
1288                    span,
1289                }
1290            }
1291        });
1292
1293        // RFC-0007: catch expression (lowest precedence)
1294        // expr catch { recovery } OR expr catch(e) { recovery }
1295        let catch_recovery = just(Token::KwCatch)
1296            .ignore_then(
1297                ident_token_parser(src.clone())
1298                    .delimited_by(just(Token::LParen), just(Token::RParen))
1299                    .or_not(),
1300            )
1301            .then(
1302                expr.clone()
1303                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
1304            );
1305
1306        or_expr.then(catch_recovery.or_not()).map_with_span({
1307            let src = src.clone();
1308            move |(inner, catch_opt), span: Range<usize>| match catch_opt {
1309                Some((error_bind, recovery)) => Expr::Catch {
1310                    expr: Box::new(inner),
1311                    error_bind,
1312                    recovery: Box::new(recovery),
1313                    span: make_span(&src, span),
1314                },
1315                None => inner,
1316            }
1317        })
1318    })
1319    .boxed()
1320}
1321
1322// =============================================================================
1323// Primitive parsers
1324// =============================================================================
1325
1326/// Create a Span from a Range<usize>.
1327fn make_span(source: &Arc<str>, range: Range<usize>) -> Span {
1328    Span::new(range.start, range.end, Arc::clone(source))
1329}
1330
1331/// Parser for identifier tokens.
1332fn ident_token_parser(source: Arc<str>) -> impl Parser<Token, Ident, Error = ParseError> + Clone {
1333    filter_map(move |span: Range<usize>, token| match token {
1334        Token::Ident => {
1335            let text = &source[span.start..span.end];
1336            Ok(Ident::new(text.to_string(), make_span(&source, span)))
1337        }
1338        _ => Err(Simple::expected_input_found(
1339            span,
1340            vec![Some(Token::Ident)],
1341            Some(token),
1342        )),
1343    })
1344}
1345
1346/// Parser for variable references.
1347fn var_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
1348    ident_token_parser(source.clone()).map_with_span(move |name, span: Range<usize>| Expr::Var {
1349        name,
1350        span: make_span(&source, span),
1351    })
1352}
1353
1354/// Parser for type expressions.
1355fn type_parser(source: Arc<str>) -> impl Parser<Token, TypeExpr, Error = ParseError> + Clone {
1356    recursive(move |ty| {
1357        let src = source.clone();
1358
1359        let primitive = choice((
1360            just(Token::TyInt).to(TypeExpr::Int),
1361            just(Token::TyFloat).to(TypeExpr::Float),
1362            just(Token::TyBool).to(TypeExpr::Bool),
1363            just(Token::TyString).to(TypeExpr::String),
1364            just(Token::TyUnit).to(TypeExpr::Unit),
1365        ));
1366
1367        let list_ty = just(Token::TyList)
1368            .ignore_then(just(Token::Lt))
1369            .ignore_then(ty.clone())
1370            .then_ignore(just(Token::Gt))
1371            .map(|inner| TypeExpr::List(Box::new(inner)));
1372
1373        let option_ty = just(Token::TyOption)
1374            .ignore_then(just(Token::Lt))
1375            .ignore_then(ty.clone())
1376            .then_ignore(just(Token::Gt))
1377            .map(|inner| TypeExpr::Option(Box::new(inner)));
1378
1379        let inferred_ty = just(Token::TyInferred)
1380            .ignore_then(just(Token::Lt))
1381            .ignore_then(ty.clone())
1382            .then_ignore(just(Token::Gt))
1383            .map(|inner| TypeExpr::Inferred(Box::new(inner)));
1384
1385        let agent_ty = just(Token::TyAgent)
1386            .ignore_then(just(Token::Lt))
1387            .ignore_then(ident_token_parser(src.clone()))
1388            .then_ignore(just(Token::Gt))
1389            .map(TypeExpr::Agent);
1390
1391        let named_ty = ident_token_parser(src.clone()).map(TypeExpr::Named);
1392
1393        // Function type: Fn(A, B) -> C
1394        let fn_ty = just(Token::TyFn)
1395            .ignore_then(
1396                ty.clone()
1397                    .separated_by(just(Token::Comma))
1398                    .allow_trailing()
1399                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1400            )
1401            .then_ignore(just(Token::Arrow))
1402            .then(ty.clone())
1403            .map(|(params, ret)| TypeExpr::Fn(params, Box::new(ret)));
1404
1405        // Map type: Map<K, V>
1406        let map_ty = just(Token::TyMap)
1407            .ignore_then(just(Token::Lt))
1408            .ignore_then(ty.clone())
1409            .then_ignore(just(Token::Comma))
1410            .then(ty.clone())
1411            .then_ignore(just(Token::Gt))
1412            .map(|(k, v)| TypeExpr::Map(Box::new(k), Box::new(v)));
1413
1414        // Result type: Result<T, E>
1415        let result_ty = just(Token::TyResult)
1416            .ignore_then(just(Token::Lt))
1417            .ignore_then(ty.clone())
1418            .then_ignore(just(Token::Comma))
1419            .then(ty.clone())
1420            .then_ignore(just(Token::Gt))
1421            .map(|(ok, err)| TypeExpr::Result(Box::new(ok), Box::new(err)));
1422
1423        // Tuple type: (A, B, C) - at least 2 elements
1424        let tuple_ty = ty
1425            .clone()
1426            .separated_by(just(Token::Comma))
1427            .at_least(2)
1428            .allow_trailing()
1429            .delimited_by(just(Token::LParen), just(Token::RParen))
1430            .map(TypeExpr::Tuple);
1431
1432        primitive
1433            .or(list_ty)
1434            .or(option_ty)
1435            .or(inferred_ty)
1436            .or(agent_ty)
1437            .or(fn_ty)
1438            .or(map_ty)
1439            .or(result_ty)
1440            .or(tuple_ty)
1441            .or(named_ty)
1442    })
1443}
1444
1445/// Parser for patterns in for loops.
1446/// Only supports simple bindings (`x`) and tuple patterns (`(k, v)`).
1447fn for_pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
1448    recursive(move |pattern| {
1449        let src = source.clone();
1450        let src2 = source.clone();
1451
1452        // Simple binding pattern: `x`
1453        let binding = ident_token_parser(src.clone()).map_with_span({
1454            let src = src.clone();
1455            move |name, span: Range<usize>| Pattern::Binding {
1456                name,
1457                span: make_span(&src, span),
1458            }
1459        });
1460
1461        // Tuple pattern: `(a, b)` - at least 2 elements
1462        let tuple_pattern = pattern
1463            .clone()
1464            .separated_by(just(Token::Comma))
1465            .at_least(2)
1466            .allow_trailing()
1467            .delimited_by(just(Token::LParen), just(Token::RParen))
1468            .map_with_span({
1469                let src = src2.clone();
1470                move |elements, span: Range<usize>| Pattern::Tuple {
1471                    elements,
1472                    span: make_span(&src, span),
1473                }
1474            });
1475
1476        tuple_pattern.or(binding)
1477    })
1478}
1479
1480/// Parser for patterns in match expressions.
1481fn pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
1482    recursive(move |pattern| {
1483        let src = source.clone();
1484        let src2 = source.clone();
1485        let src3 = source.clone();
1486        let src4 = source.clone();
1487        let src5 = source.clone();
1488
1489        // Wildcard pattern: `_`
1490        let wildcard = filter_map({
1491            let src = src.clone();
1492            move |span: Range<usize>, token| match &token {
1493                Token::Ident if src[span.start..span.end].eq("_") => Ok(()),
1494                _ => Err(Simple::expected_input_found(span, vec![], Some(token))),
1495            }
1496        })
1497        .map_with_span(move |_, span: Range<usize>| Pattern::Wildcard {
1498            span: make_span(&src2, span),
1499        });
1500
1501        // Literal patterns: 42, "hello", true, false
1502        let lit_int = filter_map({
1503            let src = src3.clone();
1504            move |span: Range<usize>, token| match token {
1505                Token::IntLit => {
1506                    let text = &src[span.start..span.end];
1507                    text.parse::<i64>()
1508                        .map(Literal::Int)
1509                        .map_err(|_| Simple::custom(span, "invalid integer literal"))
1510                }
1511                _ => Err(Simple::expected_input_found(
1512                    span,
1513                    vec![Some(Token::IntLit)],
1514                    Some(token),
1515                )),
1516            }
1517        })
1518        .map_with_span({
1519            let src = src3.clone();
1520            move |value, span: Range<usize>| Pattern::Literal {
1521                value,
1522                span: make_span(&src, span),
1523            }
1524        });
1525
1526        let lit_bool = just(Token::KwTrue)
1527            .to(Literal::Bool(true))
1528            .or(just(Token::KwFalse).to(Literal::Bool(false)))
1529            .map_with_span({
1530                let src = src3.clone();
1531                move |value, span: Range<usize>| Pattern::Literal {
1532                    value,
1533                    span: make_span(&src, span),
1534                }
1535            });
1536
1537        // Tuple pattern: (a, b, c) - at least 2 elements
1538        let tuple_pattern = pattern
1539            .clone()
1540            .separated_by(just(Token::Comma))
1541            .at_least(2)
1542            .allow_trailing()
1543            .delimited_by(just(Token::LParen), just(Token::RParen))
1544            .map_with_span({
1545                let src = src5.clone();
1546                move |elements, span: Range<usize>| Pattern::Tuple {
1547                    elements,
1548                    span: make_span(&src, span),
1549                }
1550            });
1551
1552        // Enum variant with optional payload: `Ok(x)` or `Status::Active`
1553        // Qualified with payload: EnumName::Variant(pattern)
1554        let qualified_variant_with_payload = ident_token_parser(src4.clone())
1555            .then_ignore(just(Token::ColonColon))
1556            .then(ident_token_parser(src4.clone()))
1557            .then(
1558                pattern
1559                    .clone()
1560                    .delimited_by(just(Token::LParen), just(Token::RParen))
1561                    .or_not(),
1562            )
1563            .map_with_span({
1564                let src = src4.clone();
1565                move |((enum_name, variant), payload), span: Range<usize>| Pattern::Variant {
1566                    enum_name: Some(enum_name),
1567                    variant,
1568                    payload: payload.map(Box::new),
1569                    span: make_span(&src, span),
1570                }
1571            });
1572
1573        // Unqualified variant with payload: `Ok(x)` or just `x`
1574        let unqualified_with_payload = ident_token_parser(src4.clone())
1575            .then(
1576                pattern
1577                    .clone()
1578                    .delimited_by(just(Token::LParen), just(Token::RParen))
1579                    .or_not(),
1580            )
1581            .map_with_span({
1582                let src = src4.clone();
1583                move |(name, payload), span: Range<usize>| {
1584                    // If it looks like a variant (starts with uppercase), treat as variant
1585                    // Otherwise treat as binding (only if no payload)
1586                    if name.name.chars().next().is_some_and(|c| c.is_uppercase()) || payload.is_some() {
1587                        Pattern::Variant {
1588                            enum_name: None,
1589                            variant: name,
1590                            payload: payload.map(Box::new),
1591                            span: make_span(&src, span),
1592                        }
1593                    } else {
1594                        Pattern::Binding {
1595                            name,
1596                            span: make_span(&src, span),
1597                        }
1598                    }
1599                }
1600            });
1601
1602        // Order matters: try wildcard first, then tuple pattern, then qualified variant, then literals, then unqualified
1603        wildcard
1604            .or(tuple_pattern)
1605            .or(qualified_variant_with_payload)
1606            .or(lit_int)
1607            .or(lit_bool)
1608            .or(unqualified_with_payload)
1609    })
1610}
1611
1612/// Parser for literals.
1613fn literal_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
1614    let src = source.clone();
1615    let src2 = source.clone();
1616    let src3 = source.clone();
1617    let src4 = source.clone();
1618    let src5 = source.clone();
1619
1620    let int_lit = filter_map(move |span: Range<usize>, token| match token {
1621        Token::IntLit => {
1622            let text = &src[span.start..span.end];
1623            text.parse::<i64>()
1624                .map(Literal::Int)
1625                .map_err(|_| Simple::custom(span, "invalid integer literal"))
1626        }
1627        _ => Err(Simple::expected_input_found(
1628            span,
1629            vec![Some(Token::IntLit)],
1630            Some(token),
1631        )),
1632    })
1633    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
1634        value,
1635        span: make_span(&src2, span),
1636    });
1637
1638    let float_lit = filter_map(move |span: Range<usize>, token| match token {
1639        Token::FloatLit => {
1640            let text = &src3[span.start..span.end];
1641            text.parse::<f64>()
1642                .map(Literal::Float)
1643                .map_err(|_| Simple::custom(span, "invalid float literal"))
1644        }
1645        _ => Err(Simple::expected_input_found(
1646            span,
1647            vec![Some(Token::FloatLit)],
1648            Some(token),
1649        )),
1650    })
1651    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
1652        value,
1653        span: make_span(&src4, span),
1654    });
1655
1656    let src6 = source.clone();
1657    let string_lit = filter_map(move |span: Range<usize>, token| match token {
1658        Token::StringLit => {
1659            let text = &src5[span.start..span.end];
1660            let inner = &text[1..text.len() - 1];
1661            let parts = parse_string_template(inner, &make_span(&src5, span.clone()));
1662            Ok(parts)
1663        }
1664        _ => Err(Simple::expected_input_found(
1665            span,
1666            vec![Some(Token::StringLit)],
1667            Some(token),
1668        )),
1669    })
1670    .map_with_span(move |parts, span: Range<usize>| {
1671        let span = make_span(&src6, span);
1672        // If no interpolations, use a simple string literal
1673        if parts.len() == 1 {
1674            if let StringPart::Literal(s) = &parts[0] {
1675                return Expr::Literal {
1676                    value: Literal::String(s.clone()),
1677                    span,
1678                };
1679            }
1680        }
1681        // Otherwise, use StringInterp
1682        Expr::StringInterp {
1683            template: StringTemplate {
1684                parts,
1685                span: span.clone(),
1686            },
1687            span,
1688        }
1689    });
1690
1691    let bool_lit = just(Token::KwTrue)
1692        .to(Literal::Bool(true))
1693        .or(just(Token::KwFalse).to(Literal::Bool(false)))
1694        .map_with_span(move |value, _span: Range<usize>| Expr::Literal {
1695            value,
1696            span: Span::dummy(), // bool literals don't carry source
1697        });
1698
1699    int_lit.or(float_lit).or(string_lit).or(bool_lit)
1700}
1701
1702/// Parser for string templates (handles interpolation).
1703fn string_template_parser(
1704    source: Arc<str>,
1705) -> impl Parser<Token, StringTemplate, Error = ParseError> + Clone {
1706    filter_map(move |span: Range<usize>, token| match token {
1707        Token::StringLit => {
1708            let text = &source[span.start..span.end];
1709            let inner = &text[1..text.len() - 1];
1710            let parts = parse_string_template(inner, &make_span(&source, span.clone()));
1711            Ok(StringTemplate {
1712                parts,
1713                span: make_span(&source, span),
1714            })
1715        }
1716        _ => Err(Simple::expected_input_found(
1717            span,
1718            vec![Some(Token::StringLit)],
1719            Some(token),
1720        )),
1721    })
1722}
1723
1724/// Parse a string into template parts, handling `{ident}` interpolations.
1725fn parse_string_template(s: &str, span: &Span) -> Vec<StringPart> {
1726    let mut parts = Vec::new();
1727    let mut current = String::new();
1728    let mut chars = s.chars().peekable();
1729
1730    while let Some(ch) = chars.next() {
1731        if ch == '{' {
1732            if !current.is_empty() {
1733                parts.push(StringPart::Literal(std::mem::take(&mut current)));
1734            }
1735
1736            let mut ident_name = String::new();
1737            while let Some(&c) = chars.peek() {
1738                if c == '}' {
1739                    chars.next();
1740                    break;
1741                }
1742                ident_name.push(c);
1743                chars.next();
1744            }
1745
1746            if !ident_name.is_empty() {
1747                parts.push(StringPart::Interpolation(Ident::new(
1748                    ident_name,
1749                    span.clone(),
1750                )));
1751            }
1752        } else if ch == '\\' {
1753            if let Some(escaped) = chars.next() {
1754                current.push(match escaped {
1755                    'n' => '\n',
1756                    't' => '\t',
1757                    'r' => '\r',
1758                    '\\' => '\\',
1759                    '"' => '"',
1760                    '{' => '{',
1761                    '}' => '}',
1762                    other => other,
1763                });
1764            }
1765        } else {
1766            current.push(ch);
1767        }
1768    }
1769
1770    if !current.is_empty() {
1771        parts.push(StringPart::Literal(current));
1772    }
1773
1774    if parts.is_empty() {
1775        parts.push(StringPart::Literal(String::new()));
1776    }
1777
1778    parts
1779}
1780
1781// =============================================================================
1782// Tests
1783// =============================================================================
1784
1785#[cfg(test)]
1786mod tests {
1787    use super::*;
1788    use sage_lexer::lex;
1789
1790    fn parse_str(source: &str) -> (Option<Program>, Vec<ParseError>) {
1791        let lex_result = lex(source).expect("lexing should succeed");
1792        let source_arc: Arc<str> = Arc::from(source);
1793        parse(lex_result.tokens(), source_arc)
1794    }
1795
1796    #[test]
1797    fn parse_minimal_program() {
1798        let source = r#"
1799            agent Main {
1800                on start {
1801                    emit(42);
1802                }
1803            }
1804            run Main;
1805        "#;
1806
1807        let (prog, errors) = parse_str(source);
1808        assert!(errors.is_empty(), "errors: {errors:?}");
1809        let prog = prog.expect("should parse");
1810
1811        assert_eq!(prog.agents.len(), 1);
1812        assert_eq!(prog.agents[0].name.name, "Main");
1813        assert_eq!(prog.run_agent.as_ref().unwrap().name, "Main");
1814    }
1815
1816    #[test]
1817    fn parse_agent_with_beliefs() {
1818        let source = r#"
1819            agent Researcher {
1820                topic: String
1821                max_words: Int
1822
1823                on start {
1824                    emit(self.topic);
1825                }
1826            }
1827            run Researcher;
1828        "#;
1829
1830        let (prog, errors) = parse_str(source);
1831        assert!(errors.is_empty(), "errors: {errors:?}");
1832        let prog = prog.expect("should parse");
1833
1834        assert_eq!(prog.agents[0].beliefs.len(), 2);
1835        assert_eq!(prog.agents[0].beliefs[0].name.name, "topic");
1836        assert_eq!(prog.agents[0].beliefs[1].name.name, "max_words");
1837    }
1838
1839    #[test]
1840    fn parse_multiple_handlers() {
1841        let source = r#"
1842            agent Worker {
1843                on start {
1844                    print("started");
1845                }
1846
1847                on message(msg: String) {
1848                    print(msg);
1849                }
1850
1851                on stop {
1852                    print("stopped");
1853                }
1854            }
1855            run Worker;
1856        "#;
1857
1858        let (prog, errors) = parse_str(source);
1859        assert!(errors.is_empty(), "errors: {errors:?}");
1860        let prog = prog.expect("should parse");
1861
1862        assert_eq!(prog.agents[0].handlers.len(), 3);
1863        assert_eq!(prog.agents[0].handlers[0].event, EventKind::Start);
1864        assert!(matches!(
1865            prog.agents[0].handlers[1].event,
1866            EventKind::Message { .. }
1867        ));
1868        assert_eq!(prog.agents[0].handlers[2].event, EventKind::Stop);
1869    }
1870
1871    #[test]
1872    fn parse_function() {
1873        let source = r#"
1874            fn greet(name: String) -> String {
1875                return "Hello, " ++ name;
1876            }
1877
1878            agent Main {
1879                on start {
1880                    emit(greet("World"));
1881                }
1882            }
1883            run Main;
1884        "#;
1885
1886        let (prog, errors) = parse_str(source);
1887        assert!(errors.is_empty(), "errors: {errors:?}");
1888        let prog = prog.expect("should parse");
1889
1890        assert_eq!(prog.functions.len(), 1);
1891        assert_eq!(prog.functions[0].name.name, "greet");
1892        assert_eq!(prog.functions[0].params.len(), 1);
1893    }
1894
1895    #[test]
1896    fn parse_let_statement() {
1897        let source = r#"
1898            agent Main {
1899                on start {
1900                    let x: Int = 42;
1901                    let y = "hello";
1902                    emit(x);
1903                }
1904            }
1905            run Main;
1906        "#;
1907
1908        let (prog, errors) = parse_str(source);
1909        assert!(errors.is_empty(), "errors: {errors:?}");
1910        let prog = prog.expect("should parse");
1911
1912        let stmts = &prog.agents[0].handlers[0].body.stmts;
1913        assert!(matches!(stmts[0], Stmt::Let { .. }));
1914        assert!(matches!(stmts[1], Stmt::Let { .. }));
1915    }
1916
1917    #[test]
1918    fn parse_if_statement() {
1919        let source = r#"
1920            agent Main {
1921                on start {
1922                    if true {
1923                        emit(1);
1924                    } else {
1925                        emit(2);
1926                    }
1927                }
1928            }
1929            run Main;
1930        "#;
1931
1932        let (prog, errors) = parse_str(source);
1933        assert!(errors.is_empty(), "errors: {errors:?}");
1934        let prog = prog.expect("should parse");
1935
1936        let stmts = &prog.agents[0].handlers[0].body.stmts;
1937        assert!(matches!(stmts[0], Stmt::If { .. }));
1938    }
1939
1940    #[test]
1941    fn parse_for_loop() {
1942        let source = r#"
1943            agent Main {
1944                on start {
1945                    for x in [1, 2, 3] {
1946                        print(x);
1947                    }
1948                    emit(0);
1949                }
1950            }
1951            run Main;
1952        "#;
1953
1954        let (prog, errors) = parse_str(source);
1955        assert!(errors.is_empty(), "errors: {errors:?}");
1956        let prog = prog.expect("should parse");
1957
1958        let stmts = &prog.agents[0].handlers[0].body.stmts;
1959        assert!(matches!(stmts[0], Stmt::For { .. }));
1960    }
1961
1962    #[test]
1963    fn parse_spawn_await() {
1964        let source = r#"
1965            agent Worker {
1966                name: String
1967
1968                on start {
1969                    emit(self.name);
1970                }
1971            }
1972
1973            agent Main {
1974                on start {
1975                    let w = spawn Worker { name: "test" };
1976                    let result = await w;
1977                    emit(result);
1978                }
1979            }
1980            run Main;
1981        "#;
1982
1983        let (prog, errors) = parse_str(source);
1984        assert!(errors.is_empty(), "errors: {errors:?}");
1985        prog.expect("should parse");
1986    }
1987
1988    #[test]
1989    fn parse_infer() {
1990        let source = r#"
1991            agent Main {
1992                on start {
1993                    let result = infer("What is 2+2?");
1994                    emit(result);
1995                }
1996            }
1997            run Main;
1998        "#;
1999
2000        let (prog, errors) = parse_str(source);
2001        assert!(errors.is_empty(), "errors: {errors:?}");
2002        prog.expect("should parse");
2003    }
2004
2005    #[test]
2006    fn parse_binary_precedence() {
2007        let source = r#"
2008            agent Main {
2009                on start {
2010                    let x = 2 + 3 * 4;
2011                    emit(x);
2012                }
2013            }
2014            run Main;
2015        "#;
2016
2017        let (prog, errors) = parse_str(source);
2018        assert!(errors.is_empty(), "errors: {errors:?}");
2019        let prog = prog.expect("should parse");
2020
2021        let stmts = &prog.agents[0].handlers[0].body.stmts;
2022        if let Stmt::Let { value, .. } = &stmts[0] {
2023            if let Expr::Binary { op, .. } = value {
2024                assert_eq!(*op, BinOp::Add);
2025            } else {
2026                panic!("expected binary expression");
2027            }
2028        }
2029    }
2030
2031    #[test]
2032    fn parse_string_interpolation() {
2033        let source = r#"
2034            agent Main {
2035                on start {
2036                    let name = "World";
2037                    let msg = infer("Greet {name}");
2038                    emit(msg);
2039                }
2040            }
2041            run Main;
2042        "#;
2043
2044        let (prog, errors) = parse_str(source);
2045        assert!(errors.is_empty(), "errors: {errors:?}");
2046        let prog = prog.expect("should parse");
2047
2048        let stmts = &prog.agents[0].handlers[0].body.stmts;
2049        if let Stmt::Let { value, .. } = &stmts[1] {
2050            if let Expr::Infer { template, .. } = value {
2051                assert!(template.has_interpolations());
2052            } else {
2053                panic!("expected infer expression");
2054            }
2055        }
2056    }
2057
2058    // =========================================================================
2059    // Error recovery tests
2060    // =========================================================================
2061
2062    #[test]
2063    fn recover_from_malformed_agent_continues_to_next() {
2064        // First agent has syntax error (missing type after colon), second is valid
2065        let source = r#"
2066            agent Broken {
2067                x:
2068            }
2069
2070            agent Main {
2071                on start {
2072                    emit(42);
2073                }
2074            }
2075            run Main;
2076        "#;
2077
2078        let (prog, errors) = parse_str(source);
2079        // Should have errors from the broken agent
2080        assert!(!errors.is_empty(), "should have parse errors");
2081        // But should still produce a program with the valid agent
2082        let prog = prog.expect("should produce partial AST");
2083        assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
2084    }
2085
2086    #[test]
2087    fn recover_from_mismatched_braces_in_block() {
2088        let source = r#"
2089            agent Main {
2090                on start {
2091                    let x = [1, 2, 3;
2092                    emit(42);
2093                }
2094            }
2095            run Main;
2096        "#;
2097
2098        let (prog, errors) = parse_str(source);
2099        // Should have errors but still produce an AST
2100        assert!(!errors.is_empty(), "should have parse errors");
2101        assert!(prog.is_some(), "should produce partial AST despite errors");
2102    }
2103
2104    #[test]
2105    fn parse_mod_declaration() {
2106        let source = r#"
2107            mod agents;
2108            pub mod utils;
2109
2110            agent Main {
2111                on start {
2112                    emit(42);
2113                }
2114            }
2115            run Main;
2116        "#;
2117
2118        let (prog, errors) = parse_str(source);
2119        assert!(errors.is_empty(), "errors: {errors:?}");
2120        let prog = prog.expect("should parse");
2121
2122        assert_eq!(prog.mod_decls.len(), 2);
2123        assert!(!prog.mod_decls[0].is_pub);
2124        assert_eq!(prog.mod_decls[0].name.name, "agents");
2125        assert!(prog.mod_decls[1].is_pub);
2126        assert_eq!(prog.mod_decls[1].name.name, "utils");
2127    }
2128
2129    #[test]
2130    fn parse_use_simple() {
2131        let source = r#"
2132            use agents::Researcher;
2133
2134            agent Main {
2135                on start {
2136                    emit(42);
2137                }
2138            }
2139            run Main;
2140        "#;
2141
2142        let (prog, errors) = parse_str(source);
2143        assert!(errors.is_empty(), "errors: {errors:?}");
2144        let prog = prog.expect("should parse");
2145
2146        assert_eq!(prog.use_decls.len(), 1);
2147        assert!(!prog.use_decls[0].is_pub);
2148        assert_eq!(prog.use_decls[0].path.len(), 2);
2149        assert_eq!(prog.use_decls[0].path[0].name, "agents");
2150        assert_eq!(prog.use_decls[0].path[1].name, "Researcher");
2151        assert!(matches!(prog.use_decls[0].kind, UseKind::Simple(None)));
2152    }
2153
2154    #[test]
2155    fn parse_use_with_alias() {
2156        let source = r#"
2157            use agents::Researcher as R;
2158
2159            agent Main {
2160                on start {
2161                    emit(42);
2162                }
2163            }
2164            run Main;
2165        "#;
2166
2167        let (prog, errors) = parse_str(source);
2168        assert!(errors.is_empty(), "errors: {errors:?}");
2169        let prog = prog.expect("should parse");
2170
2171        assert_eq!(prog.use_decls.len(), 1);
2172        if let UseKind::Simple(Some(alias)) = &prog.use_decls[0].kind {
2173            assert_eq!(alias.name, "R");
2174        } else {
2175            panic!("expected Simple with alias");
2176        }
2177    }
2178
2179    #[test]
2180    fn parse_pub_agent() {
2181        let source = r#"
2182            pub agent Worker {
2183                on start {
2184                    emit(42);
2185                }
2186            }
2187
2188            agent Main {
2189                on start {
2190                    emit(0);
2191                }
2192            }
2193            run Main;
2194        "#;
2195
2196        let (prog, errors) = parse_str(source);
2197        assert!(errors.is_empty(), "errors: {errors:?}");
2198        let prog = prog.expect("should parse");
2199
2200        assert_eq!(prog.agents.len(), 2);
2201        assert!(prog.agents[0].is_pub);
2202        assert_eq!(prog.agents[0].name.name, "Worker");
2203        assert!(!prog.agents[1].is_pub);
2204    }
2205
2206    #[test]
2207    fn parse_pub_function() {
2208        let source = r#"
2209            pub fn helper(x: Int) -> Int {
2210                return x;
2211            }
2212
2213            agent Main {
2214                on start {
2215                    emit(helper(42));
2216                }
2217            }
2218            run Main;
2219        "#;
2220
2221        let (prog, errors) = parse_str(source);
2222        assert!(errors.is_empty(), "errors: {errors:?}");
2223        let prog = prog.expect("should parse");
2224
2225        assert_eq!(prog.functions.len(), 1);
2226        assert!(prog.functions[0].is_pub);
2227        assert_eq!(prog.functions[0].name.name, "helper");
2228    }
2229
2230    #[test]
2231    fn parse_library_no_run() {
2232        // A library module has no `run` statement
2233        let source = r#"
2234            pub agent Worker {
2235                on start {
2236                    emit(42);
2237                }
2238            }
2239
2240            pub fn helper(x: Int) -> Int {
2241                return x;
2242            }
2243        "#;
2244
2245        let (prog, errors) = parse_str(source);
2246        assert!(errors.is_empty(), "errors: {errors:?}");
2247        let prog = prog.expect("should parse");
2248
2249        assert!(prog.run_agent.is_none());
2250        assert_eq!(prog.agents.len(), 1);
2251        assert_eq!(prog.functions.len(), 1);
2252    }
2253
2254    #[test]
2255    fn recover_multiple_errors_reported() {
2256        // Multiple errors in different places - incomplete field missing type
2257        let source = r#"
2258            agent A {
2259                x:
2260            }
2261
2262            agent Main {
2263                on start {
2264                    emit(42);
2265                }
2266            }
2267            run Main;
2268        "#;
2269
2270        let (prog, errors) = parse_str(source);
2271        // The malformed field is missing its type after `:` so should cause an error
2272        // However, with recovery the valid agent may still parse
2273        // Check that we either have errors or recovered successfully
2274        if errors.is_empty() {
2275            // Recovery succeeded - should have parsed Main agent
2276            let prog = prog.expect("should have AST with recovery");
2277            assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
2278        }
2279        // Either way, the test passes - we're testing recovery works
2280    }
2281
2282    #[test]
2283    fn parse_record_declaration() {
2284        let source = r#"
2285            record Point {
2286                x: Int,
2287                y: Int,
2288            }
2289
2290            agent Main {
2291                on start {
2292                    emit(0);
2293                }
2294            }
2295            run Main;
2296        "#;
2297
2298        let (prog, errors) = parse_str(source);
2299        assert!(errors.is_empty(), "errors: {errors:?}");
2300        let prog = prog.expect("should parse");
2301
2302        assert_eq!(prog.records.len(), 1);
2303        assert!(!prog.records[0].is_pub);
2304        assert_eq!(prog.records[0].name.name, "Point");
2305        assert_eq!(prog.records[0].fields.len(), 2);
2306        assert_eq!(prog.records[0].fields[0].name.name, "x");
2307        assert_eq!(prog.records[0].fields[1].name.name, "y");
2308    }
2309
2310    #[test]
2311    fn parse_pub_record() {
2312        let source = r#"
2313            pub record Config {
2314                host: String,
2315                port: Int,
2316            }
2317
2318            agent Main {
2319                on start { emit(0); }
2320            }
2321            run Main;
2322        "#;
2323
2324        let (prog, errors) = parse_str(source);
2325        assert!(errors.is_empty(), "errors: {errors:?}");
2326        let prog = prog.expect("should parse");
2327
2328        assert_eq!(prog.records.len(), 1);
2329        assert!(prog.records[0].is_pub);
2330        assert_eq!(prog.records[0].name.name, "Config");
2331    }
2332
2333    #[test]
2334    fn parse_enum_declaration() {
2335        let source = r#"
2336            enum Status {
2337                Active,
2338                Pending,
2339                Done,
2340            }
2341
2342            agent Main {
2343                on start {
2344                    emit(0);
2345                }
2346            }
2347            run Main;
2348        "#;
2349
2350        let (prog, errors) = parse_str(source);
2351        assert!(errors.is_empty(), "errors: {errors:?}");
2352        let prog = prog.expect("should parse");
2353
2354        assert_eq!(prog.enums.len(), 1);
2355        assert!(!prog.enums[0].is_pub);
2356        assert_eq!(prog.enums[0].name.name, "Status");
2357        assert_eq!(prog.enums[0].variants.len(), 3);
2358        assert_eq!(prog.enums[0].variants[0].name.name, "Active");
2359        assert_eq!(prog.enums[0].variants[1].name.name, "Pending");
2360        assert_eq!(prog.enums[0].variants[2].name.name, "Done");
2361    }
2362
2363    #[test]
2364    fn parse_pub_enum() {
2365        let source = r#"
2366            pub enum Priority { High, Medium, Low }
2367
2368            agent Main {
2369                on start { emit(0); }
2370            }
2371            run Main;
2372        "#;
2373
2374        let (prog, errors) = parse_str(source);
2375        assert!(errors.is_empty(), "errors: {errors:?}");
2376        let prog = prog.expect("should parse");
2377
2378        assert_eq!(prog.enums.len(), 1);
2379        assert!(prog.enums[0].is_pub);
2380        assert_eq!(prog.enums[0].name.name, "Priority");
2381    }
2382
2383    #[test]
2384    fn parse_const_declaration() {
2385        let source = r#"
2386            const MAX_RETRIES: Int = 3;
2387
2388            agent Main {
2389                on start {
2390                    emit(0);
2391                }
2392            }
2393            run Main;
2394        "#;
2395
2396        let (prog, errors) = parse_str(source);
2397        assert!(errors.is_empty(), "errors: {errors:?}");
2398        let prog = prog.expect("should parse");
2399
2400        assert_eq!(prog.consts.len(), 1);
2401        assert!(!prog.consts[0].is_pub);
2402        assert_eq!(prog.consts[0].name.name, "MAX_RETRIES");
2403        assert!(matches!(prog.consts[0].ty, sage_types::TypeExpr::Int));
2404    }
2405
2406    #[test]
2407    fn parse_pub_const() {
2408        let source = r#"
2409            pub const API_URL: String = "https://api.example.com";
2410
2411            agent Main {
2412                on start { emit(0); }
2413            }
2414            run Main;
2415        "#;
2416
2417        let (prog, errors) = parse_str(source);
2418        assert!(errors.is_empty(), "errors: {errors:?}");
2419        let prog = prog.expect("should parse");
2420
2421        assert_eq!(prog.consts.len(), 1);
2422        assert!(prog.consts[0].is_pub);
2423        assert_eq!(prog.consts[0].name.name, "API_URL");
2424    }
2425
2426    #[test]
2427    fn parse_multiple_type_declarations() {
2428        let source = r#"
2429            record Point { x: Int, y: Int }
2430            enum Color { Red, Green, Blue }
2431            const ORIGIN_X: Int = 0;
2432
2433            agent Main {
2434                on start { emit(0); }
2435            }
2436            run Main;
2437        "#;
2438
2439        let (prog, errors) = parse_str(source);
2440        assert!(errors.is_empty(), "errors: {errors:?}");
2441        let prog = prog.expect("should parse");
2442
2443        assert_eq!(prog.records.len(), 1);
2444        assert_eq!(prog.enums.len(), 1);
2445        assert_eq!(prog.consts.len(), 1);
2446    }
2447
2448    #[test]
2449    fn parse_match_expression() {
2450        let source = r#"
2451            enum Status { Active, Pending, Done }
2452
2453            agent Main {
2454                on start {
2455                    let s: Int = match Active {
2456                        Active => 1,
2457                        Pending => 2,
2458                        Done => 3,
2459                    };
2460                    emit(s);
2461                }
2462            }
2463            run Main;
2464        "#;
2465
2466        let (prog, errors) = parse_str(source);
2467        assert!(errors.is_empty(), "errors: {errors:?}");
2468        let prog = prog.expect("should parse");
2469
2470        // Check the agent parsed
2471        assert_eq!(prog.agents.len(), 1);
2472        // Match is in the handler
2473        let handler = &prog.agents[0].handlers[0];
2474        let stmt = &handler.body.stmts[0];
2475        if let Stmt::Let { value, .. } = stmt {
2476            assert!(matches!(value, Expr::Match { .. }));
2477        } else {
2478            panic!("expected let statement with match");
2479        }
2480    }
2481
2482    #[test]
2483    fn parse_match_with_wildcard() {
2484        let source = r#"
2485            agent Main {
2486                on start {
2487                    let x = 5;
2488                    let result = match x {
2489                        1 => 10,
2490                        2 => 20,
2491                        _ => 0,
2492                    };
2493                    emit(result);
2494                }
2495            }
2496            run Main;
2497        "#;
2498
2499        let (prog, errors) = parse_str(source);
2500        assert!(errors.is_empty(), "errors: {errors:?}");
2501        let prog = prog.expect("should parse");
2502
2503        assert_eq!(prog.agents.len(), 1);
2504    }
2505
2506    #[test]
2507    fn parse_record_construction() {
2508        let source = r#"
2509            record Point { x: Int, y: Int }
2510
2511            agent Main {
2512                on start {
2513                    let p = Point { x: 10, y: 20 };
2514                    emit(0);
2515                }
2516            }
2517            run Main;
2518        "#;
2519
2520        let (prog, errors) = parse_str(source);
2521        assert!(errors.is_empty(), "errors: {errors:?}");
2522        let prog = prog.expect("should parse");
2523
2524        assert_eq!(prog.records.len(), 1);
2525        assert_eq!(prog.agents.len(), 1);
2526
2527        // Check the let statement has a record construction
2528        let handler = &prog.agents[0].handlers[0];
2529        let stmt = &handler.body.stmts[0];
2530        if let Stmt::Let { value, .. } = stmt {
2531            if let Expr::RecordConstruct { name, fields, .. } = value {
2532                assert_eq!(name.name, "Point");
2533                assert_eq!(fields.len(), 2);
2534                assert_eq!(fields[0].name.name, "x");
2535                assert_eq!(fields[1].name.name, "y");
2536            } else {
2537                panic!("expected RecordConstruct");
2538            }
2539        } else {
2540            panic!("expected let statement");
2541        }
2542    }
2543
2544    #[test]
2545    fn parse_match_with_qualified_variant() {
2546        let source = r#"
2547            enum Status { Active, Pending }
2548
2549            fn get_status() -> Int {
2550                return 1;
2551            }
2552
2553            agent Main {
2554                on start {
2555                    let s = get_status();
2556                    let result = match s {
2557                        Status::Active => 1,
2558                        Status::Pending => 0,
2559                    };
2560                    emit(result);
2561                }
2562            }
2563            run Main;
2564        "#;
2565
2566        let (prog, errors) = parse_str(source);
2567        assert!(errors.is_empty(), "errors: {errors:?}");
2568        let prog = prog.expect("should parse");
2569
2570        assert_eq!(prog.enums.len(), 1);
2571        assert_eq!(prog.agents.len(), 1);
2572    }
2573
2574    #[test]
2575    fn parse_field_access() {
2576        let source = r#"
2577            record Point { x: Int, y: Int }
2578
2579            agent Main {
2580                on start {
2581                    let p = Point { x: 10, y: 20 };
2582                    let x_val = p.x;
2583                    let y_val = p.y;
2584                    emit(x_val);
2585                }
2586            }
2587            run Main;
2588        "#;
2589
2590        let (prog, errors) = parse_str(source);
2591        assert!(errors.is_empty(), "errors: {errors:?}");
2592        let prog = prog.expect("should parse");
2593
2594        assert_eq!(prog.records.len(), 1);
2595        assert_eq!(prog.agents.len(), 1);
2596
2597        // Check the field access
2598        let handler = &prog.agents[0].handlers[0];
2599        let stmt = &handler.body.stmts[1]; // p.x assignment
2600        if let Stmt::Let { value, .. } = stmt {
2601            if let Expr::FieldAccess { field, .. } = value {
2602                assert_eq!(field.name, "x");
2603            } else {
2604                panic!("expected FieldAccess");
2605            }
2606        } else {
2607            panic!("expected let statement");
2608        }
2609    }
2610
2611    #[test]
2612    fn parse_chained_field_access() {
2613        let source = r#"
2614            record Inner { val: Int }
2615            record Outer { inner: Inner }
2616
2617            agent Main {
2618                on start {
2619                    let inner = Inner { val: 42 };
2620                    let outer = Outer { inner: inner };
2621                    let v = outer.inner.val;
2622                    emit(v);
2623                }
2624            }
2625            run Main;
2626        "#;
2627
2628        let (prog, errors) = parse_str(source);
2629        assert!(errors.is_empty(), "errors: {errors:?}");
2630        let prog = prog.expect("should parse");
2631
2632        assert_eq!(prog.records.len(), 2);
2633        assert_eq!(prog.agents.len(), 1);
2634
2635        // Check the chained field access: outer.inner.val
2636        let handler = &prog.agents[0].handlers[0];
2637        let stmt = &handler.body.stmts[2]; // outer.inner.val assignment
2638        if let Stmt::Let { value, .. } = stmt {
2639            if let Expr::FieldAccess {
2640                object, field: val, ..
2641            } = value
2642            {
2643                assert_eq!(val.name, "val");
2644                // object should be outer.inner
2645                if let Expr::FieldAccess { field: inner, .. } = object.as_ref() {
2646                    assert_eq!(inner.name, "inner");
2647                } else {
2648                    panic!("expected nested FieldAccess");
2649                }
2650            } else {
2651                panic!("expected FieldAccess");
2652            }
2653        } else {
2654            panic!("expected let statement");
2655        }
2656    }
2657
2658    // =========================================================================
2659    // RFC-0006: Message passing tests
2660    // =========================================================================
2661
2662    #[test]
2663    fn parse_loop_break() {
2664        let source = r#"
2665            agent Main {
2666                on start {
2667                    let count = 0;
2668                    loop {
2669                        count = count + 1;
2670                        if count > 5 {
2671                            break;
2672                        }
2673                    }
2674                    emit(count);
2675                }
2676            }
2677            run Main;
2678        "#;
2679
2680        let (prog, errors) = parse_str(source);
2681        assert!(errors.is_empty(), "errors: {errors:?}");
2682        let prog = prog.expect("should parse");
2683
2684        assert_eq!(prog.agents.len(), 1);
2685        let handler = &prog.agents[0].handlers[0];
2686        // Check loop statement exists
2687        let loop_stmt = &handler.body.stmts[1];
2688        assert!(matches!(loop_stmt, Stmt::Loop { .. }));
2689        // Check break is inside the loop
2690        if let Stmt::Loop { body, .. } = loop_stmt {
2691            let if_stmt = &body.stmts[1];
2692            if let Stmt::If { then_block, .. } = if_stmt {
2693                assert!(matches!(then_block.stmts[0], Stmt::Break { .. }));
2694            } else {
2695                panic!("expected if statement");
2696            }
2697        }
2698    }
2699
2700    #[test]
2701    fn parse_agent_receives() {
2702        let source = r#"
2703            enum WorkerMsg {
2704                Task,
2705                Shutdown,
2706            }
2707
2708            agent Worker receives WorkerMsg {
2709                id: Int
2710
2711                on start {
2712                    emit(0);
2713                }
2714            }
2715
2716            agent Main {
2717                on start {
2718                    emit(0);
2719                }
2720            }
2721            run Main;
2722        "#;
2723
2724        let (prog, errors) = parse_str(source);
2725        assert!(errors.is_empty(), "errors: {errors:?}");
2726        let prog = prog.expect("should parse");
2727
2728        assert_eq!(prog.agents.len(), 2);
2729
2730        // Worker should have receives clause
2731        let worker = &prog.agents[0];
2732        assert_eq!(worker.name.name, "Worker");
2733        assert!(worker.receives.is_some());
2734        if let Some(TypeExpr::Named(name)) = &worker.receives {
2735            assert_eq!(name.name, "WorkerMsg");
2736        } else {
2737            panic!("expected named type for receives");
2738        }
2739
2740        // Main should not have receives
2741        let main = &prog.agents[1];
2742        assert_eq!(main.name.name, "Main");
2743        assert!(main.receives.is_none());
2744    }
2745
2746    #[test]
2747    fn parse_receive_expression() {
2748        let source = r#"
2749            enum Msg { Ping }
2750
2751            agent Worker receives Msg {
2752                on start {
2753                    let msg = receive();
2754                    emit(0);
2755                }
2756            }
2757
2758            agent Main {
2759                on start { emit(0); }
2760            }
2761            run Main;
2762        "#;
2763
2764        let (prog, errors) = parse_str(source);
2765        assert!(errors.is_empty(), "errors: {errors:?}");
2766        let prog = prog.expect("should parse");
2767
2768        // Find Worker agent
2769        let worker = prog
2770            .agents
2771            .iter()
2772            .find(|a| a.name.name == "Worker")
2773            .unwrap();
2774        let handler = &worker.handlers[0];
2775        let stmt = &handler.body.stmts[0];
2776
2777        if let Stmt::Let { value, .. } = stmt {
2778            assert!(matches!(value, Expr::Receive { .. }));
2779        } else {
2780            panic!("expected let with receive");
2781        }
2782    }
2783
2784    #[test]
2785    fn parse_message_passing_full() {
2786        let source = r#"
2787            enum WorkerMsg {
2788                Task,
2789                Shutdown,
2790            }
2791
2792            agent Worker receives WorkerMsg {
2793                id: Int
2794
2795                on start {
2796                    let msg = receive();
2797                    let result = match msg {
2798                        Task => 1,
2799                        Shutdown => 0,
2800                    };
2801                    emit(result);
2802                }
2803            }
2804
2805            agent Main {
2806                on start {
2807                    let w = spawn Worker { id: 1 };
2808                    send(w, Task);
2809                    send(w, Shutdown);
2810                    await w;
2811                    emit(0);
2812                }
2813            }
2814            run Main;
2815        "#;
2816
2817        let (prog, errors) = parse_str(source);
2818        assert!(errors.is_empty(), "errors: {errors:?}");
2819        let prog = prog.expect("should parse");
2820
2821        assert_eq!(prog.enums.len(), 1);
2822        assert_eq!(prog.agents.len(), 2);
2823
2824        // Check Worker has receives
2825        let worker = prog
2826            .agents
2827            .iter()
2828            .find(|a| a.name.name == "Worker")
2829            .unwrap();
2830        assert!(worker.receives.is_some());
2831    }
2832
2833    // =========================================================================
2834    // RFC-0007: Error handling tests
2835    // =========================================================================
2836
2837    #[test]
2838    fn parse_fallible_function() {
2839        let source = r#"
2840            fn get_data(url: String) -> String fails {
2841                return infer("Get data from {url}" -> String);
2842            }
2843
2844            agent Main {
2845                on start { emit(0); }
2846            }
2847            run Main;
2848        "#;
2849
2850        let (prog, errors) = parse_str(source);
2851        assert!(errors.is_empty(), "errors: {errors:?}");
2852        let prog = prog.expect("should parse");
2853
2854        assert_eq!(prog.functions.len(), 1);
2855        assert!(prog.functions[0].is_fallible);
2856    }
2857
2858    #[test]
2859    fn parse_try_expression() {
2860        let source = r#"
2861            fn fallible() -> Int fails { return 42; }
2862
2863            agent Main {
2864                on start {
2865                    let x = try fallible();
2866                    emit(x);
2867                }
2868            }
2869            run Main;
2870        "#;
2871
2872        let (prog, errors) = parse_str(source);
2873        assert!(errors.is_empty(), "errors: {errors:?}");
2874        let prog = prog.expect("should parse");
2875
2876        // Find the let statement and check it contains a Try expression
2877        let handler = &prog.agents[0].handlers[0];
2878        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
2879            assert!(matches!(value, Expr::Try { .. }));
2880        } else {
2881            panic!("expected Let statement");
2882        }
2883    }
2884
2885    #[test]
2886    fn parse_catch_expression() {
2887        let source = r#"
2888            fn fallible() -> Int fails { return 42; }
2889
2890            agent Main {
2891                on start {
2892                    let x = fallible() catch { 0 };
2893                    emit(x);
2894                }
2895            }
2896            run Main;
2897        "#;
2898
2899        let (prog, errors) = parse_str(source);
2900        assert!(errors.is_empty(), "errors: {errors:?}");
2901        let prog = prog.expect("should parse");
2902
2903        // Find the let statement and check it contains a Catch expression
2904        let handler = &prog.agents[0].handlers[0];
2905        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
2906            if let Expr::Catch { error_bind, .. } = value {
2907                assert!(error_bind.is_none());
2908            } else {
2909                panic!("expected Catch expression");
2910            }
2911        } else {
2912            panic!("expected Let statement");
2913        }
2914    }
2915
2916    #[test]
2917    fn parse_catch_with_error_binding() {
2918        let source = r#"
2919            fn fallible() -> Int fails { return 42; }
2920
2921            agent Main {
2922                on start {
2923                    let x = fallible() catch(e) { 0 };
2924                    emit(x);
2925                }
2926            }
2927            run Main;
2928        "#;
2929
2930        let (prog, errors) = parse_str(source);
2931        assert!(errors.is_empty(), "errors: {errors:?}");
2932        let prog = prog.expect("should parse");
2933
2934        // Find the let statement and check it contains a Catch expression with binding
2935        let handler = &prog.agents[0].handlers[0];
2936        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
2937            if let Expr::Catch { error_bind, .. } = value {
2938                assert!(error_bind.is_some());
2939                assert_eq!(error_bind.as_ref().unwrap().name, "e");
2940            } else {
2941                panic!("expected Catch expression");
2942            }
2943        } else {
2944            panic!("expected Let statement");
2945        }
2946    }
2947
2948    #[test]
2949    fn parse_on_error_handler() {
2950        let source = r#"
2951            agent Main {
2952                on start {
2953                    emit(0);
2954                }
2955
2956                on error(e) {
2957                    emit(1);
2958                }
2959            }
2960            run Main;
2961        "#;
2962
2963        let (prog, errors) = parse_str(source);
2964        assert!(errors.is_empty(), "errors: {errors:?}");
2965        let prog = prog.expect("should parse");
2966
2967        assert_eq!(prog.agents.len(), 1);
2968        assert_eq!(prog.agents[0].handlers.len(), 2);
2969
2970        // Check the error handler
2971        let error_handler = prog.agents[0]
2972            .handlers
2973            .iter()
2974            .find(|h| matches!(h.event, EventKind::Error { .. }));
2975        assert!(error_handler.is_some());
2976
2977        if let EventKind::Error { param_name } = &error_handler.unwrap().event {
2978            assert_eq!(param_name.name, "e");
2979        } else {
2980            panic!("expected Error event kind");
2981        }
2982    }
2983
2984    // =========================================================================
2985    // RFC-0009: Closures and function types
2986    // =========================================================================
2987
2988    #[test]
2989    fn parse_fn_type() {
2990        let source = r#"
2991            fn apply(f: Fn(Int) -> Int, x: Int) -> Int {
2992                return f(x);
2993            }
2994
2995            agent Main {
2996                on start {
2997                    emit(0);
2998                }
2999            }
3000            run Main;
3001        "#;
3002
3003        let (prog, errors) = parse_str(source);
3004        assert!(errors.is_empty(), "errors: {errors:?}");
3005        let prog = prog.expect("should parse");
3006
3007        assert_eq!(prog.functions.len(), 1);
3008        let func = &prog.functions[0];
3009        assert_eq!(func.name.name, "apply");
3010        assert_eq!(func.params.len(), 2);
3011
3012        // Check first param is Fn(Int) -> Int
3013        if let TypeExpr::Fn(params, ret) = &func.params[0].ty {
3014            assert_eq!(params.len(), 1);
3015            assert!(matches!(params[0], TypeExpr::Int));
3016            assert!(matches!(ret.as_ref(), TypeExpr::Int));
3017        } else {
3018            panic!("expected Fn type for first param");
3019        }
3020    }
3021
3022    #[test]
3023    fn parse_closure_with_params() {
3024        let source = r#"
3025            agent Main {
3026                on start {
3027                    let f = |x: Int| x + 1;
3028                    emit(0);
3029                }
3030            }
3031            run Main;
3032        "#;
3033
3034        let (prog, errors) = parse_str(source);
3035        assert!(errors.is_empty(), "errors: {errors:?}");
3036        let prog = prog.expect("should parse");
3037
3038        // Find the let statement in the on start handler
3039        let handler = &prog.agents[0].handlers[0];
3040        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3041            if let Expr::Closure { params, body, .. } = value {
3042                assert_eq!(params.len(), 1);
3043                assert_eq!(params[0].name.name, "x");
3044                assert!(matches!(&params[0].ty, Some(TypeExpr::Int)));
3045
3046                // Body should be a binary expression
3047                assert!(matches!(body.as_ref(), Expr::Binary { .. }));
3048            } else {
3049                panic!("expected closure expression");
3050            }
3051        } else {
3052            panic!("expected let statement");
3053        }
3054    }
3055
3056    #[test]
3057    fn parse_closure_empty_params() {
3058        let source = r#"
3059            agent Main {
3060                on start {
3061                    let f = || 42;
3062                    emit(0);
3063                }
3064            }
3065            run Main;
3066        "#;
3067
3068        let (prog, errors) = parse_str(source);
3069        assert!(errors.is_empty(), "errors: {errors:?}");
3070        let prog = prog.expect("should parse");
3071
3072        // Find the let statement
3073        let handler = &prog.agents[0].handlers[0];
3074        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3075            if let Expr::Closure { params, body, .. } = value {
3076                assert!(params.is_empty());
3077
3078                // Body should be a literal
3079                assert!(matches!(body.as_ref(), Expr::Literal { .. }));
3080            } else {
3081                panic!("expected closure expression");
3082            }
3083        } else {
3084            panic!("expected let statement");
3085        }
3086    }
3087
3088    #[test]
3089    fn parse_closure_multiple_params() {
3090        let source = r#"
3091            agent Main {
3092                on start {
3093                    let add = |x: Int, y: Int| x + y;
3094                    emit(0);
3095                }
3096            }
3097            run Main;
3098        "#;
3099
3100        let (prog, errors) = parse_str(source);
3101        assert!(errors.is_empty(), "errors: {errors:?}");
3102        let prog = prog.expect("should parse");
3103
3104        let handler = &prog.agents[0].handlers[0];
3105        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3106            if let Expr::Closure { params, .. } = value {
3107                assert_eq!(params.len(), 2);
3108                assert_eq!(params[0].name.name, "x");
3109                assert_eq!(params[1].name.name, "y");
3110            } else {
3111                panic!("expected closure expression");
3112            }
3113        } else {
3114            panic!("expected let statement");
3115        }
3116    }
3117
3118    #[test]
3119    fn parse_fn_type_multiarg() {
3120        let source = r#"
3121            fn fold_left(f: Fn(Int, Int) -> Int, init: Int) -> Int {
3122                return init;
3123            }
3124
3125            agent Main {
3126                on start {
3127                    emit(0);
3128                }
3129            }
3130            run Main;
3131        "#;
3132
3133        let (prog, errors) = parse_str(source);
3134        assert!(errors.is_empty(), "errors: {errors:?}");
3135        let prog = prog.expect("should parse");
3136
3137        // Check Fn(Int, Int) -> Int
3138        if let TypeExpr::Fn(params, ret) = &prog.functions[0].params[0].ty {
3139            assert_eq!(params.len(), 2);
3140            assert!(matches!(params[0], TypeExpr::Int));
3141            assert!(matches!(params[1], TypeExpr::Int));
3142            assert!(matches!(ret.as_ref(), TypeExpr::Int));
3143        } else {
3144            panic!("expected Fn type");
3145        }
3146    }
3147
3148    #[test]
3149    fn parse_tuple_literal() {
3150        let source = r#"
3151            agent Main {
3152                on start {
3153                    let t = (1, 2);
3154                    emit(0);
3155                }
3156            }
3157            run Main;
3158        "#;
3159
3160        let (prog, errors) = parse_str(source);
3161        assert!(errors.is_empty(), "errors: {errors:?}");
3162        let prog = prog.expect("should parse");
3163
3164        let handler = &prog.agents[0].handlers[0];
3165        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3166            if let Expr::Tuple { elements, .. } = value {
3167                assert_eq!(elements.len(), 2);
3168            } else {
3169                panic!("expected tuple expression, got {:?}", value);
3170            }
3171        } else {
3172            panic!("expected let statement");
3173        }
3174    }
3175}