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 src12 = source.clone();
777    let mock_infer_stmt = just(Token::KwMock)
778        .ignore_then(just(Token::KwInfer))
779        .ignore_then(just(Token::Arrow))
780        .ignore_then(expr_parser(src12.clone()).map(|expr| {
781            // Check if this is a fail() call expression and convert to MockValue::Fail
782            if let Expr::Fail { error, .. } = expr {
783                MockValue::Fail(*error)
784            } else {
785                MockValue::Value(expr)
786            }
787        }))
788        .then_ignore(just(Token::Semicolon))
789        .map_with_span(move |value, span: Range<usize>| Stmt::MockInfer {
790            value,
791            span: make_span(&src12, span),
792        });
793
794    // mock tool ToolName.fn_name -> value; or mock tool ToolName.fn_name -> fail("msg");
795    let src13 = source.clone();
796    let src14 = source.clone();
797    let src15 = source.clone();
798    let mock_tool_stmt = just(Token::KwMock)
799        .ignore_then(just(Token::KwTool))
800        .ignore_then(ident_token_parser(src13.clone())) // Tool name
801        .then_ignore(just(Token::Dot))
802        .then(ident_token_parser(src14.clone())) // Function name
803        .then_ignore(just(Token::Arrow))
804        .then(expr_parser(src15.clone()).map(|expr| {
805            // Check if this is a fail() call expression and convert to MockValue::Fail
806            if let Expr::Fail { error, .. } = expr {
807                MockValue::Fail(*error)
808            } else {
809                MockValue::Value(expr)
810            }
811        }))
812        .then_ignore(just(Token::Semicolon))
813        .map_with_span(move |((tool_name, fn_name), value), span: Range<usize>| Stmt::MockTool {
814            tool_name,
815            fn_name,
816            value,
817            span: make_span(&src15, span),
818        });
819
820    let assign_stmt = ident_token_parser(src5.clone())
821        .then_ignore(just(Token::Eq))
822        .then(expr_parser(src5.clone()))
823        .then_ignore(just(Token::Semicolon))
824        .map_with_span(move |(name, value), span: Range<usize>| Stmt::Assign {
825            name,
826            value,
827            span: make_span(&src5, span),
828        });
829
830    let expr_stmt = expr_parser(src6.clone())
831        .then_ignore(just(Token::Semicolon))
832        .map_with_span(move |expr, span: Range<usize>| Stmt::Expr {
833            expr,
834            span: make_span(&src6, span),
835        });
836
837    let_tuple_stmt
838        .or(let_stmt)
839        .or(return_stmt)
840        .or(if_stmt)
841        .or(for_stmt)
842        .or(while_stmt)
843        .or(loop_stmt)
844        .or(break_stmt)
845        .or(mock_infer_stmt)
846        .or(mock_tool_stmt)
847        .or(assign_stmt)
848        .or(expr_stmt)
849}
850
851// =============================================================================
852// Expression parsers
853// =============================================================================
854
855/// Parser for expressions (with precedence climbing for binary ops).
856/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
857#[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
858fn expr_parser(source: Arc<str>) -> BoxedParser<'static, Token, Expr, ParseError> {
859    recursive(move |expr: Recursive<Token, Expr, ParseError>| {
860        let src = source.clone();
861
862        let literal = literal_parser(src.clone());
863        let var = var_parser(src.clone());
864
865        // Parenthesized expression or tuple literal
866        // (expr) is a paren, (expr, expr, ...) is a tuple
867        let paren_or_tuple = just(Token::LParen)
868            .ignore_then(
869                expr.clone()
870                    .separated_by(just(Token::Comma))
871                    .allow_trailing(),
872            )
873            .then_ignore(just(Token::RParen))
874            .map_with_span({
875                let src = src.clone();
876                move |elements, span: Range<usize>| {
877                    if elements.len() == 1 {
878                        // Single element without trailing comma = parenthesized expression
879                        Expr::Paren {
880                            inner: Box::new(elements.into_iter().next().unwrap()),
881                            span: make_span(&src, span),
882                        }
883                    } else {
884                        // Multiple elements or empty = tuple
885                        Expr::Tuple {
886                            elements,
887                            span: make_span(&src, span),
888                        }
889                    }
890                }
891            });
892
893        let list = expr
894            .clone()
895            .separated_by(just(Token::Comma))
896            .allow_trailing()
897            .delimited_by(just(Token::LBracket), just(Token::RBracket))
898            .map_with_span({
899                let src = src.clone();
900                move |elements, span: Range<usize>| Expr::List {
901                    elements,
902                    span: make_span(&src, span),
903                }
904            });
905
906        // self.field or self.method(args)
907        let self_access = just(Token::KwSelf)
908            .ignore_then(just(Token::Dot))
909            .ignore_then(ident_token_parser(src.clone()))
910            .then(
911                expr.clone()
912                    .separated_by(just(Token::Comma))
913                    .allow_trailing()
914                    .delimited_by(just(Token::LParen), just(Token::RParen))
915                    .or_not(),
916            )
917            .map_with_span({
918                let src = src.clone();
919                move |(field, args), span: Range<usize>| match args {
920                    Some(args) => Expr::SelfMethodCall {
921                        method: field,
922                        args,
923                        span: make_span(&src, span),
924                    },
925                    None => Expr::SelfField {
926                        field,
927                        span: make_span(&src, span),
928                    },
929                }
930            });
931
932        // infer("template") or infer("template" -> Type)
933        let infer_expr = just(Token::KwInfer)
934            .ignore_then(just(Token::LParen))
935            .ignore_then(string_template_parser(src.clone()))
936            .then(
937                just(Token::Arrow)
938                    .ignore_then(type_parser(src.clone()))
939                    .or_not(),
940            )
941            .then_ignore(just(Token::RParen))
942            .map_with_span({
943                let src = src.clone();
944                move |(template, result_ty), span: Range<usize>| Expr::Infer {
945                    template,
946                    result_ty,
947                    span: make_span(&src, span),
948                }
949            });
950
951        // spawn Agent { field: value, ... }
952        let spawn_field_init = ident_token_parser(src.clone())
953            .then_ignore(just(Token::Colon))
954            .then(expr.clone())
955            .map_with_span({
956                let src = src.clone();
957                move |(name, value), span: Range<usize>| FieldInit {
958                    name,
959                    value,
960                    span: make_span(&src, span),
961                }
962            });
963
964        let spawn_expr = just(Token::KwSpawn)
965            .ignore_then(ident_token_parser(src.clone()))
966            .then_ignore(just(Token::LBrace))
967            .then(
968                spawn_field_init
969                    .separated_by(just(Token::Comma))
970                    .allow_trailing(),
971            )
972            .then_ignore(just(Token::RBrace))
973            .map_with_span({
974                let src = src.clone();
975                move |(agent, fields), span: Range<usize>| Expr::Spawn {
976                    agent,
977                    fields,
978                    span: make_span(&src, span),
979                }
980            });
981
982        // await expr - we need to handle this carefully to avoid left recursion
983        // Parse: await handle [timeout(ms)]
984        let timeout_clause = just(Token::KwTimeout)
985            .ignore_then(just(Token::LParen))
986            .ignore_then(expr.clone())
987            .then_ignore(just(Token::RParen));
988
989        let await_expr = just(Token::KwAwait)
990            .ignore_then(ident_token_parser(src.clone()).map_with_span({
991                let src = src.clone();
992                move |name, span: Range<usize>| Expr::Var {
993                    name,
994                    span: make_span(&src, span),
995                }
996            }))
997            .then(timeout_clause.or_not())
998            .map_with_span({
999                let src = src.clone();
1000                move |(handle, timeout), span: Range<usize>| Expr::Await {
1001                    handle: Box::new(handle),
1002                    timeout: timeout.map(Box::new),
1003                    span: make_span(&src, span),
1004                }
1005            });
1006
1007        // send(handle, message)
1008        let send_expr = just(Token::KwSend)
1009            .ignore_then(just(Token::LParen))
1010            .ignore_then(expr.clone())
1011            .then_ignore(just(Token::Comma))
1012            .then(expr.clone())
1013            .then_ignore(just(Token::RParen))
1014            .map_with_span({
1015                let src = src.clone();
1016                move |(handle, message), span: Range<usize>| Expr::Send {
1017                    handle: Box::new(handle),
1018                    message: Box::new(message),
1019                    span: make_span(&src, span),
1020                }
1021            });
1022
1023        // emit(value)
1024        let emit_expr = just(Token::KwEmit)
1025            .ignore_then(just(Token::LParen))
1026            .ignore_then(expr.clone())
1027            .then_ignore(just(Token::RParen))
1028            .map_with_span({
1029                let src = src.clone();
1030                move |value, span: Range<usize>| Expr::Emit {
1031                    value: Box::new(value),
1032                    span: make_span(&src, span),
1033                }
1034            });
1035
1036        // function call: name(args)
1037        let call_expr = ident_token_parser(src.clone())
1038            .then(
1039                expr.clone()
1040                    .separated_by(just(Token::Comma))
1041                    .allow_trailing()
1042                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1043            )
1044            .map_with_span({
1045                let src = src.clone();
1046                move |(name, args), span: Range<usize>| Expr::Call {
1047                    name,
1048                    args,
1049                    span: make_span(&src, span),
1050                }
1051            });
1052
1053        // Pattern for match arms
1054        let pattern = pattern_parser(src.clone());
1055
1056        // match expression: match expr { Pattern => expr, ... }
1057        let match_arm = pattern
1058            .then_ignore(just(Token::FatArrow))
1059            .then(expr.clone())
1060            .map_with_span({
1061                let src = src.clone();
1062                move |(pattern, body), span: Range<usize>| MatchArm {
1063                    pattern,
1064                    body,
1065                    span: make_span(&src, span),
1066                }
1067            });
1068
1069        let match_expr = just(Token::KwMatch)
1070            .ignore_then(expr.clone())
1071            .then(
1072                match_arm
1073                    .separated_by(just(Token::Comma))
1074                    .allow_trailing()
1075                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
1076            )
1077            .map_with_span({
1078                let src = src.clone();
1079                move |(scrutinee, arms), span: Range<usize>| Expr::Match {
1080                    scrutinee: Box::new(scrutinee),
1081                    arms,
1082                    span: make_span(&src, span),
1083                }
1084            });
1085
1086        // receive() - receive message from mailbox
1087        let receive_expr = just(Token::KwReceive)
1088            .ignore_then(just(Token::LParen))
1089            .ignore_then(just(Token::RParen))
1090            .map_with_span({
1091                let src = src.clone();
1092                move |_, span: Range<usize>| Expr::Receive {
1093                    span: make_span(&src, span),
1094                }
1095            });
1096
1097        // trace(message) - emit a trace event
1098        let trace_expr = just(Token::KwTrace)
1099            .ignore_then(just(Token::LParen))
1100            .ignore_then(expr.clone())
1101            .then_ignore(just(Token::RParen))
1102            .map_with_span({
1103                let src = src.clone();
1104                move |message, span: Range<usize>| Expr::Trace {
1105                    message: Box::new(message),
1106                    span: make_span(&src, span),
1107                }
1108            });
1109
1110        // Record construction: RecordName { field: value, ... }
1111        // This is similar to spawn but without the spawn keyword
1112        // Must come before var to avoid conflict
1113        let record_field_init = ident_token_parser(src.clone())
1114            .then_ignore(just(Token::Colon))
1115            .then(expr.clone())
1116            .map_with_span({
1117                let src = src.clone();
1118                move |(name, value), span: Range<usize>| FieldInit {
1119                    name,
1120                    value,
1121                    span: make_span(&src, span),
1122                }
1123            });
1124
1125        let record_construct = ident_token_parser(src.clone())
1126            .then_ignore(just(Token::LBrace))
1127            .then(
1128                record_field_init
1129                    .separated_by(just(Token::Comma))
1130                    .allow_trailing(),
1131            )
1132            .then_ignore(just(Token::RBrace))
1133            .map_with_span({
1134                let src = src.clone();
1135                move |(name, fields), span: Range<usize>| Expr::RecordConstruct {
1136                    name,
1137                    fields,
1138                    span: make_span(&src, span),
1139                }
1140            });
1141
1142        // Closure parameter: `name` or `name: Type`
1143        let closure_param = ident_token_parser(src.clone())
1144            .then(just(Token::Colon).ignore_then(type_parser(src.clone())).or_not())
1145            .map_with_span({
1146                let src = src.clone();
1147                move |(name, ty), span: Range<usize>| ClosureParam {
1148                    name,
1149                    ty,
1150                    span: make_span(&src, span),
1151                }
1152            });
1153
1154        // Closure expression: |params| body
1155        // Handle both `|| expr` (empty params using Or token) and `|params| expr`
1156        let closure_empty = just(Token::Or)
1157            .ignore_then(expr.clone())
1158            .map_with_span({
1159                let src = src.clone();
1160                move |body, span: Range<usize>| Expr::Closure {
1161                    params: vec![],
1162                    body: Box::new(body),
1163                    span: make_span(&src, span),
1164                }
1165            });
1166
1167        let closure_with_params = just(Token::Pipe)
1168            .ignore_then(
1169                closure_param
1170                    .separated_by(just(Token::Comma))
1171                    .allow_trailing(),
1172            )
1173            .then_ignore(just(Token::Pipe))
1174            .then(expr.clone())
1175            .map_with_span({
1176                let src = src.clone();
1177                move |(params, body), span: Range<usize>| Expr::Closure {
1178                    params,
1179                    body: Box::new(body),
1180                    span: make_span(&src, span),
1181                }
1182            });
1183
1184        let closure = closure_with_params.or(closure_empty);
1185
1186        // Map literal: { key: value, ... } or {}
1187        // This must be distinguished from record construction which has an identifier before the brace
1188        let map_entry = expr
1189            .clone()
1190            .then_ignore(just(Token::Colon))
1191            .then(expr.clone())
1192            .map_with_span({
1193                let src = src.clone();
1194                move |(key, value), span: Range<usize>| MapEntry {
1195                    key,
1196                    value,
1197                    span: make_span(&src, span),
1198                }
1199            });
1200
1201        let map_literal = map_entry
1202            .separated_by(just(Token::Comma))
1203            .allow_trailing()
1204            .delimited_by(just(Token::LBrace), just(Token::RBrace))
1205            .map_with_span({
1206                let src = src.clone();
1207                move |entries, span: Range<usize>| Expr::Map {
1208                    entries,
1209                    span: make_span(&src, span),
1210                }
1211            });
1212
1213        // Enum variant construction: EnumName::Variant or EnumName::Variant(payload)
1214        let variant_construct = ident_token_parser(src.clone())
1215            .then_ignore(just(Token::ColonColon))
1216            .then(ident_token_parser(src.clone()))
1217            .then(
1218                expr.clone()
1219                    .delimited_by(just(Token::LParen), just(Token::RParen))
1220                    .or_not(),
1221            )
1222            .map_with_span({
1223                let src = src.clone();
1224                move |((enum_name, variant), payload), span: Range<usize>| Expr::VariantConstruct {
1225                    enum_name,
1226                    variant,
1227                    payload: payload.map(Box::new),
1228                    span: make_span(&src, span),
1229                }
1230            });
1231
1232        // Atom: the base expression without binary ops
1233        // Box early to cut type complexity
1234        // Note: record_construct must come before call_expr and var to parse `Name { ... }` correctly
1235        // Note: receive_expr must come before call_expr to avoid being parsed as function call
1236        // Note: closure must come before other expressions to handle `|` tokens correctly
1237        // Note: map_literal must come after record_construct (record has name before brace)
1238        // Note: variant_construct must come before call_expr to parse `EnumName::Variant(...)` correctly
1239        let atom = closure
1240            .or(infer_expr)
1241            .or(spawn_expr)
1242            .or(await_expr)
1243            .or(send_expr)
1244            .or(emit_expr)
1245            .or(receive_expr)
1246            .or(trace_expr)
1247            .or(match_expr)
1248            .or(self_access)
1249            .or(record_construct)
1250            .or(variant_construct)
1251            .or(call_expr)
1252            .or(map_literal)
1253            .or(list)
1254            .or(paren_or_tuple)
1255            .or(literal)
1256            .or(var)
1257            .boxed();
1258
1259        // Postfix access: expr.field, expr.0 (tuple index), or expr.method(args) (tool call)
1260        // We need to distinguish between field access, tuple index, and method call
1261        enum PostfixOp {
1262            Field(Ident),
1263            TupleIndex(usize, Range<usize>),
1264            MethodCall(Ident, Vec<Expr>, Range<usize>), // method name, args, span of closing paren
1265        }
1266
1267        // Parse method call: .ident(args)
1268        let method_call = ident_token_parser(src.clone())
1269            .then(
1270                expr.clone()
1271                    .separated_by(just(Token::Comma))
1272                    .allow_trailing()
1273                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1274            )
1275            .map_with_span(|(name, args), span: Range<usize>| {
1276                PostfixOp::MethodCall(name, args, span)
1277            });
1278
1279        let postfix_op = just(Token::Dot).ignore_then(
1280            // Try to parse a tuple index (integer literal)
1281            filter_map({
1282                let src = src.clone();
1283                move |span: Range<usize>, token| match token {
1284                    Token::IntLit => {
1285                        let text = &src[span.start..span.end];
1286                        text.parse::<usize>()
1287                            .map(|idx| PostfixOp::TupleIndex(idx, span.clone()))
1288                            .map_err(|_| Simple::custom(span, "invalid tuple index"))
1289                    }
1290                    _ => Err(Simple::expected_input_found(
1291                        span,
1292                        vec![Some(Token::IntLit)],
1293                        Some(token),
1294                    )),
1295                }
1296            })
1297            // Try method call first, then field access
1298            .or(method_call)
1299            .or(ident_token_parser(src.clone()).map(PostfixOp::Field)),
1300        );
1301
1302        let postfix = atom
1303            .then(postfix_op.repeated())
1304            .foldl({
1305                let src = src.clone();
1306                move |object, op| match op {
1307                    PostfixOp::Field(field) => {
1308                        let span = make_span(&src, object.span().start..field.span.end);
1309                        Expr::FieldAccess {
1310                            object: Box::new(object),
1311                            field,
1312                            span,
1313                        }
1314                    }
1315                    PostfixOp::TupleIndex(index, idx_span) => {
1316                        let span = make_span(&src, object.span().start..idx_span.end);
1317                        Expr::TupleIndex {
1318                            tuple: Box::new(object),
1319                            index,
1320                            span,
1321                        }
1322                    }
1323                    PostfixOp::MethodCall(method, args, call_span) => {
1324                        // If object is a Var, this might be a tool call
1325                        // Tool calls look like: Http.get(url)
1326                        if let Expr::Var { name: tool, .. } = &object {
1327                            let span = make_span(&src, object.span().start..call_span.end);
1328                            Expr::ToolCall {
1329                                tool: tool.clone(),
1330                                function: method,
1331                                args,
1332                                span,
1333                            }
1334                        } else {
1335                            // Not a tool call - for now, produce a FieldAccess error
1336                            // (Sage doesn't support general method calls on values)
1337                            let span = make_span(&src, object.span().start..call_span.end);
1338                            Expr::FieldAccess {
1339                                object: Box::new(object),
1340                                field: method,
1341                                span,
1342                            }
1343                        }
1344                    }
1345                }
1346            })
1347            .boxed();
1348
1349        // Unary expressions
1350        let unary = just(Token::Minus)
1351            .to(UnaryOp::Neg)
1352            .or(just(Token::Bang).to(UnaryOp::Not))
1353            .repeated()
1354            .then(postfix.clone())
1355            .foldr(|op, operand| {
1356                let span = operand.span().clone();
1357                Expr::Unary {
1358                    op,
1359                    operand: Box::new(operand),
1360                    span,
1361                }
1362            })
1363            .boxed();
1364
1365        // RFC-0007: try expression - propagates errors upward
1366        // try expr
1367        let try_expr = just(Token::KwTry)
1368            .ignore_then(postfix.clone())
1369            .map_with_span({
1370                let src = src.clone();
1371                move |inner, span: Range<usize>| Expr::Try {
1372                    expr: Box::new(inner),
1373                    span: make_span(&src, span),
1374                }
1375            })
1376            .boxed();
1377
1378        // fail expression - explicit error raising
1379        // fail "message" or fail Error { ... }
1380        let fail_expr = just(Token::KwFail)
1381            .ignore_then(postfix.clone())
1382            .map_with_span({
1383                let src = src.clone();
1384                move |error, span: Range<usize>| Expr::Fail {
1385                    error: Box::new(error),
1386                    span: make_span(&src, span),
1387                }
1388            })
1389            .boxed();
1390
1391        // retry expression - retry a fallible operation
1392        // retry(count) { body }
1393        // retry(count, delay: ms) { body }
1394        // retry(count, on: [ErrorKind.Network, ...]) { body }
1395        let retry_delay = just(Token::Comma)
1396            .ignore_then(just(Token::KwDelay))
1397            .ignore_then(just(Token::Colon))
1398            .ignore_then(postfix.clone());
1399
1400        let retry_on = just(Token::Comma)
1401            .ignore_then(just(Token::KwOn))
1402            .ignore_then(just(Token::Colon))
1403            .ignore_then(
1404                postfix
1405                    .clone()
1406                    .separated_by(just(Token::Comma))
1407                    .allow_trailing()
1408                    .delimited_by(just(Token::LBracket), just(Token::RBracket)),
1409            );
1410
1411        let retry_expr = just(Token::KwRetry)
1412            .ignore_then(just(Token::LParen))
1413            .ignore_then(postfix.clone())
1414            .then(retry_delay.or_not())
1415            .then(retry_on.or_not())
1416            .then_ignore(just(Token::RParen))
1417            .then(
1418                expr.clone()
1419                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
1420            )
1421            .map_with_span({
1422                let src = src.clone();
1423                move |(((count, delay), on_errors), body), span: Range<usize>| Expr::Retry {
1424                    count: Box::new(count),
1425                    delay: delay.map(Box::new),
1426                    on_errors,
1427                    body: Box::new(body),
1428                    span: make_span(&src, span),
1429                }
1430            })
1431            .boxed();
1432
1433        // Combined unary (including try, fail, and retry)
1434        let unary = retry_expr.or(fail_expr).or(try_expr).or(unary).boxed();
1435
1436        // Binary operators with precedence levels
1437        // Level 7: * / %
1438        let mul_div_op = just(Token::Star)
1439            .to(BinOp::Mul)
1440            .or(just(Token::Slash).to(BinOp::Div))
1441            .or(just(Token::Percent).to(BinOp::Rem));
1442
1443        let mul_div = unary
1444            .clone()
1445            .then(mul_div_op.then(unary.clone()).repeated())
1446            .foldl({
1447                let src = src.clone();
1448                move |left, (op, right)| {
1449                    let span = make_span(&src, left.span().start..right.span().end);
1450                    Expr::Binary {
1451                        op,
1452                        left: Box::new(left),
1453                        right: Box::new(right),
1454                        span,
1455                    }
1456                }
1457            })
1458            .boxed();
1459
1460        // Level 6: + -
1461        let add_sub_op = just(Token::Plus)
1462            .to(BinOp::Add)
1463            .or(just(Token::Minus).to(BinOp::Sub));
1464
1465        let add_sub = mul_div
1466            .clone()
1467            .then(add_sub_op.then(mul_div).repeated())
1468            .foldl({
1469                let src = src.clone();
1470                move |left, (op, right)| {
1471                    let span = make_span(&src, left.span().start..right.span().end);
1472                    Expr::Binary {
1473                        op,
1474                        left: Box::new(left),
1475                        right: Box::new(right),
1476                        span,
1477                    }
1478                }
1479            })
1480            .boxed();
1481
1482        // Level 5: ++
1483        let concat_op = just(Token::PlusPlus).to(BinOp::Concat);
1484
1485        let concat = add_sub
1486            .clone()
1487            .then(concat_op.then(add_sub).repeated())
1488            .foldl({
1489                let src = src.clone();
1490                move |left, (op, right)| {
1491                    let span = make_span(&src, left.span().start..right.span().end);
1492                    Expr::Binary {
1493                        op,
1494                        left: Box::new(left),
1495                        right: Box::new(right),
1496                        span,
1497                    }
1498                }
1499            })
1500            .boxed();
1501
1502        // Level 4: < > <= >=
1503        let cmp_op = choice((
1504            just(Token::Le).to(BinOp::Le),
1505            just(Token::Ge).to(BinOp::Ge),
1506            just(Token::Lt).to(BinOp::Lt),
1507            just(Token::Gt).to(BinOp::Gt),
1508        ));
1509
1510        let comparison = concat
1511            .clone()
1512            .then(cmp_op.then(concat).repeated())
1513            .foldl({
1514                let src = src.clone();
1515                move |left, (op, right)| {
1516                    let span = make_span(&src, left.span().start..right.span().end);
1517                    Expr::Binary {
1518                        op,
1519                        left: Box::new(left),
1520                        right: Box::new(right),
1521                        span,
1522                    }
1523                }
1524            })
1525            .boxed();
1526
1527        // Level 3: == !=
1528        let eq_op = just(Token::EqEq)
1529            .to(BinOp::Eq)
1530            .or(just(Token::Ne).to(BinOp::Ne));
1531
1532        let equality = comparison
1533            .clone()
1534            .then(eq_op.then(comparison).repeated())
1535            .foldl({
1536                let src = src.clone();
1537                move |left, (op, right)| {
1538                    let span = make_span(&src, left.span().start..right.span().end);
1539                    Expr::Binary {
1540                        op,
1541                        left: Box::new(left),
1542                        right: Box::new(right),
1543                        span,
1544                    }
1545                }
1546            })
1547            .boxed();
1548
1549        // Level 2: &&
1550        let and_op = just(Token::And).to(BinOp::And);
1551
1552        let and = equality
1553            .clone()
1554            .then(and_op.then(equality).repeated())
1555            .foldl({
1556                let src = src.clone();
1557                move |left, (op, right)| {
1558                    let span = make_span(&src, left.span().start..right.span().end);
1559                    Expr::Binary {
1560                        op,
1561                        left: Box::new(left),
1562                        right: Box::new(right),
1563                        span,
1564                    }
1565                }
1566            })
1567            .boxed();
1568
1569        // Level 1: ||
1570        let or_op = just(Token::Or).to(BinOp::Or);
1571
1572        let or_expr = and.clone().then(or_op.then(and).repeated()).foldl({
1573            let src = src.clone();
1574            move |left, (op, right)| {
1575                let span = make_span(&src, left.span().start..right.span().end);
1576                Expr::Binary {
1577                    op,
1578                    left: Box::new(left),
1579                    right: Box::new(right),
1580                    span,
1581                }
1582            }
1583        });
1584
1585        // RFC-0007: catch expression (lowest precedence)
1586        // expr catch { recovery } OR expr catch(e) { recovery }
1587        let catch_recovery = just(Token::KwCatch)
1588            .ignore_then(
1589                ident_token_parser(src.clone())
1590                    .delimited_by(just(Token::LParen), just(Token::RParen))
1591                    .or_not(),
1592            )
1593            .then(
1594                expr.clone()
1595                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
1596            );
1597
1598        or_expr.then(catch_recovery.or_not()).map_with_span({
1599            let src = src.clone();
1600            move |(inner, catch_opt), span: Range<usize>| match catch_opt {
1601                Some((error_bind, recovery)) => Expr::Catch {
1602                    expr: Box::new(inner),
1603                    error_bind,
1604                    recovery: Box::new(recovery),
1605                    span: make_span(&src, span),
1606                },
1607                None => inner,
1608            }
1609        })
1610    })
1611    .boxed()
1612}
1613
1614// =============================================================================
1615// Primitive parsers
1616// =============================================================================
1617
1618/// Create a Span from a Range<usize>.
1619fn make_span(source: &Arc<str>, range: Range<usize>) -> Span {
1620    Span::new(range.start, range.end, Arc::clone(source))
1621}
1622
1623/// Parser for identifier tokens.
1624fn ident_token_parser(source: Arc<str>) -> impl Parser<Token, Ident, Error = ParseError> + Clone {
1625    filter_map(move |span: Range<usize>, token| match token {
1626        Token::Ident => {
1627            let text = &source[span.start..span.end];
1628            Ok(Ident::new(text.to_string(), make_span(&source, span)))
1629        }
1630        _ => Err(Simple::expected_input_found(
1631            span,
1632            vec![Some(Token::Ident)],
1633            Some(token),
1634        )),
1635    })
1636}
1637
1638/// Parser for variable references.
1639fn var_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
1640    ident_token_parser(source.clone()).map_with_span(move |name, span: Range<usize>| Expr::Var {
1641        name,
1642        span: make_span(&source, span),
1643    })
1644}
1645
1646/// Parser for type expressions.
1647fn type_parser(source: Arc<str>) -> impl Parser<Token, TypeExpr, Error = ParseError> + Clone {
1648    recursive(move |ty| {
1649        let src = source.clone();
1650
1651        let primitive = choice((
1652            just(Token::TyInt).to(TypeExpr::Int),
1653            just(Token::TyFloat).to(TypeExpr::Float),
1654            just(Token::TyBool).to(TypeExpr::Bool),
1655            just(Token::TyString).to(TypeExpr::String),
1656            just(Token::TyUnit).to(TypeExpr::Unit),
1657        ));
1658
1659        let list_ty = just(Token::TyList)
1660            .ignore_then(just(Token::Lt))
1661            .ignore_then(ty.clone())
1662            .then_ignore(just(Token::Gt))
1663            .map(|inner| TypeExpr::List(Box::new(inner)));
1664
1665        let option_ty = just(Token::TyOption)
1666            .ignore_then(just(Token::Lt))
1667            .ignore_then(ty.clone())
1668            .then_ignore(just(Token::Gt))
1669            .map(|inner| TypeExpr::Option(Box::new(inner)));
1670
1671        let inferred_ty = just(Token::TyInferred)
1672            .ignore_then(just(Token::Lt))
1673            .ignore_then(ty.clone())
1674            .then_ignore(just(Token::Gt))
1675            .map(|inner| TypeExpr::Inferred(Box::new(inner)));
1676
1677        let agent_ty = just(Token::TyAgent)
1678            .ignore_then(just(Token::Lt))
1679            .ignore_then(ident_token_parser(src.clone()))
1680            .then_ignore(just(Token::Gt))
1681            .map(TypeExpr::Agent);
1682
1683        let named_ty = ident_token_parser(src.clone()).map(TypeExpr::Named);
1684
1685        // Function type: Fn(A, B) -> C
1686        let fn_ty = just(Token::TyFn)
1687            .ignore_then(
1688                ty.clone()
1689                    .separated_by(just(Token::Comma))
1690                    .allow_trailing()
1691                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1692            )
1693            .then_ignore(just(Token::Arrow))
1694            .then(ty.clone())
1695            .map(|(params, ret)| TypeExpr::Fn(params, Box::new(ret)));
1696
1697        // Map type: Map<K, V>
1698        let map_ty = just(Token::TyMap)
1699            .ignore_then(just(Token::Lt))
1700            .ignore_then(ty.clone())
1701            .then_ignore(just(Token::Comma))
1702            .then(ty.clone())
1703            .then_ignore(just(Token::Gt))
1704            .map(|(k, v)| TypeExpr::Map(Box::new(k), Box::new(v)));
1705
1706        // Result type: Result<T, E>
1707        let result_ty = just(Token::TyResult)
1708            .ignore_then(just(Token::Lt))
1709            .ignore_then(ty.clone())
1710            .then_ignore(just(Token::Comma))
1711            .then(ty.clone())
1712            .then_ignore(just(Token::Gt))
1713            .map(|(ok, err)| TypeExpr::Result(Box::new(ok), Box::new(err)));
1714
1715        // Tuple type: (A, B, C) - at least 2 elements
1716        let tuple_ty = ty
1717            .clone()
1718            .separated_by(just(Token::Comma))
1719            .at_least(2)
1720            .allow_trailing()
1721            .delimited_by(just(Token::LParen), just(Token::RParen))
1722            .map(TypeExpr::Tuple);
1723
1724        primitive
1725            .or(list_ty)
1726            .or(option_ty)
1727            .or(inferred_ty)
1728            .or(agent_ty)
1729            .or(fn_ty)
1730            .or(map_ty)
1731            .or(result_ty)
1732            .or(tuple_ty)
1733            .or(named_ty)
1734    })
1735}
1736
1737/// Parser for patterns in for loops.
1738/// Only supports simple bindings (`x`) and tuple patterns (`(k, v)`).
1739fn for_pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
1740    recursive(move |pattern| {
1741        let src = source.clone();
1742        let src2 = source.clone();
1743
1744        // Simple binding pattern: `x`
1745        let binding = ident_token_parser(src.clone()).map_with_span({
1746            let src = src.clone();
1747            move |name, span: Range<usize>| Pattern::Binding {
1748                name,
1749                span: make_span(&src, span),
1750            }
1751        });
1752
1753        // Tuple pattern: `(a, b)` - at least 2 elements
1754        let tuple_pattern = pattern
1755            .clone()
1756            .separated_by(just(Token::Comma))
1757            .at_least(2)
1758            .allow_trailing()
1759            .delimited_by(just(Token::LParen), just(Token::RParen))
1760            .map_with_span({
1761                let src = src2.clone();
1762                move |elements, span: Range<usize>| Pattern::Tuple {
1763                    elements,
1764                    span: make_span(&src, span),
1765                }
1766            });
1767
1768        tuple_pattern.or(binding)
1769    })
1770}
1771
1772/// Parser for patterns in match expressions.
1773fn pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
1774    recursive(move |pattern| {
1775        let src = source.clone();
1776        let src2 = source.clone();
1777        let src3 = source.clone();
1778        let src4 = source.clone();
1779        let src5 = source.clone();
1780
1781        // Wildcard pattern: `_`
1782        let wildcard = filter_map({
1783            let src = src.clone();
1784            move |span: Range<usize>, token| match &token {
1785                Token::Ident if src[span.start..span.end].eq("_") => Ok(()),
1786                _ => Err(Simple::expected_input_found(span, vec![], Some(token))),
1787            }
1788        })
1789        .map_with_span(move |_, span: Range<usize>| Pattern::Wildcard {
1790            span: make_span(&src2, span),
1791        });
1792
1793        // Literal patterns: 42, "hello", true, false
1794        let lit_int = filter_map({
1795            let src = src3.clone();
1796            move |span: Range<usize>, token| match token {
1797                Token::IntLit => {
1798                    let text = &src[span.start..span.end];
1799                    text.parse::<i64>()
1800                        .map(Literal::Int)
1801                        .map_err(|_| Simple::custom(span, "invalid integer literal"))
1802                }
1803                _ => Err(Simple::expected_input_found(
1804                    span,
1805                    vec![Some(Token::IntLit)],
1806                    Some(token),
1807                )),
1808            }
1809        })
1810        .map_with_span({
1811            let src = src3.clone();
1812            move |value, span: Range<usize>| Pattern::Literal {
1813                value,
1814                span: make_span(&src, span),
1815            }
1816        });
1817
1818        let lit_bool = just(Token::KwTrue)
1819            .to(Literal::Bool(true))
1820            .or(just(Token::KwFalse).to(Literal::Bool(false)))
1821            .map_with_span({
1822                let src = src3.clone();
1823                move |value, span: Range<usize>| Pattern::Literal {
1824                    value,
1825                    span: make_span(&src, span),
1826                }
1827            });
1828
1829        // Tuple pattern: (a, b, c) - at least 2 elements
1830        let tuple_pattern = pattern
1831            .clone()
1832            .separated_by(just(Token::Comma))
1833            .at_least(2)
1834            .allow_trailing()
1835            .delimited_by(just(Token::LParen), just(Token::RParen))
1836            .map_with_span({
1837                let src = src5.clone();
1838                move |elements, span: Range<usize>| Pattern::Tuple {
1839                    elements,
1840                    span: make_span(&src, span),
1841                }
1842            });
1843
1844        // Enum variant with optional payload: `Ok(x)` or `Status::Active`
1845        // Qualified with payload: EnumName::Variant(pattern)
1846        let qualified_variant_with_payload = ident_token_parser(src4.clone())
1847            .then_ignore(just(Token::ColonColon))
1848            .then(ident_token_parser(src4.clone()))
1849            .then(
1850                pattern
1851                    .clone()
1852                    .delimited_by(just(Token::LParen), just(Token::RParen))
1853                    .or_not(),
1854            )
1855            .map_with_span({
1856                let src = src4.clone();
1857                move |((enum_name, variant), payload), span: Range<usize>| Pattern::Variant {
1858                    enum_name: Some(enum_name),
1859                    variant,
1860                    payload: payload.map(Box::new),
1861                    span: make_span(&src, span),
1862                }
1863            });
1864
1865        // Unqualified variant with payload: `Ok(x)` or just `x`
1866        let unqualified_with_payload = ident_token_parser(src4.clone())
1867            .then(
1868                pattern
1869                    .clone()
1870                    .delimited_by(just(Token::LParen), just(Token::RParen))
1871                    .or_not(),
1872            )
1873            .map_with_span({
1874                let src = src4.clone();
1875                move |(name, payload), span: Range<usize>| {
1876                    // If it looks like a variant (starts with uppercase), treat as variant
1877                    // Otherwise treat as binding (only if no payload)
1878                    if name.name.chars().next().is_some_and(|c| c.is_uppercase()) || payload.is_some() {
1879                        Pattern::Variant {
1880                            enum_name: None,
1881                            variant: name,
1882                            payload: payload.map(Box::new),
1883                            span: make_span(&src, span),
1884                        }
1885                    } else {
1886                        Pattern::Binding {
1887                            name,
1888                            span: make_span(&src, span),
1889                        }
1890                    }
1891                }
1892            });
1893
1894        // Order matters: try wildcard first, then tuple pattern, then qualified variant, then literals, then unqualified
1895        wildcard
1896            .or(tuple_pattern)
1897            .or(qualified_variant_with_payload)
1898            .or(lit_int)
1899            .or(lit_bool)
1900            .or(unqualified_with_payload)
1901    })
1902}
1903
1904/// Parser for literals.
1905fn literal_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
1906    let src = source.clone();
1907    let src2 = source.clone();
1908    let src3 = source.clone();
1909    let src4 = source.clone();
1910    let src5 = source.clone();
1911
1912    let int_lit = filter_map(move |span: Range<usize>, token| match token {
1913        Token::IntLit => {
1914            let text = &src[span.start..span.end];
1915            text.parse::<i64>()
1916                .map(Literal::Int)
1917                .map_err(|_| Simple::custom(span, "invalid integer literal"))
1918        }
1919        _ => Err(Simple::expected_input_found(
1920            span,
1921            vec![Some(Token::IntLit)],
1922            Some(token),
1923        )),
1924    })
1925    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
1926        value,
1927        span: make_span(&src2, span),
1928    });
1929
1930    let float_lit = filter_map(move |span: Range<usize>, token| match token {
1931        Token::FloatLit => {
1932            let text = &src3[span.start..span.end];
1933            text.parse::<f64>()
1934                .map(Literal::Float)
1935                .map_err(|_| Simple::custom(span, "invalid float literal"))
1936        }
1937        _ => Err(Simple::expected_input_found(
1938            span,
1939            vec![Some(Token::FloatLit)],
1940            Some(token),
1941        )),
1942    })
1943    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
1944        value,
1945        span: make_span(&src4, span),
1946    });
1947
1948    let src6 = source.clone();
1949    let string_lit = filter_map(move |span: Range<usize>, token| match token {
1950        Token::StringLit => {
1951            let text = &src5[span.start..span.end];
1952            let inner = &text[1..text.len() - 1];
1953            let parts = parse_string_template(inner, &make_span(&src5, span.clone()));
1954            Ok(parts)
1955        }
1956        _ => Err(Simple::expected_input_found(
1957            span,
1958            vec![Some(Token::StringLit)],
1959            Some(token),
1960        )),
1961    })
1962    .map_with_span(move |parts, span: Range<usize>| {
1963        let span = make_span(&src6, span);
1964        // If no interpolations, use a simple string literal
1965        if parts.len() == 1 {
1966            if let StringPart::Literal(s) = &parts[0] {
1967                return Expr::Literal {
1968                    value: Literal::String(s.clone()),
1969                    span,
1970                };
1971            }
1972        }
1973        // Otherwise, use StringInterp
1974        Expr::StringInterp {
1975            template: StringTemplate {
1976                parts,
1977                span: span.clone(),
1978            },
1979            span,
1980        }
1981    });
1982
1983    let bool_lit = just(Token::KwTrue)
1984        .to(Literal::Bool(true))
1985        .or(just(Token::KwFalse).to(Literal::Bool(false)))
1986        .map_with_span(move |value, _span: Range<usize>| Expr::Literal {
1987            value,
1988            span: Span::dummy(), // bool literals don't carry source
1989        });
1990
1991    int_lit.or(float_lit).or(string_lit).or(bool_lit)
1992}
1993
1994/// Parser for string templates (handles interpolation).
1995fn string_template_parser(
1996    source: Arc<str>,
1997) -> impl Parser<Token, StringTemplate, Error = ParseError> + Clone {
1998    filter_map(move |span: Range<usize>, token| match token {
1999        Token::StringLit => {
2000            let text = &source[span.start..span.end];
2001            let inner = &text[1..text.len() - 1];
2002            let parts = parse_string_template(inner, &make_span(&source, span.clone()));
2003            Ok(StringTemplate {
2004                parts,
2005                span: make_span(&source, span),
2006            })
2007        }
2008        _ => Err(Simple::expected_input_found(
2009            span,
2010            vec![Some(Token::StringLit)],
2011            Some(token),
2012        )),
2013    })
2014}
2015
2016/// Parse a string into template parts, handling `{expr}` interpolations.
2017/// Supports field access chains: `{name}`, `{person.name}`, `{pair.0}`
2018fn parse_string_template(s: &str, span: &Span) -> Vec<StringPart> {
2019    let mut parts = Vec::new();
2020    let mut current = String::new();
2021    let mut chars = s.chars().peekable();
2022
2023    while let Some(ch) = chars.next() {
2024        if ch == '{' {
2025            if !current.is_empty() {
2026                parts.push(StringPart::Literal(std::mem::take(&mut current)));
2027            }
2028
2029            // Collect the full interpolation expression
2030            let mut expr_str = String::new();
2031            while let Some(&c) = chars.peek() {
2032                if c == '}' {
2033                    chars.next();
2034                    break;
2035                }
2036                expr_str.push(c);
2037                chars.next();
2038            }
2039
2040            if !expr_str.is_empty() {
2041                let interp_expr = parse_interp_expr(&expr_str, span);
2042                parts.push(StringPart::Interpolation(interp_expr));
2043            }
2044        } else if ch == '\\' {
2045            if let Some(escaped) = chars.next() {
2046                current.push(match escaped {
2047                    'n' => '\n',
2048                    't' => '\t',
2049                    'r' => '\r',
2050                    '\\' => '\\',
2051                    '"' => '"',
2052                    '{' => '{',
2053                    '}' => '}',
2054                    other => other,
2055                });
2056            }
2057        } else {
2058            current.push(ch);
2059        }
2060    }
2061
2062    if !current.is_empty() {
2063        parts.push(StringPart::Literal(current));
2064    }
2065
2066    if parts.is_empty() {
2067        parts.push(StringPart::Literal(String::new()));
2068    }
2069
2070    parts
2071}
2072
2073/// Parse an interpolation expression string like "person.name" or "pair.0".
2074fn parse_interp_expr(s: &str, span: &Span) -> InterpExpr {
2075    let segments: Vec<&str> = s.split('.').collect();
2076
2077    // Start with the base identifier
2078    let mut expr = InterpExpr::Ident(Ident::new(segments[0].to_string(), span.clone()));
2079
2080    // Add field accesses or tuple indices for subsequent segments
2081    for segment in &segments[1..] {
2082        if let Ok(index) = segment.parse::<usize>() {
2083            // Numeric: tuple index
2084            expr = InterpExpr::TupleIndex {
2085                base: Box::new(expr),
2086                index,
2087                span: span.clone(),
2088            };
2089        } else {
2090            // Non-numeric: field access
2091            expr = InterpExpr::FieldAccess {
2092                base: Box::new(expr),
2093                field: Ident::new(segment.to_string(), span.clone()),
2094                span: span.clone(),
2095            };
2096        }
2097    }
2098
2099    expr
2100}
2101
2102// =============================================================================
2103// Tests
2104// =============================================================================
2105
2106#[cfg(test)]
2107mod tests {
2108    use super::*;
2109    use crate::lex;
2110
2111    fn parse_str(source: &str) -> (Option<Program>, Vec<ParseError>) {
2112        let lex_result = lex(source).expect("lexing should succeed");
2113        let source_arc: Arc<str> = Arc::from(source);
2114        parse(lex_result.tokens(), source_arc)
2115    }
2116
2117    #[test]
2118    fn parse_minimal_program() {
2119        let source = r#"
2120            agent Main {
2121                on start {
2122                    emit(42);
2123                }
2124            }
2125            run Main;
2126        "#;
2127
2128        let (prog, errors) = parse_str(source);
2129        assert!(errors.is_empty(), "errors: {errors:?}");
2130        let prog = prog.expect("should parse");
2131
2132        assert_eq!(prog.agents.len(), 1);
2133        assert_eq!(prog.agents[0].name.name, "Main");
2134        assert_eq!(prog.run_agent.as_ref().unwrap().name, "Main");
2135    }
2136
2137    #[test]
2138    fn parse_agent_with_beliefs() {
2139        let source = r#"
2140            agent Researcher {
2141                topic: String
2142                max_words: Int
2143
2144                on start {
2145                    emit(self.topic);
2146                }
2147            }
2148            run Researcher;
2149        "#;
2150
2151        let (prog, errors) = parse_str(source);
2152        assert!(errors.is_empty(), "errors: {errors:?}");
2153        let prog = prog.expect("should parse");
2154
2155        assert_eq!(prog.agents[0].beliefs.len(), 2);
2156        assert_eq!(prog.agents[0].beliefs[0].name.name, "topic");
2157        assert_eq!(prog.agents[0].beliefs[1].name.name, "max_words");
2158    }
2159
2160    #[test]
2161    fn parse_multiple_handlers() {
2162        let source = r#"
2163            agent Worker {
2164                on start {
2165                    print("started");
2166                }
2167
2168                on message(msg: String) {
2169                    print(msg);
2170                }
2171
2172                on stop {
2173                    print("stopped");
2174                }
2175            }
2176            run Worker;
2177        "#;
2178
2179        let (prog, errors) = parse_str(source);
2180        assert!(errors.is_empty(), "errors: {errors:?}");
2181        let prog = prog.expect("should parse");
2182
2183        assert_eq!(prog.agents[0].handlers.len(), 3);
2184        assert_eq!(prog.agents[0].handlers[0].event, EventKind::Start);
2185        assert!(matches!(
2186            prog.agents[0].handlers[1].event,
2187            EventKind::Message { .. }
2188        ));
2189        assert_eq!(prog.agents[0].handlers[2].event, EventKind::Stop);
2190    }
2191
2192    #[test]
2193    fn parse_function() {
2194        let source = r#"
2195            fn greet(name: String) -> String {
2196                return "Hello, " ++ name;
2197            }
2198
2199            agent Main {
2200                on start {
2201                    emit(greet("World"));
2202                }
2203            }
2204            run Main;
2205        "#;
2206
2207        let (prog, errors) = parse_str(source);
2208        assert!(errors.is_empty(), "errors: {errors:?}");
2209        let prog = prog.expect("should parse");
2210
2211        assert_eq!(prog.functions.len(), 1);
2212        assert_eq!(prog.functions[0].name.name, "greet");
2213        assert_eq!(prog.functions[0].params.len(), 1);
2214    }
2215
2216    #[test]
2217    fn parse_let_statement() {
2218        let source = r#"
2219            agent Main {
2220                on start {
2221                    let x: Int = 42;
2222                    let y = "hello";
2223                    emit(x);
2224                }
2225            }
2226            run Main;
2227        "#;
2228
2229        let (prog, errors) = parse_str(source);
2230        assert!(errors.is_empty(), "errors: {errors:?}");
2231        let prog = prog.expect("should parse");
2232
2233        let stmts = &prog.agents[0].handlers[0].body.stmts;
2234        assert!(matches!(stmts[0], Stmt::Let { .. }));
2235        assert!(matches!(stmts[1], Stmt::Let { .. }));
2236    }
2237
2238    #[test]
2239    fn parse_if_statement() {
2240        let source = r#"
2241            agent Main {
2242                on start {
2243                    if true {
2244                        emit(1);
2245                    } else {
2246                        emit(2);
2247                    }
2248                }
2249            }
2250            run Main;
2251        "#;
2252
2253        let (prog, errors) = parse_str(source);
2254        assert!(errors.is_empty(), "errors: {errors:?}");
2255        let prog = prog.expect("should parse");
2256
2257        let stmts = &prog.agents[0].handlers[0].body.stmts;
2258        assert!(matches!(stmts[0], Stmt::If { .. }));
2259    }
2260
2261    #[test]
2262    fn parse_for_loop() {
2263        let source = r#"
2264            agent Main {
2265                on start {
2266                    for x in [1, 2, 3] {
2267                        print(x);
2268                    }
2269                    emit(0);
2270                }
2271            }
2272            run Main;
2273        "#;
2274
2275        let (prog, errors) = parse_str(source);
2276        assert!(errors.is_empty(), "errors: {errors:?}");
2277        let prog = prog.expect("should parse");
2278
2279        let stmts = &prog.agents[0].handlers[0].body.stmts;
2280        assert!(matches!(stmts[0], Stmt::For { .. }));
2281    }
2282
2283    #[test]
2284    fn parse_spawn_await() {
2285        let source = r#"
2286            agent Worker {
2287                name: String
2288
2289                on start {
2290                    emit(self.name);
2291                }
2292            }
2293
2294            agent Main {
2295                on start {
2296                    let w = spawn Worker { name: "test" };
2297                    let result = await w;
2298                    emit(result);
2299                }
2300            }
2301            run Main;
2302        "#;
2303
2304        let (prog, errors) = parse_str(source);
2305        assert!(errors.is_empty(), "errors: {errors:?}");
2306        prog.expect("should parse");
2307    }
2308
2309    #[test]
2310    fn parse_await_with_timeout() {
2311        let source = r#"
2312            agent Worker {
2313                on start {
2314                    emit("done");
2315                }
2316            }
2317
2318            agent Main {
2319                on start {
2320                    let w = spawn Worker {};
2321                    let result = await w timeout(5000);
2322                    emit(result);
2323                }
2324            }
2325            run Main;
2326        "#;
2327
2328        let (prog, errors) = parse_str(source);
2329        assert!(errors.is_empty(), "errors: {errors:?}");
2330        let prog = prog.expect("should parse");
2331
2332        // Find the await statement in Main's on start handler
2333        let main = &prog.agents[1];
2334        let stmts = &main.handlers[0].body.stmts;
2335        // stmts[0] is the let w = spawn...
2336        // stmts[1] is the let result = await w timeout(5000)
2337        if let Stmt::Let { value, .. } = &stmts[1] {
2338            if let Expr::Await { timeout, .. } = value {
2339                assert!(timeout.is_some(), "timeout should be present");
2340            } else {
2341                panic!("expected Await expression");
2342            }
2343        } else {
2344            panic!("expected Let statement with value");
2345        }
2346    }
2347
2348    #[test]
2349    fn parse_infer() {
2350        let source = r#"
2351            agent Main {
2352                on start {
2353                    let result = infer("What is 2+2?");
2354                    emit(result);
2355                }
2356            }
2357            run Main;
2358        "#;
2359
2360        let (prog, errors) = parse_str(source);
2361        assert!(errors.is_empty(), "errors: {errors:?}");
2362        prog.expect("should parse");
2363    }
2364
2365    #[test]
2366    fn parse_binary_precedence() {
2367        let source = r#"
2368            agent Main {
2369                on start {
2370                    let x = 2 + 3 * 4;
2371                    emit(x);
2372                }
2373            }
2374            run Main;
2375        "#;
2376
2377        let (prog, errors) = parse_str(source);
2378        assert!(errors.is_empty(), "errors: {errors:?}");
2379        let prog = prog.expect("should parse");
2380
2381        let stmts = &prog.agents[0].handlers[0].body.stmts;
2382        if let Stmt::Let { value, .. } = &stmts[0] {
2383            if let Expr::Binary { op, .. } = value {
2384                assert_eq!(*op, BinOp::Add);
2385            } else {
2386                panic!("expected binary expression");
2387            }
2388        }
2389    }
2390
2391    #[test]
2392    fn parse_string_interpolation() {
2393        let source = r#"
2394            agent Main {
2395                on start {
2396                    let name = "World";
2397                    let msg = infer("Greet {name}");
2398                    emit(msg);
2399                }
2400            }
2401            run Main;
2402        "#;
2403
2404        let (prog, errors) = parse_str(source);
2405        assert!(errors.is_empty(), "errors: {errors:?}");
2406        let prog = prog.expect("should parse");
2407
2408        let stmts = &prog.agents[0].handlers[0].body.stmts;
2409        if let Stmt::Let { value, .. } = &stmts[1] {
2410            if let Expr::Infer { template, .. } = value {
2411                assert!(template.has_interpolations());
2412            } else {
2413                panic!("expected infer expression");
2414            }
2415        }
2416    }
2417
2418    // =========================================================================
2419    // Error recovery tests
2420    // =========================================================================
2421
2422    #[test]
2423    fn recover_from_malformed_agent_continues_to_next() {
2424        // First agent has syntax error (missing type after colon), second is valid
2425        let source = r#"
2426            agent Broken {
2427                x:
2428            }
2429
2430            agent Main {
2431                on start {
2432                    emit(42);
2433                }
2434            }
2435            run Main;
2436        "#;
2437
2438        let (prog, errors) = parse_str(source);
2439        // Should have errors from the broken agent
2440        assert!(!errors.is_empty(), "should have parse errors");
2441        // But should still produce a program with the valid agent
2442        let prog = prog.expect("should produce partial AST");
2443        assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
2444    }
2445
2446    #[test]
2447    fn recover_from_mismatched_braces_in_block() {
2448        let source = r#"
2449            agent Main {
2450                on start {
2451                    let x = [1, 2, 3;
2452                    emit(42);
2453                }
2454            }
2455            run Main;
2456        "#;
2457
2458        let (prog, errors) = parse_str(source);
2459        // Should have errors but still produce an AST
2460        assert!(!errors.is_empty(), "should have parse errors");
2461        assert!(prog.is_some(), "should produce partial AST despite errors");
2462    }
2463
2464    #[test]
2465    fn parse_mod_declaration() {
2466        let source = r#"
2467            mod agents;
2468            pub mod utils;
2469
2470            agent Main {
2471                on start {
2472                    emit(42);
2473                }
2474            }
2475            run Main;
2476        "#;
2477
2478        let (prog, errors) = parse_str(source);
2479        assert!(errors.is_empty(), "errors: {errors:?}");
2480        let prog = prog.expect("should parse");
2481
2482        assert_eq!(prog.mod_decls.len(), 2);
2483        assert!(!prog.mod_decls[0].is_pub);
2484        assert_eq!(prog.mod_decls[0].name.name, "agents");
2485        assert!(prog.mod_decls[1].is_pub);
2486        assert_eq!(prog.mod_decls[1].name.name, "utils");
2487    }
2488
2489    #[test]
2490    fn parse_use_simple() {
2491        let source = r#"
2492            use agents::Researcher;
2493
2494            agent Main {
2495                on start {
2496                    emit(42);
2497                }
2498            }
2499            run Main;
2500        "#;
2501
2502        let (prog, errors) = parse_str(source);
2503        assert!(errors.is_empty(), "errors: {errors:?}");
2504        let prog = prog.expect("should parse");
2505
2506        assert_eq!(prog.use_decls.len(), 1);
2507        assert!(!prog.use_decls[0].is_pub);
2508        assert_eq!(prog.use_decls[0].path.len(), 2);
2509        assert_eq!(prog.use_decls[0].path[0].name, "agents");
2510        assert_eq!(prog.use_decls[0].path[1].name, "Researcher");
2511        assert!(matches!(prog.use_decls[0].kind, UseKind::Simple(None)));
2512    }
2513
2514    #[test]
2515    fn parse_use_with_alias() {
2516        let source = r#"
2517            use agents::Researcher as R;
2518
2519            agent Main {
2520                on start {
2521                    emit(42);
2522                }
2523            }
2524            run Main;
2525        "#;
2526
2527        let (prog, errors) = parse_str(source);
2528        assert!(errors.is_empty(), "errors: {errors:?}");
2529        let prog = prog.expect("should parse");
2530
2531        assert_eq!(prog.use_decls.len(), 1);
2532        if let UseKind::Simple(Some(alias)) = &prog.use_decls[0].kind {
2533            assert_eq!(alias.name, "R");
2534        } else {
2535            panic!("expected Simple with alias");
2536        }
2537    }
2538
2539    #[test]
2540    fn parse_pub_agent() {
2541        let source = r#"
2542            pub agent Worker {
2543                on start {
2544                    emit(42);
2545                }
2546            }
2547
2548            agent Main {
2549                on start {
2550                    emit(0);
2551                }
2552            }
2553            run Main;
2554        "#;
2555
2556        let (prog, errors) = parse_str(source);
2557        assert!(errors.is_empty(), "errors: {errors:?}");
2558        let prog = prog.expect("should parse");
2559
2560        assert_eq!(prog.agents.len(), 2);
2561        assert!(prog.agents[0].is_pub);
2562        assert_eq!(prog.agents[0].name.name, "Worker");
2563        assert!(!prog.agents[1].is_pub);
2564    }
2565
2566    #[test]
2567    fn parse_pub_function() {
2568        let source = r#"
2569            pub fn helper(x: Int) -> Int {
2570                return x;
2571            }
2572
2573            agent Main {
2574                on start {
2575                    emit(helper(42));
2576                }
2577            }
2578            run Main;
2579        "#;
2580
2581        let (prog, errors) = parse_str(source);
2582        assert!(errors.is_empty(), "errors: {errors:?}");
2583        let prog = prog.expect("should parse");
2584
2585        assert_eq!(prog.functions.len(), 1);
2586        assert!(prog.functions[0].is_pub);
2587        assert_eq!(prog.functions[0].name.name, "helper");
2588    }
2589
2590    #[test]
2591    fn parse_library_no_run() {
2592        // A library module has no `run` statement
2593        let source = r#"
2594            pub agent Worker {
2595                on start {
2596                    emit(42);
2597                }
2598            }
2599
2600            pub fn helper(x: Int) -> Int {
2601                return x;
2602            }
2603        "#;
2604
2605        let (prog, errors) = parse_str(source);
2606        assert!(errors.is_empty(), "errors: {errors:?}");
2607        let prog = prog.expect("should parse");
2608
2609        assert!(prog.run_agent.is_none());
2610        assert_eq!(prog.agents.len(), 1);
2611        assert_eq!(prog.functions.len(), 1);
2612    }
2613
2614    #[test]
2615    fn recover_multiple_errors_reported() {
2616        // Multiple errors in different places - incomplete field missing type
2617        let source = r#"
2618            agent A {
2619                x:
2620            }
2621
2622            agent Main {
2623                on start {
2624                    emit(42);
2625                }
2626            }
2627            run Main;
2628        "#;
2629
2630        let (prog, errors) = parse_str(source);
2631        // The malformed field is missing its type after `:` so should cause an error
2632        // However, with recovery the valid agent may still parse
2633        // Check that we either have errors or recovered successfully
2634        if errors.is_empty() {
2635            // Recovery succeeded - should have parsed Main agent
2636            let prog = prog.expect("should have AST with recovery");
2637            assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
2638        }
2639        // Either way, the test passes - we're testing recovery works
2640    }
2641
2642    #[test]
2643    fn parse_record_declaration() {
2644        let source = r#"
2645            record Point {
2646                x: Int,
2647                y: Int,
2648            }
2649
2650            agent Main {
2651                on start {
2652                    emit(0);
2653                }
2654            }
2655            run Main;
2656        "#;
2657
2658        let (prog, errors) = parse_str(source);
2659        assert!(errors.is_empty(), "errors: {errors:?}");
2660        let prog = prog.expect("should parse");
2661
2662        assert_eq!(prog.records.len(), 1);
2663        assert!(!prog.records[0].is_pub);
2664        assert_eq!(prog.records[0].name.name, "Point");
2665        assert_eq!(prog.records[0].fields.len(), 2);
2666        assert_eq!(prog.records[0].fields[0].name.name, "x");
2667        assert_eq!(prog.records[0].fields[1].name.name, "y");
2668    }
2669
2670    #[test]
2671    fn parse_pub_record() {
2672        let source = r#"
2673            pub record Config {
2674                host: String,
2675                port: Int,
2676            }
2677
2678            agent Main {
2679                on start { emit(0); }
2680            }
2681            run Main;
2682        "#;
2683
2684        let (prog, errors) = parse_str(source);
2685        assert!(errors.is_empty(), "errors: {errors:?}");
2686        let prog = prog.expect("should parse");
2687
2688        assert_eq!(prog.records.len(), 1);
2689        assert!(prog.records[0].is_pub);
2690        assert_eq!(prog.records[0].name.name, "Config");
2691    }
2692
2693    #[test]
2694    fn parse_enum_declaration() {
2695        let source = r#"
2696            enum Status {
2697                Active,
2698                Pending,
2699                Done,
2700            }
2701
2702            agent Main {
2703                on start {
2704                    emit(0);
2705                }
2706            }
2707            run Main;
2708        "#;
2709
2710        let (prog, errors) = parse_str(source);
2711        assert!(errors.is_empty(), "errors: {errors:?}");
2712        let prog = prog.expect("should parse");
2713
2714        assert_eq!(prog.enums.len(), 1);
2715        assert!(!prog.enums[0].is_pub);
2716        assert_eq!(prog.enums[0].name.name, "Status");
2717        assert_eq!(prog.enums[0].variants.len(), 3);
2718        assert_eq!(prog.enums[0].variants[0].name.name, "Active");
2719        assert_eq!(prog.enums[0].variants[1].name.name, "Pending");
2720        assert_eq!(prog.enums[0].variants[2].name.name, "Done");
2721    }
2722
2723    #[test]
2724    fn parse_pub_enum() {
2725        let source = r#"
2726            pub enum Priority { High, Medium, Low }
2727
2728            agent Main {
2729                on start { emit(0); }
2730            }
2731            run Main;
2732        "#;
2733
2734        let (prog, errors) = parse_str(source);
2735        assert!(errors.is_empty(), "errors: {errors:?}");
2736        let prog = prog.expect("should parse");
2737
2738        assert_eq!(prog.enums.len(), 1);
2739        assert!(prog.enums[0].is_pub);
2740        assert_eq!(prog.enums[0].name.name, "Priority");
2741    }
2742
2743    #[test]
2744    fn parse_const_declaration() {
2745        let source = r#"
2746            const MAX_RETRIES: Int = 3;
2747
2748            agent Main {
2749                on start {
2750                    emit(0);
2751                }
2752            }
2753            run Main;
2754        "#;
2755
2756        let (prog, errors) = parse_str(source);
2757        assert!(errors.is_empty(), "errors: {errors:?}");
2758        let prog = prog.expect("should parse");
2759
2760        assert_eq!(prog.consts.len(), 1);
2761        assert!(!prog.consts[0].is_pub);
2762        assert_eq!(prog.consts[0].name.name, "MAX_RETRIES");
2763        assert!(matches!(prog.consts[0].ty, TypeExpr::Int));
2764    }
2765
2766    #[test]
2767    fn parse_pub_const() {
2768        let source = r#"
2769            pub const API_URL: String = "https://api.example.com";
2770
2771            agent Main {
2772                on start { emit(0); }
2773            }
2774            run Main;
2775        "#;
2776
2777        let (prog, errors) = parse_str(source);
2778        assert!(errors.is_empty(), "errors: {errors:?}");
2779        let prog = prog.expect("should parse");
2780
2781        assert_eq!(prog.consts.len(), 1);
2782        assert!(prog.consts[0].is_pub);
2783        assert_eq!(prog.consts[0].name.name, "API_URL");
2784    }
2785
2786    #[test]
2787    fn parse_multiple_type_declarations() {
2788        let source = r#"
2789            record Point { x: Int, y: Int }
2790            enum Color { Red, Green, Blue }
2791            const ORIGIN_X: Int = 0;
2792
2793            agent Main {
2794                on start { emit(0); }
2795            }
2796            run Main;
2797        "#;
2798
2799        let (prog, errors) = parse_str(source);
2800        assert!(errors.is_empty(), "errors: {errors:?}");
2801        let prog = prog.expect("should parse");
2802
2803        assert_eq!(prog.records.len(), 1);
2804        assert_eq!(prog.enums.len(), 1);
2805        assert_eq!(prog.consts.len(), 1);
2806    }
2807
2808    #[test]
2809    fn parse_match_expression() {
2810        let source = r#"
2811            enum Status { Active, Pending, Done }
2812
2813            agent Main {
2814                on start {
2815                    let s: Int = match Active {
2816                        Active => 1,
2817                        Pending => 2,
2818                        Done => 3,
2819                    };
2820                    emit(s);
2821                }
2822            }
2823            run Main;
2824        "#;
2825
2826        let (prog, errors) = parse_str(source);
2827        assert!(errors.is_empty(), "errors: {errors:?}");
2828        let prog = prog.expect("should parse");
2829
2830        // Check the agent parsed
2831        assert_eq!(prog.agents.len(), 1);
2832        // Match is in the handler
2833        let handler = &prog.agents[0].handlers[0];
2834        let stmt = &handler.body.stmts[0];
2835        if let Stmt::Let { value, .. } = stmt {
2836            assert!(matches!(value, Expr::Match { .. }));
2837        } else {
2838            panic!("expected let statement with match");
2839        }
2840    }
2841
2842    #[test]
2843    fn parse_match_with_wildcard() {
2844        let source = r#"
2845            agent Main {
2846                on start {
2847                    let x = 5;
2848                    let result = match x {
2849                        1 => 10,
2850                        2 => 20,
2851                        _ => 0,
2852                    };
2853                    emit(result);
2854                }
2855            }
2856            run Main;
2857        "#;
2858
2859        let (prog, errors) = parse_str(source);
2860        assert!(errors.is_empty(), "errors: {errors:?}");
2861        let prog = prog.expect("should parse");
2862
2863        assert_eq!(prog.agents.len(), 1);
2864    }
2865
2866    #[test]
2867    fn parse_record_construction() {
2868        let source = r#"
2869            record Point { x: Int, y: Int }
2870
2871            agent Main {
2872                on start {
2873                    let p = Point { x: 10, y: 20 };
2874                    emit(0);
2875                }
2876            }
2877            run Main;
2878        "#;
2879
2880        let (prog, errors) = parse_str(source);
2881        assert!(errors.is_empty(), "errors: {errors:?}");
2882        let prog = prog.expect("should parse");
2883
2884        assert_eq!(prog.records.len(), 1);
2885        assert_eq!(prog.agents.len(), 1);
2886
2887        // Check the let statement has a record construction
2888        let handler = &prog.agents[0].handlers[0];
2889        let stmt = &handler.body.stmts[0];
2890        if let Stmt::Let { value, .. } = stmt {
2891            if let Expr::RecordConstruct { name, fields, .. } = value {
2892                assert_eq!(name.name, "Point");
2893                assert_eq!(fields.len(), 2);
2894                assert_eq!(fields[0].name.name, "x");
2895                assert_eq!(fields[1].name.name, "y");
2896            } else {
2897                panic!("expected RecordConstruct");
2898            }
2899        } else {
2900            panic!("expected let statement");
2901        }
2902    }
2903
2904    #[test]
2905    fn parse_match_with_qualified_variant() {
2906        let source = r#"
2907            enum Status { Active, Pending }
2908
2909            fn get_status() -> Int {
2910                return 1;
2911            }
2912
2913            agent Main {
2914                on start {
2915                    let s = get_status();
2916                    let result = match s {
2917                        Status::Active => 1,
2918                        Status::Pending => 0,
2919                    };
2920                    emit(result);
2921                }
2922            }
2923            run Main;
2924        "#;
2925
2926        let (prog, errors) = parse_str(source);
2927        assert!(errors.is_empty(), "errors: {errors:?}");
2928        let prog = prog.expect("should parse");
2929
2930        assert_eq!(prog.enums.len(), 1);
2931        assert_eq!(prog.agents.len(), 1);
2932    }
2933
2934    #[test]
2935    fn parse_field_access() {
2936        let source = r#"
2937            record Point { x: Int, y: Int }
2938
2939            agent Main {
2940                on start {
2941                    let p = Point { x: 10, y: 20 };
2942                    let x_val = p.x;
2943                    let y_val = p.y;
2944                    emit(x_val);
2945                }
2946            }
2947            run Main;
2948        "#;
2949
2950        let (prog, errors) = parse_str(source);
2951        assert!(errors.is_empty(), "errors: {errors:?}");
2952        let prog = prog.expect("should parse");
2953
2954        assert_eq!(prog.records.len(), 1);
2955        assert_eq!(prog.agents.len(), 1);
2956
2957        // Check the field access
2958        let handler = &prog.agents[0].handlers[0];
2959        let stmt = &handler.body.stmts[1]; // p.x assignment
2960        if let Stmt::Let { value, .. } = stmt {
2961            if let Expr::FieldAccess { field, .. } = value {
2962                assert_eq!(field.name, "x");
2963            } else {
2964                panic!("expected FieldAccess");
2965            }
2966        } else {
2967            panic!("expected let statement");
2968        }
2969    }
2970
2971    #[test]
2972    fn parse_chained_field_access() {
2973        let source = r#"
2974            record Inner { val: Int }
2975            record Outer { inner: Inner }
2976
2977            agent Main {
2978                on start {
2979                    let inner = Inner { val: 42 };
2980                    let outer = Outer { inner: inner };
2981                    let v = outer.inner.val;
2982                    emit(v);
2983                }
2984            }
2985            run Main;
2986        "#;
2987
2988        let (prog, errors) = parse_str(source);
2989        assert!(errors.is_empty(), "errors: {errors:?}");
2990        let prog = prog.expect("should parse");
2991
2992        assert_eq!(prog.records.len(), 2);
2993        assert_eq!(prog.agents.len(), 1);
2994
2995        // Check the chained field access: outer.inner.val
2996        let handler = &prog.agents[0].handlers[0];
2997        let stmt = &handler.body.stmts[2]; // outer.inner.val assignment
2998        if let Stmt::Let { value, .. } = stmt {
2999            if let Expr::FieldAccess {
3000                object, field: val, ..
3001            } = value
3002            {
3003                assert_eq!(val.name, "val");
3004                // object should be outer.inner
3005                if let Expr::FieldAccess { field: inner, .. } = object.as_ref() {
3006                    assert_eq!(inner.name, "inner");
3007                } else {
3008                    panic!("expected nested FieldAccess");
3009                }
3010            } else {
3011                panic!("expected FieldAccess");
3012            }
3013        } else {
3014            panic!("expected let statement");
3015        }
3016    }
3017
3018    // =========================================================================
3019    // RFC-0006: Message passing tests
3020    // =========================================================================
3021
3022    #[test]
3023    fn parse_loop_break() {
3024        let source = r#"
3025            agent Main {
3026                on start {
3027                    let count = 0;
3028                    loop {
3029                        count = count + 1;
3030                        if count > 5 {
3031                            break;
3032                        }
3033                    }
3034                    emit(count);
3035                }
3036            }
3037            run Main;
3038        "#;
3039
3040        let (prog, errors) = parse_str(source);
3041        assert!(errors.is_empty(), "errors: {errors:?}");
3042        let prog = prog.expect("should parse");
3043
3044        assert_eq!(prog.agents.len(), 1);
3045        let handler = &prog.agents[0].handlers[0];
3046        // Check loop statement exists
3047        let loop_stmt = &handler.body.stmts[1];
3048        assert!(matches!(loop_stmt, Stmt::Loop { .. }));
3049        // Check break is inside the loop
3050        if let Stmt::Loop { body, .. } = loop_stmt {
3051            let if_stmt = &body.stmts[1];
3052            if let Stmt::If { then_block, .. } = if_stmt {
3053                assert!(matches!(then_block.stmts[0], Stmt::Break { .. }));
3054            } else {
3055                panic!("expected if statement");
3056            }
3057        }
3058    }
3059
3060    #[test]
3061    fn parse_agent_receives() {
3062        let source = r#"
3063            enum WorkerMsg {
3064                Task,
3065                Shutdown,
3066            }
3067
3068            agent Worker receives WorkerMsg {
3069                id: Int
3070
3071                on start {
3072                    emit(0);
3073                }
3074            }
3075
3076            agent Main {
3077                on start {
3078                    emit(0);
3079                }
3080            }
3081            run Main;
3082        "#;
3083
3084        let (prog, errors) = parse_str(source);
3085        assert!(errors.is_empty(), "errors: {errors:?}");
3086        let prog = prog.expect("should parse");
3087
3088        assert_eq!(prog.agents.len(), 2);
3089
3090        // Worker should have receives clause
3091        let worker = &prog.agents[0];
3092        assert_eq!(worker.name.name, "Worker");
3093        assert!(worker.receives.is_some());
3094        if let Some(TypeExpr::Named(name)) = &worker.receives {
3095            assert_eq!(name.name, "WorkerMsg");
3096        } else {
3097            panic!("expected named type for receives");
3098        }
3099
3100        // Main should not have receives
3101        let main = &prog.agents[1];
3102        assert_eq!(main.name.name, "Main");
3103        assert!(main.receives.is_none());
3104    }
3105
3106    #[test]
3107    fn parse_receive_expression() {
3108        let source = r#"
3109            enum Msg { Ping }
3110
3111            agent Worker receives Msg {
3112                on start {
3113                    let msg = receive();
3114                    emit(0);
3115                }
3116            }
3117
3118            agent Main {
3119                on start { emit(0); }
3120            }
3121            run Main;
3122        "#;
3123
3124        let (prog, errors) = parse_str(source);
3125        assert!(errors.is_empty(), "errors: {errors:?}");
3126        let prog = prog.expect("should parse");
3127
3128        // Find Worker agent
3129        let worker = prog
3130            .agents
3131            .iter()
3132            .find(|a| a.name.name == "Worker")
3133            .unwrap();
3134        let handler = &worker.handlers[0];
3135        let stmt = &handler.body.stmts[0];
3136
3137        if let Stmt::Let { value, .. } = stmt {
3138            assert!(matches!(value, Expr::Receive { .. }));
3139        } else {
3140            panic!("expected let with receive");
3141        }
3142    }
3143
3144    #[test]
3145    fn parse_message_passing_full() {
3146        let source = r#"
3147            enum WorkerMsg {
3148                Task,
3149                Shutdown,
3150            }
3151
3152            agent Worker receives WorkerMsg {
3153                id: Int
3154
3155                on start {
3156                    let msg = receive();
3157                    let result = match msg {
3158                        Task => 1,
3159                        Shutdown => 0,
3160                    };
3161                    emit(result);
3162                }
3163            }
3164
3165            agent Main {
3166                on start {
3167                    let w = spawn Worker { id: 1 };
3168                    send(w, Task);
3169                    send(w, Shutdown);
3170                    await w;
3171                    emit(0);
3172                }
3173            }
3174            run Main;
3175        "#;
3176
3177        let (prog, errors) = parse_str(source);
3178        assert!(errors.is_empty(), "errors: {errors:?}");
3179        let prog = prog.expect("should parse");
3180
3181        assert_eq!(prog.enums.len(), 1);
3182        assert_eq!(prog.agents.len(), 2);
3183
3184        // Check Worker has receives
3185        let worker = prog
3186            .agents
3187            .iter()
3188            .find(|a| a.name.name == "Worker")
3189            .unwrap();
3190        assert!(worker.receives.is_some());
3191    }
3192
3193    // =========================================================================
3194    // RFC-0007: Error handling tests
3195    // =========================================================================
3196
3197    #[test]
3198    fn parse_fallible_function() {
3199        let source = r#"
3200            fn get_data(url: String) -> String fails {
3201                return infer("Get data from {url}" -> String);
3202            }
3203
3204            agent Main {
3205                on start { emit(0); }
3206            }
3207            run Main;
3208        "#;
3209
3210        let (prog, errors) = parse_str(source);
3211        assert!(errors.is_empty(), "errors: {errors:?}");
3212        let prog = prog.expect("should parse");
3213
3214        assert_eq!(prog.functions.len(), 1);
3215        assert!(prog.functions[0].is_fallible);
3216    }
3217
3218    #[test]
3219    fn parse_try_expression() {
3220        let source = r#"
3221            fn fallible() -> Int fails { return 42; }
3222
3223            agent Main {
3224                on start {
3225                    let x = try fallible();
3226                    emit(x);
3227                }
3228            }
3229            run Main;
3230        "#;
3231
3232        let (prog, errors) = parse_str(source);
3233        assert!(errors.is_empty(), "errors: {errors:?}");
3234        let prog = prog.expect("should parse");
3235
3236        // Find the let statement and check it contains a Try expression
3237        let handler = &prog.agents[0].handlers[0];
3238        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3239            assert!(matches!(value, Expr::Try { .. }));
3240        } else {
3241            panic!("expected Let statement");
3242        }
3243    }
3244
3245    #[test]
3246    fn parse_catch_expression() {
3247        let source = r#"
3248            fn fallible() -> Int fails { return 42; }
3249
3250            agent Main {
3251                on start {
3252                    let x = fallible() catch { 0 };
3253                    emit(x);
3254                }
3255            }
3256            run Main;
3257        "#;
3258
3259        let (prog, errors) = parse_str(source);
3260        assert!(errors.is_empty(), "errors: {errors:?}");
3261        let prog = prog.expect("should parse");
3262
3263        // Find the let statement and check it contains a Catch expression
3264        let handler = &prog.agents[0].handlers[0];
3265        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3266            if let Expr::Catch { error_bind, .. } = value {
3267                assert!(error_bind.is_none());
3268            } else {
3269                panic!("expected Catch expression");
3270            }
3271        } else {
3272            panic!("expected Let statement");
3273        }
3274    }
3275
3276    #[test]
3277    fn parse_catch_with_error_binding() {
3278        let source = r#"
3279            fn fallible() -> Int fails { return 42; }
3280
3281            agent Main {
3282                on start {
3283                    let x = fallible() catch(e) { 0 };
3284                    emit(x);
3285                }
3286            }
3287            run Main;
3288        "#;
3289
3290        let (prog, errors) = parse_str(source);
3291        assert!(errors.is_empty(), "errors: {errors:?}");
3292        let prog = prog.expect("should parse");
3293
3294        // Find the let statement and check it contains a Catch expression with binding
3295        let handler = &prog.agents[0].handlers[0];
3296        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3297            if let Expr::Catch { error_bind, .. } = value {
3298                assert!(error_bind.is_some());
3299                assert_eq!(error_bind.as_ref().unwrap().name, "e");
3300            } else {
3301                panic!("expected Catch expression");
3302            }
3303        } else {
3304            panic!("expected Let statement");
3305        }
3306    }
3307
3308    #[test]
3309    fn parse_fail_expression() {
3310        let source = r#"
3311            agent Main {
3312                on start {
3313                    fail "something went wrong";
3314                }
3315                on error(e) {
3316                    emit(0);
3317                }
3318            }
3319            run Main;
3320        "#;
3321
3322        let (prog, errors) = parse_str(source);
3323        assert!(errors.is_empty(), "errors: {errors:?}");
3324        let prog = prog.expect("should parse");
3325
3326        // Find the fail statement
3327        let handler = &prog.agents[0].handlers[0];
3328        if let Stmt::Expr { expr, .. } = &handler.body.stmts[0] {
3329            if let Expr::Fail { error, .. } = expr {
3330                assert!(matches!(**error, Expr::Literal { .. }));
3331            } else {
3332                panic!("expected Fail expression, got {expr:?}");
3333            }
3334        } else {
3335            panic!("expected Expr statement");
3336        }
3337    }
3338
3339    #[test]
3340    fn parse_retry_expression() {
3341        let source = r#"
3342            agent Main {
3343                topic: String
3344
3345                on start {
3346                    let result = retry(3) {
3347                        try infer("Summarize: {self.topic}")
3348                    } catch { "fallback" };
3349                    emit(result);
3350                }
3351
3352                on error(e) {
3353                    emit("");
3354                }
3355            }
3356            run Main;
3357        "#;
3358
3359        let (prog, errors) = parse_str(source);
3360        assert!(errors.is_empty(), "errors: {errors:?}");
3361        let prog = prog.expect("should parse");
3362
3363        // Find the let statement with retry
3364        let handler = &prog.agents[0].handlers[0];
3365        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3366            // Should be Catch wrapping Retry
3367            if let Expr::Catch { expr, .. } = value {
3368                if let Expr::Retry { count, delay, .. } = expr.as_ref() {
3369                    assert!(matches!(**count, Expr::Literal { .. }));
3370                    assert!(delay.is_none());
3371                } else {
3372                    panic!("expected Retry expression");
3373                }
3374            } else {
3375                panic!("expected Catch expression");
3376            }
3377        } else {
3378            panic!("expected Let statement");
3379        }
3380    }
3381
3382    #[test]
3383    fn parse_retry_with_delay() {
3384        let source = r#"
3385            agent Main {
3386                on start {
3387                    let result = retry(3, delay: 1000) {
3388                        42
3389                    } catch { 0 };
3390                    emit(result);
3391                }
3392            }
3393            run Main;
3394        "#;
3395
3396        let (prog, errors) = parse_str(source);
3397        assert!(errors.is_empty(), "errors: {errors:?}");
3398        let prog = prog.expect("should parse");
3399
3400        // Find the let statement with retry
3401        let handler = &prog.agents[0].handlers[0];
3402        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3403            if let Expr::Catch { expr, .. } = value {
3404                if let Expr::Retry { delay, .. } = expr.as_ref() {
3405                    assert!(delay.is_some());
3406                } else {
3407                    panic!("expected Retry expression");
3408                }
3409            } else {
3410                panic!("expected Catch expression");
3411            }
3412        } else {
3413            panic!("expected Let statement");
3414        }
3415    }
3416
3417    #[test]
3418    fn parse_on_error_handler() {
3419        let source = r#"
3420            agent Main {
3421                on start {
3422                    emit(0);
3423                }
3424
3425                on error(e) {
3426                    emit(1);
3427                }
3428            }
3429            run Main;
3430        "#;
3431
3432        let (prog, errors) = parse_str(source);
3433        assert!(errors.is_empty(), "errors: {errors:?}");
3434        let prog = prog.expect("should parse");
3435
3436        assert_eq!(prog.agents.len(), 1);
3437        assert_eq!(prog.agents[0].handlers.len(), 2);
3438
3439        // Check the error handler
3440        let error_handler = prog.agents[0]
3441            .handlers
3442            .iter()
3443            .find(|h| matches!(h.event, EventKind::Error { .. }));
3444        assert!(error_handler.is_some());
3445
3446        if let EventKind::Error { param_name } = &error_handler.unwrap().event {
3447            assert_eq!(param_name.name, "e");
3448        } else {
3449            panic!("expected Error event kind");
3450        }
3451    }
3452
3453    // =========================================================================
3454    // RFC-0009: Closures and function types
3455    // =========================================================================
3456
3457    #[test]
3458    fn parse_fn_type() {
3459        let source = r#"
3460            fn apply(f: Fn(Int) -> Int, x: Int) -> Int {
3461                return f(x);
3462            }
3463
3464            agent Main {
3465                on start {
3466                    emit(0);
3467                }
3468            }
3469            run Main;
3470        "#;
3471
3472        let (prog, errors) = parse_str(source);
3473        assert!(errors.is_empty(), "errors: {errors:?}");
3474        let prog = prog.expect("should parse");
3475
3476        assert_eq!(prog.functions.len(), 1);
3477        let func = &prog.functions[0];
3478        assert_eq!(func.name.name, "apply");
3479        assert_eq!(func.params.len(), 2);
3480
3481        // Check first param is Fn(Int) -> Int
3482        if let TypeExpr::Fn(params, ret) = &func.params[0].ty {
3483            assert_eq!(params.len(), 1);
3484            assert!(matches!(params[0], TypeExpr::Int));
3485            assert!(matches!(ret.as_ref(), TypeExpr::Int));
3486        } else {
3487            panic!("expected Fn type for first param");
3488        }
3489    }
3490
3491    #[test]
3492    fn parse_closure_with_params() {
3493        let source = r#"
3494            agent Main {
3495                on start {
3496                    let f = |x: Int| x + 1;
3497                    emit(0);
3498                }
3499            }
3500            run Main;
3501        "#;
3502
3503        let (prog, errors) = parse_str(source);
3504        assert!(errors.is_empty(), "errors: {errors:?}");
3505        let prog = prog.expect("should parse");
3506
3507        // Find the let statement in the on start handler
3508        let handler = &prog.agents[0].handlers[0];
3509        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3510            if let Expr::Closure { params, body, .. } = value {
3511                assert_eq!(params.len(), 1);
3512                assert_eq!(params[0].name.name, "x");
3513                assert!(matches!(&params[0].ty, Some(TypeExpr::Int)));
3514
3515                // Body should be a binary expression
3516                assert!(matches!(body.as_ref(), Expr::Binary { .. }));
3517            } else {
3518                panic!("expected closure expression");
3519            }
3520        } else {
3521            panic!("expected let statement");
3522        }
3523    }
3524
3525    #[test]
3526    fn parse_closure_empty_params() {
3527        let source = r#"
3528            agent Main {
3529                on start {
3530                    let f = || 42;
3531                    emit(0);
3532                }
3533            }
3534            run Main;
3535        "#;
3536
3537        let (prog, errors) = parse_str(source);
3538        assert!(errors.is_empty(), "errors: {errors:?}");
3539        let prog = prog.expect("should parse");
3540
3541        // Find the let statement
3542        let handler = &prog.agents[0].handlers[0];
3543        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3544            if let Expr::Closure { params, body, .. } = value {
3545                assert!(params.is_empty());
3546
3547                // Body should be a literal
3548                assert!(matches!(body.as_ref(), Expr::Literal { .. }));
3549            } else {
3550                panic!("expected closure expression");
3551            }
3552        } else {
3553            panic!("expected let statement");
3554        }
3555    }
3556
3557    #[test]
3558    fn parse_closure_multiple_params() {
3559        let source = r#"
3560            agent Main {
3561                on start {
3562                    let add = |x: Int, y: Int| x + y;
3563                    emit(0);
3564                }
3565            }
3566            run Main;
3567        "#;
3568
3569        let (prog, errors) = parse_str(source);
3570        assert!(errors.is_empty(), "errors: {errors:?}");
3571        let prog = prog.expect("should parse");
3572
3573        let handler = &prog.agents[0].handlers[0];
3574        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3575            if let Expr::Closure { params, .. } = value {
3576                assert_eq!(params.len(), 2);
3577                assert_eq!(params[0].name.name, "x");
3578                assert_eq!(params[1].name.name, "y");
3579            } else {
3580                panic!("expected closure expression");
3581            }
3582        } else {
3583            panic!("expected let statement");
3584        }
3585    }
3586
3587    #[test]
3588    fn parse_fn_type_multiarg() {
3589        let source = r#"
3590            fn fold_left(f: Fn(Int, Int) -> Int, init: Int) -> Int {
3591                return init;
3592            }
3593
3594            agent Main {
3595                on start {
3596                    emit(0);
3597                }
3598            }
3599            run Main;
3600        "#;
3601
3602        let (prog, errors) = parse_str(source);
3603        assert!(errors.is_empty(), "errors: {errors:?}");
3604        let prog = prog.expect("should parse");
3605
3606        // Check Fn(Int, Int) -> Int
3607        if let TypeExpr::Fn(params, ret) = &prog.functions[0].params[0].ty {
3608            assert_eq!(params.len(), 2);
3609            assert!(matches!(params[0], TypeExpr::Int));
3610            assert!(matches!(params[1], TypeExpr::Int));
3611            assert!(matches!(ret.as_ref(), TypeExpr::Int));
3612        } else {
3613            panic!("expected Fn type");
3614        }
3615    }
3616
3617    #[test]
3618    fn parse_tuple_literal() {
3619        let source = r#"
3620            agent Main {
3621                on start {
3622                    let t = (1, 2);
3623                    emit(0);
3624                }
3625            }
3626            run Main;
3627        "#;
3628
3629        let (prog, errors) = parse_str(source);
3630        assert!(errors.is_empty(), "errors: {errors:?}");
3631        let prog = prog.expect("should parse");
3632
3633        let handler = &prog.agents[0].handlers[0];
3634        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3635            if let Expr::Tuple { elements, .. } = value {
3636                assert_eq!(elements.len(), 2);
3637            } else {
3638                panic!("expected tuple expression, got {:?}", value);
3639            }
3640        } else {
3641            panic!("expected let statement");
3642        }
3643    }
3644
3645    // =========================================================================
3646    // RFC-0011: Tool support tests
3647    // =========================================================================
3648
3649    #[test]
3650    fn parse_tool_declaration() {
3651        let source = r#"
3652            tool Http {
3653                fn get(url: String) -> Result<String, String>
3654                fn post(url: String, body: String) -> Result<String, String>
3655            }
3656            agent Main {
3657                on start { emit(0); }
3658            }
3659            run Main;
3660        "#;
3661
3662        let (prog, errors) = parse_str(source);
3663        assert!(errors.is_empty(), "errors: {errors:?}");
3664        let prog = prog.expect("should parse");
3665
3666        assert_eq!(prog.tools.len(), 1);
3667        assert_eq!(prog.tools[0].name.name, "Http");
3668        assert_eq!(prog.tools[0].functions.len(), 2);
3669        assert_eq!(prog.tools[0].functions[0].name.name, "get");
3670        assert_eq!(prog.tools[0].functions[1].name.name, "post");
3671    }
3672
3673    #[test]
3674    fn parse_pub_tool_declaration() {
3675        let source = r#"
3676            pub tool Database {
3677                fn query(sql: String) -> Result<List<String>, String>
3678            }
3679            agent Main {
3680                on start { emit(0); }
3681            }
3682            run Main;
3683        "#;
3684
3685        let (prog, errors) = parse_str(source);
3686        assert!(errors.is_empty(), "errors: {errors:?}");
3687        let prog = prog.expect("should parse");
3688
3689        assert!(prog.tools[0].is_pub);
3690        assert_eq!(prog.tools[0].name.name, "Database");
3691    }
3692
3693    #[test]
3694    fn parse_agent_with_tool_use() {
3695        let source = r#"
3696            agent Fetcher {
3697                use Http
3698
3699                url: String
3700
3701                on start {
3702                    emit(0);
3703                }
3704            }
3705            run Fetcher;
3706        "#;
3707
3708        let (prog, errors) = parse_str(source);
3709        assert!(errors.is_empty(), "errors: {errors:?}");
3710        let prog = prog.expect("should parse");
3711
3712        assert_eq!(prog.agents[0].tool_uses.len(), 1);
3713        assert_eq!(prog.agents[0].tool_uses[0].name, "Http");
3714        assert_eq!(prog.agents[0].beliefs.len(), 1);
3715    }
3716
3717    #[test]
3718    fn parse_agent_with_multiple_tool_uses() {
3719        let source = r#"
3720            agent Pipeline {
3721                use Http, Fs
3722
3723                on start {
3724                    emit(0);
3725                }
3726            }
3727            run Pipeline;
3728        "#;
3729
3730        let (prog, errors) = parse_str(source);
3731        assert!(errors.is_empty(), "errors: {errors:?}");
3732        let prog = prog.expect("should parse");
3733
3734        assert_eq!(prog.agents[0].tool_uses.len(), 2);
3735        assert_eq!(prog.agents[0].tool_uses[0].name, "Http");
3736        assert_eq!(prog.agents[0].tool_uses[1].name, "Fs");
3737    }
3738
3739    #[test]
3740    fn parse_tool_call_expression() {
3741        let source = r#"
3742            agent Fetcher {
3743                use Http
3744
3745                on start {
3746                    let response = Http.get("https://example.com");
3747                    emit(0);
3748                }
3749            }
3750            run Fetcher;
3751        "#;
3752
3753        let (prog, errors) = parse_str(source);
3754        assert!(errors.is_empty(), "errors: {errors:?}");
3755        let prog = prog.expect("should parse");
3756
3757        let handler = &prog.agents[0].handlers[0];
3758        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3759            if let Expr::ToolCall {
3760                tool,
3761                function,
3762                args,
3763                ..
3764            } = value
3765            {
3766                assert_eq!(tool.name, "Http");
3767                assert_eq!(function.name, "get");
3768                assert_eq!(args.len(), 1);
3769            } else {
3770                panic!("expected ToolCall expression, got {:?}", value);
3771            }
3772        } else {
3773            panic!("expected let statement");
3774        }
3775    }
3776
3777    #[test]
3778    fn parse_tool_call_with_multiple_args() {
3779        let source = r#"
3780            agent Writer {
3781                use Fs
3782
3783                on start {
3784                    let result = Fs.write("/tmp/test.txt", "hello world");
3785                    emit(0);
3786                }
3787            }
3788            run Writer;
3789        "#;
3790
3791        let (prog, errors) = parse_str(source);
3792        assert!(errors.is_empty(), "errors: {errors:?}");
3793        let prog = prog.expect("should parse");
3794
3795        let handler = &prog.agents[0].handlers[0];
3796        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
3797            if let Expr::ToolCall { args, .. } = value {
3798                assert_eq!(args.len(), 2);
3799            } else {
3800                panic!("expected ToolCall expression, got {:?}", value);
3801            }
3802        } else {
3803            panic!("expected let statement");
3804        }
3805    }
3806
3807    #[test]
3808    fn parse_string_interp_with_field_access() {
3809        let source = r#"
3810            record Person { name: String }
3811            agent Main {
3812                on start {
3813                    let p = Person { name: "Alice" };
3814                    print("Hello, {p.name}!");
3815                    emit(0);
3816                }
3817            }
3818            run Main;
3819        "#;
3820
3821        let (prog, errors) = parse_str(source);
3822        assert!(errors.is_empty(), "errors: {errors:?}");
3823        let prog = prog.expect("should parse");
3824
3825        // Find the print statement with interpolation
3826        let handler = &prog.agents[0].handlers[0];
3827        if let Stmt::Expr { expr, .. } = &handler.body.stmts[1] {
3828            if let Expr::Call { args, .. } = expr {
3829                if let Expr::StringInterp { template, .. } = &args[0] {
3830                    assert!(template.has_interpolations());
3831                    let interps: Vec<_> = template.interpolations().collect();
3832                    assert_eq!(interps.len(), 1);
3833                    // Should be a field access: p.name
3834                    match interps[0] {
3835                        InterpExpr::FieldAccess { base, field, .. } => {
3836                            assert_eq!(base.base_ident().name, "p");
3837                            assert_eq!(field.name, "name");
3838                        }
3839                        _ => panic!("expected FieldAccess, got {:?}", interps[0]),
3840                    }
3841                } else {
3842                    panic!("expected StringInterp");
3843                }
3844            } else {
3845                panic!("expected Call");
3846            }
3847        } else {
3848            panic!("expected Expr statement");
3849        }
3850    }
3851
3852    #[test]
3853    fn parse_string_interp_with_tuple_index() {
3854        let source = r#"
3855            agent Main {
3856                on start {
3857                    let pair = (1, 2);
3858                    print("First: {pair.0}");
3859                    emit(0);
3860                }
3861            }
3862            run Main;
3863        "#;
3864
3865        let (prog, errors) = parse_str(source);
3866        assert!(errors.is_empty(), "errors: {errors:?}");
3867        let prog = prog.expect("should parse");
3868
3869        let handler = &prog.agents[0].handlers[0];
3870        if let Stmt::Expr { expr, .. } = &handler.body.stmts[1] {
3871            if let Expr::Call { args, .. } = expr {
3872                if let Expr::StringInterp { template, .. } = &args[0] {
3873                    let interps: Vec<_> = template.interpolations().collect();
3874                    assert_eq!(interps.len(), 1);
3875                    match interps[0] {
3876                        InterpExpr::TupleIndex { base, index, .. } => {
3877                            assert_eq!(base.base_ident().name, "pair");
3878                            assert_eq!(*index, 0);
3879                        }
3880                        _ => panic!("expected TupleIndex, got {:?}", interps[0]),
3881                    }
3882                } else {
3883                    panic!("expected StringInterp");
3884                }
3885            } else {
3886                panic!("expected Call");
3887            }
3888        } else {
3889            panic!("expected Expr statement");
3890        }
3891    }
3892
3893    #[test]
3894    fn parse_mock_tool_with_fail() {
3895        let source = r#"
3896            test "mock tool fail" {
3897                mock tool Http.get -> fail("network error");
3898            }
3899        "#;
3900
3901        let (prog, errors) = parse_str(source);
3902        assert!(errors.is_empty(), "errors: {errors:?}");
3903        let prog = prog.expect("should parse");
3904
3905        let test = &prog.tests[0];
3906        assert_eq!(test.body.stmts.len(), 1);
3907
3908        if let Stmt::MockTool {
3909            tool_name,
3910            fn_name,
3911            value,
3912            ..
3913        } = &test.body.stmts[0]
3914        {
3915            assert_eq!(tool_name.name, "Http");
3916            assert_eq!(fn_name.name, "get");
3917            assert!(
3918                matches!(value, MockValue::Fail(_)),
3919                "expected MockValue::Fail, got {:?}",
3920                value
3921            );
3922        } else {
3923            panic!("expected MockTool statement, got {:?}", test.body.stmts[0]);
3924        }
3925    }
3926
3927    #[test]
3928    fn parse_mock_tool_with_value() {
3929        let source = r#"
3930            test "mock tool value" {
3931                mock tool Http.get -> "response";
3932            }
3933        "#;
3934
3935        let (prog, errors) = parse_str(source);
3936        assert!(errors.is_empty(), "errors: {errors:?}");
3937        let prog = prog.expect("should parse");
3938
3939        let test = &prog.tests[0];
3940        if let Stmt::MockTool { value, .. } = &test.body.stmts[0] {
3941            assert!(
3942                matches!(value, MockValue::Value(_)),
3943                "expected MockValue::Value, got {:?}",
3944                value
3945            );
3946        } else {
3947            panic!("expected MockTool statement");
3948        }
3949    }
3950}