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