Skip to main content

sage_parser/
parser.rs

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