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, ConstDecl, ElseBranch, EnumDecl, EventKind, Expr,
7    FieldInit, FnDecl, HandlerDecl, Literal, MatchArm, ModDecl, Param, Pattern, Program,
8    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 }`
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
285    just(Token::KwPub)
286        .or_not()
287        .then_ignore(just(Token::KwEnum))
288        .then(ident_token_parser(src.clone()))
289        .then(
290            ident_token_parser(src.clone())
291                .separated_by(just(Token::Comma))
292                .allow_trailing()
293                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
294        )
295        .map_with_span(move |((is_pub, name), variants), span: Range<usize>| {
296            TopLevel::Enum(EnumDecl {
297                is_pub: is_pub.is_some(),
298                name,
299                variants,
300                span: make_span(&src2, span),
301            })
302        })
303}
304
305/// Parser for a const declaration: `const MAX_RETRIES: Int = 3`
306#[allow(clippy::needless_pass_by_value)]
307fn const_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
308    let src = source.clone();
309    let src2 = source.clone();
310
311    just(Token::KwPub)
312        .or_not()
313        .then_ignore(just(Token::KwConst))
314        .then(ident_token_parser(src.clone()))
315        .then_ignore(just(Token::Colon))
316        .then(type_parser(src.clone()))
317        .then_ignore(just(Token::Eq))
318        .then(expr_parser(src.clone()))
319        .then_ignore(just(Token::Semicolon))
320        .map_with_span(move |(((is_pub, name), ty), value), span: Range<usize>| {
321            TopLevel::Const(ConstDecl {
322                is_pub: is_pub.is_some(),
323                name,
324                ty,
325                value,
326                span: make_span(&src2, span),
327            })
328        })
329}
330
331// =============================================================================
332// Agent parsers
333// =============================================================================
334
335/// Parser for an agent declaration.
336#[allow(clippy::needless_pass_by_value)]
337fn agent_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
338    let src = source.clone();
339    let src2 = source.clone();
340    let src3 = source.clone();
341    let src4 = source.clone();
342
343    // Agent state fields: `name: Type` (no `belief` keyword in RFC-0005)
344    // We still call them "beliefs" internally for backwards compatibility
345    let belief = ident_token_parser(src.clone())
346        .then_ignore(just(Token::Colon))
347        .then(type_parser(src.clone()))
348        .map_with_span(move |(name, ty), span: Range<usize>| BeliefDecl {
349            name,
350            ty,
351            span: make_span(&src, span),
352        });
353
354    let handler = just(Token::KwOn)
355        .ignore_then(event_kind_parser(src2.clone()))
356        .then(block_parser(src2.clone()))
357        .map_with_span(move |(event, body), span: Range<usize>| HandlerDecl {
358            event,
359            body,
360            span: make_span(&src2, span),
361        });
362
363    // Optional `receives MsgType` clause
364    let receives_clause = just(Token::KwReceives)
365        .ignore_then(type_parser(src3.clone()))
366        .or_not();
367
368    just(Token::KwPub)
369        .or_not()
370        .then_ignore(just(Token::KwAgent))
371        .then(ident_token_parser(src3.clone()))
372        .then(receives_clause)
373        .then_ignore(just(Token::LBrace))
374        .then(belief.repeated())
375        .then(handler.repeated())
376        .then_ignore(just(Token::RBrace))
377        .map_with_span(
378            move |((((is_pub, name), receives), beliefs), handlers), span: Range<usize>| {
379                TopLevel::Agent(AgentDecl {
380                    is_pub: is_pub.is_some(),
381                    name,
382                    receives,
383                    beliefs,
384                    handlers,
385                    span: make_span(&src4, span),
386                })
387            },
388        )
389}
390
391/// Parser for event kinds.
392#[allow(clippy::needless_pass_by_value)]
393fn event_kind_parser(source: Arc<str>) -> impl Parser<Token, EventKind, Error = ParseError> {
394    let src = source.clone();
395
396    let start = just(Token::KwStart).to(EventKind::Start);
397    let stop = just(Token::KwStop).to(EventKind::Stop);
398
399    let message = just(Token::KwMessage)
400        .ignore_then(just(Token::LParen))
401        .ignore_then(ident_token_parser(src.clone()))
402        .then_ignore(just(Token::Colon))
403        .then(type_parser(src))
404        .then_ignore(just(Token::RParen))
405        .map(|(param_name, param_ty)| EventKind::Message {
406            param_name,
407            param_ty,
408        });
409
410    start.or(stop).or(message)
411}
412
413// =============================================================================
414// Function parsers
415// =============================================================================
416
417/// Parser for a function declaration.
418#[allow(clippy::needless_pass_by_value)]
419fn fn_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
420    let src = source.clone();
421    let src2 = source.clone();
422    let src3 = source.clone();
423
424    let param = ident_token_parser(src.clone())
425        .then_ignore(just(Token::Colon))
426        .then(type_parser(src.clone()))
427        .map_with_span(move |(name, ty), span: Range<usize>| Param {
428            name,
429            ty,
430            span: make_span(&src, span),
431        });
432
433    let params = param
434        .separated_by(just(Token::Comma))
435        .allow_trailing()
436        .delimited_by(just(Token::LParen), just(Token::RParen));
437
438    just(Token::KwPub)
439        .or_not()
440        .then_ignore(just(Token::KwFn))
441        .then(ident_token_parser(src2.clone()))
442        .then(params)
443        .then_ignore(just(Token::Arrow))
444        .then(type_parser(src2.clone()))
445        .then(block_parser(src2))
446        .map_with_span(
447            move |((((is_pub, name), params), return_ty), body), span: Range<usize>| {
448                TopLevel::Function(FnDecl {
449                    is_pub: is_pub.is_some(),
450                    name,
451                    params,
452                    return_ty,
453                    body,
454                    span: make_span(&src3, span),
455                })
456            },
457        )
458}
459
460// =============================================================================
461// Statement parsers
462// =============================================================================
463
464/// Parser for a block of statements.
465/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
466#[allow(clippy::needless_pass_by_value)]
467fn block_parser(source: Arc<str>) -> BoxedParser<'static, Token, Block, ParseError> {
468    let src = source.clone();
469
470    recursive(move |block: Recursive<Token, Block, ParseError>| {
471        let src_inner = src.clone();
472        stmt_parser(src.clone(), block)
473            .repeated()
474            .delimited_by(just(Token::LBrace), just(Token::RBrace))
475            .recover_with(nested_delimiters(
476                Token::LBrace,
477                Token::RBrace,
478                [
479                    (Token::LParen, Token::RParen),
480                    (Token::LBracket, Token::RBracket),
481                ],
482                |_span: Range<usize>| vec![],
483            ))
484            .map_with_span(move |stmts, span: Range<usize>| Block {
485                stmts,
486                span: make_span(&src_inner, span),
487            })
488    })
489    .boxed()
490}
491
492/// Parser for statements.
493#[allow(clippy::needless_pass_by_value)]
494fn stmt_parser(
495    source: Arc<str>,
496    block: impl Parser<Token, Block, Error = ParseError> + Clone + 'static,
497) -> impl Parser<Token, Stmt, Error = ParseError> + Clone {
498    let src = source.clone();
499    let src2 = source.clone();
500    let src3 = source.clone();
501    let src4 = source.clone();
502    let src5 = source.clone();
503    let src6 = source.clone();
504    let src7 = source.clone();
505
506    let let_stmt = just(Token::KwLet)
507        .ignore_then(ident_token_parser(src.clone()))
508        .then(
509            just(Token::Colon)
510                .ignore_then(type_parser(src.clone()))
511                .or_not(),
512        )
513        .then_ignore(just(Token::Eq))
514        .then(expr_parser(src.clone()))
515        .then_ignore(just(Token::Semicolon))
516        .map_with_span(move |((name, ty), value), span: Range<usize>| Stmt::Let {
517            name,
518            ty,
519            value,
520            span: make_span(&src, span),
521        });
522
523    let return_stmt = just(Token::KwReturn)
524        .ignore_then(expr_parser(src2.clone()).or_not())
525        .then_ignore(just(Token::Semicolon))
526        .map_with_span(move |value, span: Range<usize>| Stmt::Return {
527            value,
528            span: make_span(&src2, span),
529        });
530
531    let if_stmt = recursive(|if_stmt| {
532        let src_if = src3.clone();
533        let block_clone = block.clone();
534
535        just(Token::KwIf)
536            .ignore_then(expr_parser(src3.clone()))
537            .then(block_clone.clone())
538            .then(
539                just(Token::KwElse)
540                    .ignore_then(
541                        if_stmt
542                            .map(|s| ElseBranch::ElseIf(Box::new(s)))
543                            .or(block_clone.map(ElseBranch::Block)),
544                    )
545                    .or_not(),
546            )
547            .map_with_span(
548                move |((condition, then_block), else_block), span: Range<usize>| Stmt::If {
549                    condition,
550                    then_block,
551                    else_block,
552                    span: make_span(&src_if, span),
553                },
554            )
555    });
556
557    let for_stmt = just(Token::KwFor)
558        .ignore_then(ident_token_parser(src4.clone()))
559        .then_ignore(just(Token::KwIn))
560        .then(expr_parser(src4.clone()))
561        .then(block.clone())
562        .map_with_span(move |((var, iter), body), span: Range<usize>| Stmt::For {
563            var,
564            iter,
565            body,
566            span: make_span(&src4, span),
567        });
568
569    let while_stmt = just(Token::KwWhile)
570        .ignore_then(expr_parser(src7.clone()))
571        .then(block.clone())
572        .map_with_span(move |(condition, body), span: Range<usize>| Stmt::While {
573            condition,
574            body,
575            span: make_span(&src7, span),
576        });
577
578    let src8 = source.clone();
579    let loop_stmt = just(Token::KwLoop)
580        .ignore_then(block.clone())
581        .map_with_span(move |body, span: Range<usize>| Stmt::Loop {
582            body,
583            span: make_span(&src8, span),
584        });
585
586    let src9 = source.clone();
587    let break_stmt = just(Token::KwBreak)
588        .then_ignore(just(Token::Semicolon))
589        .map_with_span(move |_, span: Range<usize>| Stmt::Break {
590            span: make_span(&src9, span),
591        });
592
593    let assign_stmt = ident_token_parser(src5.clone())
594        .then_ignore(just(Token::Eq))
595        .then(expr_parser(src5.clone()))
596        .then_ignore(just(Token::Semicolon))
597        .map_with_span(move |(name, value), span: Range<usize>| Stmt::Assign {
598            name,
599            value,
600            span: make_span(&src5, span),
601        });
602
603    let expr_stmt = expr_parser(src6.clone())
604        .then_ignore(just(Token::Semicolon))
605        .map_with_span(move |expr, span: Range<usize>| Stmt::Expr {
606            expr,
607            span: make_span(&src6, span),
608        });
609
610    let_stmt
611        .or(return_stmt)
612        .or(if_stmt)
613        .or(for_stmt)
614        .or(while_stmt)
615        .or(loop_stmt)
616        .or(break_stmt)
617        .or(assign_stmt)
618        .or(expr_stmt)
619}
620
621// =============================================================================
622// Expression parsers
623// =============================================================================
624
625/// Parser for expressions (with precedence climbing for binary ops).
626/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
627#[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
628fn expr_parser(source: Arc<str>) -> BoxedParser<'static, Token, Expr, ParseError> {
629    recursive(move |expr: Recursive<Token, Expr, ParseError>| {
630        let src = source.clone();
631
632        let literal = literal_parser(src.clone());
633        let var = var_parser(src.clone());
634
635        let paren = expr
636            .clone()
637            .delimited_by(just(Token::LParen), just(Token::RParen))
638            .map_with_span({
639                let src = src.clone();
640                move |inner, span: Range<usize>| Expr::Paren {
641                    inner: Box::new(inner),
642                    span: make_span(&src, span),
643                }
644            });
645
646        let list = expr
647            .clone()
648            .separated_by(just(Token::Comma))
649            .allow_trailing()
650            .delimited_by(just(Token::LBracket), just(Token::RBracket))
651            .map_with_span({
652                let src = src.clone();
653                move |elements, span: Range<usize>| Expr::List {
654                    elements,
655                    span: make_span(&src, span),
656                }
657            });
658
659        // self.field or self.method(args)
660        let self_access = just(Token::KwSelf)
661            .ignore_then(just(Token::Dot))
662            .ignore_then(ident_token_parser(src.clone()))
663            .then(
664                expr.clone()
665                    .separated_by(just(Token::Comma))
666                    .allow_trailing()
667                    .delimited_by(just(Token::LParen), just(Token::RParen))
668                    .or_not(),
669            )
670            .map_with_span({
671                let src = src.clone();
672                move |(field, args), span: Range<usize>| match args {
673                    Some(args) => Expr::SelfMethodCall {
674                        method: field,
675                        args,
676                        span: make_span(&src, span),
677                    },
678                    None => Expr::SelfField {
679                        field,
680                        span: make_span(&src, span),
681                    },
682                }
683            });
684
685        // infer("template") or infer("template" -> Type)
686        let infer_expr = just(Token::KwInfer)
687            .ignore_then(just(Token::LParen))
688            .ignore_then(string_template_parser(src.clone()))
689            .then(
690                just(Token::Arrow)
691                    .ignore_then(type_parser(src.clone()))
692                    .or_not(),
693            )
694            .then_ignore(just(Token::RParen))
695            .map_with_span({
696                let src = src.clone();
697                move |(template, result_ty), span: Range<usize>| Expr::Infer {
698                    template,
699                    result_ty,
700                    span: make_span(&src, span),
701                }
702            });
703
704        // spawn Agent { field: value, ... }
705        let spawn_field_init = ident_token_parser(src.clone())
706            .then_ignore(just(Token::Colon))
707            .then(expr.clone())
708            .map_with_span({
709                let src = src.clone();
710                move |(name, value), span: Range<usize>| FieldInit {
711                    name,
712                    value,
713                    span: make_span(&src, span),
714                }
715            });
716
717        let spawn_expr = just(Token::KwSpawn)
718            .ignore_then(ident_token_parser(src.clone()))
719            .then_ignore(just(Token::LBrace))
720            .then(
721                spawn_field_init
722                    .separated_by(just(Token::Comma))
723                    .allow_trailing(),
724            )
725            .then_ignore(just(Token::RBrace))
726            .map_with_span({
727                let src = src.clone();
728                move |(agent, fields), span: Range<usize>| Expr::Spawn {
729                    agent,
730                    fields,
731                    span: make_span(&src, span),
732                }
733            });
734
735        // await expr - we need to handle this carefully to avoid left recursion
736        let await_expr = just(Token::KwAwait)
737            .ignore_then(ident_token_parser(src.clone()).map_with_span({
738                let src = src.clone();
739                move |name, span: Range<usize>| Expr::Var {
740                    name,
741                    span: make_span(&src, span),
742                }
743            }))
744            .map_with_span({
745                let src = src.clone();
746                move |handle, span: Range<usize>| Expr::Await {
747                    handle: Box::new(handle),
748                    span: make_span(&src, span),
749                }
750            });
751
752        // send(handle, message)
753        let send_expr = just(Token::KwSend)
754            .ignore_then(just(Token::LParen))
755            .ignore_then(expr.clone())
756            .then_ignore(just(Token::Comma))
757            .then(expr.clone())
758            .then_ignore(just(Token::RParen))
759            .map_with_span({
760                let src = src.clone();
761                move |(handle, message), span: Range<usize>| Expr::Send {
762                    handle: Box::new(handle),
763                    message: Box::new(message),
764                    span: make_span(&src, span),
765                }
766            });
767
768        // emit(value)
769        let emit_expr = just(Token::KwEmit)
770            .ignore_then(just(Token::LParen))
771            .ignore_then(expr.clone())
772            .then_ignore(just(Token::RParen))
773            .map_with_span({
774                let src = src.clone();
775                move |value, span: Range<usize>| Expr::Emit {
776                    value: Box::new(value),
777                    span: make_span(&src, span),
778                }
779            });
780
781        // function call: name(args)
782        let call_expr = ident_token_parser(src.clone())
783            .then(
784                expr.clone()
785                    .separated_by(just(Token::Comma))
786                    .allow_trailing()
787                    .delimited_by(just(Token::LParen), just(Token::RParen)),
788            )
789            .map_with_span({
790                let src = src.clone();
791                move |(name, args), span: Range<usize>| Expr::Call {
792                    name,
793                    args,
794                    span: make_span(&src, span),
795                }
796            });
797
798        // Pattern for match arms
799        let pattern = pattern_parser(src.clone());
800
801        // match expression: match expr { Pattern => expr, ... }
802        let match_arm = pattern
803            .then_ignore(just(Token::FatArrow))
804            .then(expr.clone())
805            .map_with_span({
806                let src = src.clone();
807                move |(pattern, body), span: Range<usize>| MatchArm {
808                    pattern,
809                    body,
810                    span: make_span(&src, span),
811                }
812            });
813
814        let match_expr = just(Token::KwMatch)
815            .ignore_then(expr.clone())
816            .then(
817                match_arm
818                    .separated_by(just(Token::Comma))
819                    .allow_trailing()
820                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
821            )
822            .map_with_span({
823                let src = src.clone();
824                move |(scrutinee, arms), span: Range<usize>| Expr::Match {
825                    scrutinee: Box::new(scrutinee),
826                    arms,
827                    span: make_span(&src, span),
828                }
829            });
830
831        // receive() - receive message from mailbox
832        let receive_expr = just(Token::KwReceive)
833            .ignore_then(just(Token::LParen))
834            .ignore_then(just(Token::RParen))
835            .map_with_span({
836                let src = src.clone();
837                move |_, span: Range<usize>| Expr::Receive {
838                    span: make_span(&src, span),
839                }
840            });
841
842        // Record construction: RecordName { field: value, ... }
843        // This is similar to spawn but without the spawn keyword
844        // Must come before var to avoid conflict
845        let record_field_init = ident_token_parser(src.clone())
846            .then_ignore(just(Token::Colon))
847            .then(expr.clone())
848            .map_with_span({
849                let src = src.clone();
850                move |(name, value), span: Range<usize>| FieldInit {
851                    name,
852                    value,
853                    span: make_span(&src, span),
854                }
855            });
856
857        let record_construct = ident_token_parser(src.clone())
858            .then_ignore(just(Token::LBrace))
859            .then(
860                record_field_init
861                    .separated_by(just(Token::Comma))
862                    .allow_trailing(),
863            )
864            .then_ignore(just(Token::RBrace))
865            .map_with_span({
866                let src = src.clone();
867                move |(name, fields), span: Range<usize>| Expr::RecordConstruct {
868                    name,
869                    fields,
870                    span: make_span(&src, span),
871                }
872            });
873
874        // Atom: the base expression without binary ops
875        // Box early to cut type complexity
876        // Note: record_construct must come before call_expr and var to parse `Name { ... }` correctly
877        // Note: receive_expr must come before call_expr to avoid being parsed as function call
878        let atom = infer_expr
879            .or(spawn_expr)
880            .or(await_expr)
881            .or(send_expr)
882            .or(emit_expr)
883            .or(receive_expr)
884            .or(match_expr)
885            .or(self_access)
886            .or(record_construct)
887            .or(call_expr)
888            .or(list)
889            .or(paren)
890            .or(literal)
891            .or(var)
892            .boxed();
893
894        // Postfix field access: expr.field
895        let postfix = atom
896            .then(
897                just(Token::Dot)
898                    .ignore_then(ident_token_parser(src.clone()))
899                    .repeated(),
900            )
901            .foldl({
902                let src = src.clone();
903                move |object, field| {
904                    let span = make_span(&src, object.span().start..field.span.end);
905                    Expr::FieldAccess {
906                        object: Box::new(object),
907                        field,
908                        span,
909                    }
910                }
911            })
912            .boxed();
913
914        // Unary expressions
915        let unary = just(Token::Minus)
916            .to(UnaryOp::Neg)
917            .or(just(Token::Bang).to(UnaryOp::Not))
918            .repeated()
919            .then(postfix)
920            .foldr(|op, operand| {
921                let span = operand.span().clone();
922                Expr::Unary {
923                    op,
924                    operand: Box::new(operand),
925                    span,
926                }
927            })
928            .boxed();
929
930        // Binary operators with precedence levels
931        // Level 7: * /
932        let mul_div_op = just(Token::Star)
933            .to(BinOp::Mul)
934            .or(just(Token::Slash).to(BinOp::Div));
935
936        let mul_div = unary
937            .clone()
938            .then(mul_div_op.then(unary.clone()).repeated())
939            .foldl({
940                let src = src.clone();
941                move |left, (op, right)| {
942                    let span = make_span(&src, left.span().start..right.span().end);
943                    Expr::Binary {
944                        op,
945                        left: Box::new(left),
946                        right: Box::new(right),
947                        span,
948                    }
949                }
950            })
951            .boxed();
952
953        // Level 6: + -
954        let add_sub_op = just(Token::Plus)
955            .to(BinOp::Add)
956            .or(just(Token::Minus).to(BinOp::Sub));
957
958        let add_sub = mul_div
959            .clone()
960            .then(add_sub_op.then(mul_div).repeated())
961            .foldl({
962                let src = src.clone();
963                move |left, (op, right)| {
964                    let span = make_span(&src, left.span().start..right.span().end);
965                    Expr::Binary {
966                        op,
967                        left: Box::new(left),
968                        right: Box::new(right),
969                        span,
970                    }
971                }
972            })
973            .boxed();
974
975        // Level 5: ++
976        let concat_op = just(Token::PlusPlus).to(BinOp::Concat);
977
978        let concat = add_sub
979            .clone()
980            .then(concat_op.then(add_sub).repeated())
981            .foldl({
982                let src = src.clone();
983                move |left, (op, right)| {
984                    let span = make_span(&src, left.span().start..right.span().end);
985                    Expr::Binary {
986                        op,
987                        left: Box::new(left),
988                        right: Box::new(right),
989                        span,
990                    }
991                }
992            })
993            .boxed();
994
995        // Level 4: < > <= >=
996        let cmp_op = choice((
997            just(Token::Le).to(BinOp::Le),
998            just(Token::Ge).to(BinOp::Ge),
999            just(Token::Lt).to(BinOp::Lt),
1000            just(Token::Gt).to(BinOp::Gt),
1001        ));
1002
1003        let comparison = concat
1004            .clone()
1005            .then(cmp_op.then(concat).repeated())
1006            .foldl({
1007                let src = src.clone();
1008                move |left, (op, right)| {
1009                    let span = make_span(&src, left.span().start..right.span().end);
1010                    Expr::Binary {
1011                        op,
1012                        left: Box::new(left),
1013                        right: Box::new(right),
1014                        span,
1015                    }
1016                }
1017            })
1018            .boxed();
1019
1020        // Level 3: == !=
1021        let eq_op = just(Token::EqEq)
1022            .to(BinOp::Eq)
1023            .or(just(Token::Ne).to(BinOp::Ne));
1024
1025        let equality = comparison
1026            .clone()
1027            .then(eq_op.then(comparison).repeated())
1028            .foldl({
1029                let src = src.clone();
1030                move |left, (op, right)| {
1031                    let span = make_span(&src, left.span().start..right.span().end);
1032                    Expr::Binary {
1033                        op,
1034                        left: Box::new(left),
1035                        right: Box::new(right),
1036                        span,
1037                    }
1038                }
1039            })
1040            .boxed();
1041
1042        // Level 2: &&
1043        let and_op = just(Token::And).to(BinOp::And);
1044
1045        let and = equality
1046            .clone()
1047            .then(and_op.then(equality).repeated())
1048            .foldl({
1049                let src = src.clone();
1050                move |left, (op, right)| {
1051                    let span = make_span(&src, left.span().start..right.span().end);
1052                    Expr::Binary {
1053                        op,
1054                        left: Box::new(left),
1055                        right: Box::new(right),
1056                        span,
1057                    }
1058                }
1059            })
1060            .boxed();
1061
1062        // Level 1: ||
1063        let or_op = just(Token::Or).to(BinOp::Or);
1064
1065        and.clone().then(or_op.then(and).repeated()).foldl({
1066            let src = src.clone();
1067            move |left, (op, right)| {
1068                let span = make_span(&src, left.span().start..right.span().end);
1069                Expr::Binary {
1070                    op,
1071                    left: Box::new(left),
1072                    right: Box::new(right),
1073                    span,
1074                }
1075            }
1076        })
1077    })
1078    .boxed()
1079}
1080
1081// =============================================================================
1082// Primitive parsers
1083// =============================================================================
1084
1085/// Create a Span from a Range<usize>.
1086fn make_span(source: &Arc<str>, range: Range<usize>) -> Span {
1087    Span::new(range.start, range.end, Arc::clone(source))
1088}
1089
1090/// Parser for identifier tokens.
1091fn ident_token_parser(source: Arc<str>) -> impl Parser<Token, Ident, Error = ParseError> + Clone {
1092    filter_map(move |span: Range<usize>, token| match token {
1093        Token::Ident => {
1094            let text = &source[span.start..span.end];
1095            Ok(Ident::new(text.to_string(), make_span(&source, span)))
1096        }
1097        _ => Err(Simple::expected_input_found(
1098            span,
1099            vec![Some(Token::Ident)],
1100            Some(token),
1101        )),
1102    })
1103}
1104
1105/// Parser for variable references.
1106fn var_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
1107    ident_token_parser(source.clone()).map_with_span(move |name, span: Range<usize>| Expr::Var {
1108        name,
1109        span: make_span(&source, span),
1110    })
1111}
1112
1113/// Parser for type expressions.
1114fn type_parser(source: Arc<str>) -> impl Parser<Token, TypeExpr, Error = ParseError> + Clone {
1115    recursive(move |ty| {
1116        let src = source.clone();
1117
1118        let primitive = choice((
1119            just(Token::TyInt).to(TypeExpr::Int),
1120            just(Token::TyFloat).to(TypeExpr::Float),
1121            just(Token::TyBool).to(TypeExpr::Bool),
1122            just(Token::TyString).to(TypeExpr::String),
1123            just(Token::TyUnit).to(TypeExpr::Unit),
1124        ));
1125
1126        let list_ty = just(Token::TyList)
1127            .ignore_then(just(Token::Lt))
1128            .ignore_then(ty.clone())
1129            .then_ignore(just(Token::Gt))
1130            .map(|inner| TypeExpr::List(Box::new(inner)));
1131
1132        let option_ty = just(Token::TyOption)
1133            .ignore_then(just(Token::Lt))
1134            .ignore_then(ty.clone())
1135            .then_ignore(just(Token::Gt))
1136            .map(|inner| TypeExpr::Option(Box::new(inner)));
1137
1138        let inferred_ty = just(Token::TyInferred)
1139            .ignore_then(just(Token::Lt))
1140            .ignore_then(ty.clone())
1141            .then_ignore(just(Token::Gt))
1142            .map(|inner| TypeExpr::Inferred(Box::new(inner)));
1143
1144        let agent_ty = just(Token::TyAgent)
1145            .ignore_then(just(Token::Lt))
1146            .ignore_then(ident_token_parser(src.clone()))
1147            .then_ignore(just(Token::Gt))
1148            .map(TypeExpr::Agent);
1149
1150        let named_ty = ident_token_parser(src).map(TypeExpr::Named);
1151
1152        primitive
1153            .or(list_ty)
1154            .or(option_ty)
1155            .or(inferred_ty)
1156            .or(agent_ty)
1157            .or(named_ty)
1158    })
1159}
1160
1161/// Parser for patterns in match expressions.
1162fn pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
1163    let src = source.clone();
1164    let src2 = source.clone();
1165    let src3 = source.clone();
1166    let src4 = source.clone();
1167
1168    // Wildcard pattern: `_`
1169    let wildcard = filter_map(move |span: Range<usize>, token| match &token {
1170        Token::Ident if src[span.start..span.end].eq("_") => Ok(()),
1171        _ => Err(Simple::expected_input_found(span, vec![], Some(token))),
1172    })
1173    .map_with_span(move |_, span: Range<usize>| Pattern::Wildcard {
1174        span: make_span(&src2, span),
1175    });
1176
1177    // Literal patterns: 42, "hello", true, false
1178    let lit_int = filter_map({
1179        let src = src3.clone();
1180        move |span: Range<usize>, token| match token {
1181            Token::IntLit => {
1182                let text = &src[span.start..span.end];
1183                text.parse::<i64>()
1184                    .map(Literal::Int)
1185                    .map_err(|_| Simple::custom(span, "invalid integer literal"))
1186            }
1187            _ => Err(Simple::expected_input_found(
1188                span,
1189                vec![Some(Token::IntLit)],
1190                Some(token),
1191            )),
1192        }
1193    })
1194    .map_with_span({
1195        let src = src3.clone();
1196        move |value, span: Range<usize>| Pattern::Literal {
1197            value,
1198            span: make_span(&src, span),
1199        }
1200    });
1201
1202    let lit_bool = just(Token::KwTrue)
1203        .to(Literal::Bool(true))
1204        .or(just(Token::KwFalse).to(Literal::Bool(false)))
1205        .map_with_span({
1206            let src = src3.clone();
1207            move |value, span: Range<usize>| Pattern::Literal {
1208                value,
1209                span: make_span(&src, span),
1210            }
1211        });
1212
1213    // Enum variant: `EnumName::Variant` or just `Variant`
1214    // Qualified: EnumName::Variant
1215    let qualified_variant = ident_token_parser(src4.clone())
1216        .then_ignore(just(Token::ColonColon))
1217        .then(ident_token_parser(src4.clone()))
1218        .map_with_span({
1219            let src = src4.clone();
1220            move |(enum_name, variant), span: Range<usize>| Pattern::Variant {
1221                enum_name: Some(enum_name),
1222                variant,
1223                span: make_span(&src, span),
1224            }
1225        });
1226
1227    // Unqualified variant or binding: just an identifier
1228    // We'll treat PascalCase as variant, snake_case as binding
1229    // For now, let's just parse it as a binding (the checker will resolve)
1230    let unqualified = ident_token_parser(src4.clone()).map_with_span({
1231        let src = src4.clone();
1232        move |name, span: Range<usize>| {
1233            // If it looks like a variant (starts with uppercase), treat as variant
1234            // Otherwise treat as binding
1235            if name.name.chars().next().is_some_and(|c| c.is_uppercase()) {
1236                Pattern::Variant {
1237                    enum_name: None,
1238                    variant: name,
1239                    span: make_span(&src, span),
1240                }
1241            } else {
1242                Pattern::Binding {
1243                    name,
1244                    span: make_span(&src, span),
1245                }
1246            }
1247        }
1248    });
1249
1250    // Order matters: try wildcard first, then qualified variant, then literals, then unqualified
1251    wildcard
1252        .or(qualified_variant)
1253        .or(lit_int)
1254        .or(lit_bool)
1255        .or(unqualified)
1256}
1257
1258/// Parser for literals.
1259fn literal_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
1260    let src = source.clone();
1261    let src2 = source.clone();
1262    let src3 = source.clone();
1263    let src4 = source.clone();
1264    let src5 = source.clone();
1265
1266    let int_lit = filter_map(move |span: Range<usize>, token| match token {
1267        Token::IntLit => {
1268            let text = &src[span.start..span.end];
1269            text.parse::<i64>()
1270                .map(Literal::Int)
1271                .map_err(|_| Simple::custom(span, "invalid integer literal"))
1272        }
1273        _ => Err(Simple::expected_input_found(
1274            span,
1275            vec![Some(Token::IntLit)],
1276            Some(token),
1277        )),
1278    })
1279    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
1280        value,
1281        span: make_span(&src2, span),
1282    });
1283
1284    let float_lit = filter_map(move |span: Range<usize>, token| match token {
1285        Token::FloatLit => {
1286            let text = &src3[span.start..span.end];
1287            text.parse::<f64>()
1288                .map(Literal::Float)
1289                .map_err(|_| Simple::custom(span, "invalid float literal"))
1290        }
1291        _ => Err(Simple::expected_input_found(
1292            span,
1293            vec![Some(Token::FloatLit)],
1294            Some(token),
1295        )),
1296    })
1297    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
1298        value,
1299        span: make_span(&src4, span),
1300    });
1301
1302    let src6 = source.clone();
1303    let string_lit = filter_map(move |span: Range<usize>, token| match token {
1304        Token::StringLit => {
1305            let text = &src5[span.start..span.end];
1306            let inner = &text[1..text.len() - 1];
1307            let parts = parse_string_template(inner, &make_span(&src5, span.clone()));
1308            Ok(parts)
1309        }
1310        _ => Err(Simple::expected_input_found(
1311            span,
1312            vec![Some(Token::StringLit)],
1313            Some(token),
1314        )),
1315    })
1316    .map_with_span(move |parts, span: Range<usize>| {
1317        let span = make_span(&src6, span);
1318        // If no interpolations, use a simple string literal
1319        if parts.len() == 1 {
1320            if let StringPart::Literal(s) = &parts[0] {
1321                return Expr::Literal {
1322                    value: Literal::String(s.clone()),
1323                    span,
1324                };
1325            }
1326        }
1327        // Otherwise, use StringInterp
1328        Expr::StringInterp {
1329            template: StringTemplate {
1330                parts,
1331                span: span.clone(),
1332            },
1333            span,
1334        }
1335    });
1336
1337    let bool_lit = just(Token::KwTrue)
1338        .to(Literal::Bool(true))
1339        .or(just(Token::KwFalse).to(Literal::Bool(false)))
1340        .map_with_span(move |value, _span: Range<usize>| Expr::Literal {
1341            value,
1342            span: Span::dummy(), // bool literals don't carry source
1343        });
1344
1345    int_lit.or(float_lit).or(string_lit).or(bool_lit)
1346}
1347
1348/// Parser for string templates (handles interpolation).
1349fn string_template_parser(
1350    source: Arc<str>,
1351) -> impl Parser<Token, StringTemplate, Error = ParseError> + Clone {
1352    filter_map(move |span: Range<usize>, token| match token {
1353        Token::StringLit => {
1354            let text = &source[span.start..span.end];
1355            let inner = &text[1..text.len() - 1];
1356            let parts = parse_string_template(inner, &make_span(&source, span.clone()));
1357            Ok(StringTemplate {
1358                parts,
1359                span: make_span(&source, span),
1360            })
1361        }
1362        _ => Err(Simple::expected_input_found(
1363            span,
1364            vec![Some(Token::StringLit)],
1365            Some(token),
1366        )),
1367    })
1368}
1369
1370/// Parse a string into template parts, handling `{ident}` interpolations.
1371fn parse_string_template(s: &str, span: &Span) -> Vec<StringPart> {
1372    let mut parts = Vec::new();
1373    let mut current = String::new();
1374    let mut chars = s.chars().peekable();
1375
1376    while let Some(ch) = chars.next() {
1377        if ch == '{' {
1378            if !current.is_empty() {
1379                parts.push(StringPart::Literal(std::mem::take(&mut current)));
1380            }
1381
1382            let mut ident_name = String::new();
1383            while let Some(&c) = chars.peek() {
1384                if c == '}' {
1385                    chars.next();
1386                    break;
1387                }
1388                ident_name.push(c);
1389                chars.next();
1390            }
1391
1392            if !ident_name.is_empty() {
1393                parts.push(StringPart::Interpolation(Ident::new(
1394                    ident_name,
1395                    span.clone(),
1396                )));
1397            }
1398        } else if ch == '\\' {
1399            if let Some(escaped) = chars.next() {
1400                current.push(match escaped {
1401                    'n' => '\n',
1402                    't' => '\t',
1403                    'r' => '\r',
1404                    '\\' => '\\',
1405                    '"' => '"',
1406                    '{' => '{',
1407                    '}' => '}',
1408                    other => other,
1409                });
1410            }
1411        } else {
1412            current.push(ch);
1413        }
1414    }
1415
1416    if !current.is_empty() {
1417        parts.push(StringPart::Literal(current));
1418    }
1419
1420    if parts.is_empty() {
1421        parts.push(StringPart::Literal(String::new()));
1422    }
1423
1424    parts
1425}
1426
1427// =============================================================================
1428// Tests
1429// =============================================================================
1430
1431#[cfg(test)]
1432mod tests {
1433    use super::*;
1434    use sage_lexer::lex;
1435
1436    fn parse_str(source: &str) -> (Option<Program>, Vec<ParseError>) {
1437        let lex_result = lex(source).expect("lexing should succeed");
1438        let source_arc: Arc<str> = Arc::from(source);
1439        parse(lex_result.tokens(), source_arc)
1440    }
1441
1442    #[test]
1443    fn parse_minimal_program() {
1444        let source = r#"
1445            agent Main {
1446                on start {
1447                    emit(42);
1448                }
1449            }
1450            run Main;
1451        "#;
1452
1453        let (prog, errors) = parse_str(source);
1454        assert!(errors.is_empty(), "errors: {errors:?}");
1455        let prog = prog.expect("should parse");
1456
1457        assert_eq!(prog.agents.len(), 1);
1458        assert_eq!(prog.agents[0].name.name, "Main");
1459        assert_eq!(prog.run_agent.as_ref().unwrap().name, "Main");
1460    }
1461
1462    #[test]
1463    fn parse_agent_with_beliefs() {
1464        let source = r#"
1465            agent Researcher {
1466                topic: String
1467                max_words: Int
1468
1469                on start {
1470                    emit(self.topic);
1471                }
1472            }
1473            run Researcher;
1474        "#;
1475
1476        let (prog, errors) = parse_str(source);
1477        assert!(errors.is_empty(), "errors: {errors:?}");
1478        let prog = prog.expect("should parse");
1479
1480        assert_eq!(prog.agents[0].beliefs.len(), 2);
1481        assert_eq!(prog.agents[0].beliefs[0].name.name, "topic");
1482        assert_eq!(prog.agents[0].beliefs[1].name.name, "max_words");
1483    }
1484
1485    #[test]
1486    fn parse_multiple_handlers() {
1487        let source = r#"
1488            agent Worker {
1489                on start {
1490                    print("started");
1491                }
1492
1493                on message(msg: String) {
1494                    print(msg);
1495                }
1496
1497                on stop {
1498                    print("stopped");
1499                }
1500            }
1501            run Worker;
1502        "#;
1503
1504        let (prog, errors) = parse_str(source);
1505        assert!(errors.is_empty(), "errors: {errors:?}");
1506        let prog = prog.expect("should parse");
1507
1508        assert_eq!(prog.agents[0].handlers.len(), 3);
1509        assert_eq!(prog.agents[0].handlers[0].event, EventKind::Start);
1510        assert!(matches!(
1511            prog.agents[0].handlers[1].event,
1512            EventKind::Message { .. }
1513        ));
1514        assert_eq!(prog.agents[0].handlers[2].event, EventKind::Stop);
1515    }
1516
1517    #[test]
1518    fn parse_function() {
1519        let source = r#"
1520            fn greet(name: String) -> String {
1521                return "Hello, " ++ name;
1522            }
1523
1524            agent Main {
1525                on start {
1526                    emit(greet("World"));
1527                }
1528            }
1529            run Main;
1530        "#;
1531
1532        let (prog, errors) = parse_str(source);
1533        assert!(errors.is_empty(), "errors: {errors:?}");
1534        let prog = prog.expect("should parse");
1535
1536        assert_eq!(prog.functions.len(), 1);
1537        assert_eq!(prog.functions[0].name.name, "greet");
1538        assert_eq!(prog.functions[0].params.len(), 1);
1539    }
1540
1541    #[test]
1542    fn parse_let_statement() {
1543        let source = r#"
1544            agent Main {
1545                on start {
1546                    let x: Int = 42;
1547                    let y = "hello";
1548                    emit(x);
1549                }
1550            }
1551            run Main;
1552        "#;
1553
1554        let (prog, errors) = parse_str(source);
1555        assert!(errors.is_empty(), "errors: {errors:?}");
1556        let prog = prog.expect("should parse");
1557
1558        let stmts = &prog.agents[0].handlers[0].body.stmts;
1559        assert!(matches!(stmts[0], Stmt::Let { .. }));
1560        assert!(matches!(stmts[1], Stmt::Let { .. }));
1561    }
1562
1563    #[test]
1564    fn parse_if_statement() {
1565        let source = r#"
1566            agent Main {
1567                on start {
1568                    if true {
1569                        emit(1);
1570                    } else {
1571                        emit(2);
1572                    }
1573                }
1574            }
1575            run Main;
1576        "#;
1577
1578        let (prog, errors) = parse_str(source);
1579        assert!(errors.is_empty(), "errors: {errors:?}");
1580        let prog = prog.expect("should parse");
1581
1582        let stmts = &prog.agents[0].handlers[0].body.stmts;
1583        assert!(matches!(stmts[0], Stmt::If { .. }));
1584    }
1585
1586    #[test]
1587    fn parse_for_loop() {
1588        let source = r#"
1589            agent Main {
1590                on start {
1591                    for x in [1, 2, 3] {
1592                        print(x);
1593                    }
1594                    emit(0);
1595                }
1596            }
1597            run Main;
1598        "#;
1599
1600        let (prog, errors) = parse_str(source);
1601        assert!(errors.is_empty(), "errors: {errors:?}");
1602        let prog = prog.expect("should parse");
1603
1604        let stmts = &prog.agents[0].handlers[0].body.stmts;
1605        assert!(matches!(stmts[0], Stmt::For { .. }));
1606    }
1607
1608    #[test]
1609    fn parse_spawn_await() {
1610        let source = r#"
1611            agent Worker {
1612                name: String
1613
1614                on start {
1615                    emit(self.name);
1616                }
1617            }
1618
1619            agent Main {
1620                on start {
1621                    let w = spawn Worker { name: "test" };
1622                    let result = await w;
1623                    emit(result);
1624                }
1625            }
1626            run Main;
1627        "#;
1628
1629        let (prog, errors) = parse_str(source);
1630        assert!(errors.is_empty(), "errors: {errors:?}");
1631        prog.expect("should parse");
1632    }
1633
1634    #[test]
1635    fn parse_infer() {
1636        let source = r#"
1637            agent Main {
1638                on start {
1639                    let result = infer("What is 2+2?");
1640                    emit(result);
1641                }
1642            }
1643            run Main;
1644        "#;
1645
1646        let (prog, errors) = parse_str(source);
1647        assert!(errors.is_empty(), "errors: {errors:?}");
1648        prog.expect("should parse");
1649    }
1650
1651    #[test]
1652    fn parse_binary_precedence() {
1653        let source = r#"
1654            agent Main {
1655                on start {
1656                    let x = 2 + 3 * 4;
1657                    emit(x);
1658                }
1659            }
1660            run Main;
1661        "#;
1662
1663        let (prog, errors) = parse_str(source);
1664        assert!(errors.is_empty(), "errors: {errors:?}");
1665        let prog = prog.expect("should parse");
1666
1667        let stmts = &prog.agents[0].handlers[0].body.stmts;
1668        if let Stmt::Let { value, .. } = &stmts[0] {
1669            if let Expr::Binary { op, .. } = value {
1670                assert_eq!(*op, BinOp::Add);
1671            } else {
1672                panic!("expected binary expression");
1673            }
1674        }
1675    }
1676
1677    #[test]
1678    fn parse_string_interpolation() {
1679        let source = r#"
1680            agent Main {
1681                on start {
1682                    let name = "World";
1683                    let msg = infer("Greet {name}");
1684                    emit(msg);
1685                }
1686            }
1687            run Main;
1688        "#;
1689
1690        let (prog, errors) = parse_str(source);
1691        assert!(errors.is_empty(), "errors: {errors:?}");
1692        let prog = prog.expect("should parse");
1693
1694        let stmts = &prog.agents[0].handlers[0].body.stmts;
1695        if let Stmt::Let { value, .. } = &stmts[1] {
1696            if let Expr::Infer { template, .. } = value {
1697                assert!(template.has_interpolations());
1698            } else {
1699                panic!("expected infer expression");
1700            }
1701        }
1702    }
1703
1704    // =========================================================================
1705    // Error recovery tests
1706    // =========================================================================
1707
1708    #[test]
1709    fn recover_from_malformed_agent_continues_to_next() {
1710        // First agent has syntax error (missing type after colon), second is valid
1711        let source = r#"
1712            agent Broken {
1713                x:
1714            }
1715
1716            agent Main {
1717                on start {
1718                    emit(42);
1719                }
1720            }
1721            run Main;
1722        "#;
1723
1724        let (prog, errors) = parse_str(source);
1725        // Should have errors from the broken agent
1726        assert!(!errors.is_empty(), "should have parse errors");
1727        // But should still produce a program with the valid agent
1728        let prog = prog.expect("should produce partial AST");
1729        assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
1730    }
1731
1732    #[test]
1733    fn recover_from_mismatched_braces_in_block() {
1734        let source = r#"
1735            agent Main {
1736                on start {
1737                    let x = [1, 2, 3;
1738                    emit(42);
1739                }
1740            }
1741            run Main;
1742        "#;
1743
1744        let (prog, errors) = parse_str(source);
1745        // Should have errors but still produce an AST
1746        assert!(!errors.is_empty(), "should have parse errors");
1747        assert!(prog.is_some(), "should produce partial AST despite errors");
1748    }
1749
1750    #[test]
1751    fn parse_mod_declaration() {
1752        let source = r#"
1753            mod agents;
1754            pub mod utils;
1755
1756            agent Main {
1757                on start {
1758                    emit(42);
1759                }
1760            }
1761            run Main;
1762        "#;
1763
1764        let (prog, errors) = parse_str(source);
1765        assert!(errors.is_empty(), "errors: {errors:?}");
1766        let prog = prog.expect("should parse");
1767
1768        assert_eq!(prog.mod_decls.len(), 2);
1769        assert!(!prog.mod_decls[0].is_pub);
1770        assert_eq!(prog.mod_decls[0].name.name, "agents");
1771        assert!(prog.mod_decls[1].is_pub);
1772        assert_eq!(prog.mod_decls[1].name.name, "utils");
1773    }
1774
1775    #[test]
1776    fn parse_use_simple() {
1777        let source = r#"
1778            use agents::Researcher;
1779
1780            agent Main {
1781                on start {
1782                    emit(42);
1783                }
1784            }
1785            run Main;
1786        "#;
1787
1788        let (prog, errors) = parse_str(source);
1789        assert!(errors.is_empty(), "errors: {errors:?}");
1790        let prog = prog.expect("should parse");
1791
1792        assert_eq!(prog.use_decls.len(), 1);
1793        assert!(!prog.use_decls[0].is_pub);
1794        assert_eq!(prog.use_decls[0].path.len(), 2);
1795        assert_eq!(prog.use_decls[0].path[0].name, "agents");
1796        assert_eq!(prog.use_decls[0].path[1].name, "Researcher");
1797        assert!(matches!(prog.use_decls[0].kind, UseKind::Simple(None)));
1798    }
1799
1800    #[test]
1801    fn parse_use_with_alias() {
1802        let source = r#"
1803            use agents::Researcher as R;
1804
1805            agent Main {
1806                on start {
1807                    emit(42);
1808                }
1809            }
1810            run Main;
1811        "#;
1812
1813        let (prog, errors) = parse_str(source);
1814        assert!(errors.is_empty(), "errors: {errors:?}");
1815        let prog = prog.expect("should parse");
1816
1817        assert_eq!(prog.use_decls.len(), 1);
1818        if let UseKind::Simple(Some(alias)) = &prog.use_decls[0].kind {
1819            assert_eq!(alias.name, "R");
1820        } else {
1821            panic!("expected Simple with alias");
1822        }
1823    }
1824
1825    #[test]
1826    fn parse_pub_agent() {
1827        let source = r#"
1828            pub agent Worker {
1829                on start {
1830                    emit(42);
1831                }
1832            }
1833
1834            agent Main {
1835                on start {
1836                    emit(0);
1837                }
1838            }
1839            run Main;
1840        "#;
1841
1842        let (prog, errors) = parse_str(source);
1843        assert!(errors.is_empty(), "errors: {errors:?}");
1844        let prog = prog.expect("should parse");
1845
1846        assert_eq!(prog.agents.len(), 2);
1847        assert!(prog.agents[0].is_pub);
1848        assert_eq!(prog.agents[0].name.name, "Worker");
1849        assert!(!prog.agents[1].is_pub);
1850    }
1851
1852    #[test]
1853    fn parse_pub_function() {
1854        let source = r#"
1855            pub fn helper(x: Int) -> Int {
1856                return x;
1857            }
1858
1859            agent Main {
1860                on start {
1861                    emit(helper(42));
1862                }
1863            }
1864            run Main;
1865        "#;
1866
1867        let (prog, errors) = parse_str(source);
1868        assert!(errors.is_empty(), "errors: {errors:?}");
1869        let prog = prog.expect("should parse");
1870
1871        assert_eq!(prog.functions.len(), 1);
1872        assert!(prog.functions[0].is_pub);
1873        assert_eq!(prog.functions[0].name.name, "helper");
1874    }
1875
1876    #[test]
1877    fn parse_library_no_run() {
1878        // A library module has no `run` statement
1879        let source = r#"
1880            pub agent Worker {
1881                on start {
1882                    emit(42);
1883                }
1884            }
1885
1886            pub fn helper(x: Int) -> Int {
1887                return x;
1888            }
1889        "#;
1890
1891        let (prog, errors) = parse_str(source);
1892        assert!(errors.is_empty(), "errors: {errors:?}");
1893        let prog = prog.expect("should parse");
1894
1895        assert!(prog.run_agent.is_none());
1896        assert_eq!(prog.agents.len(), 1);
1897        assert_eq!(prog.functions.len(), 1);
1898    }
1899
1900    #[test]
1901    fn recover_multiple_errors_reported() {
1902        // Multiple errors in different places - incomplete field missing type
1903        let source = r#"
1904            agent A {
1905                x:
1906            }
1907
1908            agent Main {
1909                on start {
1910                    emit(42);
1911                }
1912            }
1913            run Main;
1914        "#;
1915
1916        let (prog, errors) = parse_str(source);
1917        // The malformed field is missing its type after `:` so should cause an error
1918        // However, with recovery the valid agent may still parse
1919        // Check that we either have errors or recovered successfully
1920        if errors.is_empty() {
1921            // Recovery succeeded - should have parsed Main agent
1922            let prog = prog.expect("should have AST with recovery");
1923            assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
1924        }
1925        // Either way, the test passes - we're testing recovery works
1926    }
1927
1928    #[test]
1929    fn parse_record_declaration() {
1930        let source = r#"
1931            record Point {
1932                x: Int,
1933                y: Int,
1934            }
1935
1936            agent Main {
1937                on start {
1938                    emit(0);
1939                }
1940            }
1941            run Main;
1942        "#;
1943
1944        let (prog, errors) = parse_str(source);
1945        assert!(errors.is_empty(), "errors: {errors:?}");
1946        let prog = prog.expect("should parse");
1947
1948        assert_eq!(prog.records.len(), 1);
1949        assert!(!prog.records[0].is_pub);
1950        assert_eq!(prog.records[0].name.name, "Point");
1951        assert_eq!(prog.records[0].fields.len(), 2);
1952        assert_eq!(prog.records[0].fields[0].name.name, "x");
1953        assert_eq!(prog.records[0].fields[1].name.name, "y");
1954    }
1955
1956    #[test]
1957    fn parse_pub_record() {
1958        let source = r#"
1959            pub record Config {
1960                host: String,
1961                port: Int,
1962            }
1963
1964            agent Main {
1965                on start { emit(0); }
1966            }
1967            run Main;
1968        "#;
1969
1970        let (prog, errors) = parse_str(source);
1971        assert!(errors.is_empty(), "errors: {errors:?}");
1972        let prog = prog.expect("should parse");
1973
1974        assert_eq!(prog.records.len(), 1);
1975        assert!(prog.records[0].is_pub);
1976        assert_eq!(prog.records[0].name.name, "Config");
1977    }
1978
1979    #[test]
1980    fn parse_enum_declaration() {
1981        let source = r#"
1982            enum Status {
1983                Active,
1984                Pending,
1985                Done,
1986            }
1987
1988            agent Main {
1989                on start {
1990                    emit(0);
1991                }
1992            }
1993            run Main;
1994        "#;
1995
1996        let (prog, errors) = parse_str(source);
1997        assert!(errors.is_empty(), "errors: {errors:?}");
1998        let prog = prog.expect("should parse");
1999
2000        assert_eq!(prog.enums.len(), 1);
2001        assert!(!prog.enums[0].is_pub);
2002        assert_eq!(prog.enums[0].name.name, "Status");
2003        assert_eq!(prog.enums[0].variants.len(), 3);
2004        assert_eq!(prog.enums[0].variants[0].name, "Active");
2005        assert_eq!(prog.enums[0].variants[1].name, "Pending");
2006        assert_eq!(prog.enums[0].variants[2].name, "Done");
2007    }
2008
2009    #[test]
2010    fn parse_pub_enum() {
2011        let source = r#"
2012            pub enum Priority { High, Medium, Low }
2013
2014            agent Main {
2015                on start { emit(0); }
2016            }
2017            run Main;
2018        "#;
2019
2020        let (prog, errors) = parse_str(source);
2021        assert!(errors.is_empty(), "errors: {errors:?}");
2022        let prog = prog.expect("should parse");
2023
2024        assert_eq!(prog.enums.len(), 1);
2025        assert!(prog.enums[0].is_pub);
2026        assert_eq!(prog.enums[0].name.name, "Priority");
2027    }
2028
2029    #[test]
2030    fn parse_const_declaration() {
2031        let source = r#"
2032            const MAX_RETRIES: Int = 3;
2033
2034            agent Main {
2035                on start {
2036                    emit(0);
2037                }
2038            }
2039            run Main;
2040        "#;
2041
2042        let (prog, errors) = parse_str(source);
2043        assert!(errors.is_empty(), "errors: {errors:?}");
2044        let prog = prog.expect("should parse");
2045
2046        assert_eq!(prog.consts.len(), 1);
2047        assert!(!prog.consts[0].is_pub);
2048        assert_eq!(prog.consts[0].name.name, "MAX_RETRIES");
2049        assert!(matches!(prog.consts[0].ty, sage_types::TypeExpr::Int));
2050    }
2051
2052    #[test]
2053    fn parse_pub_const() {
2054        let source = r#"
2055            pub const API_URL: String = "https://api.example.com";
2056
2057            agent Main {
2058                on start { emit(0); }
2059            }
2060            run Main;
2061        "#;
2062
2063        let (prog, errors) = parse_str(source);
2064        assert!(errors.is_empty(), "errors: {errors:?}");
2065        let prog = prog.expect("should parse");
2066
2067        assert_eq!(prog.consts.len(), 1);
2068        assert!(prog.consts[0].is_pub);
2069        assert_eq!(prog.consts[0].name.name, "API_URL");
2070    }
2071
2072    #[test]
2073    fn parse_multiple_type_declarations() {
2074        let source = r#"
2075            record Point { x: Int, y: Int }
2076            enum Color { Red, Green, Blue }
2077            const ORIGIN_X: Int = 0;
2078
2079            agent Main {
2080                on start { emit(0); }
2081            }
2082            run Main;
2083        "#;
2084
2085        let (prog, errors) = parse_str(source);
2086        assert!(errors.is_empty(), "errors: {errors:?}");
2087        let prog = prog.expect("should parse");
2088
2089        assert_eq!(prog.records.len(), 1);
2090        assert_eq!(prog.enums.len(), 1);
2091        assert_eq!(prog.consts.len(), 1);
2092    }
2093
2094    #[test]
2095    fn parse_match_expression() {
2096        let source = r#"
2097            enum Status { Active, Pending, Done }
2098
2099            agent Main {
2100                on start {
2101                    let s: Int = match Active {
2102                        Active => 1,
2103                        Pending => 2,
2104                        Done => 3,
2105                    };
2106                    emit(s);
2107                }
2108            }
2109            run Main;
2110        "#;
2111
2112        let (prog, errors) = parse_str(source);
2113        assert!(errors.is_empty(), "errors: {errors:?}");
2114        let prog = prog.expect("should parse");
2115
2116        // Check the agent parsed
2117        assert_eq!(prog.agents.len(), 1);
2118        // Match is in the handler
2119        let handler = &prog.agents[0].handlers[0];
2120        let stmt = &handler.body.stmts[0];
2121        if let Stmt::Let { value, .. } = stmt {
2122            assert!(matches!(value, Expr::Match { .. }));
2123        } else {
2124            panic!("expected let statement with match");
2125        }
2126    }
2127
2128    #[test]
2129    fn parse_match_with_wildcard() {
2130        let source = r#"
2131            agent Main {
2132                on start {
2133                    let x = 5;
2134                    let result = match x {
2135                        1 => 10,
2136                        2 => 20,
2137                        _ => 0,
2138                    };
2139                    emit(result);
2140                }
2141            }
2142            run Main;
2143        "#;
2144
2145        let (prog, errors) = parse_str(source);
2146        assert!(errors.is_empty(), "errors: {errors:?}");
2147        let prog = prog.expect("should parse");
2148
2149        assert_eq!(prog.agents.len(), 1);
2150    }
2151
2152    #[test]
2153    fn parse_record_construction() {
2154        let source = r#"
2155            record Point { x: Int, y: Int }
2156
2157            agent Main {
2158                on start {
2159                    let p = Point { x: 10, y: 20 };
2160                    emit(0);
2161                }
2162            }
2163            run Main;
2164        "#;
2165
2166        let (prog, errors) = parse_str(source);
2167        assert!(errors.is_empty(), "errors: {errors:?}");
2168        let prog = prog.expect("should parse");
2169
2170        assert_eq!(prog.records.len(), 1);
2171        assert_eq!(prog.agents.len(), 1);
2172
2173        // Check the let statement has a record construction
2174        let handler = &prog.agents[0].handlers[0];
2175        let stmt = &handler.body.stmts[0];
2176        if let Stmt::Let { value, .. } = stmt {
2177            if let Expr::RecordConstruct { name, fields, .. } = value {
2178                assert_eq!(name.name, "Point");
2179                assert_eq!(fields.len(), 2);
2180                assert_eq!(fields[0].name.name, "x");
2181                assert_eq!(fields[1].name.name, "y");
2182            } else {
2183                panic!("expected RecordConstruct");
2184            }
2185        } else {
2186            panic!("expected let statement");
2187        }
2188    }
2189
2190    #[test]
2191    fn parse_match_with_qualified_variant() {
2192        let source = r#"
2193            enum Status { Active, Pending }
2194
2195            fn get_status() -> Int {
2196                return 1;
2197            }
2198
2199            agent Main {
2200                on start {
2201                    let s = get_status();
2202                    let result = match s {
2203                        Status::Active => 1,
2204                        Status::Pending => 0,
2205                    };
2206                    emit(result);
2207                }
2208            }
2209            run Main;
2210        "#;
2211
2212        let (prog, errors) = parse_str(source);
2213        assert!(errors.is_empty(), "errors: {errors:?}");
2214        let prog = prog.expect("should parse");
2215
2216        assert_eq!(prog.enums.len(), 1);
2217        assert_eq!(prog.agents.len(), 1);
2218    }
2219
2220    #[test]
2221    fn parse_field_access() {
2222        let source = r#"
2223            record Point { x: Int, y: Int }
2224
2225            agent Main {
2226                on start {
2227                    let p = Point { x: 10, y: 20 };
2228                    let x_val = p.x;
2229                    let y_val = p.y;
2230                    emit(x_val);
2231                }
2232            }
2233            run Main;
2234        "#;
2235
2236        let (prog, errors) = parse_str(source);
2237        assert!(errors.is_empty(), "errors: {errors:?}");
2238        let prog = prog.expect("should parse");
2239
2240        assert_eq!(prog.records.len(), 1);
2241        assert_eq!(prog.agents.len(), 1);
2242
2243        // Check the field access
2244        let handler = &prog.agents[0].handlers[0];
2245        let stmt = &handler.body.stmts[1]; // p.x assignment
2246        if let Stmt::Let { value, .. } = stmt {
2247            if let Expr::FieldAccess { field, .. } = value {
2248                assert_eq!(field.name, "x");
2249            } else {
2250                panic!("expected FieldAccess");
2251            }
2252        } else {
2253            panic!("expected let statement");
2254        }
2255    }
2256
2257    #[test]
2258    fn parse_chained_field_access() {
2259        let source = r#"
2260            record Inner { val: Int }
2261            record Outer { inner: Inner }
2262
2263            agent Main {
2264                on start {
2265                    let inner = Inner { val: 42 };
2266                    let outer = Outer { inner: inner };
2267                    let v = outer.inner.val;
2268                    emit(v);
2269                }
2270            }
2271            run Main;
2272        "#;
2273
2274        let (prog, errors) = parse_str(source);
2275        assert!(errors.is_empty(), "errors: {errors:?}");
2276        let prog = prog.expect("should parse");
2277
2278        assert_eq!(prog.records.len(), 2);
2279        assert_eq!(prog.agents.len(), 1);
2280
2281        // Check the chained field access: outer.inner.val
2282        let handler = &prog.agents[0].handlers[0];
2283        let stmt = &handler.body.stmts[2]; // outer.inner.val assignment
2284        if let Stmt::Let { value, .. } = stmt {
2285            if let Expr::FieldAccess {
2286                object, field: val, ..
2287            } = value
2288            {
2289                assert_eq!(val.name, "val");
2290                // object should be outer.inner
2291                if let Expr::FieldAccess { field: inner, .. } = object.as_ref() {
2292                    assert_eq!(inner.name, "inner");
2293                } else {
2294                    panic!("expected nested FieldAccess");
2295                }
2296            } else {
2297                panic!("expected FieldAccess");
2298            }
2299        } else {
2300            panic!("expected let statement");
2301        }
2302    }
2303
2304    // =========================================================================
2305    // RFC-0006: Message passing tests
2306    // =========================================================================
2307
2308    #[test]
2309    fn parse_loop_break() {
2310        let source = r#"
2311            agent Main {
2312                on start {
2313                    let count = 0;
2314                    loop {
2315                        count = count + 1;
2316                        if count > 5 {
2317                            break;
2318                        }
2319                    }
2320                    emit(count);
2321                }
2322            }
2323            run Main;
2324        "#;
2325
2326        let (prog, errors) = parse_str(source);
2327        assert!(errors.is_empty(), "errors: {errors:?}");
2328        let prog = prog.expect("should parse");
2329
2330        assert_eq!(prog.agents.len(), 1);
2331        let handler = &prog.agents[0].handlers[0];
2332        // Check loop statement exists
2333        let loop_stmt = &handler.body.stmts[1];
2334        assert!(matches!(loop_stmt, Stmt::Loop { .. }));
2335        // Check break is inside the loop
2336        if let Stmt::Loop { body, .. } = loop_stmt {
2337            let if_stmt = &body.stmts[1];
2338            if let Stmt::If { then_block, .. } = if_stmt {
2339                assert!(matches!(then_block.stmts[0], Stmt::Break { .. }));
2340            } else {
2341                panic!("expected if statement");
2342            }
2343        }
2344    }
2345
2346    #[test]
2347    fn parse_agent_receives() {
2348        let source = r#"
2349            enum WorkerMsg {
2350                Task,
2351                Shutdown,
2352            }
2353
2354            agent Worker receives WorkerMsg {
2355                id: Int
2356
2357                on start {
2358                    emit(0);
2359                }
2360            }
2361
2362            agent Main {
2363                on start {
2364                    emit(0);
2365                }
2366            }
2367            run Main;
2368        "#;
2369
2370        let (prog, errors) = parse_str(source);
2371        assert!(errors.is_empty(), "errors: {errors:?}");
2372        let prog = prog.expect("should parse");
2373
2374        assert_eq!(prog.agents.len(), 2);
2375
2376        // Worker should have receives clause
2377        let worker = &prog.agents[0];
2378        assert_eq!(worker.name.name, "Worker");
2379        assert!(worker.receives.is_some());
2380        if let Some(TypeExpr::Named(name)) = &worker.receives {
2381            assert_eq!(name.name, "WorkerMsg");
2382        } else {
2383            panic!("expected named type for receives");
2384        }
2385
2386        // Main should not have receives
2387        let main = &prog.agents[1];
2388        assert_eq!(main.name.name, "Main");
2389        assert!(main.receives.is_none());
2390    }
2391
2392    #[test]
2393    fn parse_receive_expression() {
2394        let source = r#"
2395            enum Msg { Ping }
2396
2397            agent Worker receives Msg {
2398                on start {
2399                    let msg = receive();
2400                    emit(0);
2401                }
2402            }
2403
2404            agent Main {
2405                on start { emit(0); }
2406            }
2407            run Main;
2408        "#;
2409
2410        let (prog, errors) = parse_str(source);
2411        assert!(errors.is_empty(), "errors: {errors:?}");
2412        let prog = prog.expect("should parse");
2413
2414        // Find Worker agent
2415        let worker = prog
2416            .agents
2417            .iter()
2418            .find(|a| a.name.name == "Worker")
2419            .unwrap();
2420        let handler = &worker.handlers[0];
2421        let stmt = &handler.body.stmts[0];
2422
2423        if let Stmt::Let { value, .. } = stmt {
2424            assert!(matches!(value, Expr::Receive { .. }));
2425        } else {
2426            panic!("expected let with receive");
2427        }
2428    }
2429
2430    #[test]
2431    fn parse_message_passing_full() {
2432        let source = r#"
2433            enum WorkerMsg {
2434                Task,
2435                Shutdown,
2436            }
2437
2438            agent Worker receives WorkerMsg {
2439                id: Int
2440
2441                on start {
2442                    let msg = receive();
2443                    let result = match msg {
2444                        Task => 1,
2445                        Shutdown => 0,
2446                    };
2447                    emit(result);
2448                }
2449            }
2450
2451            agent Main {
2452                on start {
2453                    let w = spawn Worker { id: 1 };
2454                    send(w, Task);
2455                    send(w, Shutdown);
2456                    await w;
2457                    emit(0);
2458                }
2459            }
2460            run Main;
2461        "#;
2462
2463        let (prog, errors) = parse_str(source);
2464        assert!(errors.is_empty(), "errors: {errors:?}");
2465        let prog = prog.expect("should parse");
2466
2467        assert_eq!(prog.enums.len(), 1);
2468        assert_eq!(prog.agents.len(), 2);
2469
2470        // Check Worker has receives
2471        let worker = prog
2472            .agents
2473            .iter()
2474            .find(|a| a.name.name == "Worker")
2475            .unwrap();
2476        assert!(worker.receives.is_some());
2477    }
2478}