Skip to main content

sage_parser/
parser.rs

1//! Parser implementation using chumsky.
2//!
3//! This module transforms a token stream into an AST.
4
5use crate::ast::{
6    AgentDecl, BeliefDecl, BinOp, Block, ClosureParam, ConstDecl, ElseBranch, EnumDecl, EventKind,
7    Expr, FieldInit, FnDecl, HandlerDecl, Literal, MapEntry, MatchArm, MockValue, ModDecl, Param,
8    Pattern, Program, RecordDecl, RecordField, Stmt, StringPart, StringTemplate, TestDecl,
9    ToolDecl, ToolFnDecl, UnaryOp, UseDecl, UseKind,
10};
11use crate::{Ident, Span, TypeExpr};
12use crate::{Spanned, Token};
13use chumsky::prelude::*;
14use chumsky::BoxedParser;
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)]
258/// Parser for type parameters: `<T>`, `<A, B>`, `<T, U, V>`, etc.
259/// Returns empty Vec if no type parameters present.
260fn type_params_parser(
261    source: Arc<str>,
262) -> impl Parser<Token, Vec<Ident>, Error = ParseError> + Clone {
263    ident_token_parser(source)
264        .separated_by(just(Token::Comma))
265        .allow_trailing()
266        .delimited_by(just(Token::Lt), just(Token::Gt))
267        .or_not()
268        .map(|params| params.unwrap_or_default())
269}
270
271fn record_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
272    let src = source.clone();
273    let src2 = source.clone();
274
275    // Record field: `name: Type`
276    let field = ident_token_parser(src.clone())
277        .then_ignore(just(Token::Colon))
278        .then(type_parser(src.clone()))
279        .map_with_span(move |(name, ty), span: Range<usize>| RecordField {
280            name,
281            ty,
282            span: make_span(&src, span),
283        });
284
285    just(Token::KwPub)
286        .or_not()
287        .then_ignore(just(Token::KwRecord))
288        .then(ident_token_parser(src2.clone()))
289        .then(type_params_parser(src2.clone()))
290        .then(
291            field
292                .separated_by(just(Token::Comma))
293                .allow_trailing()
294                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
295        )
296        .map_with_span(
297            move |(((is_pub, name), type_params), fields), span: Range<usize>| {
298                TopLevel::Record(RecordDecl {
299                    is_pub: is_pub.is_some(),
300                    name,
301                    type_params,
302                    fields,
303                    span: make_span(&src2, span),
304                })
305            },
306        )
307}
308
309/// Parser for an enum declaration: `enum Status { Active, Pending, Done }` or `enum Result { Ok(T), Err(E) }`
310#[allow(clippy::needless_pass_by_value)]
311fn enum_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
312    let src = source.clone();
313    let src2 = source.clone();
314    let src3 = source.clone();
315
316    // Enum variant with optional payload: `Ok(T)` or `None`
317    let variant = ident_token_parser(src.clone())
318        .then(
319            type_parser(src.clone())
320                .delimited_by(just(Token::LParen), just(Token::RParen))
321                .or_not(),
322        )
323        .map_with_span({
324            let src = src.clone();
325            move |(name, payload), span: Range<usize>| crate::ast::EnumVariant {
326                name,
327                payload,
328                span: make_span(&src, span),
329            }
330        });
331
332    just(Token::KwPub)
333        .or_not()
334        .then_ignore(just(Token::KwEnum))
335        .then(ident_token_parser(src3.clone()))
336        .then(type_params_parser(src3.clone()))
337        .then(
338            variant
339                .separated_by(just(Token::Comma))
340                .allow_trailing()
341                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
342        )
343        .map_with_span(
344            move |(((is_pub, name), type_params), variants), span: Range<usize>| {
345                TopLevel::Enum(EnumDecl {
346                    is_pub: is_pub.is_some(),
347                    name,
348                    type_params,
349                    variants,
350                    span: make_span(&src2, span),
351                })
352            },
353        )
354}
355
356/// Parser for a const declaration: `const MAX_RETRIES: Int = 3`
357#[allow(clippy::needless_pass_by_value)]
358fn const_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
359    let src = source.clone();
360    let src2 = source.clone();
361
362    just(Token::KwPub)
363        .or_not()
364        .then_ignore(just(Token::KwConst))
365        .then(ident_token_parser(src.clone()))
366        .then_ignore(just(Token::Colon))
367        .then(type_parser(src.clone()))
368        .then_ignore(just(Token::Eq))
369        .then(expr_parser(src.clone()))
370        .then_ignore(just(Token::Semicolon))
371        .map_with_span(move |(((is_pub, name), ty), value), span: Range<usize>| {
372            TopLevel::Const(ConstDecl {
373                is_pub: is_pub.is_some(),
374                name,
375                ty,
376                value,
377                span: make_span(&src2, span),
378            })
379        })
380}
381
382// =============================================================================
383// Tool parsers (RFC-0011)
384// =============================================================================
385
386/// Parser for a tool declaration: `tool Http { fn get(url: String) -> String }`
387#[allow(clippy::needless_pass_by_value)]
388fn tool_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
389    let src = source.clone();
390    let src2 = source.clone();
391    let src3 = source.clone();
392
393    // Tool function parameter: `name: Type`
394    let param = ident_token_parser(src.clone())
395        .then_ignore(just(Token::Colon))
396        .then(type_parser(src.clone()))
397        .map_with_span(move |(name, ty), span: Range<usize>| Param {
398            name,
399            ty,
400            span: make_span(&src, span),
401        });
402
403    let params = param
404        .separated_by(just(Token::Comma))
405        .allow_trailing()
406        .delimited_by(just(Token::LParen), just(Token::RParen));
407
408    // Tool function signature: `fn name(params) -> ReturnType`
409    let tool_fn = just(Token::KwFn)
410        .ignore_then(ident_token_parser(src2.clone()))
411        .then(params)
412        .then_ignore(just(Token::Arrow))
413        .then(type_parser(src2.clone()))
414        .map_with_span(
415            move |((name, params), return_ty), span: Range<usize>| ToolFnDecl {
416                name,
417                params,
418                return_ty,
419                span: make_span(&src2, span),
420            },
421        );
422
423    just(Token::KwPub)
424        .or_not()
425        .then_ignore(just(Token::KwTool))
426        .then(ident_token_parser(src3.clone()))
427        .then(
428            tool_fn
429                .repeated()
430                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
431        )
432        .map_with_span(move |((is_pub, name), functions), span: Range<usize>| {
433            TopLevel::Tool(ToolDecl {
434                is_pub: is_pub.is_some(),
435                name,
436                functions,
437                span: make_span(&src3, span),
438            })
439        })
440}
441
442// =============================================================================
443// Test parsers (RFC-0012)
444// =============================================================================
445
446/// Parser for a test declaration: `test "name" { ... }` or `@serial test "name" { ... }`
447#[allow(clippy::needless_pass_by_value)]
448fn test_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
449    let src = source.clone();
450    let src2 = source.clone();
451
452    // Parse @serial annotation (@ followed by identifier "serial")
453    // For now, we'll use a simple approach: look for @ then ident
454    let serial_annotation = just(Token::At)
455        .then(filter(|t: &Token| matches!(t, Token::Ident)))
456        .or_not()
457        .map(|opt| opt.is_some());
458
459    // Parse test name (string literal)
460    let test_name = filter_map(|span: Range<usize>, tok: Token| match tok {
461        Token::StringLit => Ok(()),
462        _ => Err(Simple::expected_input_found(span, [], Some(tok))),
463    })
464    .map_with_span(move |_, span: Range<usize>| {
465        // Extract the string content without quotes (handles both " and ')
466        let s = &src[span.clone()];
467        s[1..s.len() - 1].to_string()
468    });
469
470    // Test body - use the statement parser
471    let body = block_parser(src2.clone());
472
473    serial_annotation
474        .then_ignore(just(Token::KwTest))
475        .then(test_name)
476        .then(body)
477        .map_with_span(move |((is_serial, name), body), span: Range<usize>| {
478            TopLevel::Test(TestDecl {
479                name,
480                is_serial,
481                body,
482                span: make_span(&src2, span),
483            })
484        })
485}
486
487// =============================================================================
488// Agent parsers
489// =============================================================================
490
491/// Parser for an agent declaration.
492#[allow(clippy::needless_pass_by_value)]
493fn agent_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
494    let src = source.clone();
495    let src2 = source.clone();
496    let src3 = source.clone();
497    let src4 = source.clone();
498    let src5 = source.clone();
499
500    // Tool use clause: `use Http, Fs`
501    let tool_use = just(Token::KwUse)
502        .ignore_then(
503            ident_token_parser(src5.clone())
504                .separated_by(just(Token::Comma))
505                .at_least(1),
506        )
507        .or_not()
508        .map(|tools| tools.unwrap_or_default());
509
510    // Agent state fields: `name: Type` (no `belief` keyword in RFC-0005)
511    // We still call them "beliefs" internally for backwards compatibility
512    let belief = ident_token_parser(src.clone())
513        .then_ignore(just(Token::Colon))
514        .then(type_parser(src.clone()))
515        .map_with_span(move |(name, ty), span: Range<usize>| BeliefDecl {
516            name,
517            ty,
518            span: make_span(&src, span),
519        });
520
521    let handler = just(Token::KwOn)
522        .ignore_then(event_kind_parser(src2.clone()))
523        .then(block_parser(src2.clone()))
524        .map_with_span(move |(event, body), span: Range<usize>| HandlerDecl {
525            event,
526            body,
527            span: make_span(&src2, span),
528        });
529
530    // Optional `receives MsgType` clause
531    let receives_clause = just(Token::KwReceives)
532        .ignore_then(type_parser(src3.clone()))
533        .or_not();
534
535    just(Token::KwPub)
536        .or_not()
537        .then_ignore(just(Token::KwAgent))
538        .then(ident_token_parser(src3.clone()))
539        .then(receives_clause)
540        .then_ignore(just(Token::LBrace))
541        .then(tool_use)
542        .then(belief.repeated())
543        .then(handler.repeated())
544        .then_ignore(just(Token::RBrace))
545        .map_with_span(
546            move |(((((is_pub, name), receives), tool_uses), beliefs), handlers),
547                  span: Range<usize>| {
548                TopLevel::Agent(AgentDecl {
549                    is_pub: is_pub.is_some(),
550                    name,
551                    receives,
552                    tool_uses,
553                    beliefs,
554                    handlers,
555                    span: make_span(&src4, span),
556                })
557            },
558        )
559}
560
561/// Parser for event kinds.
562#[allow(clippy::needless_pass_by_value)]
563fn event_kind_parser(source: Arc<str>) -> impl Parser<Token, EventKind, Error = ParseError> {
564    let src = source.clone();
565
566    let start = just(Token::KwStart).to(EventKind::Start);
567    let stop = just(Token::KwStop).to(EventKind::Stop);
568
569    let message = just(Token::KwMessage)
570        .ignore_then(just(Token::LParen))
571        .ignore_then(ident_token_parser(src.clone()))
572        .then_ignore(just(Token::Colon))
573        .then(type_parser(src.clone()))
574        .then_ignore(just(Token::RParen))
575        .map(|(param_name, param_ty)| EventKind::Message {
576            param_name,
577            param_ty,
578        });
579
580    // RFC-0007: on error(e) handler
581    let error = just(Token::KwError)
582        .ignore_then(just(Token::LParen))
583        .ignore_then(ident_token_parser(src))
584        .then_ignore(just(Token::RParen))
585        .map(|param_name| EventKind::Error { param_name });
586
587    start.or(stop).or(message).or(error)
588}
589
590// =============================================================================
591// Function parsers
592// =============================================================================
593
594/// Parser for a function declaration.
595#[allow(clippy::needless_pass_by_value)]
596fn fn_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
597    let src = source.clone();
598    let src2 = source.clone();
599    let src3 = source.clone();
600
601    let param = ident_token_parser(src.clone())
602        .then_ignore(just(Token::Colon))
603        .then(type_parser(src.clone()))
604        .map_with_span(move |(name, ty), span: Range<usize>| Param {
605            name,
606            ty,
607            span: make_span(&src, span),
608        });
609
610    let params = param
611        .separated_by(just(Token::Comma))
612        .allow_trailing()
613        .delimited_by(just(Token::LParen), just(Token::RParen));
614
615    just(Token::KwPub)
616        .or_not()
617        .then_ignore(just(Token::KwFn))
618        .then(ident_token_parser(src2.clone()))
619        .then(type_params_parser(src2.clone()))
620        .then(params)
621        .then_ignore(just(Token::Arrow))
622        .then(type_parser(src2.clone()))
623        .then(just(Token::KwFails).or_not())
624        .then(block_parser(src2))
625        .map_with_span(
626            move |((((((is_pub, name), type_params), params), return_ty), is_fallible), body),
627                  span: Range<usize>| {
628                TopLevel::Function(FnDecl {
629                    is_pub: is_pub.is_some(),
630                    name,
631                    type_params,
632                    params,
633                    return_ty,
634                    is_fallible: is_fallible.is_some(),
635                    body,
636                    span: make_span(&src3, span),
637                })
638            },
639        )
640}
641
642// =============================================================================
643// Statement parsers
644// =============================================================================
645
646/// Parser for a block of statements.
647/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
648#[allow(clippy::needless_pass_by_value)]
649fn block_parser(source: Arc<str>) -> BoxedParser<'static, Token, Block, ParseError> {
650    let src = source.clone();
651
652    recursive(move |block: Recursive<Token, Block, ParseError>| {
653        let src_inner = src.clone();
654        stmt_parser(src.clone(), block)
655            .repeated()
656            .delimited_by(just(Token::LBrace), just(Token::RBrace))
657            .recover_with(nested_delimiters(
658                Token::LBrace,
659                Token::RBrace,
660                [
661                    (Token::LParen, Token::RParen),
662                    (Token::LBracket, Token::RBracket),
663                ],
664                |_span: Range<usize>| vec![],
665            ))
666            .map_with_span(move |stmts, span: Range<usize>| Block {
667                stmts,
668                span: make_span(&src_inner, span),
669            })
670    })
671    .boxed()
672}
673
674/// Parser for statements.
675#[allow(clippy::needless_pass_by_value)]
676fn stmt_parser(
677    source: Arc<str>,
678    block: impl Parser<Token, Block, Error = ParseError> + Clone + 'static,
679) -> impl Parser<Token, Stmt, Error = ParseError> + Clone {
680    let src = source.clone();
681    let src2 = source.clone();
682    let src3 = source.clone();
683    let src4 = source.clone();
684    let src5 = source.clone();
685    let src6 = source.clone();
686    let src7 = source.clone();
687
688    // Let tuple destructuring: let (a, b) = expr;
689    let src10 = source.clone();
690    let let_tuple_stmt = just(Token::KwLet)
691        .ignore_then(
692            ident_token_parser(src10.clone())
693                .separated_by(just(Token::Comma))
694                .at_least(2)
695                .allow_trailing()
696                .delimited_by(just(Token::LParen), just(Token::RParen)),
697        )
698        .then(
699            just(Token::Colon)
700                .ignore_then(type_parser(src10.clone()))
701                .or_not(),
702        )
703        .then_ignore(just(Token::Eq))
704        .then(expr_parser(src10.clone()))
705        .then_ignore(just(Token::Semicolon))
706        .map_with_span(
707            move |((names, ty), value), span: Range<usize>| Stmt::LetTuple {
708                names,
709                ty,
710                value,
711                span: make_span(&src10, span),
712            },
713        );
714
715    let let_stmt = just(Token::KwLet)
716        .ignore_then(ident_token_parser(src.clone()))
717        .then(
718            just(Token::Colon)
719                .ignore_then(type_parser(src.clone()))
720                .or_not(),
721        )
722        .then_ignore(just(Token::Eq))
723        .then(expr_parser(src.clone()))
724        .then_ignore(just(Token::Semicolon))
725        .map_with_span(move |((name, ty), value), span: Range<usize>| Stmt::Let {
726            name,
727            ty,
728            value,
729            span: make_span(&src, span),
730        });
731
732    let return_stmt = just(Token::KwReturn)
733        .ignore_then(expr_parser(src2.clone()).or_not())
734        .then_ignore(just(Token::Semicolon))
735        .map_with_span(move |value, span: Range<usize>| Stmt::Return {
736            value,
737            span: make_span(&src2, span),
738        });
739
740    let if_stmt = recursive(|if_stmt| {
741        let src_if = src3.clone();
742        let block_clone = block.clone();
743
744        just(Token::KwIf)
745            .ignore_then(expr_parser(src3.clone()))
746            .then(block_clone.clone())
747            .then(
748                just(Token::KwElse)
749                    .ignore_then(
750                        if_stmt
751                            .map(|s| ElseBranch::ElseIf(Box::new(s)))
752                            .or(block_clone.map(ElseBranch::Block)),
753                    )
754                    .or_not(),
755            )
756            .map_with_span(
757                move |((condition, then_block), else_block), span: Range<usize>| Stmt::If {
758                    condition,
759                    then_block,
760                    else_block,
761                    span: make_span(&src_if, span),
762                },
763            )
764    });
765
766    let for_stmt = just(Token::KwFor)
767        .ignore_then(for_pattern_parser(src4.clone()))
768        .then_ignore(just(Token::KwIn))
769        .then(expr_parser(src4.clone()))
770        .then(block.clone())
771        .map_with_span(
772            move |((pattern, iter), body), span: Range<usize>| Stmt::For {
773                pattern,
774                iter,
775                body,
776                span: make_span(&src4, span),
777            },
778        );
779
780    let while_stmt = just(Token::KwWhile)
781        .ignore_then(expr_parser(src7.clone()))
782        .then(block.clone())
783        .map_with_span(move |(condition, body), span: Range<usize>| Stmt::While {
784            condition,
785            body,
786            span: make_span(&src7, span),
787        });
788
789    let src8 = source.clone();
790    let loop_stmt = just(Token::KwLoop)
791        .ignore_then(block.clone())
792        .map_with_span(move |body, span: Range<usize>| Stmt::Loop {
793            body,
794            span: make_span(&src8, span),
795        });
796
797    let src9 = source.clone();
798    let break_stmt = just(Token::KwBreak)
799        .then_ignore(just(Token::Semicolon))
800        .map_with_span(move |_, span: Range<usize>| Stmt::Break {
801            span: make_span(&src9, span),
802        });
803
804    // RFC-0012: mock divine -> value; or mock divine -> fail("msg");
805    let src12 = source.clone();
806    let mock_divine_stmt = just(Token::KwMock)
807        .ignore_then(just(Token::KwDivine))
808        .ignore_then(just(Token::Arrow))
809        .ignore_then(expr_parser(src12.clone()).map(|expr| {
810            // Check if this is a fail() call expression and convert to MockValue::Fail
811            if let Expr::Fail { error, .. } = expr {
812                MockValue::Fail(*error)
813            } else {
814                MockValue::Value(expr)
815            }
816        }))
817        .then_ignore(just(Token::Semicolon))
818        .map_with_span(move |value, span: Range<usize>| Stmt::MockDivine {
819            value,
820            span: make_span(&src12, span),
821        });
822
823    // mock tool ToolName.fn_name -> value; or mock tool ToolName.fn_name -> fail("msg");
824    let src13 = source.clone();
825    let src14 = source.clone();
826    let src15 = source.clone();
827    let mock_tool_stmt = just(Token::KwMock)
828        .ignore_then(just(Token::KwTool))
829        .ignore_then(ident_token_parser(src13.clone())) // Tool name
830        .then_ignore(just(Token::Dot))
831        .then(ident_token_parser(src14.clone())) // Function name
832        .then_ignore(just(Token::Arrow))
833        .then(expr_parser(src15.clone()).map(|expr| {
834            // Check if this is a fail() call expression and convert to MockValue::Fail
835            if let Expr::Fail { error, .. } = expr {
836                MockValue::Fail(*error)
837            } else {
838                MockValue::Value(expr)
839            }
840        }))
841        .then_ignore(just(Token::Semicolon))
842        .map_with_span(
843            move |((tool_name, fn_name), value), span: Range<usize>| Stmt::MockTool {
844                tool_name,
845                fn_name,
846                value,
847                span: make_span(&src15, span),
848            },
849        );
850
851    let assign_stmt = ident_token_parser(src5.clone())
852        .then_ignore(just(Token::Eq))
853        .then(expr_parser(src5.clone()))
854        .then_ignore(just(Token::Semicolon))
855        .map_with_span(move |(name, value), span: Range<usize>| Stmt::Assign {
856            name,
857            value,
858            span: make_span(&src5, span),
859        });
860
861    let expr_stmt = expr_parser(src6.clone())
862        .then_ignore(just(Token::Semicolon))
863        .map_with_span(move |expr, span: Range<usize>| Stmt::Expr {
864            expr,
865            span: make_span(&src6, span),
866        });
867
868    let_tuple_stmt
869        .or(let_stmt)
870        .or(return_stmt)
871        .or(if_stmt)
872        .or(for_stmt)
873        .or(while_stmt)
874        .or(loop_stmt)
875        .or(break_stmt)
876        .or(mock_divine_stmt)
877        .or(mock_tool_stmt)
878        .or(assign_stmt)
879        .or(expr_stmt)
880}
881
882// =============================================================================
883// Expression parsers
884// =============================================================================
885
886/// Parser for expressions (with precedence climbing for binary ops).
887/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
888#[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
889fn expr_parser(source: Arc<str>) -> BoxedParser<'static, Token, Expr, ParseError> {
890    recursive(move |expr: Recursive<Token, Expr, ParseError>| {
891        let src = source.clone();
892
893        let literal = literal_parser(src.clone());
894        let var = var_parser(src.clone());
895
896        // Parenthesized expression or tuple literal
897        // (expr) is a paren, (expr, expr, ...) is a tuple
898        let paren_or_tuple = just(Token::LParen)
899            .ignore_then(
900                expr.clone()
901                    .separated_by(just(Token::Comma))
902                    .allow_trailing(),
903            )
904            .then_ignore(just(Token::RParen))
905            .map_with_span({
906                let src = src.clone();
907                move |elements, span: Range<usize>| {
908                    if elements.len() == 1 {
909                        // Single element without trailing comma = parenthesized expression
910                        Expr::Paren {
911                            inner: Box::new(elements.into_iter().next().unwrap()),
912                            span: make_span(&src, span),
913                        }
914                    } else {
915                        // Multiple elements or empty = tuple
916                        Expr::Tuple {
917                            elements,
918                            span: make_span(&src, span),
919                        }
920                    }
921                }
922            });
923
924        let list = expr
925            .clone()
926            .separated_by(just(Token::Comma))
927            .allow_trailing()
928            .delimited_by(just(Token::LBracket), just(Token::RBracket))
929            .map_with_span({
930                let src = src.clone();
931                move |elements, span: Range<usize>| Expr::List {
932                    elements,
933                    span: make_span(&src, span),
934                }
935            });
936
937        // self.field or self.method(args)
938        let self_access = just(Token::KwSelf)
939            .ignore_then(just(Token::Dot))
940            .ignore_then(ident_token_parser(src.clone()))
941            .then(
942                expr.clone()
943                    .separated_by(just(Token::Comma))
944                    .allow_trailing()
945                    .delimited_by(just(Token::LParen), just(Token::RParen))
946                    .or_not(),
947            )
948            .map_with_span({
949                let src = src.clone();
950                move |(field, args), span: Range<usize>| match args {
951                    Some(args) => Expr::SelfMethodCall {
952                        method: field,
953                        args,
954                        span: make_span(&src, span),
955                    },
956                    None => Expr::SelfField {
957                        field,
958                        span: make_span(&src, span),
959                    },
960                }
961            });
962
963        // divine("template") or divine("template" -> Type)
964        let divine_expr = just(Token::KwDivine)
965            .ignore_then(just(Token::LParen))
966            .ignore_then(string_template_parser(src.clone()))
967            .then(
968                just(Token::Arrow)
969                    .ignore_then(type_parser(src.clone()))
970                    .or_not(),
971            )
972            .then_ignore(just(Token::RParen))
973            .map_with_span({
974                let src = src.clone();
975                move |(template, result_ty), span: Range<usize>| Expr::Divine {
976                    template,
977                    result_ty,
978                    span: make_span(&src, span),
979                }
980            });
981
982        // summon Agent { field: value, ... }
983        let summon_field_init = ident_token_parser(src.clone())
984            .then_ignore(just(Token::Colon))
985            .then(expr.clone())
986            .map_with_span({
987                let src = src.clone();
988                move |(name, value), span: Range<usize>| FieldInit {
989                    name,
990                    value,
991                    span: make_span(&src, span),
992                }
993            });
994
995        let summon_expr = just(Token::KwSummon)
996            .ignore_then(ident_token_parser(src.clone()))
997            .then_ignore(just(Token::LBrace))
998            .then(
999                summon_field_init
1000                    .separated_by(just(Token::Comma))
1001                    .allow_trailing(),
1002            )
1003            .then_ignore(just(Token::RBrace))
1004            .map_with_span({
1005                let src = src.clone();
1006                move |(agent, fields), span: Range<usize>| Expr::Summon {
1007                    agent,
1008                    fields,
1009                    span: make_span(&src, span),
1010                }
1011            });
1012
1013        // await expr - we need to handle this carefully to avoid left recursion
1014        // Parse: await handle [timeout(ms)]
1015        let timeout_clause = just(Token::KwTimeout)
1016            .ignore_then(just(Token::LParen))
1017            .ignore_then(expr.clone())
1018            .then_ignore(just(Token::RParen));
1019
1020        let await_expr = just(Token::KwAwait)
1021            .ignore_then(ident_token_parser(src.clone()).map_with_span({
1022                let src = src.clone();
1023                move |name, span: Range<usize>| Expr::Var {
1024                    name,
1025                    span: make_span(&src, span),
1026                }
1027            }))
1028            .then(timeout_clause.or_not())
1029            .map_with_span({
1030                let src = src.clone();
1031                move |(handle, timeout), span: Range<usize>| Expr::Await {
1032                    handle: Box::new(handle),
1033                    timeout: timeout.map(Box::new),
1034                    span: make_span(&src, span),
1035                }
1036            });
1037
1038        // send(handle, message)
1039        let send_expr = just(Token::KwSend)
1040            .ignore_then(just(Token::LParen))
1041            .ignore_then(expr.clone())
1042            .then_ignore(just(Token::Comma))
1043            .then(expr.clone())
1044            .then_ignore(just(Token::RParen))
1045            .map_with_span({
1046                let src = src.clone();
1047                move |(handle, message), span: Range<usize>| Expr::Send {
1048                    handle: Box::new(handle),
1049                    message: Box::new(message),
1050                    span: make_span(&src, span),
1051                }
1052            });
1053
1054        // yield(value)
1055        let yield_expr = just(Token::KwYield)
1056            .ignore_then(just(Token::LParen))
1057            .ignore_then(expr.clone())
1058            .then_ignore(just(Token::RParen))
1059            .map_with_span({
1060                let src = src.clone();
1061                move |value, span: Range<usize>| Expr::Yield {
1062                    value: Box::new(value),
1063                    span: make_span(&src, span),
1064                }
1065            });
1066
1067        // Turbofish type arguments: ::<Int, String>
1068        let turbofish = just(Token::ColonColon)
1069            .ignore_then(
1070                type_parser(src.clone())
1071                    .separated_by(just(Token::Comma))
1072                    .allow_trailing()
1073                    .delimited_by(just(Token::Lt), just(Token::Gt)),
1074            )
1075            .or_not()
1076            .map(|args| args.unwrap_or_default());
1077
1078        // function call: name(args) or name::<T, U>(args)
1079        let call_expr = ident_token_parser(src.clone())
1080            .then(turbofish.clone())
1081            .then(
1082                expr.clone()
1083                    .separated_by(just(Token::Comma))
1084                    .allow_trailing()
1085                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1086            )
1087            .map_with_span({
1088                let src = src.clone();
1089                move |((name, type_args), args), span: Range<usize>| Expr::Call {
1090                    name,
1091                    type_args,
1092                    args,
1093                    span: make_span(&src, span),
1094                }
1095            });
1096
1097        // Pattern for match arms
1098        let pattern = pattern_parser(src.clone());
1099
1100        // match expression: match expr { Pattern => expr, ... }
1101        let match_arm = pattern
1102            .then_ignore(just(Token::FatArrow))
1103            .then(expr.clone())
1104            .map_with_span({
1105                let src = src.clone();
1106                move |(pattern, body), span: Range<usize>| MatchArm {
1107                    pattern,
1108                    body,
1109                    span: make_span(&src, span),
1110                }
1111            });
1112
1113        let match_expr = just(Token::KwMatch)
1114            .ignore_then(expr.clone())
1115            .then(
1116                match_arm
1117                    .separated_by(just(Token::Comma))
1118                    .allow_trailing()
1119                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
1120            )
1121            .map_with_span({
1122                let src = src.clone();
1123                move |(scrutinee, arms), span: Range<usize>| Expr::Match {
1124                    scrutinee: Box::new(scrutinee),
1125                    arms,
1126                    span: make_span(&src, span),
1127                }
1128            });
1129
1130        // receive() - receive message from mailbox
1131        let receive_expr = just(Token::KwReceive)
1132            .ignore_then(just(Token::LParen))
1133            .ignore_then(just(Token::RParen))
1134            .map_with_span({
1135                let src = src.clone();
1136                move |_, span: Range<usize>| Expr::Receive {
1137                    span: make_span(&src, span),
1138                }
1139            });
1140
1141        // trace(message) - emit a trace event
1142        let trace_expr = just(Token::KwTrace)
1143            .ignore_then(just(Token::LParen))
1144            .ignore_then(expr.clone())
1145            .then_ignore(just(Token::RParen))
1146            .map_with_span({
1147                let src = src.clone();
1148                move |message, span: Range<usize>| Expr::Trace {
1149                    message: Box::new(message),
1150                    span: make_span(&src, span),
1151                }
1152            });
1153
1154        // Record construction: RecordName { field: value, ... }
1155        // This is similar to summon but without the summon keyword
1156        // Must come before var to avoid conflict
1157        let record_field_init = ident_token_parser(src.clone())
1158            .then_ignore(just(Token::Colon))
1159            .then(expr.clone())
1160            .map_with_span({
1161                let src = src.clone();
1162                move |(name, value), span: Range<usize>| FieldInit {
1163                    name,
1164                    value,
1165                    span: make_span(&src, span),
1166                }
1167            });
1168
1169        // Turbofish for record construction: Pair::<Int, String> { ... }
1170        let record_turbofish = just(Token::ColonColon)
1171            .ignore_then(
1172                type_parser(src.clone())
1173                    .separated_by(just(Token::Comma))
1174                    .allow_trailing()
1175                    .delimited_by(just(Token::Lt), just(Token::Gt)),
1176            )
1177            .or_not()
1178            .map(|args| args.unwrap_or_default());
1179
1180        let record_construct = ident_token_parser(src.clone())
1181            .then(record_turbofish)
1182            .then_ignore(just(Token::LBrace))
1183            .then(
1184                record_field_init
1185                    .separated_by(just(Token::Comma))
1186                    .allow_trailing(),
1187            )
1188            .then_ignore(just(Token::RBrace))
1189            .map_with_span({
1190                let src = src.clone();
1191                move |((name, type_args), fields), span: Range<usize>| Expr::RecordConstruct {
1192                    name,
1193                    type_args,
1194                    fields,
1195                    span: make_span(&src, span),
1196                }
1197            });
1198
1199        // Closure parameter: `name` or `name: Type`
1200        let closure_param = ident_token_parser(src.clone())
1201            .then(
1202                just(Token::Colon)
1203                    .ignore_then(type_parser(src.clone()))
1204                    .or_not(),
1205            )
1206            .map_with_span({
1207                let src = src.clone();
1208                move |(name, ty), span: Range<usize>| ClosureParam {
1209                    name,
1210                    ty,
1211                    span: make_span(&src, span),
1212                }
1213            });
1214
1215        // Closure expression: |params| body
1216        // Handle both `|| expr` (empty params using Or token) and `|params| expr`
1217        let closure_empty = just(Token::Or).ignore_then(expr.clone()).map_with_span({
1218            let src = src.clone();
1219            move |body, span: Range<usize>| Expr::Closure {
1220                params: vec![],
1221                body: Box::new(body),
1222                span: make_span(&src, span),
1223            }
1224        });
1225
1226        let closure_with_params = just(Token::Pipe)
1227            .ignore_then(
1228                closure_param
1229                    .separated_by(just(Token::Comma))
1230                    .allow_trailing(),
1231            )
1232            .then_ignore(just(Token::Pipe))
1233            .then(expr.clone())
1234            .map_with_span({
1235                let src = src.clone();
1236                move |(params, body), span: Range<usize>| Expr::Closure {
1237                    params,
1238                    body: Box::new(body),
1239                    span: make_span(&src, span),
1240                }
1241            });
1242
1243        let closure = closure_with_params.or(closure_empty);
1244
1245        // Map literal: { key: value, ... } or {}
1246        // This must be distinguished from record construction which has an identifier before the brace
1247        let map_entry = expr
1248            .clone()
1249            .then_ignore(just(Token::Colon))
1250            .then(expr.clone())
1251            .map_with_span({
1252                let src = src.clone();
1253                move |(key, value), span: Range<usize>| MapEntry {
1254                    key,
1255                    value,
1256                    span: make_span(&src, span),
1257                }
1258            });
1259
1260        let map_literal = map_entry
1261            .separated_by(just(Token::Comma))
1262            .allow_trailing()
1263            .delimited_by(just(Token::LBrace), just(Token::RBrace))
1264            .map_with_span({
1265                let src = src.clone();
1266                move |entries, span: Range<usize>| Expr::Map {
1267                    entries,
1268                    span: make_span(&src, span),
1269                }
1270            });
1271
1272        // Enum variant construction: EnumName::Variant or Either::<L, R>::Left(payload)
1273        // Turbofish comes between enum name and ::Variant
1274        let variant_turbofish = just(Token::ColonColon)
1275            .ignore_then(
1276                type_parser(src.clone())
1277                    .separated_by(just(Token::Comma))
1278                    .allow_trailing()
1279                    .delimited_by(just(Token::Lt), just(Token::Gt)),
1280            )
1281            .or_not()
1282            .map(|args| args.unwrap_or_default());
1283
1284        let variant_construct = ident_token_parser(src.clone())
1285            .then(variant_turbofish)
1286            .then_ignore(just(Token::ColonColon))
1287            .then(ident_token_parser(src.clone()))
1288            .then(
1289                expr.clone()
1290                    .delimited_by(just(Token::LParen), just(Token::RParen))
1291                    .or_not(),
1292            )
1293            .map_with_span({
1294                let src = src.clone();
1295                move |(((enum_name, type_args), variant), payload), span: Range<usize>| {
1296                    Expr::VariantConstruct {
1297                        enum_name,
1298                        type_args,
1299                        variant,
1300                        payload: payload.map(Box::new),
1301                        span: make_span(&src, span),
1302                    }
1303                }
1304            });
1305
1306        // Atom: the base expression without binary ops
1307        // Box early to cut type complexity
1308        // Note: record_construct must come before call_expr and var to parse `Name { ... }` correctly
1309        // Note: receive_expr must come before call_expr to avoid being parsed as function call
1310        // Note: closure must come before other expressions to handle `|` tokens correctly
1311        // Note: map_literal must come after record_construct (record has name before brace)
1312        // Note: variant_construct must come before call_expr to parse `EnumName::Variant(...)` correctly
1313        let atom = closure
1314            .or(divine_expr)
1315            .or(summon_expr)
1316            .or(await_expr)
1317            .or(send_expr)
1318            .or(yield_expr)
1319            .or(receive_expr)
1320            .or(trace_expr)
1321            .or(match_expr)
1322            .or(self_access)
1323            .or(record_construct)
1324            .or(variant_construct)
1325            .or(call_expr)
1326            .or(map_literal)
1327            .or(list)
1328            .or(paren_or_tuple)
1329            .or(literal)
1330            .or(var)
1331            .boxed();
1332
1333        // Postfix access: expr.field, expr.0 (tuple index), or expr.method(args) (tool call)
1334        // We need to distinguish between field access, tuple index, and method call
1335        enum PostfixOp {
1336            Field(Ident),
1337            TupleIndex(usize, Range<usize>),
1338            MethodCall(Ident, Vec<Expr>, Range<usize>), // method name, args, span of closing paren
1339        }
1340
1341        // Parse method call: .ident(args)
1342        let method_call = ident_token_parser(src.clone())
1343            .then(
1344                expr.clone()
1345                    .separated_by(just(Token::Comma))
1346                    .allow_trailing()
1347                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1348            )
1349            .map_with_span(|(name, args), span: Range<usize>| {
1350                PostfixOp::MethodCall(name, args, span)
1351            });
1352
1353        let postfix_op = just(Token::Dot).ignore_then(
1354            // Try to parse a tuple index (integer literal)
1355            filter_map({
1356                let src = src.clone();
1357                move |span: Range<usize>, token| match token {
1358                    Token::IntLit => {
1359                        let text = &src[span.start..span.end];
1360                        text.parse::<usize>()
1361                            .map(|idx| PostfixOp::TupleIndex(idx, span.clone()))
1362                            .map_err(|_| Simple::custom(span, "invalid tuple index"))
1363                    }
1364                    _ => Err(Simple::expected_input_found(
1365                        span,
1366                        vec![Some(Token::IntLit)],
1367                        Some(token),
1368                    )),
1369                }
1370            })
1371            // Try method call first, then field access
1372            .or(method_call)
1373            .or(ident_token_parser(src.clone()).map(PostfixOp::Field)),
1374        );
1375
1376        let postfix = atom
1377            .then(postfix_op.repeated())
1378            .foldl({
1379                let src = src.clone();
1380                move |object, op| match op {
1381                    PostfixOp::Field(field) => {
1382                        let span = make_span(&src, object.span().start..field.span.end);
1383                        Expr::FieldAccess {
1384                            object: Box::new(object),
1385                            field,
1386                            span,
1387                        }
1388                    }
1389                    PostfixOp::TupleIndex(index, idx_span) => {
1390                        let span = make_span(&src, object.span().start..idx_span.end);
1391                        Expr::TupleIndex {
1392                            tuple: Box::new(object),
1393                            index,
1394                            span,
1395                        }
1396                    }
1397                    PostfixOp::MethodCall(method, args, call_span) => {
1398                        // If object is a Var, this might be a tool call
1399                        // Tool calls look like: Http.get(url)
1400                        if let Expr::Var { name: tool, .. } = &object {
1401                            let span = make_span(&src, object.span().start..call_span.end);
1402                            Expr::ToolCall {
1403                                tool: tool.clone(),
1404                                function: method,
1405                                args,
1406                                span,
1407                            }
1408                        } else {
1409                            // Not a tool call - for now, produce a FieldAccess error
1410                            // (Sage doesn't support general method calls on values)
1411                            let span = make_span(&src, object.span().start..call_span.end);
1412                            Expr::FieldAccess {
1413                                object: Box::new(object),
1414                                field: method,
1415                                span,
1416                            }
1417                        }
1418                    }
1419                }
1420            })
1421            .boxed();
1422
1423        // Unary expressions
1424        let unary = just(Token::Minus)
1425            .to(UnaryOp::Neg)
1426            .or(just(Token::Bang).to(UnaryOp::Not))
1427            .repeated()
1428            .then(postfix.clone())
1429            .foldr(|op, operand| {
1430                let span = operand.span().clone();
1431                Expr::Unary {
1432                    op,
1433                    operand: Box::new(operand),
1434                    span,
1435                }
1436            })
1437            .boxed();
1438
1439        // RFC-0007: try expression - propagates errors upward
1440        // try expr
1441        let try_expr = just(Token::KwTry)
1442            .ignore_then(postfix.clone())
1443            .map_with_span({
1444                let src = src.clone();
1445                move |inner, span: Range<usize>| Expr::Try {
1446                    expr: Box::new(inner),
1447                    span: make_span(&src, span),
1448                }
1449            })
1450            .boxed();
1451
1452        // fail expression - explicit error raising
1453        // fail "message" or fail Error { ... }
1454        let fail_expr = just(Token::KwFail)
1455            .ignore_then(postfix.clone())
1456            .map_with_span({
1457                let src = src.clone();
1458                move |error, span: Range<usize>| Expr::Fail {
1459                    error: Box::new(error),
1460                    span: make_span(&src, span),
1461                }
1462            })
1463            .boxed();
1464
1465        // retry expression - retry a fallible operation
1466        // retry(count) { body }
1467        // retry(count, delay: ms) { body }
1468        // retry(count, on: [ErrorKind.Network, ...]) { body }
1469        let retry_delay = just(Token::Comma)
1470            .ignore_then(just(Token::KwDelay))
1471            .ignore_then(just(Token::Colon))
1472            .ignore_then(postfix.clone());
1473
1474        let retry_on = just(Token::Comma)
1475            .ignore_then(just(Token::KwOn))
1476            .ignore_then(just(Token::Colon))
1477            .ignore_then(
1478                postfix
1479                    .clone()
1480                    .separated_by(just(Token::Comma))
1481                    .allow_trailing()
1482                    .delimited_by(just(Token::LBracket), just(Token::RBracket)),
1483            );
1484
1485        let retry_expr = just(Token::KwRetry)
1486            .ignore_then(just(Token::LParen))
1487            .ignore_then(postfix.clone())
1488            .then(retry_delay.or_not())
1489            .then(retry_on.or_not())
1490            .then_ignore(just(Token::RParen))
1491            .then(
1492                expr.clone()
1493                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
1494            )
1495            .map_with_span({
1496                let src = src.clone();
1497                move |(((count, delay), on_errors), body), span: Range<usize>| Expr::Retry {
1498                    count: Box::new(count),
1499                    delay: delay.map(Box::new),
1500                    on_errors,
1501                    body: Box::new(body),
1502                    span: make_span(&src, span),
1503                }
1504            })
1505            .boxed();
1506
1507        // Combined unary (including try, fail, and retry)
1508        let unary = retry_expr.or(fail_expr).or(try_expr).or(unary).boxed();
1509
1510        // Binary operators with precedence levels
1511        // Level 7: * / %
1512        let mul_div_op = just(Token::Star)
1513            .to(BinOp::Mul)
1514            .or(just(Token::Slash).to(BinOp::Div))
1515            .or(just(Token::Percent).to(BinOp::Rem));
1516
1517        let mul_div = unary
1518            .clone()
1519            .then(mul_div_op.then(unary.clone()).repeated())
1520            .foldl({
1521                let src = src.clone();
1522                move |left, (op, right)| {
1523                    let span = make_span(&src, left.span().start..right.span().end);
1524                    Expr::Binary {
1525                        op,
1526                        left: Box::new(left),
1527                        right: Box::new(right),
1528                        span,
1529                    }
1530                }
1531            })
1532            .boxed();
1533
1534        // Level 6: + -
1535        let add_sub_op = just(Token::Plus)
1536            .to(BinOp::Add)
1537            .or(just(Token::Minus).to(BinOp::Sub));
1538
1539        let add_sub = mul_div
1540            .clone()
1541            .then(add_sub_op.then(mul_div).repeated())
1542            .foldl({
1543                let src = src.clone();
1544                move |left, (op, right)| {
1545                    let span = make_span(&src, left.span().start..right.span().end);
1546                    Expr::Binary {
1547                        op,
1548                        left: Box::new(left),
1549                        right: Box::new(right),
1550                        span,
1551                    }
1552                }
1553            })
1554            .boxed();
1555
1556        // Level 5: ++
1557        let concat_op = just(Token::PlusPlus).to(BinOp::Concat);
1558
1559        let concat = add_sub
1560            .clone()
1561            .then(concat_op.then(add_sub).repeated())
1562            .foldl({
1563                let src = src.clone();
1564                move |left, (op, right)| {
1565                    let span = make_span(&src, left.span().start..right.span().end);
1566                    Expr::Binary {
1567                        op,
1568                        left: Box::new(left),
1569                        right: Box::new(right),
1570                        span,
1571                    }
1572                }
1573            })
1574            .boxed();
1575
1576        // Level 4: < > <= >=
1577        let cmp_op = choice((
1578            just(Token::Le).to(BinOp::Le),
1579            just(Token::Ge).to(BinOp::Ge),
1580            just(Token::Lt).to(BinOp::Lt),
1581            just(Token::Gt).to(BinOp::Gt),
1582        ));
1583
1584        let comparison = concat
1585            .clone()
1586            .then(cmp_op.then(concat).repeated())
1587            .foldl({
1588                let src = src.clone();
1589                move |left, (op, right)| {
1590                    let span = make_span(&src, left.span().start..right.span().end);
1591                    Expr::Binary {
1592                        op,
1593                        left: Box::new(left),
1594                        right: Box::new(right),
1595                        span,
1596                    }
1597                }
1598            })
1599            .boxed();
1600
1601        // Level 3: == !=
1602        let eq_op = just(Token::EqEq)
1603            .to(BinOp::Eq)
1604            .or(just(Token::Ne).to(BinOp::Ne));
1605
1606        let equality = comparison
1607            .clone()
1608            .then(eq_op.then(comparison).repeated())
1609            .foldl({
1610                let src = src.clone();
1611                move |left, (op, right)| {
1612                    let span = make_span(&src, left.span().start..right.span().end);
1613                    Expr::Binary {
1614                        op,
1615                        left: Box::new(left),
1616                        right: Box::new(right),
1617                        span,
1618                    }
1619                }
1620            })
1621            .boxed();
1622
1623        // Level 2: &&
1624        let and_op = just(Token::And).to(BinOp::And);
1625
1626        let and = equality
1627            .clone()
1628            .then(and_op.then(equality).repeated())
1629            .foldl({
1630                let src = src.clone();
1631                move |left, (op, right)| {
1632                    let span = make_span(&src, left.span().start..right.span().end);
1633                    Expr::Binary {
1634                        op,
1635                        left: Box::new(left),
1636                        right: Box::new(right),
1637                        span,
1638                    }
1639                }
1640            })
1641            .boxed();
1642
1643        // Level 1: ||
1644        let or_op = just(Token::Or).to(BinOp::Or);
1645
1646        let or_expr = and.clone().then(or_op.then(and).repeated()).foldl({
1647            let src = src.clone();
1648            move |left, (op, right)| {
1649                let span = make_span(&src, left.span().start..right.span().end);
1650                Expr::Binary {
1651                    op,
1652                    left: Box::new(left),
1653                    right: Box::new(right),
1654                    span,
1655                }
1656            }
1657        });
1658
1659        // RFC-0007: catch expression (lowest precedence)
1660        // expr catch { recovery } OR expr catch(e) { recovery }
1661        let catch_recovery = just(Token::KwCatch)
1662            .ignore_then(
1663                ident_token_parser(src.clone())
1664                    .delimited_by(just(Token::LParen), just(Token::RParen))
1665                    .or_not(),
1666            )
1667            .then(
1668                expr.clone()
1669                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
1670            );
1671
1672        or_expr.then(catch_recovery.or_not()).map_with_span({
1673            let src = src.clone();
1674            move |(inner, catch_opt), span: Range<usize>| match catch_opt {
1675                Some((error_bind, recovery)) => Expr::Catch {
1676                    expr: Box::new(inner),
1677                    error_bind,
1678                    recovery: Box::new(recovery),
1679                    span: make_span(&src, span),
1680                },
1681                None => inner,
1682            }
1683        })
1684    })
1685    .boxed()
1686}
1687
1688// =============================================================================
1689// Primitive parsers
1690// =============================================================================
1691
1692/// Create a Span from a Range<usize>.
1693fn make_span(source: &Arc<str>, range: Range<usize>) -> Span {
1694    Span::new(range.start, range.end, Arc::clone(source))
1695}
1696
1697/// Parser for identifier tokens.
1698fn ident_token_parser(source: Arc<str>) -> impl Parser<Token, Ident, Error = ParseError> + Clone {
1699    filter_map(move |span: Range<usize>, token| match token {
1700        Token::Ident => {
1701            let text = &source[span.start..span.end];
1702            Ok(Ident::new(text.to_string(), make_span(&source, span)))
1703        }
1704        _ => Err(Simple::expected_input_found(
1705            span,
1706            vec![Some(Token::Ident)],
1707            Some(token),
1708        )),
1709    })
1710}
1711
1712/// Parser for variable references.
1713fn var_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
1714    ident_token_parser(source.clone()).map_with_span(move |name, span: Range<usize>| Expr::Var {
1715        name,
1716        span: make_span(&source, span),
1717    })
1718}
1719
1720/// Parser for type expressions.
1721fn type_parser(source: Arc<str>) -> impl Parser<Token, TypeExpr, Error = ParseError> + Clone {
1722    recursive(move |ty| {
1723        let src = source.clone();
1724
1725        let primitive = choice((
1726            just(Token::TyInt).to(TypeExpr::Int),
1727            just(Token::TyFloat).to(TypeExpr::Float),
1728            just(Token::TyBool).to(TypeExpr::Bool),
1729            just(Token::TyString).to(TypeExpr::String),
1730            just(Token::TyUnit).to(TypeExpr::Unit),
1731        ));
1732
1733        let list_ty = just(Token::TyList)
1734            .ignore_then(just(Token::Lt))
1735            .ignore_then(ty.clone())
1736            .then_ignore(just(Token::Gt))
1737            .map(|inner| TypeExpr::List(Box::new(inner)));
1738
1739        let option_ty = just(Token::TyOption)
1740            .ignore_then(just(Token::Lt))
1741            .ignore_then(ty.clone())
1742            .then_ignore(just(Token::Gt))
1743            .map(|inner| TypeExpr::Option(Box::new(inner)));
1744
1745        let oracle_ty = just(Token::TyOracle)
1746            .ignore_then(just(Token::Lt))
1747            .ignore_then(ty.clone())
1748            .then_ignore(just(Token::Gt))
1749            .map(|inner| TypeExpr::Oracle(Box::new(inner)));
1750
1751        let agent_ty = just(Token::TyAgent)
1752            .ignore_then(just(Token::Lt))
1753            .ignore_then(ident_token_parser(src.clone()))
1754            .then_ignore(just(Token::Gt))
1755            .map(TypeExpr::Agent);
1756
1757        // Named type with optional type arguments: MyRecord, Pair<Int, String>
1758        let named_ty = ident_token_parser(src.clone())
1759            .then(
1760                ty.clone()
1761                    .separated_by(just(Token::Comma))
1762                    .allow_trailing()
1763                    .delimited_by(just(Token::Lt), just(Token::Gt))
1764                    .or_not(),
1765            )
1766            .map(|(name, type_args)| TypeExpr::Named(name, type_args.unwrap_or_default()));
1767
1768        // Function type: Fn(A, B) -> C
1769        let fn_ty = just(Token::TyFn)
1770            .ignore_then(
1771                ty.clone()
1772                    .separated_by(just(Token::Comma))
1773                    .allow_trailing()
1774                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1775            )
1776            .then_ignore(just(Token::Arrow))
1777            .then(ty.clone())
1778            .map(|(params, ret)| TypeExpr::Fn(params, Box::new(ret)));
1779
1780        // Map type: Map<K, V>
1781        let map_ty = just(Token::TyMap)
1782            .ignore_then(just(Token::Lt))
1783            .ignore_then(ty.clone())
1784            .then_ignore(just(Token::Comma))
1785            .then(ty.clone())
1786            .then_ignore(just(Token::Gt))
1787            .map(|(k, v)| TypeExpr::Map(Box::new(k), Box::new(v)));
1788
1789        // Result type: Result<T, E>
1790        let result_ty = just(Token::TyResult)
1791            .ignore_then(just(Token::Lt))
1792            .ignore_then(ty.clone())
1793            .then_ignore(just(Token::Comma))
1794            .then(ty.clone())
1795            .then_ignore(just(Token::Gt))
1796            .map(|(ok, err)| TypeExpr::Result(Box::new(ok), Box::new(err)));
1797
1798        // Tuple type: (A, B, C) - at least 2 elements
1799        let tuple_ty = ty
1800            .clone()
1801            .separated_by(just(Token::Comma))
1802            .at_least(2)
1803            .allow_trailing()
1804            .delimited_by(just(Token::LParen), just(Token::RParen))
1805            .map(TypeExpr::Tuple);
1806
1807        primitive
1808            .or(list_ty)
1809            .or(option_ty)
1810            .or(oracle_ty)
1811            .or(agent_ty)
1812            .or(fn_ty)
1813            .or(map_ty)
1814            .or(result_ty)
1815            .or(tuple_ty)
1816            .or(named_ty)
1817    })
1818}
1819
1820/// Parser for patterns in for loops.
1821/// Only supports simple bindings (`x`) and tuple patterns (`(k, v)`).
1822fn for_pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
1823    recursive(move |pattern| {
1824        let src = source.clone();
1825        let src2 = source.clone();
1826
1827        // Simple binding pattern: `x`
1828        let binding = ident_token_parser(src.clone()).map_with_span({
1829            let src = src.clone();
1830            move |name, span: Range<usize>| Pattern::Binding {
1831                name,
1832                span: make_span(&src, span),
1833            }
1834        });
1835
1836        // Tuple pattern: `(a, b)` - at least 2 elements
1837        let tuple_pattern = pattern
1838            .clone()
1839            .separated_by(just(Token::Comma))
1840            .at_least(2)
1841            .allow_trailing()
1842            .delimited_by(just(Token::LParen), just(Token::RParen))
1843            .map_with_span({
1844                let src = src2.clone();
1845                move |elements, span: Range<usize>| Pattern::Tuple {
1846                    elements,
1847                    span: make_span(&src, span),
1848                }
1849            });
1850
1851        tuple_pattern.or(binding)
1852    })
1853}
1854
1855/// Parser for patterns in match expressions.
1856fn pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
1857    recursive(move |pattern| {
1858        let src = source.clone();
1859        let src2 = source.clone();
1860        let src3 = source.clone();
1861        let src4 = source.clone();
1862        let src5 = source.clone();
1863
1864        // Wildcard pattern: `_`
1865        let wildcard = filter_map({
1866            let src = src.clone();
1867            move |span: Range<usize>, token| match &token {
1868                Token::Ident if src[span.start..span.end].eq("_") => Ok(()),
1869                _ => Err(Simple::expected_input_found(span, vec![], Some(token))),
1870            }
1871        })
1872        .map_with_span(move |_, span: Range<usize>| Pattern::Wildcard {
1873            span: make_span(&src2, span),
1874        });
1875
1876        // Literal patterns: 42, "hello", true, false
1877        let lit_int = filter_map({
1878            let src = src3.clone();
1879            move |span: Range<usize>, token| match token {
1880                Token::IntLit => {
1881                    let text = &src[span.start..span.end];
1882                    text.parse::<i64>()
1883                        .map(Literal::Int)
1884                        .map_err(|_| Simple::custom(span, "invalid integer literal"))
1885                }
1886                _ => Err(Simple::expected_input_found(
1887                    span,
1888                    vec![Some(Token::IntLit)],
1889                    Some(token),
1890                )),
1891            }
1892        })
1893        .map_with_span({
1894            let src = src3.clone();
1895            move |value, span: Range<usize>| Pattern::Literal {
1896                value,
1897                span: make_span(&src, span),
1898            }
1899        });
1900
1901        let lit_bool = just(Token::KwTrue)
1902            .to(Literal::Bool(true))
1903            .or(just(Token::KwFalse).to(Literal::Bool(false)))
1904            .map_with_span({
1905                let src = src3.clone();
1906                move |value, span: Range<usize>| Pattern::Literal {
1907                    value,
1908                    span: make_span(&src, span),
1909                }
1910            });
1911
1912        // Tuple pattern: (a, b, c) - at least 2 elements
1913        let tuple_pattern = pattern
1914            .clone()
1915            .separated_by(just(Token::Comma))
1916            .at_least(2)
1917            .allow_trailing()
1918            .delimited_by(just(Token::LParen), just(Token::RParen))
1919            .map_with_span({
1920                let src = src5.clone();
1921                move |elements, span: Range<usize>| Pattern::Tuple {
1922                    elements,
1923                    span: make_span(&src, span),
1924                }
1925            });
1926
1927        // Enum variant with optional payload: `Ok(x)` or `Status::Active`
1928        // Qualified with payload: EnumName::Variant(pattern)
1929        let qualified_variant_with_payload = ident_token_parser(src4.clone())
1930            .then_ignore(just(Token::ColonColon))
1931            .then(ident_token_parser(src4.clone()))
1932            .then(
1933                pattern
1934                    .clone()
1935                    .delimited_by(just(Token::LParen), just(Token::RParen))
1936                    .or_not(),
1937            )
1938            .map_with_span({
1939                let src = src4.clone();
1940                move |((enum_name, variant), payload), span: Range<usize>| Pattern::Variant {
1941                    enum_name: Some(enum_name),
1942                    variant,
1943                    payload: payload.map(Box::new),
1944                    span: make_span(&src, span),
1945                }
1946            });
1947
1948        // Unqualified variant with payload: `Ok(x)` or just `x`
1949        let unqualified_with_payload = ident_token_parser(src4.clone())
1950            .then(
1951                pattern
1952                    .clone()
1953                    .delimited_by(just(Token::LParen), just(Token::RParen))
1954                    .or_not(),
1955            )
1956            .map_with_span({
1957                let src = src4.clone();
1958                move |(name, payload), span: Range<usize>| {
1959                    // If it looks like a variant (starts with uppercase), treat as variant
1960                    // Otherwise treat as binding (only if no payload)
1961                    if name.name.chars().next().is_some_and(|c| c.is_uppercase())
1962                        || payload.is_some()
1963                    {
1964                        Pattern::Variant {
1965                            enum_name: None,
1966                            variant: name,
1967                            payload: payload.map(Box::new),
1968                            span: make_span(&src, span),
1969                        }
1970                    } else {
1971                        Pattern::Binding {
1972                            name,
1973                            span: make_span(&src, span),
1974                        }
1975                    }
1976                }
1977            });
1978
1979        // Order matters: try wildcard first, then tuple pattern, then qualified variant, then literals, then unqualified
1980        wildcard
1981            .or(tuple_pattern)
1982            .or(qualified_variant_with_payload)
1983            .or(lit_int)
1984            .or(lit_bool)
1985            .or(unqualified_with_payload)
1986    })
1987}
1988
1989/// Parser for literals.
1990fn literal_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
1991    let src = source.clone();
1992    let src2 = source.clone();
1993    let src3 = source.clone();
1994    let src4 = source.clone();
1995    let src5 = source.clone();
1996
1997    let int_lit = filter_map(move |span: Range<usize>, token| match token {
1998        Token::IntLit => {
1999            let text = &src[span.start..span.end];
2000            text.parse::<i64>()
2001                .map(Literal::Int)
2002                .map_err(|_| Simple::custom(span, "invalid integer literal"))
2003        }
2004        _ => Err(Simple::expected_input_found(
2005            span,
2006            vec![Some(Token::IntLit)],
2007            Some(token),
2008        )),
2009    })
2010    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
2011        value,
2012        span: make_span(&src2, span),
2013    });
2014
2015    let float_lit = filter_map(move |span: Range<usize>, token| match token {
2016        Token::FloatLit => {
2017            let text = &src3[span.start..span.end];
2018            text.parse::<f64>()
2019                .map(Literal::Float)
2020                .map_err(|_| Simple::custom(span, "invalid float literal"))
2021        }
2022        _ => Err(Simple::expected_input_found(
2023            span,
2024            vec![Some(Token::FloatLit)],
2025            Some(token),
2026        )),
2027    })
2028    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
2029        value,
2030        span: make_span(&src4, span),
2031    });
2032
2033    let src6 = source.clone();
2034    let string_lit = filter_map(move |span: Range<usize>, token| match token {
2035        Token::StringLit => {
2036            let text = &src5[span.start..span.end];
2037            let inner = &text[1..text.len() - 1];
2038            let parts = parse_string_template(inner, &make_span(&src5, span.clone()));
2039            Ok(parts)
2040        }
2041        _ => Err(Simple::expected_input_found(
2042            span,
2043            vec![Some(Token::StringLit)],
2044            Some(token),
2045        )),
2046    })
2047    .map_with_span(move |parts, span: Range<usize>| {
2048        let span = make_span(&src6, span);
2049        // If no interpolations, use a simple string literal
2050        if parts.len() == 1 {
2051            if let StringPart::Literal(s) = &parts[0] {
2052                return Expr::Literal {
2053                    value: Literal::String(s.clone()),
2054                    span,
2055                };
2056            }
2057        }
2058        // Otherwise, use StringInterp
2059        Expr::StringInterp {
2060            template: StringTemplate {
2061                parts,
2062                span: span.clone(),
2063            },
2064            span,
2065        }
2066    });
2067
2068    let bool_lit = just(Token::KwTrue)
2069        .to(Literal::Bool(true))
2070        .or(just(Token::KwFalse).to(Literal::Bool(false)))
2071        .map_with_span(move |value, _span: Range<usize>| Expr::Literal {
2072            value,
2073            span: Span::dummy(), // bool literals don't carry source
2074        });
2075
2076    int_lit.or(float_lit).or(string_lit).or(bool_lit)
2077}
2078
2079/// Parser for string templates (handles interpolation).
2080fn string_template_parser(
2081    source: Arc<str>,
2082) -> impl Parser<Token, StringTemplate, Error = ParseError> + Clone {
2083    filter_map(move |span: Range<usize>, token| match token {
2084        Token::StringLit => {
2085            let text = &source[span.start..span.end];
2086            let inner = &text[1..text.len() - 1];
2087            let parts = parse_string_template(inner, &make_span(&source, span.clone()));
2088            Ok(StringTemplate {
2089                parts,
2090                span: make_span(&source, span),
2091            })
2092        }
2093        _ => Err(Simple::expected_input_found(
2094            span,
2095            vec![Some(Token::StringLit)],
2096            Some(token),
2097        )),
2098    })
2099}
2100
2101/// Parse a string into template parts, handling `{expr}` interpolations.
2102/// Supports arbitrary expressions: `{name}`, `{a + b}`, `{foo(x, y)}`
2103fn parse_string_template(s: &str, span: &Span) -> Vec<StringPart> {
2104    let mut parts = Vec::new();
2105    let mut current = String::new();
2106    let mut chars = s.chars().peekable();
2107
2108    while let Some(ch) = chars.next() {
2109        if ch == '{' {
2110            if !current.is_empty() {
2111                parts.push(StringPart::Literal(std::mem::take(&mut current)));
2112            }
2113
2114            // Collect the full interpolation expression, handling nested braces
2115            let mut expr_str = String::new();
2116            let mut brace_depth = 1;
2117            let mut string_quote: Option<char> = None; // Track which quote type we're in
2118            let mut escape_next = false;
2119
2120            while let Some(&c) = chars.peek() {
2121                if escape_next {
2122                    expr_str.push(c);
2123                    chars.next();
2124                    escape_next = false;
2125                    continue;
2126                }
2127
2128                if c == '\\' && string_quote.is_some() {
2129                    escape_next = true;
2130                    expr_str.push(c);
2131                    chars.next();
2132                    continue;
2133                }
2134
2135                // Handle string delimiters (both " and ')
2136                if c == '"' || c == '\'' {
2137                    match string_quote {
2138                        None => string_quote = Some(c),           // Start of string
2139                        Some(q) if q == c => string_quote = None, // End of string
2140                        Some(_) => {} // Inside different type of string, ignore
2141                    }
2142                }
2143
2144                if string_quote.is_none() {
2145                    if c == '{' {
2146                        brace_depth += 1;
2147                    } else if c == '}' {
2148                        brace_depth -= 1;
2149                        if brace_depth == 0 {
2150                            chars.next();
2151                            break;
2152                        }
2153                    }
2154                }
2155
2156                expr_str.push(c);
2157                chars.next();
2158            }
2159
2160            if !expr_str.is_empty() {
2161                let expr = parse_interp_expr(&expr_str, span);
2162                parts.push(StringPart::Interpolation(Box::new(expr)));
2163            }
2164        } else if ch == '\\' {
2165            if let Some(escaped) = chars.next() {
2166                current.push(match escaped {
2167                    'n' => '\n',
2168                    't' => '\t',
2169                    'r' => '\r',
2170                    '\\' => '\\',
2171                    '"' => '"',
2172                    '{' => '{',
2173                    '}' => '}',
2174                    other => other,
2175                });
2176            }
2177        } else {
2178            current.push(ch);
2179        }
2180    }
2181
2182    if !current.is_empty() {
2183        parts.push(StringPart::Literal(current));
2184    }
2185
2186    if parts.is_empty() {
2187        parts.push(StringPart::Literal(String::new()));
2188    }
2189
2190    parts
2191}
2192
2193/// Parse an interpolation expression string into an Expr AST node.
2194/// Lexes and parses the substring using a mini recursive-descent parser.
2195fn parse_interp_expr(s: &str, span: &Span) -> Expr {
2196    let trimmed = s.trim();
2197    if trimmed.is_empty() {
2198        // Empty interpolation, return a placeholder
2199        return Expr::Literal {
2200            value: Literal::String(String::new()),
2201            span: span.clone(),
2202        };
2203    }
2204
2205    // Lex the expression substring
2206    let lex_result = crate::lex(trimmed);
2207    let (tokens, source) = match lex_result {
2208        Ok(result) => (result.tokens().to_vec(), trimmed.to_string()),
2209        Err(_) => {
2210            // Lexing failed, return the raw string as a variable name (fallback)
2211            return Expr::Var {
2212                name: Ident::new(trimmed.to_string(), span.clone()),
2213                span: span.clone(),
2214            };
2215        }
2216    };
2217
2218    if tokens.is_empty() {
2219        return Expr::Var {
2220            name: Ident::new(trimmed.to_string(), span.clone()),
2221            span: span.clone(),
2222        };
2223    }
2224
2225    // Parse with a simple recursive descent parser
2226    let mut parser = InterpExprParser::new(&tokens, &source, span.clone());
2227    parser.parse_expr()
2228}
2229
2230/// A simple recursive-descent parser for interpolation expressions.
2231/// Supports: variables, field access, tuple index, binary ops, function calls, parens
2232struct InterpExprParser<'a> {
2233    tokens: &'a [crate::Spanned],
2234    source: &'a str,
2235    pos: usize,
2236    span: Span,
2237}
2238
2239impl<'a> InterpExprParser<'a> {
2240    fn new(tokens: &'a [crate::Spanned], source: &'a str, span: Span) -> Self {
2241        Self {
2242            tokens,
2243            source,
2244            pos: 0,
2245            span,
2246        }
2247    }
2248
2249    fn current(&self) -> Option<&Token> {
2250        self.tokens.get(self.pos).map(|s| &s.token)
2251    }
2252
2253    fn current_text(&self) -> Option<&str> {
2254        self.tokens
2255            .get(self.pos)
2256            .map(|s| &self.source[s.start..s.end])
2257    }
2258
2259    fn advance(&mut self) {
2260        if self.pos < self.tokens.len() {
2261            self.pos += 1;
2262        }
2263    }
2264
2265    fn parse_expr(&mut self) -> Expr {
2266        self.parse_or()
2267    }
2268
2269    fn parse_or(&mut self) -> Expr {
2270        let mut left = self.parse_and();
2271        while matches!(self.current(), Some(Token::Or)) {
2272            self.advance();
2273            let right = self.parse_and();
2274            left = Expr::Binary {
2275                left: Box::new(left),
2276                op: BinOp::Or,
2277                right: Box::new(right),
2278                span: self.span.clone(),
2279            };
2280        }
2281        left
2282    }
2283
2284    fn parse_and(&mut self) -> Expr {
2285        let mut left = self.parse_comparison();
2286        while matches!(self.current(), Some(Token::And)) {
2287            self.advance();
2288            let right = self.parse_comparison();
2289            left = Expr::Binary {
2290                left: Box::new(left),
2291                op: BinOp::And,
2292                right: Box::new(right),
2293                span: self.span.clone(),
2294            };
2295        }
2296        left
2297    }
2298
2299    fn parse_comparison(&mut self) -> Expr {
2300        let mut left = self.parse_additive();
2301        loop {
2302            let op = match self.current() {
2303                Some(Token::EqEq) => BinOp::Eq,
2304                Some(Token::Ne) => BinOp::Ne,
2305                Some(Token::Lt) => BinOp::Lt,
2306                Some(Token::Le) => BinOp::Le,
2307                Some(Token::Gt) => BinOp::Gt,
2308                Some(Token::Ge) => BinOp::Ge,
2309                _ => break,
2310            };
2311            self.advance();
2312            let right = self.parse_additive();
2313            left = Expr::Binary {
2314                left: Box::new(left),
2315                op,
2316                right: Box::new(right),
2317                span: self.span.clone(),
2318            };
2319        }
2320        left
2321    }
2322
2323    fn parse_additive(&mut self) -> Expr {
2324        let mut left = self.parse_multiplicative();
2325        loop {
2326            let op = match self.current() {
2327                Some(Token::Plus) => BinOp::Add,
2328                Some(Token::Minus) => BinOp::Sub,
2329                Some(Token::PlusPlus) => BinOp::Concat,
2330                _ => break,
2331            };
2332            self.advance();
2333            let right = self.parse_multiplicative();
2334            left = Expr::Binary {
2335                left: Box::new(left),
2336                op,
2337                right: Box::new(right),
2338                span: self.span.clone(),
2339            };
2340        }
2341        left
2342    }
2343
2344    fn parse_multiplicative(&mut self) -> Expr {
2345        let mut left = self.parse_unary();
2346        loop {
2347            let op = match self.current() {
2348                Some(Token::Star) => BinOp::Mul,
2349                Some(Token::Slash) => BinOp::Div,
2350                Some(Token::Percent) => BinOp::Rem,
2351                _ => break,
2352            };
2353            self.advance();
2354            let right = self.parse_unary();
2355            left = Expr::Binary {
2356                left: Box::new(left),
2357                op,
2358                right: Box::new(right),
2359                span: self.span.clone(),
2360            };
2361        }
2362        left
2363    }
2364
2365    fn parse_unary(&mut self) -> Expr {
2366        match self.current() {
2367            Some(Token::Minus) => {
2368                self.advance();
2369                let operand = self.parse_unary();
2370                Expr::Unary {
2371                    op: UnaryOp::Neg,
2372                    operand: Box::new(operand),
2373                    span: self.span.clone(),
2374                }
2375            }
2376            Some(Token::Bang) => {
2377                self.advance();
2378                let operand = self.parse_unary();
2379                Expr::Unary {
2380                    op: UnaryOp::Not,
2381                    operand: Box::new(operand),
2382                    span: self.span.clone(),
2383                }
2384            }
2385            _ => self.parse_postfix(),
2386        }
2387    }
2388
2389    fn parse_postfix(&mut self) -> Expr {
2390        let mut expr = self.parse_primary();
2391
2392        loop {
2393            match self.current() {
2394                Some(Token::Dot) => {
2395                    self.advance();
2396                    match self.current() {
2397                        Some(Token::IntLit) => {
2398                            let text = self.current_text().unwrap_or("0");
2399                            let index = text.parse::<usize>().unwrap_or(0);
2400                            self.advance();
2401                            expr = Expr::TupleIndex {
2402                                tuple: Box::new(expr),
2403                                index,
2404                                span: self.span.clone(),
2405                            };
2406                        }
2407                        Some(Token::Ident) => {
2408                            let name = self.current_text().unwrap_or("").to_string();
2409                            let field = Ident::new(name, self.span.clone());
2410                            self.advance();
2411                            expr = Expr::FieldAccess {
2412                                object: Box::new(expr),
2413                                field,
2414                                span: self.span.clone(),
2415                            };
2416                        }
2417                        _ => break,
2418                    }
2419                }
2420                Some(Token::LParen) if matches!(expr, Expr::Var { .. }) => {
2421                    // Function call
2422                    if let Expr::Var { name, .. } = expr {
2423                        self.advance();
2424                        let args = self.parse_args();
2425                        expr = Expr::Call {
2426                            name,
2427                            type_args: vec![], // TODO: Parse turbofish
2428                            args,
2429                            span: self.span.clone(),
2430                        };
2431                    }
2432                }
2433                _ => break,
2434            }
2435        }
2436
2437        expr
2438    }
2439
2440    fn parse_primary(&mut self) -> Expr {
2441        match self.current() {
2442            Some(Token::IntLit) => {
2443                let text = self.current_text().unwrap_or("0");
2444                let n = text.parse::<i64>().unwrap_or(0);
2445                self.advance();
2446                Expr::Literal {
2447                    value: Literal::Int(n),
2448                    span: self.span.clone(),
2449                }
2450            }
2451            Some(Token::FloatLit) => {
2452                let text = self.current_text().unwrap_or("0.0");
2453                let f = text.parse::<f64>().unwrap_or(0.0);
2454                self.advance();
2455                Expr::Literal {
2456                    value: Literal::Float(f),
2457                    span: self.span.clone(),
2458                }
2459            }
2460            Some(Token::StringLit) => {
2461                let text = self.current_text().unwrap_or("\"\"");
2462                // Remove surrounding quotes
2463                let s = if text.len() >= 2 {
2464                    text[1..text.len() - 1].to_string()
2465                } else {
2466                    String::new()
2467                };
2468                self.advance();
2469                Expr::Literal {
2470                    value: Literal::String(s),
2471                    span: self.span.clone(),
2472                }
2473            }
2474            Some(Token::KwTrue) => {
2475                self.advance();
2476                Expr::Literal {
2477                    value: Literal::Bool(true),
2478                    span: self.span.clone(),
2479                }
2480            }
2481            Some(Token::KwFalse) => {
2482                self.advance();
2483                Expr::Literal {
2484                    value: Literal::Bool(false),
2485                    span: self.span.clone(),
2486                }
2487            }
2488            Some(Token::Ident) => {
2489                let name = self.current_text().unwrap_or("").to_string();
2490                self.advance();
2491                Expr::Var {
2492                    name: Ident::new(name, self.span.clone()),
2493                    span: self.span.clone(),
2494                }
2495            }
2496            Some(Token::KwSelf) => {
2497                self.advance();
2498                // Handle self.field
2499                if matches!(self.current(), Some(Token::Dot)) {
2500                    self.advance();
2501                    if let Some(Token::Ident) = self.current() {
2502                        let field_name = self.current_text().unwrap_or("").to_string();
2503                        let field = Ident::new(field_name, self.span.clone());
2504                        self.advance();
2505                        return Expr::SelfField {
2506                            field,
2507                            span: self.span.clone(),
2508                        };
2509                    }
2510                }
2511                Expr::Var {
2512                    name: Ident::new("self".to_string(), self.span.clone()),
2513                    span: self.span.clone(),
2514                }
2515            }
2516            Some(Token::LParen) => {
2517                self.advance();
2518                let inner = self.parse_expr();
2519                if matches!(self.current(), Some(Token::RParen)) {
2520                    self.advance();
2521                }
2522                Expr::Paren {
2523                    inner: Box::new(inner),
2524                    span: self.span.clone(),
2525                }
2526            }
2527            Some(Token::LBracket) => {
2528                // List literal
2529                self.advance();
2530                let mut elements = Vec::new();
2531                while !matches!(self.current(), Some(Token::RBracket) | None) {
2532                    elements.push(self.parse_expr());
2533                    if matches!(self.current(), Some(Token::Comma)) {
2534                        self.advance();
2535                    } else {
2536                        break;
2537                    }
2538                }
2539                if matches!(self.current(), Some(Token::RBracket)) {
2540                    self.advance();
2541                }
2542                Expr::List {
2543                    elements,
2544                    span: self.span.clone(),
2545                }
2546            }
2547            _ => {
2548                // Unknown token, return empty string literal as fallback
2549                Expr::Literal {
2550                    value: Literal::String(String::new()),
2551                    span: self.span.clone(),
2552                }
2553            }
2554        }
2555    }
2556
2557    fn parse_args(&mut self) -> Vec<Expr> {
2558        let mut args = Vec::new();
2559        while !matches!(self.current(), Some(Token::RParen) | None) {
2560            args.push(self.parse_expr());
2561            if matches!(self.current(), Some(Token::Comma)) {
2562                self.advance();
2563            } else {
2564                break;
2565            }
2566        }
2567        if matches!(self.current(), Some(Token::RParen)) {
2568            self.advance();
2569        }
2570        args
2571    }
2572}
2573
2574// =============================================================================
2575// Tests
2576// =============================================================================
2577
2578#[cfg(test)]
2579mod tests {
2580    use super::*;
2581    use crate::lex;
2582
2583    fn parse_str(source: &str) -> (Option<Program>, Vec<ParseError>) {
2584        let lex_result = lex(source).expect("lexing should succeed");
2585        let source_arc: Arc<str> = Arc::from(source);
2586        parse(lex_result.tokens(), source_arc)
2587    }
2588
2589    #[test]
2590    fn parse_minimal_program() {
2591        let source = r#"
2592            agent Main {
2593                on start {
2594                    yield(42);
2595                }
2596            }
2597            run Main;
2598        "#;
2599
2600        let (prog, errors) = parse_str(source);
2601        assert!(errors.is_empty(), "errors: {errors:?}");
2602        let prog = prog.expect("should parse");
2603
2604        assert_eq!(prog.agents.len(), 1);
2605        assert_eq!(prog.agents[0].name.name, "Main");
2606        assert_eq!(prog.run_agent.as_ref().unwrap().name, "Main");
2607    }
2608
2609    #[test]
2610    fn parse_agent_with_beliefs() {
2611        let source = r#"
2612            agent Researcher {
2613                topic: String
2614                max_words: Int
2615
2616                on start {
2617                    yield(self.topic);
2618                }
2619            }
2620            run Researcher;
2621        "#;
2622
2623        let (prog, errors) = parse_str(source);
2624        assert!(errors.is_empty(), "errors: {errors:?}");
2625        let prog = prog.expect("should parse");
2626
2627        assert_eq!(prog.agents[0].beliefs.len(), 2);
2628        assert_eq!(prog.agents[0].beliefs[0].name.name, "topic");
2629        assert_eq!(prog.agents[0].beliefs[1].name.name, "max_words");
2630    }
2631
2632    #[test]
2633    fn parse_multiple_handlers() {
2634        let source = r#"
2635            agent Worker {
2636                on start {
2637                    print("started");
2638                }
2639
2640                on message(msg: String) {
2641                    print(msg);
2642                }
2643
2644                on stop {
2645                    print("stopped");
2646                }
2647            }
2648            run Worker;
2649        "#;
2650
2651        let (prog, errors) = parse_str(source);
2652        assert!(errors.is_empty(), "errors: {errors:?}");
2653        let prog = prog.expect("should parse");
2654
2655        assert_eq!(prog.agents[0].handlers.len(), 3);
2656        assert_eq!(prog.agents[0].handlers[0].event, EventKind::Start);
2657        assert!(matches!(
2658            prog.agents[0].handlers[1].event,
2659            EventKind::Message { .. }
2660        ));
2661        assert_eq!(prog.agents[0].handlers[2].event, EventKind::Stop);
2662    }
2663
2664    #[test]
2665    fn parse_function() {
2666        let source = r#"
2667            fn greet(name: String) -> String {
2668                return "Hello, " ++ name;
2669            }
2670
2671            agent Main {
2672                on start {
2673                    yield(greet("World"));
2674                }
2675            }
2676            run Main;
2677        "#;
2678
2679        let (prog, errors) = parse_str(source);
2680        assert!(errors.is_empty(), "errors: {errors:?}");
2681        let prog = prog.expect("should parse");
2682
2683        assert_eq!(prog.functions.len(), 1);
2684        assert_eq!(prog.functions[0].name.name, "greet");
2685        assert_eq!(prog.functions[0].params.len(), 1);
2686    }
2687
2688    #[test]
2689    fn parse_let_statement() {
2690        let source = r#"
2691            agent Main {
2692                on start {
2693                    let x: Int = 42;
2694                    let y = "hello";
2695                    yield(x);
2696                }
2697            }
2698            run Main;
2699        "#;
2700
2701        let (prog, errors) = parse_str(source);
2702        assert!(errors.is_empty(), "errors: {errors:?}");
2703        let prog = prog.expect("should parse");
2704
2705        let stmts = &prog.agents[0].handlers[0].body.stmts;
2706        assert!(matches!(stmts[0], Stmt::Let { .. }));
2707        assert!(matches!(stmts[1], Stmt::Let { .. }));
2708    }
2709
2710    #[test]
2711    fn parse_if_statement() {
2712        let source = r#"
2713            agent Main {
2714                on start {
2715                    if true {
2716                        yield(1);
2717                    } else {
2718                        yield(2);
2719                    }
2720                }
2721            }
2722            run Main;
2723        "#;
2724
2725        let (prog, errors) = parse_str(source);
2726        assert!(errors.is_empty(), "errors: {errors:?}");
2727        let prog = prog.expect("should parse");
2728
2729        let stmts = &prog.agents[0].handlers[0].body.stmts;
2730        assert!(matches!(stmts[0], Stmt::If { .. }));
2731    }
2732
2733    #[test]
2734    fn parse_for_loop() {
2735        let source = r#"
2736            agent Main {
2737                on start {
2738                    for x in [1, 2, 3] {
2739                        print(x);
2740                    }
2741                    yield(0);
2742                }
2743            }
2744            run Main;
2745        "#;
2746
2747        let (prog, errors) = parse_str(source);
2748        assert!(errors.is_empty(), "errors: {errors:?}");
2749        let prog = prog.expect("should parse");
2750
2751        let stmts = &prog.agents[0].handlers[0].body.stmts;
2752        assert!(matches!(stmts[0], Stmt::For { .. }));
2753    }
2754
2755    #[test]
2756    fn parse_spawn_await() {
2757        let source = r#"
2758            agent Worker {
2759                name: String
2760
2761                on start {
2762                    yield(self.name);
2763                }
2764            }
2765
2766            agent Main {
2767                on start {
2768                    let w = summon Worker { name: "test" };
2769                    let result = await w;
2770                    yield(result);
2771                }
2772            }
2773            run Main;
2774        "#;
2775
2776        let (prog, errors) = parse_str(source);
2777        assert!(errors.is_empty(), "errors: {errors:?}");
2778        prog.expect("should parse");
2779    }
2780
2781    #[test]
2782    fn parse_await_with_timeout() {
2783        let source = r#"
2784            agent Worker {
2785                on start {
2786                    yield("done");
2787                }
2788            }
2789
2790            agent Main {
2791                on start {
2792                    let w = summon Worker {};
2793                    let result = await w timeout(5000);
2794                    yield(result);
2795                }
2796            }
2797            run Main;
2798        "#;
2799
2800        let (prog, errors) = parse_str(source);
2801        assert!(errors.is_empty(), "errors: {errors:?}");
2802        let prog = prog.expect("should parse");
2803
2804        // Find the await statement in Main's on start handler
2805        let main = &prog.agents[1];
2806        let stmts = &main.handlers[0].body.stmts;
2807        // stmts[0] is the let w = spawn...
2808        // stmts[1] is the let result = await w timeout(5000)
2809        if let Stmt::Let { value, .. } = &stmts[1] {
2810            if let Expr::Await { timeout, .. } = value {
2811                assert!(timeout.is_some(), "timeout should be present");
2812            } else {
2813                panic!("expected Await expression");
2814            }
2815        } else {
2816            panic!("expected Let statement with value");
2817        }
2818    }
2819
2820    #[test]
2821    fn parse_divine() {
2822        let source = r#"
2823            agent Main {
2824                on start {
2825                    let result = divine("What is 2+2?");
2826                    yield(result);
2827                }
2828            }
2829            run Main;
2830        "#;
2831
2832        let (prog, errors) = parse_str(source);
2833        assert!(errors.is_empty(), "errors: {errors:?}");
2834        prog.expect("should parse");
2835    }
2836
2837    #[test]
2838    fn parse_binary_precedence() {
2839        let source = r#"
2840            agent Main {
2841                on start {
2842                    let x = 2 + 3 * 4;
2843                    yield(x);
2844                }
2845            }
2846            run Main;
2847        "#;
2848
2849        let (prog, errors) = parse_str(source);
2850        assert!(errors.is_empty(), "errors: {errors:?}");
2851        let prog = prog.expect("should parse");
2852
2853        let stmts = &prog.agents[0].handlers[0].body.stmts;
2854        if let Stmt::Let { value, .. } = &stmts[0] {
2855            if let Expr::Binary { op, .. } = value {
2856                assert_eq!(*op, BinOp::Add);
2857            } else {
2858                panic!("expected binary expression");
2859            }
2860        }
2861    }
2862
2863    #[test]
2864    fn parse_string_interpolation() {
2865        let source = r#"
2866            agent Main {
2867                on start {
2868                    let name = "World";
2869                    let msg = divine("Greet {name}");
2870                    yield(msg);
2871                }
2872            }
2873            run Main;
2874        "#;
2875
2876        let (prog, errors) = parse_str(source);
2877        assert!(errors.is_empty(), "errors: {errors:?}");
2878        let prog = prog.expect("should parse");
2879
2880        let stmts = &prog.agents[0].handlers[0].body.stmts;
2881        if let Stmt::Let { value, .. } = &stmts[1] {
2882            if let Expr::Divine { template, .. } = value {
2883                assert!(template.has_interpolations());
2884            } else {
2885                panic!("expected infer expression");
2886            }
2887        }
2888    }
2889
2890    #[test]
2891    fn parse_single_quoted_string() {
2892        let source = r#"
2893            agent Main {
2894                on start {
2895                    let x = 'hello';
2896                    yield(0);
2897                }
2898            }
2899            run Main;
2900        "#;
2901
2902        let (prog, errors) = parse_str(source);
2903        assert!(errors.is_empty(), "errors: {errors:?}");
2904        let prog = prog.expect("should parse");
2905
2906        let stmts = &prog.agents[0].handlers[0].body.stmts;
2907        if let Stmt::Let { value, .. } = &stmts[0] {
2908            if let Expr::Literal {
2909                value: Literal::String(s),
2910                ..
2911            } = value
2912            {
2913                assert_eq!(s, "hello");
2914            } else {
2915                panic!("expected string literal, got {:?}", value);
2916            }
2917        } else {
2918            panic!("expected let statement");
2919        }
2920    }
2921
2922    #[test]
2923    fn parse_single_quoted_string_in_interpolation() {
2924        // Use single quotes for strings inside interpolations to avoid
2925        // conflicts with the outer double-quoted string
2926        let source = r#"
2927            fn reverse(s: String) -> String {
2928                return s;
2929            }
2930            agent Main {
2931                on start {
2932                    print("Result: {reverse('hello')}");
2933                    print("Concat: {'abc' ++ 'def'}");
2934                    yield(0);
2935                }
2936            }
2937            run Main;
2938        "#;
2939
2940        let (prog, errors) = parse_str(source);
2941        assert!(errors.is_empty(), "errors: {errors:?}");
2942        let prog = prog.expect("should parse");
2943
2944        // Verify the string interpolation was parsed
2945        let stmts = &prog.agents[0].handlers[0].body.stmts;
2946        if let Stmt::Expr {
2947            expr: Expr::Call { args, .. },
2948            ..
2949        } = &stmts[0]
2950        {
2951            if let Expr::StringInterp { template, .. } = &args[0] {
2952                assert!(template.has_interpolations());
2953            } else {
2954                panic!("expected string interpolation");
2955            }
2956        } else {
2957            panic!("expected print call");
2958        }
2959    }
2960
2961    // =========================================================================
2962    // Error recovery tests
2963    // =========================================================================
2964
2965    #[test]
2966    fn recover_from_malformed_agent_continues_to_next() {
2967        // First agent has syntax error (missing type after colon), second is valid
2968        let source = r#"
2969            agent Broken {
2970                x:
2971            }
2972
2973            agent Main {
2974                on start {
2975                    yield(42);
2976                }
2977            }
2978            run Main;
2979        "#;
2980
2981        let (prog, errors) = parse_str(source);
2982        // Should have errors from the broken agent
2983        assert!(!errors.is_empty(), "should have parse errors");
2984        // But should still produce a program with the valid agent
2985        let prog = prog.expect("should produce partial AST");
2986        assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
2987    }
2988
2989    #[test]
2990    fn recover_from_mismatched_braces_in_block() {
2991        let source = r#"
2992            agent Main {
2993                on start {
2994                    let x = [1, 2, 3;
2995                    yield(42);
2996                }
2997            }
2998            run Main;
2999        "#;
3000
3001        let (prog, errors) = parse_str(source);
3002        // Should have errors but still produce an AST
3003        assert!(!errors.is_empty(), "should have parse errors");
3004        assert!(prog.is_some(), "should produce partial AST despite errors");
3005    }
3006
3007    #[test]
3008    fn parse_mod_declaration() {
3009        let source = r#"
3010            mod agents;
3011            pub mod utils;
3012
3013            agent Main {
3014                on start {
3015                    yield(42);
3016                }
3017            }
3018            run Main;
3019        "#;
3020
3021        let (prog, errors) = parse_str(source);
3022        assert!(errors.is_empty(), "errors: {errors:?}");
3023        let prog = prog.expect("should parse");
3024
3025        assert_eq!(prog.mod_decls.len(), 2);
3026        assert!(!prog.mod_decls[0].is_pub);
3027        assert_eq!(prog.mod_decls[0].name.name, "agents");
3028        assert!(prog.mod_decls[1].is_pub);
3029        assert_eq!(prog.mod_decls[1].name.name, "utils");
3030    }
3031
3032    #[test]
3033    fn parse_use_simple() {
3034        let source = r#"
3035            use agents::Researcher;
3036
3037            agent Main {
3038                on start {
3039                    yield(42);
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.use_decls.len(), 1);
3050        assert!(!prog.use_decls[0].is_pub);
3051        assert_eq!(prog.use_decls[0].path.len(), 2);
3052        assert_eq!(prog.use_decls[0].path[0].name, "agents");
3053        assert_eq!(prog.use_decls[0].path[1].name, "Researcher");
3054        assert!(matches!(prog.use_decls[0].kind, UseKind::Simple(None)));
3055    }
3056
3057    #[test]
3058    fn parse_use_with_alias() {
3059        let source = r#"
3060            use agents::Researcher as R;
3061
3062            agent Main {
3063                on start {
3064                    yield(42);
3065                }
3066            }
3067            run Main;
3068        "#;
3069
3070        let (prog, errors) = parse_str(source);
3071        assert!(errors.is_empty(), "errors: {errors:?}");
3072        let prog = prog.expect("should parse");
3073
3074        assert_eq!(prog.use_decls.len(), 1);
3075        if let UseKind::Simple(Some(alias)) = &prog.use_decls[0].kind {
3076            assert_eq!(alias.name, "R");
3077        } else {
3078            panic!("expected Simple with alias");
3079        }
3080    }
3081
3082    #[test]
3083    fn parse_pub_agent() {
3084        let source = r#"
3085            pub agent Worker {
3086                on start {
3087                    yield(42);
3088                }
3089            }
3090
3091            agent Main {
3092                on start {
3093                    yield(0);
3094                }
3095            }
3096            run Main;
3097        "#;
3098
3099        let (prog, errors) = parse_str(source);
3100        assert!(errors.is_empty(), "errors: {errors:?}");
3101        let prog = prog.expect("should parse");
3102
3103        assert_eq!(prog.agents.len(), 2);
3104        assert!(prog.agents[0].is_pub);
3105        assert_eq!(prog.agents[0].name.name, "Worker");
3106        assert!(!prog.agents[1].is_pub);
3107    }
3108
3109    #[test]
3110    fn parse_pub_function() {
3111        let source = r#"
3112            pub fn helper(x: Int) -> Int {
3113                return x;
3114            }
3115
3116            agent Main {
3117                on start {
3118                    yield(helper(42));
3119                }
3120            }
3121            run Main;
3122        "#;
3123
3124        let (prog, errors) = parse_str(source);
3125        assert!(errors.is_empty(), "errors: {errors:?}");
3126        let prog = prog.expect("should parse");
3127
3128        assert_eq!(prog.functions.len(), 1);
3129        assert!(prog.functions[0].is_pub);
3130        assert_eq!(prog.functions[0].name.name, "helper");
3131    }
3132
3133    #[test]
3134    fn parse_library_no_run() {
3135        // A library module has no `run` statement
3136        let source = r#"
3137            pub agent Worker {
3138                on start {
3139                    yield(42);
3140                }
3141            }
3142
3143            pub fn helper(x: Int) -> Int {
3144                return x;
3145            }
3146        "#;
3147
3148        let (prog, errors) = parse_str(source);
3149        assert!(errors.is_empty(), "errors: {errors:?}");
3150        let prog = prog.expect("should parse");
3151
3152        assert!(prog.run_agent.is_none());
3153        assert_eq!(prog.agents.len(), 1);
3154        assert_eq!(prog.functions.len(), 1);
3155    }
3156
3157    #[test]
3158    fn recover_multiple_errors_reported() {
3159        // Multiple errors in different places - incomplete field missing type
3160        let source = r#"
3161            agent A {
3162                x:
3163            }
3164
3165            agent Main {
3166                on start {
3167                    yield(42);
3168                }
3169            }
3170            run Main;
3171        "#;
3172
3173        let (prog, errors) = parse_str(source);
3174        // The malformed field is missing its type after `:` so should cause an error
3175        // However, with recovery the valid agent may still parse
3176        // Check that we either have errors or recovered successfully
3177        if errors.is_empty() {
3178            // Recovery succeeded - should have parsed Main agent
3179            let prog = prog.expect("should have AST with recovery");
3180            assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
3181        }
3182        // Either way, the test passes - we're testing recovery works
3183    }
3184
3185    #[test]
3186    fn parse_record_declaration() {
3187        let source = r#"
3188            record Point {
3189                x: Int,
3190                y: Int,
3191            }
3192
3193            agent Main {
3194                on start {
3195                    yield(0);
3196                }
3197            }
3198            run Main;
3199        "#;
3200
3201        let (prog, errors) = parse_str(source);
3202        assert!(errors.is_empty(), "errors: {errors:?}");
3203        let prog = prog.expect("should parse");
3204
3205        assert_eq!(prog.records.len(), 1);
3206        assert!(!prog.records[0].is_pub);
3207        assert_eq!(prog.records[0].name.name, "Point");
3208        assert_eq!(prog.records[0].fields.len(), 2);
3209        assert_eq!(prog.records[0].fields[0].name.name, "x");
3210        assert_eq!(prog.records[0].fields[1].name.name, "y");
3211    }
3212
3213    #[test]
3214    fn parse_pub_record() {
3215        let source = r#"
3216            pub record Config {
3217                host: String,
3218                port: Int,
3219            }
3220
3221            agent Main {
3222                on start { yield(0); }
3223            }
3224            run Main;
3225        "#;
3226
3227        let (prog, errors) = parse_str(source);
3228        assert!(errors.is_empty(), "errors: {errors:?}");
3229        let prog = prog.expect("should parse");
3230
3231        assert_eq!(prog.records.len(), 1);
3232        assert!(prog.records[0].is_pub);
3233        assert_eq!(prog.records[0].name.name, "Config");
3234    }
3235
3236    #[test]
3237    fn parse_enum_declaration() {
3238        let source = r#"
3239            enum Status {
3240                Active,
3241                Pending,
3242                Done,
3243            }
3244
3245            agent Main {
3246                on start {
3247                    yield(0);
3248                }
3249            }
3250            run Main;
3251        "#;
3252
3253        let (prog, errors) = parse_str(source);
3254        assert!(errors.is_empty(), "errors: {errors:?}");
3255        let prog = prog.expect("should parse");
3256
3257        assert_eq!(prog.enums.len(), 1);
3258        assert!(!prog.enums[0].is_pub);
3259        assert_eq!(prog.enums[0].name.name, "Status");
3260        assert_eq!(prog.enums[0].variants.len(), 3);
3261        assert_eq!(prog.enums[0].variants[0].name.name, "Active");
3262        assert_eq!(prog.enums[0].variants[1].name.name, "Pending");
3263        assert_eq!(prog.enums[0].variants[2].name.name, "Done");
3264    }
3265
3266    #[test]
3267    fn parse_pub_enum() {
3268        let source = r#"
3269            pub enum Priority { High, Medium, Low }
3270
3271            agent Main {
3272                on start { yield(0); }
3273            }
3274            run Main;
3275        "#;
3276
3277        let (prog, errors) = parse_str(source);
3278        assert!(errors.is_empty(), "errors: {errors:?}");
3279        let prog = prog.expect("should parse");
3280
3281        assert_eq!(prog.enums.len(), 1);
3282        assert!(prog.enums[0].is_pub);
3283        assert_eq!(prog.enums[0].name.name, "Priority");
3284    }
3285
3286    #[test]
3287    fn parse_const_declaration() {
3288        let source = r#"
3289            const MAX_RETRIES: Int = 3;
3290
3291            agent Main {
3292                on start {
3293                    yield(0);
3294                }
3295            }
3296            run Main;
3297        "#;
3298
3299        let (prog, errors) = parse_str(source);
3300        assert!(errors.is_empty(), "errors: {errors:?}");
3301        let prog = prog.expect("should parse");
3302
3303        assert_eq!(prog.consts.len(), 1);
3304        assert!(!prog.consts[0].is_pub);
3305        assert_eq!(prog.consts[0].name.name, "MAX_RETRIES");
3306        assert!(matches!(prog.consts[0].ty, TypeExpr::Int));
3307    }
3308
3309    #[test]
3310    fn parse_pub_const() {
3311        let source = r#"
3312            pub const API_URL: String = "https://api.example.com";
3313
3314            agent Main {
3315                on start { yield(0); }
3316            }
3317            run Main;
3318        "#;
3319
3320        let (prog, errors) = parse_str(source);
3321        assert!(errors.is_empty(), "errors: {errors:?}");
3322        let prog = prog.expect("should parse");
3323
3324        assert_eq!(prog.consts.len(), 1);
3325        assert!(prog.consts[0].is_pub);
3326        assert_eq!(prog.consts[0].name.name, "API_URL");
3327    }
3328
3329    #[test]
3330    fn parse_multiple_type_declarations() {
3331        let source = r#"
3332            record Point { x: Int, y: Int }
3333            enum Color { Red, Green, Blue }
3334            const ORIGIN_X: Int = 0;
3335
3336            agent Main {
3337                on start { yield(0); }
3338            }
3339            run Main;
3340        "#;
3341
3342        let (prog, errors) = parse_str(source);
3343        assert!(errors.is_empty(), "errors: {errors:?}");
3344        let prog = prog.expect("should parse");
3345
3346        assert_eq!(prog.records.len(), 1);
3347        assert_eq!(prog.enums.len(), 1);
3348        assert_eq!(prog.consts.len(), 1);
3349    }
3350
3351    #[test]
3352    fn parse_match_expression() {
3353        let source = r#"
3354            enum Status { Active, Pending, Done }
3355
3356            agent Main {
3357                on start {
3358                    let s: Int = match Active {
3359                        Active => 1,
3360                        Pending => 2,
3361                        Done => 3,
3362                    };
3363                    yield(s);
3364                }
3365            }
3366            run Main;
3367        "#;
3368
3369        let (prog, errors) = parse_str(source);
3370        assert!(errors.is_empty(), "errors: {errors:?}");
3371        let prog = prog.expect("should parse");
3372
3373        // Check the agent parsed
3374        assert_eq!(prog.agents.len(), 1);
3375        // Match is in the handler
3376        let handler = &prog.agents[0].handlers[0];
3377        let stmt = &handler.body.stmts[0];
3378        if let Stmt::Let { value, .. } = stmt {
3379            assert!(matches!(value, Expr::Match { .. }));
3380        } else {
3381            panic!("expected let statement with match");
3382        }
3383    }
3384
3385    #[test]
3386    fn parse_match_with_wildcard() {
3387        let source = r#"
3388            agent Main {
3389                on start {
3390                    let x = 5;
3391                    let result = match x {
3392                        1 => 10,
3393                        2 => 20,
3394                        _ => 0,
3395                    };
3396                    yield(result);
3397                }
3398            }
3399            run Main;
3400        "#;
3401
3402        let (prog, errors) = parse_str(source);
3403        assert!(errors.is_empty(), "errors: {errors:?}");
3404        let prog = prog.expect("should parse");
3405
3406        assert_eq!(prog.agents.len(), 1);
3407    }
3408
3409    #[test]
3410    fn parse_record_construction() {
3411        let source = r#"
3412            record Point { x: Int, y: Int }
3413
3414            agent Main {
3415                on start {
3416                    let p = Point { x: 10, y: 20 };
3417                    yield(0);
3418                }
3419            }
3420            run Main;
3421        "#;
3422
3423        let (prog, errors) = parse_str(source);
3424        assert!(errors.is_empty(), "errors: {errors:?}");
3425        let prog = prog.expect("should parse");
3426
3427        assert_eq!(prog.records.len(), 1);
3428        assert_eq!(prog.agents.len(), 1);
3429
3430        // Check the let statement has a record construction
3431        let handler = &prog.agents[0].handlers[0];
3432        let stmt = &handler.body.stmts[0];
3433        if let Stmt::Let { value, .. } = stmt {
3434            if let Expr::RecordConstruct { name, fields, .. } = value {
3435                assert_eq!(name.name, "Point");
3436                assert_eq!(fields.len(), 2);
3437                assert_eq!(fields[0].name.name, "x");
3438                assert_eq!(fields[1].name.name, "y");
3439            } else {
3440                panic!("expected RecordConstruct");
3441            }
3442        } else {
3443            panic!("expected let statement");
3444        }
3445    }
3446
3447    #[test]
3448    fn parse_match_with_qualified_variant() {
3449        let source = r#"
3450            enum Status { Active, Pending }
3451
3452            fn get_status() -> Int {
3453                return 1;
3454            }
3455
3456            agent Main {
3457                on start {
3458                    let s = get_status();
3459                    let result = match s {
3460                        Status::Active => 1,
3461                        Status::Pending => 0,
3462                    };
3463                    yield(result);
3464                }
3465            }
3466            run Main;
3467        "#;
3468
3469        let (prog, errors) = parse_str(source);
3470        assert!(errors.is_empty(), "errors: {errors:?}");
3471        let prog = prog.expect("should parse");
3472
3473        assert_eq!(prog.enums.len(), 1);
3474        assert_eq!(prog.agents.len(), 1);
3475    }
3476
3477    #[test]
3478    fn parse_field_access() {
3479        let source = r#"
3480            record Point { x: Int, y: Int }
3481
3482            agent Main {
3483                on start {
3484                    let p = Point { x: 10, y: 20 };
3485                    let x_val = p.x;
3486                    let y_val = p.y;
3487                    yield(x_val);
3488                }
3489            }
3490            run Main;
3491        "#;
3492
3493        let (prog, errors) = parse_str(source);
3494        assert!(errors.is_empty(), "errors: {errors:?}");
3495        let prog = prog.expect("should parse");
3496
3497        assert_eq!(prog.records.len(), 1);
3498        assert_eq!(prog.agents.len(), 1);
3499
3500        // Check the field access
3501        let handler = &prog.agents[0].handlers[0];
3502        let stmt = &handler.body.stmts[1]; // p.x assignment
3503        if let Stmt::Let { value, .. } = stmt {
3504            if let Expr::FieldAccess { field, .. } = value {
3505                assert_eq!(field.name, "x");
3506            } else {
3507                panic!("expected FieldAccess");
3508            }
3509        } else {
3510            panic!("expected let statement");
3511        }
3512    }
3513
3514    #[test]
3515    fn parse_chained_field_access() {
3516        let source = r#"
3517            record Inner { val: Int }
3518            record Outer { inner: Inner }
3519
3520            agent Main {
3521                on start {
3522                    let inner = Inner { val: 42 };
3523                    let outer = Outer { inner: inner };
3524                    let v = outer.inner.val;
3525                    yield(v);
3526                }
3527            }
3528            run Main;
3529        "#;
3530
3531        let (prog, errors) = parse_str(source);
3532        assert!(errors.is_empty(), "errors: {errors:?}");
3533        let prog = prog.expect("should parse");
3534
3535        assert_eq!(prog.records.len(), 2);
3536        assert_eq!(prog.agents.len(), 1);
3537
3538        // Check the chained field access: outer.inner.val
3539        let handler = &prog.agents[0].handlers[0];
3540        let stmt = &handler.body.stmts[2]; // outer.inner.val assignment
3541        if let Stmt::Let { value, .. } = stmt {
3542            if let Expr::FieldAccess {
3543                object, field: val, ..
3544            } = value
3545            {
3546                assert_eq!(val.name, "val");
3547                // object should be outer.inner
3548                if let Expr::FieldAccess { field: inner, .. } = object.as_ref() {
3549                    assert_eq!(inner.name, "inner");
3550                } else {
3551                    panic!("expected nested FieldAccess");
3552                }
3553            } else {
3554                panic!("expected FieldAccess");
3555            }
3556        } else {
3557            panic!("expected let statement");
3558        }
3559    }
3560
3561    // =========================================================================
3562    // RFC-0006: Message passing tests
3563    // =========================================================================
3564
3565    #[test]
3566    fn parse_loop_break() {
3567        let source = r#"
3568            agent Main {
3569                on start {
3570                    let count = 0;
3571                    loop {
3572                        count = count + 1;
3573                        if count > 5 {
3574                            break;
3575                        }
3576                    }
3577                    yield(count);
3578                }
3579            }
3580            run Main;
3581        "#;
3582
3583        let (prog, errors) = parse_str(source);
3584        assert!(errors.is_empty(), "errors: {errors:?}");
3585        let prog = prog.expect("should parse");
3586
3587        assert_eq!(prog.agents.len(), 1);
3588        let handler = &prog.agents[0].handlers[0];
3589        // Check loop statement exists
3590        let loop_stmt = &handler.body.stmts[1];
3591        assert!(matches!(loop_stmt, Stmt::Loop { .. }));
3592        // Check break is inside the loop
3593        if let Stmt::Loop { body, .. } = loop_stmt {
3594            let if_stmt = &body.stmts[1];
3595            if let Stmt::If { then_block, .. } = if_stmt {
3596                assert!(matches!(then_block.stmts[0], Stmt::Break { .. }));
3597            } else {
3598                panic!("expected if statement");
3599            }
3600        }
3601    }
3602
3603    #[test]
3604    fn parse_agent_receives() {
3605        let source = r#"
3606            enum WorkerMsg {
3607                Task,
3608                Shutdown,
3609            }
3610
3611            agent Worker receives WorkerMsg {
3612                id: Int
3613
3614                on start {
3615                    yield(0);
3616                }
3617            }
3618
3619            agent Main {
3620                on start {
3621                    yield(0);
3622                }
3623            }
3624            run Main;
3625        "#;
3626
3627        let (prog, errors) = parse_str(source);
3628        assert!(errors.is_empty(), "errors: {errors:?}");
3629        let prog = prog.expect("should parse");
3630
3631        assert_eq!(prog.agents.len(), 2);
3632
3633        // Worker should have receives clause
3634        let worker = &prog.agents[0];
3635        assert_eq!(worker.name.name, "Worker");
3636        assert!(worker.receives.is_some());
3637        if let Some(TypeExpr::Named(name, _)) = &worker.receives {
3638            assert_eq!(name.name, "WorkerMsg");
3639        } else {
3640            panic!("expected named type for receives");
3641        }
3642
3643        // Main should not have receives
3644        let main = &prog.agents[1];
3645        assert_eq!(main.name.name, "Main");
3646        assert!(main.receives.is_none());
3647    }
3648
3649    #[test]
3650    fn parse_receive_expression() {
3651        let source = r#"
3652            enum Msg { Ping }
3653
3654            agent Worker receives Msg {
3655                on start {
3656                    let msg = receive();
3657                    yield(0);
3658                }
3659            }
3660
3661            agent Main {
3662                on start { yield(0); }
3663            }
3664            run Main;
3665        "#;
3666
3667        let (prog, errors) = parse_str(source);
3668        assert!(errors.is_empty(), "errors: {errors:?}");
3669        let prog = prog.expect("should parse");
3670
3671        // Find Worker agent
3672        let worker = prog
3673            .agents
3674            .iter()
3675            .find(|a| a.name.name == "Worker")
3676            .unwrap();
3677        let handler = &worker.handlers[0];
3678        let stmt = &handler.body.stmts[0];
3679
3680        if let Stmt::Let { value, .. } = stmt {
3681            assert!(matches!(value, Expr::Receive { .. }));
3682        } else {
3683            panic!("expected let with receive");
3684        }
3685    }
3686
3687    #[test]
3688    fn parse_message_passing_full() {
3689        let source = r#"
3690            enum WorkerMsg {
3691                Task,
3692                Shutdown,
3693            }
3694
3695            agent Worker receives WorkerMsg {
3696                id: Int
3697
3698                on start {
3699                    let msg = receive();
3700                    let result = match msg {
3701                        Task => 1,
3702                        Shutdown => 0,
3703                    };
3704                    yield(result);
3705                }
3706            }
3707
3708            agent Main {
3709                on start {
3710                    let w = summon Worker { id: 1 };
3711                    send(w, Task);
3712                    send(w, Shutdown);
3713                    await w;
3714                    yield(0);
3715                }
3716            }
3717            run Main;
3718        "#;
3719
3720        let (prog, errors) = parse_str(source);
3721        assert!(errors.is_empty(), "errors: {errors:?}");
3722        let prog = prog.expect("should parse");
3723
3724        assert_eq!(prog.enums.len(), 1);
3725        assert_eq!(prog.agents.len(), 2);
3726
3727        // Check Worker has receives
3728        let worker = prog
3729            .agents
3730            .iter()
3731            .find(|a| a.name.name == "Worker")
3732            .unwrap();
3733        assert!(worker.receives.is_some());
3734    }
3735
3736    // =========================================================================
3737    // RFC-0007: Error handling tests
3738    // =========================================================================
3739
3740    #[test]
3741    fn parse_fallible_function() {
3742        let source = r#"
3743            fn get_data(url: String) -> String fails {
3744                return divine("Get data from {url}" -> String);
3745            }
3746
3747            agent Main {
3748                on start { yield(0); }
3749            }
3750            run Main;
3751        "#;
3752
3753        let (prog, errors) = parse_str(source);
3754        assert!(errors.is_empty(), "errors: {errors:?}");
3755        let prog = prog.expect("should parse");
3756
3757        assert_eq!(prog.functions.len(), 1);
3758        assert!(prog.functions[0].is_fallible);
3759    }
3760
3761    #[test]
3762    fn parse_try_expression() {
3763        let source = r#"
3764            fn fallible() -> Int fails { return 42; }
3765
3766            agent Main {
3767                on start {
3768                    let x = try fallible();
3769                    yield(x);
3770                }
3771            }
3772            run Main;
3773        "#;
3774
3775        let (prog, errors) = parse_str(source);
3776        assert!(errors.is_empty(), "errors: {errors:?}");
3777        let prog = prog.expect("should parse");
3778
3779        // Find the let statement and check it contains a Try expression
3780        let handler = &prog.agents[0].handlers[0];
3781        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3782            assert!(matches!(value, Expr::Try { .. }));
3783        } else {
3784            panic!("expected Let statement");
3785        }
3786    }
3787
3788    #[test]
3789    fn parse_catch_expression() {
3790        let source = r#"
3791            fn fallible() -> Int fails { return 42; }
3792
3793            agent Main {
3794                on start {
3795                    let x = fallible() catch { 0 };
3796                    yield(x);
3797                }
3798            }
3799            run Main;
3800        "#;
3801
3802        let (prog, errors) = parse_str(source);
3803        assert!(errors.is_empty(), "errors: {errors:?}");
3804        let prog = prog.expect("should parse");
3805
3806        // Find the let statement and check it contains a Catch expression
3807        let handler = &prog.agents[0].handlers[0];
3808        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3809            if let Expr::Catch { error_bind, .. } = value {
3810                assert!(error_bind.is_none());
3811            } else {
3812                panic!("expected Catch expression");
3813            }
3814        } else {
3815            panic!("expected Let statement");
3816        }
3817    }
3818
3819    #[test]
3820    fn parse_catch_with_error_binding() {
3821        let source = r#"
3822            fn fallible() -> Int fails { return 42; }
3823
3824            agent Main {
3825                on start {
3826                    let x = fallible() catch(e) { 0 };
3827                    yield(x);
3828                }
3829            }
3830            run Main;
3831        "#;
3832
3833        let (prog, errors) = parse_str(source);
3834        assert!(errors.is_empty(), "errors: {errors:?}");
3835        let prog = prog.expect("should parse");
3836
3837        // Find the let statement and check it contains a Catch expression with binding
3838        let handler = &prog.agents[0].handlers[0];
3839        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3840            if let Expr::Catch { error_bind, .. } = value {
3841                assert!(error_bind.is_some());
3842                assert_eq!(error_bind.as_ref().unwrap().name, "e");
3843            } else {
3844                panic!("expected Catch expression");
3845            }
3846        } else {
3847            panic!("expected Let statement");
3848        }
3849    }
3850
3851    #[test]
3852    fn parse_fail_expression() {
3853        let source = r#"
3854            agent Main {
3855                on start {
3856                    fail "something went wrong";
3857                }
3858                on error(e) {
3859                    yield(0);
3860                }
3861            }
3862            run Main;
3863        "#;
3864
3865        let (prog, errors) = parse_str(source);
3866        assert!(errors.is_empty(), "errors: {errors:?}");
3867        let prog = prog.expect("should parse");
3868
3869        // Find the fail statement
3870        let handler = &prog.agents[0].handlers[0];
3871        if let Stmt::Expr { expr, .. } = &handler.body.stmts[0] {
3872            if let Expr::Fail { error, .. } = expr {
3873                assert!(matches!(**error, Expr::Literal { .. }));
3874            } else {
3875                panic!("expected Fail expression, got {expr:?}");
3876            }
3877        } else {
3878            panic!("expected Expr statement");
3879        }
3880    }
3881
3882    #[test]
3883    fn parse_retry_expression() {
3884        let source = r#"
3885            agent Main {
3886                topic: String
3887
3888                on start {
3889                    let result = retry(3) {
3890                        try divine("Summarize: {self.topic}")
3891                    } catch { "fallback" };
3892                    yield(result);
3893                }
3894
3895                on error(e) {
3896                    yield("");
3897                }
3898            }
3899            run Main;
3900        "#;
3901
3902        let (prog, errors) = parse_str(source);
3903        assert!(errors.is_empty(), "errors: {errors:?}");
3904        let prog = prog.expect("should parse");
3905
3906        // Find the let statement with retry
3907        let handler = &prog.agents[0].handlers[0];
3908        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3909            // Should be Catch wrapping Retry
3910            if let Expr::Catch { expr, .. } = value {
3911                if let Expr::Retry { count, delay, .. } = expr.as_ref() {
3912                    assert!(matches!(**count, Expr::Literal { .. }));
3913                    assert!(delay.is_none());
3914                } else {
3915                    panic!("expected Retry expression");
3916                }
3917            } else {
3918                panic!("expected Catch expression");
3919            }
3920        } else {
3921            panic!("expected Let statement");
3922        }
3923    }
3924
3925    #[test]
3926    fn parse_retry_with_delay() {
3927        let source = r#"
3928            agent Main {
3929                on start {
3930                    let result = retry(3, delay: 1000) {
3931                        42
3932                    } catch { 0 };
3933                    yield(result);
3934                }
3935            }
3936            run Main;
3937        "#;
3938
3939        let (prog, errors) = parse_str(source);
3940        assert!(errors.is_empty(), "errors: {errors:?}");
3941        let prog = prog.expect("should parse");
3942
3943        // Find the let statement with retry
3944        let handler = &prog.agents[0].handlers[0];
3945        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3946            if let Expr::Catch { expr, .. } = value {
3947                if let Expr::Retry { delay, .. } = expr.as_ref() {
3948                    assert!(delay.is_some());
3949                } else {
3950                    panic!("expected Retry expression");
3951                }
3952            } else {
3953                panic!("expected Catch expression");
3954            }
3955        } else {
3956            panic!("expected Let statement");
3957        }
3958    }
3959
3960    #[test]
3961    fn parse_on_error_handler() {
3962        let source = r#"
3963            agent Main {
3964                on start {
3965                    yield(0);
3966                }
3967
3968                on error(e) {
3969                    yield(1);
3970                }
3971            }
3972            run Main;
3973        "#;
3974
3975        let (prog, errors) = parse_str(source);
3976        assert!(errors.is_empty(), "errors: {errors:?}");
3977        let prog = prog.expect("should parse");
3978
3979        assert_eq!(prog.agents.len(), 1);
3980        assert_eq!(prog.agents[0].handlers.len(), 2);
3981
3982        // Check the error handler
3983        let error_handler = prog.agents[0]
3984            .handlers
3985            .iter()
3986            .find(|h| matches!(h.event, EventKind::Error { .. }));
3987        assert!(error_handler.is_some());
3988
3989        if let EventKind::Error { param_name } = &error_handler.unwrap().event {
3990            assert_eq!(param_name.name, "e");
3991        } else {
3992            panic!("expected Error event kind");
3993        }
3994    }
3995
3996    // =========================================================================
3997    // RFC-0009: Closures and function types
3998    // =========================================================================
3999
4000    #[test]
4001    fn parse_fn_type() {
4002        let source = r#"
4003            fn apply(f: Fn(Int) -> Int, x: Int) -> Int {
4004                return f(x);
4005            }
4006
4007            agent Main {
4008                on start {
4009                    yield(0);
4010                }
4011            }
4012            run Main;
4013        "#;
4014
4015        let (prog, errors) = parse_str(source);
4016        assert!(errors.is_empty(), "errors: {errors:?}");
4017        let prog = prog.expect("should parse");
4018
4019        assert_eq!(prog.functions.len(), 1);
4020        let func = &prog.functions[0];
4021        assert_eq!(func.name.name, "apply");
4022        assert_eq!(func.params.len(), 2);
4023
4024        // Check first param is Fn(Int) -> Int
4025        if let TypeExpr::Fn(params, ret) = &func.params[0].ty {
4026            assert_eq!(params.len(), 1);
4027            assert!(matches!(params[0], TypeExpr::Int));
4028            assert!(matches!(ret.as_ref(), TypeExpr::Int));
4029        } else {
4030            panic!("expected Fn type for first param");
4031        }
4032    }
4033
4034    #[test]
4035    fn parse_closure_with_params() {
4036        let source = r#"
4037            agent Main {
4038                on start {
4039                    let f = |x: Int| x + 1;
4040                    yield(0);
4041                }
4042            }
4043            run Main;
4044        "#;
4045
4046        let (prog, errors) = parse_str(source);
4047        assert!(errors.is_empty(), "errors: {errors:?}");
4048        let prog = prog.expect("should parse");
4049
4050        // Find the let statement in the on start handler
4051        let handler = &prog.agents[0].handlers[0];
4052        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4053            if let Expr::Closure { params, body, .. } = value {
4054                assert_eq!(params.len(), 1);
4055                assert_eq!(params[0].name.name, "x");
4056                assert!(matches!(&params[0].ty, Some(TypeExpr::Int)));
4057
4058                // Body should be a binary expression
4059                assert!(matches!(body.as_ref(), Expr::Binary { .. }));
4060            } else {
4061                panic!("expected closure expression");
4062            }
4063        } else {
4064            panic!("expected let statement");
4065        }
4066    }
4067
4068    #[test]
4069    fn parse_closure_empty_params() {
4070        let source = r#"
4071            agent Main {
4072                on start {
4073                    let f = || 42;
4074                    yield(0);
4075                }
4076            }
4077            run Main;
4078        "#;
4079
4080        let (prog, errors) = parse_str(source);
4081        assert!(errors.is_empty(), "errors: {errors:?}");
4082        let prog = prog.expect("should parse");
4083
4084        // Find the let statement
4085        let handler = &prog.agents[0].handlers[0];
4086        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4087            if let Expr::Closure { params, body, .. } = value {
4088                assert!(params.is_empty());
4089
4090                // Body should be a literal
4091                assert!(matches!(body.as_ref(), Expr::Literal { .. }));
4092            } else {
4093                panic!("expected closure expression");
4094            }
4095        } else {
4096            panic!("expected let statement");
4097        }
4098    }
4099
4100    #[test]
4101    fn parse_closure_multiple_params() {
4102        let source = r#"
4103            agent Main {
4104                on start {
4105                    let add = |x: Int, y: Int| x + y;
4106                    yield(0);
4107                }
4108            }
4109            run Main;
4110        "#;
4111
4112        let (prog, errors) = parse_str(source);
4113        assert!(errors.is_empty(), "errors: {errors:?}");
4114        let prog = prog.expect("should parse");
4115
4116        let handler = &prog.agents[0].handlers[0];
4117        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4118            if let Expr::Closure { params, .. } = value {
4119                assert_eq!(params.len(), 2);
4120                assert_eq!(params[0].name.name, "x");
4121                assert_eq!(params[1].name.name, "y");
4122            } else {
4123                panic!("expected closure expression");
4124            }
4125        } else {
4126            panic!("expected let statement");
4127        }
4128    }
4129
4130    #[test]
4131    fn parse_fn_type_multiarg() {
4132        let source = r#"
4133            fn fold_left(f: Fn(Int, Int) -> Int, init: Int) -> Int {
4134                return init;
4135            }
4136
4137            agent Main {
4138                on start {
4139                    yield(0);
4140                }
4141            }
4142            run Main;
4143        "#;
4144
4145        let (prog, errors) = parse_str(source);
4146        assert!(errors.is_empty(), "errors: {errors:?}");
4147        let prog = prog.expect("should parse");
4148
4149        // Check Fn(Int, Int) -> Int
4150        if let TypeExpr::Fn(params, ret) = &prog.functions[0].params[0].ty {
4151            assert_eq!(params.len(), 2);
4152            assert!(matches!(params[0], TypeExpr::Int));
4153            assert!(matches!(params[1], TypeExpr::Int));
4154            assert!(matches!(ret.as_ref(), TypeExpr::Int));
4155        } else {
4156            panic!("expected Fn type");
4157        }
4158    }
4159
4160    #[test]
4161    fn parse_tuple_literal() {
4162        let source = r#"
4163            agent Main {
4164                on start {
4165                    let t = (1, 2);
4166                    yield(0);
4167                }
4168            }
4169            run Main;
4170        "#;
4171
4172        let (prog, errors) = parse_str(source);
4173        assert!(errors.is_empty(), "errors: {errors:?}");
4174        let prog = prog.expect("should parse");
4175
4176        let handler = &prog.agents[0].handlers[0];
4177        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4178            if let Expr::Tuple { elements, .. } = value {
4179                assert_eq!(elements.len(), 2);
4180            } else {
4181                panic!("expected tuple expression, got {:?}", value);
4182            }
4183        } else {
4184            panic!("expected let statement");
4185        }
4186    }
4187
4188    // =========================================================================
4189    // RFC-0011: Tool support tests
4190    // =========================================================================
4191
4192    #[test]
4193    fn parse_tool_declaration() {
4194        let source = r#"
4195            tool Http {
4196                fn get(url: String) -> Result<String, String>
4197                fn post(url: String, body: String) -> Result<String, String>
4198            }
4199            agent Main {
4200                on start { yield(0); }
4201            }
4202            run Main;
4203        "#;
4204
4205        let (prog, errors) = parse_str(source);
4206        assert!(errors.is_empty(), "errors: {errors:?}");
4207        let prog = prog.expect("should parse");
4208
4209        assert_eq!(prog.tools.len(), 1);
4210        assert_eq!(prog.tools[0].name.name, "Http");
4211        assert_eq!(prog.tools[0].functions.len(), 2);
4212        assert_eq!(prog.tools[0].functions[0].name.name, "get");
4213        assert_eq!(prog.tools[0].functions[1].name.name, "post");
4214    }
4215
4216    #[test]
4217    fn parse_pub_tool_declaration() {
4218        let source = r#"
4219            pub tool Database {
4220                fn query(sql: String) -> Result<List<String>, String>
4221            }
4222            agent Main {
4223                on start { yield(0); }
4224            }
4225            run Main;
4226        "#;
4227
4228        let (prog, errors) = parse_str(source);
4229        assert!(errors.is_empty(), "errors: {errors:?}");
4230        let prog = prog.expect("should parse");
4231
4232        assert!(prog.tools[0].is_pub);
4233        assert_eq!(prog.tools[0].name.name, "Database");
4234    }
4235
4236    #[test]
4237    fn parse_agent_with_tool_use() {
4238        let source = r#"
4239            agent Fetcher {
4240                use Http
4241
4242                url: String
4243
4244                on start {
4245                    yield(0);
4246                }
4247            }
4248            run Fetcher;
4249        "#;
4250
4251        let (prog, errors) = parse_str(source);
4252        assert!(errors.is_empty(), "errors: {errors:?}");
4253        let prog = prog.expect("should parse");
4254
4255        assert_eq!(prog.agents[0].tool_uses.len(), 1);
4256        assert_eq!(prog.agents[0].tool_uses[0].name, "Http");
4257        assert_eq!(prog.agents[0].beliefs.len(), 1);
4258    }
4259
4260    #[test]
4261    fn parse_agent_with_multiple_tool_uses() {
4262        let source = r#"
4263            agent Pipeline {
4264                use Http, Fs
4265
4266                on start {
4267                    yield(0);
4268                }
4269            }
4270            run Pipeline;
4271        "#;
4272
4273        let (prog, errors) = parse_str(source);
4274        assert!(errors.is_empty(), "errors: {errors:?}");
4275        let prog = prog.expect("should parse");
4276
4277        assert_eq!(prog.agents[0].tool_uses.len(), 2);
4278        assert_eq!(prog.agents[0].tool_uses[0].name, "Http");
4279        assert_eq!(prog.agents[0].tool_uses[1].name, "Fs");
4280    }
4281
4282    #[test]
4283    fn parse_tool_call_expression() {
4284        let source = r#"
4285            agent Fetcher {
4286                use Http
4287
4288                on start {
4289                    let response = Http.get("https://example.com");
4290                    yield(0);
4291                }
4292            }
4293            run Fetcher;
4294        "#;
4295
4296        let (prog, errors) = parse_str(source);
4297        assert!(errors.is_empty(), "errors: {errors:?}");
4298        let prog = prog.expect("should parse");
4299
4300        let handler = &prog.agents[0].handlers[0];
4301        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4302            if let Expr::ToolCall {
4303                tool,
4304                function,
4305                args,
4306                ..
4307            } = value
4308            {
4309                assert_eq!(tool.name, "Http");
4310                assert_eq!(function.name, "get");
4311                assert_eq!(args.len(), 1);
4312            } else {
4313                panic!("expected ToolCall expression, got {:?}", value);
4314            }
4315        } else {
4316            panic!("expected let statement");
4317        }
4318    }
4319
4320    #[test]
4321    fn parse_tool_call_with_multiple_args() {
4322        let source = r#"
4323            agent Writer {
4324                use Fs
4325
4326                on start {
4327                    let result = Fs.write("/tmp/test.txt", "hello world");
4328                    yield(0);
4329                }
4330            }
4331            run Writer;
4332        "#;
4333
4334        let (prog, errors) = parse_str(source);
4335        assert!(errors.is_empty(), "errors: {errors:?}");
4336        let prog = prog.expect("should parse");
4337
4338        let handler = &prog.agents[0].handlers[0];
4339        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4340            if let Expr::ToolCall { args, .. } = value {
4341                assert_eq!(args.len(), 2);
4342            } else {
4343                panic!("expected ToolCall expression, got {:?}", value);
4344            }
4345        } else {
4346            panic!("expected let statement");
4347        }
4348    }
4349
4350    #[test]
4351    fn parse_string_interp_with_field_access() {
4352        let source = r#"
4353            record Person { name: String }
4354            agent Main {
4355                on start {
4356                    let p = Person { name: "Alice" };
4357                    print("Hello, {p.name}!");
4358                    yield(0);
4359                }
4360            }
4361            run Main;
4362        "#;
4363
4364        let (prog, errors) = parse_str(source);
4365        assert!(errors.is_empty(), "errors: {errors:?}");
4366        let prog = prog.expect("should parse");
4367
4368        // Find the print statement with interpolation
4369        let handler = &prog.agents[0].handlers[0];
4370        if let Stmt::Expr { expr, .. } = &handler.body.stmts[1] {
4371            if let Expr::Call { args, .. } = expr {
4372                if let Expr::StringInterp { template, .. } = &args[0] {
4373                    assert!(template.has_interpolations());
4374                    let interps: Vec<_> = template.interpolations().collect();
4375                    assert_eq!(interps.len(), 1);
4376                    // Should be a field access: p.name
4377                    match interps[0] {
4378                        Expr::FieldAccess { object, field, .. } => {
4379                            if let Expr::Var { name, .. } = object.as_ref() {
4380                                assert_eq!(name.name, "p");
4381                            } else {
4382                                panic!("expected Var as base");
4383                            }
4384                            assert_eq!(field.name, "name");
4385                        }
4386                        _ => panic!("expected FieldAccess, got {:?}", interps[0]),
4387                    }
4388                } else {
4389                    panic!("expected StringInterp");
4390                }
4391            } else {
4392                panic!("expected Call");
4393            }
4394        } else {
4395            panic!("expected Expr statement");
4396        }
4397    }
4398
4399    #[test]
4400    fn parse_string_interp_with_tuple_index() {
4401        let source = r#"
4402            agent Main {
4403                on start {
4404                    let pair = (1, 2);
4405                    print("First: {pair.0}");
4406                    yield(0);
4407                }
4408            }
4409            run Main;
4410        "#;
4411
4412        let (prog, errors) = parse_str(source);
4413        assert!(errors.is_empty(), "errors: {errors:?}");
4414        let prog = prog.expect("should parse");
4415
4416        let handler = &prog.agents[0].handlers[0];
4417        if let Stmt::Expr { expr, .. } = &handler.body.stmts[1] {
4418            if let Expr::Call { args, .. } = expr {
4419                if let Expr::StringInterp { template, .. } = &args[0] {
4420                    let interps: Vec<_> = template.interpolations().collect();
4421                    assert_eq!(interps.len(), 1);
4422                    match interps[0] {
4423                        Expr::TupleIndex { tuple, index, .. } => {
4424                            if let Expr::Var { name, .. } = tuple.as_ref() {
4425                                assert_eq!(name.name, "pair");
4426                            } else {
4427                                panic!("expected Var as tuple base");
4428                            }
4429                            assert_eq!(*index, 0);
4430                        }
4431                        _ => panic!("expected TupleIndex, got {:?}", interps[0]),
4432                    }
4433                } else {
4434                    panic!("expected StringInterp");
4435                }
4436            } else {
4437                panic!("expected Call");
4438            }
4439        } else {
4440            panic!("expected Expr statement");
4441        }
4442    }
4443
4444    #[test]
4445    fn parse_mock_tool_with_fail() {
4446        let source = r#"
4447            test "mock tool fail" {
4448                mock tool Http.get -> fail("network error");
4449            }
4450        "#;
4451
4452        let (prog, errors) = parse_str(source);
4453        assert!(errors.is_empty(), "errors: {errors:?}");
4454        let prog = prog.expect("should parse");
4455
4456        let test = &prog.tests[0];
4457        assert_eq!(test.body.stmts.len(), 1);
4458
4459        if let Stmt::MockTool {
4460            tool_name,
4461            fn_name,
4462            value,
4463            ..
4464        } = &test.body.stmts[0]
4465        {
4466            assert_eq!(tool_name.name, "Http");
4467            assert_eq!(fn_name.name, "get");
4468            assert!(
4469                matches!(value, MockValue::Fail(_)),
4470                "expected MockValue::Fail, got {:?}",
4471                value
4472            );
4473        } else {
4474            panic!("expected MockTool statement, got {:?}", test.body.stmts[0]);
4475        }
4476    }
4477
4478    #[test]
4479    fn parse_mock_tool_with_value() {
4480        let source = r#"
4481            test "mock tool value" {
4482                mock tool Http.get -> "response";
4483            }
4484        "#;
4485
4486        let (prog, errors) = parse_str(source);
4487        assert!(errors.is_empty(), "errors: {errors:?}");
4488        let prog = prog.expect("should parse");
4489
4490        let test = &prog.tests[0];
4491        if let Stmt::MockTool { value, .. } = &test.body.stmts[0] {
4492            assert!(
4493                matches!(value, MockValue::Value(_)),
4494                "expected MockValue::Value, got {:?}",
4495                value
4496            );
4497        } else {
4498            panic!("expected MockTool statement");
4499        }
4500    }
4501
4502    // =========================================================================
4503    // RFC-0015: Generics tests
4504    // =========================================================================
4505
4506    #[test]
4507    fn parse_generic_function() {
4508        let source = r#"
4509            fn identity<T>(x: T) -> T {
4510                return x;
4511            }
4512
4513            agent Main {
4514                on start { yield(0); }
4515            }
4516            run Main;
4517        "#;
4518
4519        let (prog, errors) = parse_str(source);
4520        assert!(errors.is_empty(), "errors: {errors:?}");
4521        let prog = prog.expect("should parse");
4522
4523        assert_eq!(prog.functions.len(), 1);
4524        let func = &prog.functions[0];
4525        assert_eq!(func.name.name, "identity");
4526        assert_eq!(func.type_params.len(), 1);
4527        assert_eq!(func.type_params[0].name, "T");
4528    }
4529
4530    #[test]
4531    fn parse_generic_function_multiple_params() {
4532        let source = r#"
4533            fn map<T, U>(list: List<T>, f: Fn(T) -> U) -> List<U> {
4534                return [];
4535            }
4536
4537            agent Main {
4538                on start { yield(0); }
4539            }
4540            run Main;
4541        "#;
4542
4543        let (prog, errors) = parse_str(source);
4544        assert!(errors.is_empty(), "errors: {errors:?}");
4545        let prog = prog.expect("should parse");
4546
4547        let func = &prog.functions[0];
4548        assert_eq!(func.name.name, "map");
4549        assert_eq!(func.type_params.len(), 2);
4550        assert_eq!(func.type_params[0].name, "T");
4551        assert_eq!(func.type_params[1].name, "U");
4552    }
4553
4554    #[test]
4555    fn parse_generic_record() {
4556        let source = r#"
4557            record Pair<A, B> {
4558                first: A,
4559                second: B,
4560            }
4561
4562            agent Main {
4563                on start { yield(0); }
4564            }
4565            run Main;
4566        "#;
4567
4568        let (prog, errors) = parse_str(source);
4569        assert!(errors.is_empty(), "errors: {errors:?}");
4570        let prog = prog.expect("should parse");
4571
4572        assert_eq!(prog.records.len(), 1);
4573        let record = &prog.records[0];
4574        assert_eq!(record.name.name, "Pair");
4575        assert_eq!(record.type_params.len(), 2);
4576        assert_eq!(record.type_params[0].name, "A");
4577        assert_eq!(record.type_params[1].name, "B");
4578    }
4579
4580    #[test]
4581    fn parse_generic_enum() {
4582        let source = r#"
4583            enum Tree<T> {
4584                Leaf(T),
4585                Node(Tree<T>),
4586            }
4587
4588            agent Main {
4589                on start { yield(0); }
4590            }
4591            run Main;
4592        "#;
4593
4594        let (prog, errors) = parse_str(source);
4595        assert!(errors.is_empty(), "errors: {errors:?}");
4596        let prog = prog.expect("should parse");
4597
4598        assert_eq!(prog.enums.len(), 1);
4599        let enumm = &prog.enums[0];
4600        assert_eq!(enumm.name.name, "Tree");
4601        assert_eq!(enumm.type_params.len(), 1);
4602        assert_eq!(enumm.type_params[0].name, "T");
4603    }
4604
4605    #[test]
4606    fn parse_generic_type_argument() {
4607        let source = r#"
4608            record Wrapper<T> {
4609                value: T,
4610            }
4611
4612            fn make_wrapper<T>(value: T) -> Wrapper<T> {
4613                return Wrapper { value: value };
4614            }
4615
4616            agent Main {
4617                on start { yield(0); }
4618            }
4619            run Main;
4620        "#;
4621
4622        let (prog, errors) = parse_str(source);
4623        assert!(errors.is_empty(), "errors: {errors:?}");
4624        let prog = prog.expect("should parse");
4625
4626        let func = &prog.functions[0];
4627        // Check return type is Wrapper<T>
4628        if let TypeExpr::Named(name, type_args) = &func.return_ty {
4629            assert_eq!(name.name, "Wrapper");
4630            assert_eq!(type_args.len(), 1);
4631            if let TypeExpr::Named(inner_name, _) = &type_args[0] {
4632                assert_eq!(inner_name.name, "T");
4633            } else {
4634                panic!("expected Named type argument");
4635            }
4636        } else {
4637            panic!("expected Named return type");
4638        }
4639    }
4640
4641    #[test]
4642    fn parse_turbofish_function_call() {
4643        let source = r#"
4644            fn identity<T>(x: T) -> T {
4645                return x;
4646            }
4647
4648            agent Main {
4649                on start {
4650                    let result = identity::<Int>(42);
4651                    yield(0);
4652                }
4653            }
4654            run Main;
4655        "#;
4656
4657        let (prog, errors) = parse_str(source);
4658        assert!(errors.is_empty(), "errors: {errors:?}");
4659        let prog = prog.expect("should parse");
4660
4661        let handler = &prog.agents[0].handlers[0];
4662        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4663            if let Expr::Call {
4664                name, type_args, ..
4665            } = value
4666            {
4667                assert_eq!(name.name, "identity");
4668                assert_eq!(type_args.len(), 1);
4669                assert!(matches!(type_args[0], TypeExpr::Int));
4670            } else {
4671                panic!("expected Call expression");
4672            }
4673        } else {
4674            panic!("expected Let statement");
4675        }
4676    }
4677
4678    #[test]
4679    fn parse_turbofish_multiple_type_args() {
4680        let source = r#"
4681            fn make_pair<A, B>(a: A, b: B) -> (A, B) {
4682                return (a, b);
4683            }
4684
4685            agent Main {
4686                on start {
4687                    let pair = make_pair::<Int, String>(42, "hello");
4688                    yield(0);
4689                }
4690            }
4691            run Main;
4692        "#;
4693
4694        let (prog, errors) = parse_str(source);
4695        assert!(errors.is_empty(), "errors: {errors:?}");
4696        let prog = prog.expect("should parse");
4697
4698        let handler = &prog.agents[0].handlers[0];
4699        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4700            if let Expr::Call {
4701                name, type_args, ..
4702            } = value
4703            {
4704                assert_eq!(name.name, "make_pair");
4705                assert_eq!(type_args.len(), 2);
4706                assert!(matches!(type_args[0], TypeExpr::Int));
4707                assert!(matches!(type_args[1], TypeExpr::String));
4708            } else {
4709                panic!("expected Call expression");
4710            }
4711        } else {
4712            panic!("expected Let statement");
4713        }
4714    }
4715
4716    #[test]
4717    fn parse_turbofish_record_construction() {
4718        let source = r#"
4719            record Pair<A, B> {
4720                first: A,
4721                second: B,
4722            }
4723
4724            agent Main {
4725                on start {
4726                    let p = Pair::<Int, String> { first: 42, second: "hi" };
4727                    yield(0);
4728                }
4729            }
4730            run Main;
4731        "#;
4732
4733        let (prog, errors) = parse_str(source);
4734        assert!(errors.is_empty(), "errors: {errors:?}");
4735        let prog = prog.expect("should parse");
4736
4737        let handler = &prog.agents[0].handlers[0];
4738        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4739            if let Expr::RecordConstruct {
4740                name, type_args, ..
4741            } = value
4742            {
4743                assert_eq!(name.name, "Pair");
4744                assert_eq!(type_args.len(), 2);
4745                assert!(matches!(type_args[0], TypeExpr::Int));
4746                assert!(matches!(type_args[1], TypeExpr::String));
4747            } else {
4748                panic!("expected RecordConstruct expression");
4749            }
4750        } else {
4751            panic!("expected Let statement");
4752        }
4753    }
4754
4755    #[test]
4756    fn parse_turbofish_variant_construction() {
4757        let source = r#"
4758            enum Either<L, R> {
4759                Left(L),
4760                Right(R),
4761            }
4762
4763            agent Main {
4764                on start {
4765                    let e = Either::<String, Int>::Left("hello");
4766                    yield(0);
4767                }
4768            }
4769            run Main;
4770        "#;
4771
4772        let (prog, errors) = parse_str(source);
4773        assert!(errors.is_empty(), "errors: {errors:?}");
4774        let prog = prog.expect("should parse");
4775
4776        let handler = &prog.agents[0].handlers[0];
4777        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4778            if let Expr::VariantConstruct {
4779                enum_name,
4780                type_args,
4781                variant,
4782                ..
4783            } = value
4784            {
4785                assert_eq!(enum_name.name, "Either");
4786                assert_eq!(variant.name, "Left");
4787                assert_eq!(type_args.len(), 2);
4788                assert!(matches!(type_args[0], TypeExpr::String));
4789                assert!(matches!(type_args[1], TypeExpr::Int));
4790            } else {
4791                panic!("expected VariantConstruct expression");
4792            }
4793        } else {
4794            panic!("expected Let statement");
4795        }
4796    }
4797
4798    #[test]
4799    fn parse_generic_in_type_annotation() {
4800        let source = r#"
4801            record Page<T> {
4802                items: List<T>,
4803                count: Int,
4804            }
4805
4806            agent Main {
4807                on start {
4808                    let page: Page<String> = Page { items: [], count: 0 };
4809                    yield(0);
4810                }
4811            }
4812            run Main;
4813        "#;
4814
4815        let (prog, errors) = parse_str(source);
4816        assert!(errors.is_empty(), "errors: {errors:?}");
4817        let prog = prog.expect("should parse");
4818
4819        let handler = &prog.agents[0].handlers[0];
4820        if let Stmt::Let { ty: Some(ty), .. } = &handler.body.stmts[0] {
4821            if let TypeExpr::Named(name, type_args) = ty {
4822                assert_eq!(name.name, "Page");
4823                assert_eq!(type_args.len(), 1);
4824                assert!(matches!(type_args[0], TypeExpr::String));
4825            } else {
4826                panic!("expected Named type");
4827            }
4828        } else {
4829            panic!("expected Let statement with type annotation");
4830        }
4831    }
4832
4833    #[test]
4834    fn parse_nested_generic_types() {
4835        let source = r#"
4836            record Nested<T> {
4837                value: Option<List<T>>,
4838            }
4839
4840            agent Main {
4841                on start { yield(0); }
4842            }
4843            run Main;
4844        "#;
4845
4846        let (prog, errors) = parse_str(source);
4847        assert!(errors.is_empty(), "errors: {errors:?}");
4848        let prog = prog.expect("should parse");
4849
4850        let record = &prog.records[0];
4851        let field_ty = &record.fields[0].ty;
4852        // Should be Option<List<T>>
4853        if let TypeExpr::Option(inner) = field_ty {
4854            if let TypeExpr::List(elem) = inner.as_ref() {
4855                if let TypeExpr::Named(name, _) = elem.as_ref() {
4856                    assert_eq!(name.name, "T");
4857                } else {
4858                    panic!("expected Named type T");
4859                }
4860            } else {
4861                panic!("expected List type");
4862            }
4863        } else {
4864            panic!("expected Option type");
4865        }
4866    }
4867}