Skip to main content

sage_parser/
parser.rs

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