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, ChildSpec, ClosureParam, ConstDecl, EffectHandlerDecl,
7    ElseBranch, EnumDecl, EventKind, Expr, ExternFnDecl, FieldInit, FnDecl, HandlerAssignment,
8    HandlerConfig, HandlerDecl, Literal, MapEntry, MatchArm, MockValue, ModDecl, Param, Pattern,
9    Program, ProtocolDecl, ProtocolRole, ProtocolStep, RecordDecl, RecordField, RestartPolicy,
10    Stmt, StringPart, StringTemplate, SupervisionStrategy, SupervisorDecl, TestDecl, ToolDecl,
11    ToolFnDecl, UnaryOp, UseDecl, UseKind,
12};
13use crate::{Ident, Span, TypeExpr};
14use crate::{Spanned, Token};
15use chumsky::prelude::*;
16use chumsky::BoxedParser;
17use std::ops::Range;
18use std::sync::Arc;
19
20/// Parse error type using byte range spans.
21pub type ParseError = Simple<Token>;
22
23/// Parse a sequence of tokens into a Program AST.
24///
25/// # Errors
26///
27/// Returns parse errors if the token stream doesn't form a valid program.
28#[must_use]
29#[allow(clippy::needless_pass_by_value)] // Arc<str> is cheap to clone and idiomatic here
30pub fn parse(tokens: &[Spanned], source: Arc<str>) -> (Option<Program>, Vec<ParseError>) {
31    let len = source.len();
32
33    // Convert our Spanned tokens to (Token, Range<usize>) for chumsky
34    let token_spans: Vec<(Token, Range<usize>)> = tokens
35        .iter()
36        .map(|s| (s.token.clone(), s.start..s.end))
37        .collect();
38
39    let stream = chumsky::Stream::from_iter(len..len, token_spans.into_iter());
40
41    let (ast, errors) = program_parser(Arc::clone(&source)).parse_recovery(stream);
42
43    (ast, errors)
44}
45
46// =============================================================================
47// Top-level parsers
48// =============================================================================
49
50/// Parser for a complete program.
51#[allow(clippy::needless_pass_by_value)]
52fn program_parser(source: Arc<str>) -> impl Parser<Token, Program, Error = ParseError> {
53    let src = source.clone();
54    let src2 = source.clone();
55
56    // Top-level declarations with recovery - skip to next keyword on error
57    let top_level = mod_parser(source.clone())
58        .or(use_parser(source.clone()))
59        .or(record_parser(source.clone()))
60        .or(enum_parser(source.clone()))
61        .or(const_parser(source.clone()))
62        .or(tool_parser(source.clone()))
63        .or(protocol_parser(source.clone()))
64        .or(effect_handler_parser(source.clone()))
65        .or(agent_parser(source.clone()))
66        .or(supervisor_parser(source.clone()))
67        .or(extern_fn_parser(source.clone()))
68        .or(fn_parser(source.clone()))
69        .or(test_parser(source.clone()))
70        .recover_with(skip_then_retry_until([
71            Token::KwMod,
72            Token::KwUse,
73            Token::KwPub,
74            Token::KwRecord,
75            Token::KwEnum,
76            Token::KwConst,
77            Token::KwTool,
78            Token::KwProtocol,
79            Token::KwHandler,
80            Token::KwAgent,
81            Token::KwSupervisor,
82            Token::KwExtern,
83            Token::KwFn,
84            Token::KwRun,
85            Token::KwTest,
86        ]));
87
88    let run_stmt = just(Token::KwRun)
89        .ignore_then(ident_token_parser(src.clone()))
90        .then_ignore(just(Token::Semicolon))
91        .or_not();
92
93    top_level.repeated().then(run_stmt).map_with_span(
94        move |(items, run_agent), span: Range<usize>| {
95            let mut mod_decls = Vec::new();
96            let mut use_decls = Vec::new();
97            let mut records = Vec::new();
98            let mut enums = Vec::new();
99            let mut consts = Vec::new();
100            let mut tools = Vec::new();
101            let mut protocols = Vec::new();
102            let mut effect_handlers = Vec::new();
103            let mut agents = Vec::new();
104            let mut supervisors = Vec::new();
105            let mut functions = Vec::new();
106            let mut extern_fns = Vec::new();
107            let mut tests = Vec::new();
108
109            for item in items {
110                match item {
111                    TopLevel::Mod(m) => mod_decls.push(m),
112                    TopLevel::Use(u) => use_decls.push(u),
113                    TopLevel::Record(r) => records.push(r),
114                    TopLevel::Enum(e) => enums.push(e),
115                    TopLevel::Const(c) => consts.push(c),
116                    TopLevel::Tool(t) => tools.push(t),
117                    TopLevel::Protocol(p) => protocols.push(p),
118                    TopLevel::EffectHandler(h) => effect_handlers.push(h),
119                    TopLevel::Agent(a) => agents.push(a),
120                    TopLevel::Supervisor(s) => supervisors.push(s),
121                    TopLevel::Function(f) => functions.push(f),
122                    TopLevel::ExternFn(e) => extern_fns.push(e),
123                    TopLevel::Test(t) => tests.push(t),
124                }
125            }
126
127            Program {
128                mod_decls,
129                use_decls,
130                records,
131                enums,
132                consts,
133                tools,
134                protocols,
135                effect_handlers,
136                agents,
137                supervisors,
138                functions,
139                extern_fns,
140                tests,
141                run_agent,
142                span: make_span(&src2, span),
143            }
144        },
145    )
146}
147
148/// Helper enum for collecting top-level declarations.
149enum TopLevel {
150    Mod(ModDecl),
151    Use(UseDecl),
152    Record(RecordDecl),
153    Enum(EnumDecl),
154    Const(ConstDecl),
155    Tool(ToolDecl),
156    Protocol(ProtocolDecl),
157    EffectHandler(EffectHandlerDecl),
158    Agent(AgentDecl),
159    Supervisor(SupervisorDecl),
160    Function(FnDecl),
161    ExternFn(ExternFnDecl),
162    Test(TestDecl),
163}
164
165// =============================================================================
166// Module declaration parsers
167// =============================================================================
168
169/// Parser for a mod declaration: `mod foo` or `pub mod foo`
170#[allow(clippy::needless_pass_by_value)]
171fn mod_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
172    let src = source.clone();
173
174    just(Token::KwPub)
175        .or_not()
176        .then_ignore(just(Token::KwMod))
177        .then(ident_token_parser(src.clone()))
178        .then_ignore(just(Token::Semicolon))
179        .map_with_span(move |(is_pub, name), span: Range<usize>| {
180            TopLevel::Mod(ModDecl {
181                is_pub: is_pub.is_some(),
182                name,
183                span: make_span(&src, span),
184            })
185        })
186}
187
188/// Parser for a use declaration: `use path::to::Item` or `use path::{A, B}`
189#[allow(clippy::needless_pass_by_value)]
190fn use_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
191    let src = source.clone();
192    let src2 = source.clone();
193    let src3 = source.clone();
194    let src4 = source.clone();
195
196    // Simple use: `use a::b::C` or `use a::b::C as D`
197    let simple_use = just(Token::KwPub)
198        .or_not()
199        .then_ignore(just(Token::KwUse))
200        .then(
201            ident_token_parser(src.clone())
202                .separated_by(just(Token::ColonColon))
203                .at_least(1),
204        )
205        .then(
206            just(Token::KwAs)
207                .ignore_then(ident_token_parser(src.clone()))
208                .or_not(),
209        )
210        .then_ignore(just(Token::Semicolon))
211        .map_with_span(move |((is_pub, path), alias), span: Range<usize>| {
212            TopLevel::Use(UseDecl {
213                is_pub: is_pub.is_some(),
214                path,
215                kind: UseKind::Simple(alias),
216                span: make_span(&src, span),
217            })
218        });
219
220    // Group import item: `Name` or `Name as Alias`
221    let group_item = ident_token_parser(src2.clone()).then(
222        just(Token::KwAs)
223            .ignore_then(ident_token_parser(src2.clone()))
224            .or_not(),
225    );
226
227    // Group use: `use a::b::{C, D as E}`
228    let group_use = just(Token::KwPub)
229        .or_not()
230        .then_ignore(just(Token::KwUse))
231        .then(
232            ident_token_parser(src3.clone())
233                .then_ignore(just(Token::ColonColon))
234                .repeated()
235                .at_least(1),
236        )
237        .then(
238            group_item
239                .separated_by(just(Token::Comma))
240                .allow_trailing()
241                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
242        )
243        .then_ignore(just(Token::Semicolon))
244        .map_with_span(move |((is_pub, path), items), span: Range<usize>| {
245            TopLevel::Use(UseDecl {
246                is_pub: is_pub.is_some(),
247                path,
248                kind: UseKind::Group(items),
249                span: make_span(&src3, span),
250            })
251        });
252
253    // Glob use: `use a::b::*`
254    let glob_use = just(Token::KwPub)
255        .or_not()
256        .then_ignore(just(Token::KwUse))
257        .then(
258            ident_token_parser(src4.clone())
259                .then_ignore(just(Token::ColonColon))
260                .repeated()
261                .at_least(1),
262        )
263        .then_ignore(just(Token::Star))
264        .then_ignore(just(Token::Semicolon))
265        .map_with_span(move |(is_pub, path), span: Range<usize>| {
266            TopLevel::Use(UseDecl {
267                is_pub: is_pub.is_some(),
268                path,
269                kind: UseKind::Glob,
270                span: make_span(&src4, span),
271            })
272        });
273
274    // Try group/glob first (they need :: before { or *), then simple
275    group_use.or(glob_use).or(simple_use)
276}
277
278// =============================================================================
279// Record, Enum, Const parsers
280// =============================================================================
281
282/// Parser for a record declaration: `record Point { x: Int, y: Int }`
283#[allow(clippy::needless_pass_by_value)]
284/// Parser for type parameters: `<T>`, `<A, B>`, `<T, U, V>`, etc.
285/// Returns empty Vec if no type parameters present.
286fn type_params_parser(
287    source: Arc<str>,
288) -> impl Parser<Token, Vec<Ident>, Error = ParseError> + Clone {
289    ident_token_parser(source)
290        .separated_by(just(Token::Comma))
291        .allow_trailing()
292        .delimited_by(just(Token::Lt), just(Token::Gt))
293        .or_not()
294        .map(|params| params.unwrap_or_default())
295}
296
297fn record_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
298    let src = source.clone();
299    let src2 = source.clone();
300
301    // Record field: `name: Type`
302    let field = ident_token_parser(src.clone())
303        .then_ignore(just(Token::Colon))
304        .then(type_parser(src.clone()))
305        .map_with_span(move |(name, ty), span: Range<usize>| RecordField {
306            name,
307            ty,
308            span: make_span(&src, span),
309        });
310
311    just(Token::KwPub)
312        .or_not()
313        .then_ignore(just(Token::KwRecord))
314        .then(ident_token_parser(src2.clone()))
315        .then(type_params_parser(src2.clone()))
316        .then(
317            field
318                .separated_by(just(Token::Comma))
319                .allow_trailing()
320                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
321        )
322        .map_with_span(
323            move |(((is_pub, name), type_params), fields), span: Range<usize>| {
324                TopLevel::Record(RecordDecl {
325                    is_pub: is_pub.is_some(),
326                    name,
327                    type_params,
328                    fields,
329                    span: make_span(&src2, span),
330                })
331            },
332        )
333}
334
335/// Parser for an enum declaration: `enum Status { Active, Pending, Done }` or `enum Result { Ok(T), Err(E) }`
336#[allow(clippy::needless_pass_by_value)]
337fn enum_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
338    let src = source.clone();
339    let src2 = source.clone();
340    let src3 = source.clone();
341
342    // Enum variant with optional payload: `Ok(T)` or `None`
343    let variant = ident_token_parser(src.clone())
344        .then(
345            type_parser(src.clone())
346                .delimited_by(just(Token::LParen), just(Token::RParen))
347                .or_not(),
348        )
349        .map_with_span({
350            let src = src.clone();
351            move |(name, payload), span: Range<usize>| crate::ast::EnumVariant {
352                name,
353                payload,
354                span: make_span(&src, span),
355            }
356        });
357
358    just(Token::KwPub)
359        .or_not()
360        .then_ignore(just(Token::KwEnum))
361        .then(ident_token_parser(src3.clone()))
362        .then(type_params_parser(src3.clone()))
363        .then(
364            variant
365                .separated_by(just(Token::Comma))
366                .allow_trailing()
367                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
368        )
369        .map_with_span(
370            move |(((is_pub, name), type_params), variants), span: Range<usize>| {
371                TopLevel::Enum(EnumDecl {
372                    is_pub: is_pub.is_some(),
373                    name,
374                    type_params,
375                    variants,
376                    span: make_span(&src2, span),
377                })
378            },
379        )
380}
381
382/// Parser for a const declaration: `const MAX_RETRIES: Int = 3`
383#[allow(clippy::needless_pass_by_value)]
384fn const_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
385    let src = source.clone();
386    let src2 = source.clone();
387
388    just(Token::KwPub)
389        .or_not()
390        .then_ignore(just(Token::KwConst))
391        .then(ident_token_parser(src.clone()))
392        .then_ignore(just(Token::Colon))
393        .then(type_parser(src.clone()))
394        .then_ignore(just(Token::Eq))
395        .then(expr_parser(src.clone()))
396        .then_ignore(just(Token::Semicolon))
397        .map_with_span(move |(((is_pub, name), ty), value), span: Range<usize>| {
398            TopLevel::Const(ConstDecl {
399                is_pub: is_pub.is_some(),
400                name,
401                ty,
402                value,
403                span: make_span(&src2, span),
404            })
405        })
406}
407
408// =============================================================================
409// Tool parsers (RFC-0011)
410// =============================================================================
411
412/// Parser for a tool declaration: `tool Http { fn get(url: String) -> String }`
413#[allow(clippy::needless_pass_by_value)]
414fn tool_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
415    let src = source.clone();
416    let src2 = source.clone();
417    let src3 = source.clone();
418
419    // Tool function parameter: `name: Type`
420    let param = ident_token_parser(src.clone())
421        .then_ignore(just(Token::Colon))
422        .then(type_parser(src.clone()))
423        .map_with_span(move |(name, ty), span: Range<usize>| Param {
424            name,
425            ty,
426            span: make_span(&src, span),
427        });
428
429    let params = param
430        .separated_by(just(Token::Comma))
431        .allow_trailing()
432        .delimited_by(just(Token::LParen), just(Token::RParen));
433
434    // Tool function signature: `fn name(params) -> ReturnType`
435    let tool_fn = just(Token::KwFn)
436        .ignore_then(ident_token_parser(src2.clone()))
437        .then(params)
438        .then_ignore(just(Token::Arrow))
439        .then(type_parser(src2.clone()))
440        .map_with_span(
441            move |((name, params), return_ty), span: Range<usize>| ToolFnDecl {
442                name,
443                params,
444                return_ty,
445                span: make_span(&src2, span),
446            },
447        );
448
449    just(Token::KwPub)
450        .or_not()
451        .then_ignore(just(Token::KwTool))
452        .then(ident_token_parser(src3.clone()))
453        .then(
454            tool_fn
455                .repeated()
456                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
457        )
458        .map_with_span(move |((is_pub, name), functions), span: Range<usize>| {
459            TopLevel::Tool(ToolDecl {
460                is_pub: is_pub.is_some(),
461                name,
462                functions,
463                span: make_span(&src3, span),
464            })
465        })
466}
467
468// =============================================================================
469// Protocol parsers (Phase 3 session types)
470// =============================================================================
471
472/// Parser for a protocol declaration.
473///
474/// Syntax:
475/// ```sage
476/// protocol SchemaSync {
477///     DatabaseSteward -> APISteward: SchemaChanged
478///     APISteward -> DatabaseSteward: Acknowledged
479/// }
480/// ```
481#[allow(clippy::needless_pass_by_value)]
482fn protocol_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
483    let src = source.clone();
484    let src2 = source.clone();
485    let src3 = source.clone();
486
487    // Protocol step: Sender -> Receiver: MessageType
488    let protocol_step = ident_token_parser(src.clone())
489        .then_ignore(just(Token::Arrow))
490        .then(ident_token_parser(src.clone()))
491        .then_ignore(just(Token::Colon))
492        .then(type_parser(src.clone()))
493        .map_with_span({
494            let src = src.clone();
495            move |((sender, receiver), message_type), span: Range<usize>| ProtocolStep {
496                sender,
497                receiver,
498                message_type,
499                span: make_span(&src, span),
500            }
501        });
502
503    // Full protocol declaration
504    just(Token::KwPub)
505        .or_not()
506        .then_ignore(just(Token::KwProtocol))
507        .then(ident_token_parser(src2.clone()))
508        .then(
509            protocol_step
510                .repeated()
511                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
512        )
513        .map_with_span(move |((is_pub, name), steps), span: Range<usize>| {
514            TopLevel::Protocol(ProtocolDecl {
515                is_pub: is_pub.is_some(),
516                name,
517                steps,
518                span: make_span(&src3, span),
519            })
520        })
521}
522
523// =============================================================================
524// Effect handler parsers (Phase 3 algebraic effects)
525// =============================================================================
526
527/// Parser for an effect handler declaration.
528///
529/// Syntax:
530/// ```sage
531/// handler DefaultLLM handles Infer {
532///     model: "gpt-4o"
533///     temperature: 0.7
534///     max_tokens: 1024
535/// }
536/// ```
537#[allow(clippy::needless_pass_by_value)]
538fn effect_handler_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
539    let src = source.clone();
540    let src2 = source.clone();
541
542    // Handler config entry: key: value (value must be a literal)
543    let config_entry = ident_token_parser(src.clone())
544        .then_ignore(just(Token::Colon))
545        .then(literal_value_parser(src.clone()))
546        .map_with_span({
547            let src = src.clone();
548            move |(key, value), span: Range<usize>| HandlerConfig {
549                key,
550                value,
551                span: make_span(&src, span),
552            }
553        });
554
555    // Full handler declaration
556    just(Token::KwPub)
557        .or_not()
558        .then_ignore(just(Token::KwHandler))
559        .then(ident_token_parser(src2.clone()))
560        .then_ignore(just(Token::KwHandles))
561        .then(ident_token_parser(src2.clone()))
562        .then(
563            config_entry
564                .repeated()
565                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
566        )
567        .map_with_span(move |(((is_pub, name), effect), config), span: Range<usize>| {
568            TopLevel::EffectHandler(EffectHandlerDecl {
569                is_pub: is_pub.is_some(),
570                name,
571                effect,
572                config,
573                span: make_span(&src2, span),
574            })
575        })
576}
577
578/// Parser for literal values only (for handler config).
579#[allow(clippy::needless_pass_by_value)]
580fn literal_value_parser(source: Arc<str>) -> impl Parser<Token, Literal, Error = ParseError> {
581    let src = source.clone();
582
583    filter_map(move |span: Range<usize>, tok| match tok {
584        Token::IntLit => {
585            let s = &src[span.clone()];
586            let n = s.parse::<i64>().map_err(|_| {
587                Simple::custom(span, format!("invalid integer literal `{s}`"))
588            })?;
589            Ok(Literal::Int(n))
590        }
591        Token::FloatLit => {
592            let s = &src[span.clone()];
593            let n = s.parse::<f64>().map_err(|_| {
594                Simple::custom(span, format!("invalid float literal `{s}`"))
595            })?;
596            Ok(Literal::Float(n))
597        }
598        Token::StringLit => {
599            let s = &src[span.clone()];
600            // Remove quotes and handle escape sequences
601            let content = &s[1..s.len() - 1];
602            let unescaped = unescape_string(content);
603            Ok(Literal::String(unescaped))
604        }
605        Token::KwTrue => Ok(Literal::Bool(true)),
606        Token::KwFalse => Ok(Literal::Bool(false)),
607        _ => Err(Simple::expected_input_found(span, [], Some(tok))),
608    })
609}
610
611/// Helper to unescape string literals.
612fn unescape_string(s: &str) -> String {
613    let mut result = String::new();
614    let mut chars = s.chars().peekable();
615    while let Some(c) = chars.next() {
616        if c == '\\' {
617            match chars.next() {
618                Some('n') => result.push('\n'),
619                Some('t') => result.push('\t'),
620                Some('r') => result.push('\r'),
621                Some('\\') => result.push('\\'),
622                Some('"') => result.push('"'),
623                Some('\'') => result.push('\''),
624                Some(other) => {
625                    result.push('\\');
626                    result.push(other);
627                }
628                None => result.push('\\'),
629            }
630        } else {
631            result.push(c);
632        }
633    }
634    result
635}
636
637// =============================================================================
638// Test parsers (RFC-0012)
639// =============================================================================
640
641/// Parser for a test declaration: `test "name" { ... }` or `@serial test "name" { ... }`
642#[allow(clippy::needless_pass_by_value)]
643fn test_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
644    let src = source.clone();
645    let src2 = source.clone();
646
647    // Parse @serial annotation (@ followed by identifier "serial")
648    // For now, we'll use a simple approach: look for @ then ident
649    let serial_annotation = just(Token::At)
650        .then(filter(|t: &Token| matches!(t, Token::Ident)))
651        .or_not()
652        .map(|opt| opt.is_some());
653
654    // Parse test name (string literal)
655    let test_name = filter_map(|span: Range<usize>, tok: Token| match tok {
656        Token::StringLit => Ok(()),
657        _ => Err(Simple::expected_input_found(span, [], Some(tok))),
658    })
659    .map_with_span(move |_, span: Range<usize>| {
660        // Extract the string content without quotes (handles both " and ')
661        let s = &src[span.clone()];
662        s[1..s.len() - 1].to_string()
663    });
664
665    // Test body - use the statement parser
666    let body = block_parser(src2.clone());
667
668    serial_annotation
669        .then_ignore(just(Token::KwTest))
670        .then(test_name)
671        .then(body)
672        .map_with_span(move |((is_serial, name), body), span: Range<usize>| {
673            TopLevel::Test(TestDecl {
674                name,
675                is_serial,
676                body,
677                span: make_span(&src2, span),
678            })
679        })
680}
681
682// =============================================================================
683// Supervisor parsers (v2 supervision trees)
684// =============================================================================
685
686/// Parser for a supervisor declaration.
687///
688/// Syntax:
689/// ```sage
690/// supervisor Name {
691///     strategy: OneForOne
692///     children {
693///         AgentName { restart: Permanent, belief: value }
694///     }
695/// }
696/// ```
697#[allow(clippy::needless_pass_by_value)]
698fn supervisor_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
699    let src = source.clone();
700    let src2 = source.clone();
701    let src3 = source.clone();
702    let src4 = source.clone();
703
704    // Strategy parser: strategy: OneForOne | OneForAll | RestForOne
705    let strategy = just(Token::KwStrategy)
706        .ignore_then(just(Token::Colon))
707        .ignore_then(filter_map({
708            let src = src.clone();
709            move |span: Range<usize>, token| match token {
710                Token::Ident => {
711                    let text = &src[span.start..span.end];
712                    match text {
713                        "OneForOne" => Ok(SupervisionStrategy::OneForOne),
714                        "OneForAll" => Ok(SupervisionStrategy::OneForAll),
715                        "RestForOne" => Ok(SupervisionStrategy::RestForOne),
716                        _ => Err(Simple::custom(
717                            span,
718                            format!("unknown strategy `{text}`, expected OneForOne, OneForAll, or RestForOne"),
719                        )),
720                    }
721                }
722                _ => Err(Simple::expected_input_found(
723                    span,
724                    vec![Some(Token::Ident)],
725                    Some(token),
726                )),
727            }
728        }));
729
730    // Restart policy parser: restart: Permanent | Transient | Temporary
731    let restart_policy = just(Token::KwRestart)
732        .ignore_then(just(Token::Colon))
733        .ignore_then(filter_map({
734            let src = src2.clone();
735            move |span: Range<usize>, token| match token {
736                Token::Ident => {
737                    let text = &src[span.start..span.end];
738                    match text {
739                        "Permanent" => Ok(RestartPolicy::Permanent),
740                        "Transient" => Ok(RestartPolicy::Transient),
741                        "Temporary" => Ok(RestartPolicy::Temporary),
742                        _ => Err(Simple::custom(
743                            span,
744                            format!("unknown restart policy `{text}`, expected Permanent, Transient, or Temporary"),
745                        )),
746                    }
747                }
748                _ => Err(Simple::expected_input_found(
749                    span,
750                    vec![Some(Token::Ident)],
751                    Some(token),
752                )),
753            }
754        }));
755
756    // Handler assignment: handler Effect: HandlerName (Phase 3: Algebraic Effects)
757    let src_handler = src3.clone();
758    let handler_assignment = just(Token::KwHandler)
759        .ignore_then(ident_token_parser(src3.clone()))
760        .then_ignore(just(Token::Colon))
761        .then(ident_token_parser(src3.clone()))
762        .map_with_span(move |(effect, handler), span: Range<usize>| HandlerAssignment {
763            effect,
764            handler,
765            span: make_span(&src_handler, span),
766        });
767
768    // Field init for beliefs: name: value
769    let field_init = ident_token_parser(src3.clone())
770        .then_ignore(just(Token::Colon))
771        .then(expr_parser(src3.clone()))
772        .map_with_span({
773            let src = src3.clone();
774            move |(name, value), span: Range<usize>| FieldInit {
775                name,
776                value,
777                span: make_span(&src, span),
778            }
779        });
780
781    // Child spec: AgentName { restart: Permanent, handler Infer: DefaultLLM, belief1: value1, ... }
782    // Or just: AgentName { belief1: value1, ... } (defaults to Permanent)
783    // Commas between fields are optional for consistent multiline style.
784    let child_spec = ident_token_parser(src3.clone())
785        .then_ignore(just(Token::LBrace))
786        .then(restart_policy.then_ignore(just(Token::Comma).or_not()).or_not())
787        .then(
788            handler_assignment
789                .then_ignore(just(Token::Comma).or_not())
790                .repeated(),
791        )
792        .then(
793            field_init
794                .then_ignore(just(Token::Comma).or_not())
795                .repeated(),
796        )
797        .then_ignore(just(Token::RBrace))
798        .map_with_span({
799            let src = src3.clone();
800            move |(((agent_name, restart), handler_assignments), beliefs), span: Range<usize>| {
801                ChildSpec {
802                    agent_name,
803                    restart: restart.unwrap_or_default(),
804                    beliefs,
805                    handler_assignments,
806                    span: make_span(&src, span),
807                }
808            }
809        });
810
811    // Children block: children { child1 child2 ... }
812    let children = just(Token::KwChildren)
813        .ignore_then(
814            child_spec
815                .repeated()
816                .at_least(1)
817                .delimited_by(just(Token::LBrace), just(Token::RBrace)),
818        );
819
820    // Full supervisor declaration
821    just(Token::KwPub)
822        .or_not()
823        .then_ignore(just(Token::KwSupervisor))
824        .then(ident_token_parser(src4.clone()))
825        .then_ignore(just(Token::LBrace))
826        .then(strategy)
827        .then(children)
828        .then_ignore(just(Token::RBrace))
829        .map_with_span(
830            move |(((is_pub, name), strategy), children), span: Range<usize>| {
831                TopLevel::Supervisor(SupervisorDecl {
832                    is_pub: is_pub.is_some(),
833                    name,
834                    strategy,
835                    children,
836                    span: make_span(&src4, span),
837                })
838            },
839        )
840}
841
842// =============================================================================
843// Agent parsers
844// =============================================================================
845
846/// Parser for an agent declaration.
847#[allow(clippy::needless_pass_by_value)]
848fn agent_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
849    let src2 = source.clone();
850    let src3 = source.clone();
851    let src4 = source.clone();
852    let src5 = source.clone();
853    let src6 = source.clone();
854
855    // Tool use clause: `use Http, Fs`
856    let tool_use = just(Token::KwUse)
857        .ignore_then(
858            ident_token_parser(src5.clone())
859                .separated_by(just(Token::Comma))
860                .at_least(1),
861        )
862        .or_not()
863        .map(|tools| tools.unwrap_or_default());
864
865    // Agent state fields: `name: Type` or `@persistent name: Type`
866    // We still call them "beliefs" internally for backwards compatibility
867    let src_annot = source.clone();
868    let persistent_annotation = just(Token::At)
869        .ignore_then(filter_map(move |span: Range<usize>, token| match token {
870            Token::Ident => {
871                let text = &src_annot[span.start..span.end];
872                if text == "persistent" {
873                    Ok(true)
874                } else {
875                    Err(Simple::custom(
876                        span,
877                        format!("unknown annotation @{}, expected @persistent", text),
878                    ))
879                }
880            }
881            _ => Err(Simple::expected_input_found(
882                span,
883                vec![Some(Token::Ident)],
884                Some(token),
885            )),
886        }))
887        .or_not();
888
889    let src_belief = source.clone();
890    let belief = persistent_annotation
891        .then(ident_token_parser(src_belief.clone()))
892        .then_ignore(just(Token::Colon))
893        .then(type_parser(src_belief.clone()))
894        .map_with_span(move |((is_persistent, name), ty), span: Range<usize>| BeliefDecl {
895            is_persistent: is_persistent.unwrap_or(false),
896            name,
897            ty,
898            span: make_span(&src_belief, span),
899        });
900
901    let handler = just(Token::KwOn)
902        .ignore_then(event_kind_parser(src2.clone()))
903        .then(block_parser(src2.clone()))
904        .map_with_span(move |(event, body), span: Range<usize>| HandlerDecl {
905            event,
906            body,
907            span: make_span(&src2, span),
908        });
909
910    // Optional `receives MsgType` clause
911    let receives_clause = just(Token::KwReceives)
912        .ignore_then(type_parser(src3.clone()))
913        .or_not();
914
915    // Optional `follows Protocol as Role` clause (Phase 3: Session Types)
916    // Syntax: follows Protocol as Role [, Protocol2 as Role2]*
917    let src_follows = src6.clone();
918    let single_follows = ident_token_parser(src6.clone())
919        .then_ignore(just(Token::KwAs))
920        .then(ident_token_parser(src6.clone()))
921        .map_with_span(move |(protocol, role), span: Range<usize>| ProtocolRole {
922            protocol,
923            role,
924            span: make_span(&src_follows, span),
925        });
926
927    let follows_clause = just(Token::KwFollows)
928        .ignore_then(single_follows.separated_by(just(Token::Comma)).at_least(1))
929        .or_not()
930        .map(|follows| follows.unwrap_or_default());
931
932    just(Token::KwPub)
933        .or_not()
934        .then_ignore(just(Token::KwAgent))
935        .then(ident_token_parser(src3.clone()))
936        .then(receives_clause)
937        .then(follows_clause)
938        .then_ignore(just(Token::LBrace))
939        .then(tool_use)
940        .then(belief.repeated())
941        .then(handler.repeated())
942        .then_ignore(just(Token::RBrace))
943        .map_with_span(
944            move |((((((is_pub, name), receives), follows), tool_uses), beliefs), handlers),
945                  span: Range<usize>| {
946                TopLevel::Agent(AgentDecl {
947                    is_pub: is_pub.is_some(),
948                    name,
949                    receives,
950                    follows,
951                    tool_uses,
952                    beliefs,
953                    handlers,
954                    span: make_span(&src4, span),
955                })
956            },
957        )
958}
959
960/// Parser for event kinds.
961#[allow(clippy::needless_pass_by_value)]
962fn event_kind_parser(source: Arc<str>) -> impl Parser<Token, EventKind, Error = ParseError> {
963    let src = source.clone();
964
965    // v2 lifecycle: waking runs before start
966    let waking = just(Token::KwWaking).to(EventKind::Waking);
967    let start = just(Token::KwStart).to(EventKind::Start);
968    // v2 lifecycle: pause/resume for graceful suspend
969    let pause = just(Token::KwPause).to(EventKind::Pause);
970    let resume = just(Token::KwResume).to(EventKind::Resume);
971    let stop = just(Token::KwStop).to(EventKind::Stop);
972    // v2 lifecycle: resting is an alias for stop
973    let resting = just(Token::KwResting).to(EventKind::Resting);
974
975    let message = just(Token::KwMessage)
976        .ignore_then(just(Token::LParen))
977        .ignore_then(ident_token_parser(src.clone()))
978        .then_ignore(just(Token::Colon))
979        .then(type_parser(src.clone()))
980        .then_ignore(just(Token::RParen))
981        .map(|(param_name, param_ty)| EventKind::Message {
982            param_name,
983            param_ty,
984        });
985
986    // RFC-0007: on error(e) handler
987    let error = just(Token::KwError)
988        .ignore_then(just(Token::LParen))
989        .ignore_then(ident_token_parser(src))
990        .then_ignore(just(Token::RParen))
991        .map(|param_name| EventKind::Error { param_name });
992
993    waking
994        .or(start)
995        .or(pause)
996        .or(resume)
997        .or(stop)
998        .or(resting)
999        .or(message)
1000        .or(error)
1001}
1002
1003// =============================================================================
1004// Function parsers
1005// =============================================================================
1006
1007/// Parser for an extern function declaration: `extern fn name(params) -> Type`
1008#[allow(clippy::needless_pass_by_value)]
1009fn extern_fn_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
1010    let src = source.clone();
1011    let src2 = source.clone();
1012    let src3 = source.clone();
1013
1014    let param = ident_token_parser(src.clone())
1015        .then_ignore(just(Token::Colon))
1016        .then(type_parser(src.clone()))
1017        .map_with_span(move |(name, ty), span: Range<usize>| Param {
1018            name,
1019            ty,
1020            span: make_span(&src, span),
1021        });
1022
1023    let params = param
1024        .separated_by(just(Token::Comma))
1025        .allow_trailing()
1026        .delimited_by(just(Token::LParen), just(Token::RParen));
1027
1028    just(Token::KwExtern)
1029        .ignore_then(just(Token::KwFn))
1030        .ignore_then(ident_token_parser(src2.clone()))
1031        .then(params)
1032        .then_ignore(just(Token::Arrow))
1033        .then(type_parser(src2.clone()))
1034        .then(just(Token::KwFails).or_not())
1035        .map_with_span(
1036            move |(((name, params), return_ty), is_fallible), span: Range<usize>| {
1037                TopLevel::ExternFn(ExternFnDecl {
1038                    name,
1039                    params,
1040                    return_ty,
1041                    is_fallible: is_fallible.is_some(),
1042                    span: make_span(&src3, span),
1043                })
1044            },
1045        )
1046}
1047
1048/// Parser for a function declaration.
1049#[allow(clippy::needless_pass_by_value)]
1050fn fn_parser(source: Arc<str>) -> impl Parser<Token, TopLevel, Error = ParseError> {
1051    let src = source.clone();
1052    let src2 = source.clone();
1053    let src3 = source.clone();
1054
1055    let param = ident_token_parser(src.clone())
1056        .then_ignore(just(Token::Colon))
1057        .then(type_parser(src.clone()))
1058        .map_with_span(move |(name, ty), span: Range<usize>| Param {
1059            name,
1060            ty,
1061            span: make_span(&src, span),
1062        });
1063
1064    let params = param
1065        .separated_by(just(Token::Comma))
1066        .allow_trailing()
1067        .delimited_by(just(Token::LParen), just(Token::RParen));
1068
1069    just(Token::KwPub)
1070        .or_not()
1071        .then_ignore(just(Token::KwFn))
1072        .then(ident_token_parser(src2.clone()))
1073        .then(type_params_parser(src2.clone()))
1074        .then(params)
1075        .then_ignore(just(Token::Arrow))
1076        .then(type_parser(src2.clone()))
1077        .then(just(Token::KwFails).or_not())
1078        .then(block_parser(src2))
1079        .map_with_span(
1080            move |((((((is_pub, name), type_params), params), return_ty), is_fallible), body),
1081                  span: Range<usize>| {
1082                TopLevel::Function(FnDecl {
1083                    is_pub: is_pub.is_some(),
1084                    name,
1085                    type_params,
1086                    params,
1087                    return_ty,
1088                    is_fallible: is_fallible.is_some(),
1089                    body,
1090                    span: make_span(&src3, span),
1091                })
1092            },
1093        )
1094}
1095
1096// =============================================================================
1097// Statement parsers
1098// =============================================================================
1099
1100/// Parser for a block of statements.
1101/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
1102#[allow(clippy::needless_pass_by_value)]
1103fn block_parser(source: Arc<str>) -> BoxedParser<'static, Token, Block, ParseError> {
1104    let src = source.clone();
1105
1106    recursive(move |block: Recursive<Token, Block, ParseError>| {
1107        let src_inner = src.clone();
1108        stmt_parser(src.clone(), block)
1109            .repeated()
1110            .delimited_by(just(Token::LBrace), just(Token::RBrace))
1111            .recover_with(nested_delimiters(
1112                Token::LBrace,
1113                Token::RBrace,
1114                [
1115                    (Token::LParen, Token::RParen),
1116                    (Token::LBracket, Token::RBracket),
1117                ],
1118                |_span: Range<usize>| vec![],
1119            ))
1120            .map_with_span(move |stmts, span: Range<usize>| Block {
1121                stmts,
1122                span: make_span(&src_inner, span),
1123            })
1124    })
1125    .boxed()
1126}
1127
1128/// Parser for statements.
1129#[allow(clippy::needless_pass_by_value)]
1130fn stmt_parser(
1131    source: Arc<str>,
1132    block: impl Parser<Token, Block, Error = ParseError> + Clone + 'static,
1133) -> impl Parser<Token, Stmt, Error = ParseError> + Clone {
1134    let src = source.clone();
1135    let src2 = source.clone();
1136    let src3 = source.clone();
1137    let src4 = source.clone();
1138    let src5 = source.clone();
1139    let src6 = source.clone();
1140    let src7 = source.clone();
1141
1142    // Let tuple destructuring: let (a, b) = expr;
1143    let src10 = source.clone();
1144    let let_tuple_stmt = just(Token::KwLet)
1145        .ignore_then(
1146            ident_token_parser(src10.clone())
1147                .separated_by(just(Token::Comma))
1148                .at_least(2)
1149                .allow_trailing()
1150                .delimited_by(just(Token::LParen), just(Token::RParen)),
1151        )
1152        .then(
1153            just(Token::Colon)
1154                .ignore_then(type_parser(src10.clone()))
1155                .or_not(),
1156        )
1157        .then_ignore(just(Token::Eq))
1158        .then(expr_parser(src10.clone()))
1159        .then_ignore(just(Token::Semicolon))
1160        .map_with_span(
1161            move |((names, ty), value), span: Range<usize>| Stmt::LetTuple {
1162                names,
1163                ty,
1164                value,
1165                span: make_span(&src10, span),
1166            },
1167        );
1168
1169    let let_stmt = just(Token::KwLet)
1170        .ignore_then(ident_token_parser(src.clone()))
1171        .then(
1172            just(Token::Colon)
1173                .ignore_then(type_parser(src.clone()))
1174                .or_not(),
1175        )
1176        .then_ignore(just(Token::Eq))
1177        .then(expr_parser(src.clone()))
1178        .then_ignore(just(Token::Semicolon))
1179        .map_with_span(move |((name, ty), value), span: Range<usize>| Stmt::Let {
1180            name,
1181            ty,
1182            value,
1183            span: make_span(&src, span),
1184        });
1185
1186    let return_stmt = just(Token::KwReturn)
1187        .ignore_then(expr_parser(src2.clone()).or_not())
1188        .then_ignore(just(Token::Semicolon))
1189        .map_with_span(move |value, span: Range<usize>| Stmt::Return {
1190            value,
1191            span: make_span(&src2, span),
1192        });
1193
1194    let if_stmt = recursive(|if_stmt| {
1195        let src_if = src3.clone();
1196        let block_clone = block.clone();
1197
1198        just(Token::KwIf)
1199            .ignore_then(expr_parser(src3.clone()))
1200            .then(block_clone.clone())
1201            .then(
1202                just(Token::KwElse)
1203                    .ignore_then(
1204                        if_stmt
1205                            .map(|s| ElseBranch::ElseIf(Box::new(s)))
1206                            .or(block_clone.map(ElseBranch::Block)),
1207                    )
1208                    .or_not(),
1209            )
1210            .map_with_span(
1211                move |((condition, then_block), else_block), span: Range<usize>| Stmt::If {
1212                    condition,
1213                    then_block,
1214                    else_block,
1215                    span: make_span(&src_if, span),
1216                },
1217            )
1218    });
1219
1220    let for_stmt = just(Token::KwFor)
1221        .ignore_then(for_pattern_parser(src4.clone()))
1222        .then_ignore(just(Token::KwIn))
1223        .then(expr_parser(src4.clone()))
1224        .then(block.clone())
1225        .map_with_span(
1226            move |((pattern, iter), body), span: Range<usize>| Stmt::For {
1227                pattern,
1228                iter,
1229                body,
1230                span: make_span(&src4, span),
1231            },
1232        );
1233
1234    let while_stmt = just(Token::KwWhile)
1235        .ignore_then(expr_parser(src7.clone()))
1236        .then(block.clone())
1237        .map_with_span(move |(condition, body), span: Range<usize>| Stmt::While {
1238            condition,
1239            body,
1240            span: make_span(&src7, span),
1241        });
1242
1243    let src8 = source.clone();
1244    let loop_stmt = just(Token::KwLoop)
1245        .ignore_then(block.clone())
1246        .map_with_span(move |body, span: Range<usize>| Stmt::Loop {
1247            body,
1248            span: make_span(&src8, span),
1249        });
1250
1251    let src9 = source.clone();
1252    let break_stmt = just(Token::KwBreak)
1253        .then_ignore(just(Token::Semicolon))
1254        .map_with_span(move |_, span: Range<usize>| Stmt::Break {
1255            span: make_span(&src9, span),
1256        });
1257
1258    // RFC-0012: mock divine -> value; or mock divine -> fail("msg");
1259    // Also accepts: mock infer -> value; (alias for mock divine)
1260    let src12 = source.clone();
1261    let mock_divine_stmt = just(Token::KwMock)
1262        .ignore_then(just(Token::KwDivine).or(just(Token::KwInfer)))
1263        .ignore_then(just(Token::Arrow))
1264        .ignore_then(expr_parser(src12.clone()).map(|expr| {
1265            // Check if this is a fail() call expression and convert to MockValue::Fail
1266            if let Expr::Fail { error, .. } = expr {
1267                MockValue::Fail(*error)
1268            } else {
1269                MockValue::Value(expr)
1270            }
1271        }))
1272        .then_ignore(just(Token::Semicolon))
1273        .map_with_span(move |value, span: Range<usize>| Stmt::MockDivine {
1274            value,
1275            span: make_span(&src12, span),
1276        });
1277
1278    // mock tool ToolName.fn_name -> value; or mock tool ToolName.fn_name -> fail("msg");
1279    let src13 = source.clone();
1280    let src14 = source.clone();
1281    let src15 = source.clone();
1282    let mock_tool_stmt = just(Token::KwMock)
1283        .ignore_then(just(Token::KwTool))
1284        .ignore_then(ident_token_parser(src13.clone())) // Tool name
1285        .then_ignore(just(Token::Dot))
1286        .then(ident_token_parser(src14.clone())) // Function name
1287        .then_ignore(just(Token::Arrow))
1288        .then(expr_parser(src15.clone()).map(|expr| {
1289            // Check if this is a fail() call expression and convert to MockValue::Fail
1290            if let Expr::Fail { error, .. } = expr {
1291                MockValue::Fail(*error)
1292            } else {
1293                MockValue::Value(expr)
1294            }
1295        }))
1296        .then_ignore(just(Token::Semicolon))
1297        .map_with_span(
1298            move |((tool_name, fn_name), value), span: Range<usize>| Stmt::MockTool {
1299                tool_name,
1300                fn_name,
1301                value,
1302                span: make_span(&src15, span),
1303            },
1304        );
1305
1306    let assign_stmt = ident_token_parser(src5.clone())
1307        .then_ignore(just(Token::Eq))
1308        .then(expr_parser(src5.clone()))
1309        .then_ignore(just(Token::Semicolon))
1310        .map_with_span(move |(name, value), span: Range<usize>| Stmt::Assign {
1311            name,
1312            value,
1313            span: make_span(&src5, span),
1314        });
1315
1316    // span "name" { body } - timed observability block
1317    let src16 = source.clone();
1318    let span_block_stmt = just(Token::KwSpan)
1319        .ignore_then(expr_parser(src16.clone()))
1320        .then(block.clone())
1321        .map_with_span(move |(name, body), span: Range<usize>| Stmt::SpanBlock {
1322            name,
1323            body,
1324            span: make_span(&src16, span),
1325        });
1326
1327    // checkpoint(); - explicit persistence checkpoint
1328    let src17 = source.clone();
1329    let checkpoint_stmt = just(Token::KwCheckpoint)
1330        .then_ignore(just(Token::LParen))
1331        .then_ignore(just(Token::RParen))
1332        .then_ignore(just(Token::Semicolon))
1333        .map_with_span(move |_, span: Range<usize>| Stmt::Checkpoint {
1334            span: make_span(&src17, span),
1335        });
1336
1337    let expr_stmt = expr_parser(src6.clone())
1338        .then_ignore(just(Token::Semicolon))
1339        .map_with_span(move |expr, span: Range<usize>| Stmt::Expr {
1340            expr,
1341            span: make_span(&src6, span),
1342        });
1343
1344    let_tuple_stmt
1345        .or(let_stmt)
1346        .or(return_stmt)
1347        .or(if_stmt)
1348        .or(for_stmt)
1349        .or(while_stmt)
1350        .or(loop_stmt)
1351        .or(break_stmt)
1352        .or(span_block_stmt)
1353        .or(checkpoint_stmt)
1354        .or(mock_divine_stmt)
1355        .or(mock_tool_stmt)
1356        .or(assign_stmt)
1357        .or(expr_stmt)
1358}
1359
1360// =============================================================================
1361// Expression parsers
1362// =============================================================================
1363
1364/// Parser for expressions (with precedence climbing for binary ops).
1365/// Uses `boxed()` to reduce type complexity and avoid macOS linker symbol length limits.
1366#[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
1367fn expr_parser(source: Arc<str>) -> BoxedParser<'static, Token, Expr, ParseError> {
1368    recursive(move |expr: Recursive<Token, Expr, ParseError>| {
1369        let src = source.clone();
1370
1371        let literal = literal_parser(src.clone());
1372        let var = var_parser(src.clone());
1373
1374        // Parenthesized expression or tuple literal
1375        // (expr) is a paren, (expr, expr, ...) is a tuple
1376        let paren_or_tuple = just(Token::LParen)
1377            .ignore_then(
1378                expr.clone()
1379                    .separated_by(just(Token::Comma))
1380                    .allow_trailing(),
1381            )
1382            .then_ignore(just(Token::RParen))
1383            .map_with_span({
1384                let src = src.clone();
1385                move |elements, span: Range<usize>| {
1386                    if elements.len() == 1 {
1387                        // Single element without trailing comma = parenthesized expression
1388                        Expr::Paren {
1389                            inner: Box::new(elements.into_iter().next().unwrap()),
1390                            span: make_span(&src, span),
1391                        }
1392                    } else {
1393                        // Multiple elements or empty = tuple
1394                        Expr::Tuple {
1395                            elements,
1396                            span: make_span(&src, span),
1397                        }
1398                    }
1399                }
1400            });
1401
1402        let list = expr
1403            .clone()
1404            .separated_by(just(Token::Comma))
1405            .allow_trailing()
1406            .delimited_by(just(Token::LBracket), just(Token::RBracket))
1407            .map_with_span({
1408                let src = src.clone();
1409                move |elements, span: Range<usize>| Expr::List {
1410                    elements,
1411                    span: make_span(&src, span),
1412                }
1413            });
1414
1415        // self.field or self.method(args)
1416        let self_access = just(Token::KwSelf)
1417            .ignore_then(just(Token::Dot))
1418            .ignore_then(ident_token_parser(src.clone()))
1419            .then(
1420                expr.clone()
1421                    .separated_by(just(Token::Comma))
1422                    .allow_trailing()
1423                    .delimited_by(just(Token::LParen), just(Token::RParen))
1424                    .or_not(),
1425            )
1426            .map_with_span({
1427                let src = src.clone();
1428                move |(field, args), span: Range<usize>| match args {
1429                    Some(args) => Expr::SelfMethodCall {
1430                        method: field,
1431                        args,
1432                        span: make_span(&src, span),
1433                    },
1434                    None => Expr::SelfField {
1435                        field,
1436                        span: make_span(&src, span),
1437                    },
1438                }
1439            });
1440
1441        // divine("template") or divine("template" -> Type)
1442        // Also accepts: infer("template") or infer("template" -> Type)
1443        let divine_expr = just(Token::KwDivine)
1444            .or(just(Token::KwInfer))
1445            .ignore_then(just(Token::LParen))
1446            .ignore_then(string_template_parser(src.clone()))
1447            .then(
1448                just(Token::Arrow)
1449                    .ignore_then(type_parser(src.clone()))
1450                    .or_not(),
1451            )
1452            .then_ignore(just(Token::RParen))
1453            .map_with_span({
1454                let src = src.clone();
1455                move |(template, result_ty), span: Range<usize>| Expr::Divine {
1456                    template,
1457                    result_ty,
1458                    span: make_span(&src, span),
1459                }
1460            });
1461
1462        // summon Agent { field: value, ... }
1463        let summon_field_init = ident_token_parser(src.clone())
1464            .then_ignore(just(Token::Colon))
1465            .then(expr.clone())
1466            .map_with_span({
1467                let src = src.clone();
1468                move |(name, value), span: Range<usize>| FieldInit {
1469                    name,
1470                    value,
1471                    span: make_span(&src, span),
1472                }
1473            });
1474
1475        let summon_expr = just(Token::KwSummon)
1476            .ignore_then(ident_token_parser(src.clone()))
1477            .then_ignore(just(Token::LBrace))
1478            .then(
1479                summon_field_init
1480                    .separated_by(just(Token::Comma))
1481                    .allow_trailing(),
1482            )
1483            .then_ignore(just(Token::RBrace))
1484            .map_with_span({
1485                let src = src.clone();
1486                move |(agent, fields), span: Range<usize>| Expr::Summon {
1487                    agent,
1488                    fields,
1489                    span: make_span(&src, span),
1490                }
1491            });
1492
1493        // await expr - we need to handle this carefully to avoid left recursion
1494        // Parse: await handle [timeout(ms)]
1495        let timeout_clause = just(Token::KwTimeout)
1496            .ignore_then(just(Token::LParen))
1497            .ignore_then(expr.clone())
1498            .then_ignore(just(Token::RParen));
1499
1500        let await_expr = just(Token::KwAwait)
1501            .ignore_then(ident_token_parser(src.clone()).map_with_span({
1502                let src = src.clone();
1503                move |name, span: Range<usize>| Expr::Var {
1504                    name,
1505                    span: make_span(&src, span),
1506                }
1507            }))
1508            .then(timeout_clause.or_not())
1509            .map_with_span({
1510                let src = src.clone();
1511                move |(handle, timeout), span: Range<usize>| Expr::Await {
1512                    handle: Box::new(handle),
1513                    timeout: timeout.map(Box::new),
1514                    span: make_span(&src, span),
1515                }
1516            });
1517
1518        // send(handle, message)
1519        let send_expr = just(Token::KwSend)
1520            .ignore_then(just(Token::LParen))
1521            .ignore_then(expr.clone())
1522            .then_ignore(just(Token::Comma))
1523            .then(expr.clone())
1524            .then_ignore(just(Token::RParen))
1525            .map_with_span({
1526                let src = src.clone();
1527                move |(handle, message), span: Range<usize>| Expr::Send {
1528                    handle: Box::new(handle),
1529                    message: Box::new(message),
1530                    span: make_span(&src, span),
1531                }
1532            });
1533
1534        // yield(value)
1535        let yield_expr = just(Token::KwYield)
1536            .ignore_then(just(Token::LParen))
1537            .ignore_then(expr.clone())
1538            .then_ignore(just(Token::RParen))
1539            .map_with_span({
1540                let src = src.clone();
1541                move |value, span: Range<usize>| Expr::Yield {
1542                    value: Box::new(value),
1543                    span: make_span(&src, span),
1544                }
1545            });
1546
1547        // reply(message) - Phase 3 session types
1548        let reply_expr = just(Token::KwReply)
1549            .ignore_then(just(Token::LParen))
1550            .ignore_then(expr.clone())
1551            .then_ignore(just(Token::RParen))
1552            .map_with_span({
1553                let src = src.clone();
1554                move |message, span: Range<usize>| Expr::Reply {
1555                    message: Box::new(message),
1556                    span: make_span(&src, span),
1557                }
1558            });
1559
1560        // Turbofish type arguments: ::<Int, String>
1561        let turbofish = just(Token::ColonColon)
1562            .ignore_then(
1563                type_parser(src.clone())
1564                    .separated_by(just(Token::Comma))
1565                    .allow_trailing()
1566                    .delimited_by(just(Token::Lt), just(Token::Gt)),
1567            )
1568            .or_not()
1569            .map(|args| args.unwrap_or_default());
1570
1571        // function call: name(args) or name::<T, U>(args)
1572        let call_expr = ident_token_parser(src.clone())
1573            .then(turbofish.clone())
1574            .then(
1575                expr.clone()
1576                    .separated_by(just(Token::Comma))
1577                    .allow_trailing()
1578                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1579            )
1580            .map_with_span({
1581                let src = src.clone();
1582                move |((name, type_args), args), span: Range<usize>| Expr::Call {
1583                    name,
1584                    type_args,
1585                    args,
1586                    span: make_span(&src, span),
1587                }
1588            });
1589
1590        // Pattern for match arms
1591        let pattern = pattern_parser(src.clone());
1592
1593        // match expression: match expr { Pattern => expr, ... }
1594        let match_arm = pattern
1595            .then_ignore(just(Token::FatArrow))
1596            .then(expr.clone())
1597            .map_with_span({
1598                let src = src.clone();
1599                move |(pattern, body), span: Range<usize>| MatchArm {
1600                    pattern,
1601                    body,
1602                    span: make_span(&src, span),
1603                }
1604            });
1605
1606        let match_expr = just(Token::KwMatch)
1607            .ignore_then(expr.clone())
1608            .then(
1609                match_arm
1610                    .separated_by(just(Token::Comma))
1611                    .allow_trailing()
1612                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
1613            )
1614            .map_with_span({
1615                let src = src.clone();
1616                move |(scrutinee, arms), span: Range<usize>| Expr::Match {
1617                    scrutinee: Box::new(scrutinee),
1618                    arms,
1619                    span: make_span(&src, span),
1620                }
1621            });
1622
1623        // receive() - receive message from mailbox
1624        let receive_expr = just(Token::KwReceive)
1625            .ignore_then(just(Token::LParen))
1626            .ignore_then(just(Token::RParen))
1627            .map_with_span({
1628                let src = src.clone();
1629                move |_, span: Range<usize>| Expr::Receive {
1630                    span: make_span(&src, span),
1631                }
1632            });
1633
1634        // trace(message) - emit a trace event
1635        let trace_expr = just(Token::KwTrace)
1636            .ignore_then(just(Token::LParen))
1637            .ignore_then(expr.clone())
1638            .then_ignore(just(Token::RParen))
1639            .map_with_span({
1640                let src = src.clone();
1641                move |message, span: Range<usize>| Expr::Trace {
1642                    message: Box::new(message),
1643                    span: make_span(&src, span),
1644                }
1645            });
1646
1647        // Record construction: RecordName { field: value, ... }
1648        // This is similar to summon but without the summon keyword
1649        // Must come before var to avoid conflict
1650        let record_field_init = ident_token_parser(src.clone())
1651            .then_ignore(just(Token::Colon))
1652            .then(expr.clone())
1653            .map_with_span({
1654                let src = src.clone();
1655                move |(name, value), span: Range<usize>| FieldInit {
1656                    name,
1657                    value,
1658                    span: make_span(&src, span),
1659                }
1660            });
1661
1662        // Turbofish for record construction: Pair::<Int, String> { ... }
1663        let record_turbofish = just(Token::ColonColon)
1664            .ignore_then(
1665                type_parser(src.clone())
1666                    .separated_by(just(Token::Comma))
1667                    .allow_trailing()
1668                    .delimited_by(just(Token::Lt), just(Token::Gt)),
1669            )
1670            .or_not()
1671            .map(|args| args.unwrap_or_default());
1672
1673        let record_construct = ident_token_parser(src.clone())
1674            .then(record_turbofish)
1675            .then_ignore(just(Token::LBrace))
1676            .then(
1677                record_field_init
1678                    .separated_by(just(Token::Comma))
1679                    .allow_trailing(),
1680            )
1681            .then_ignore(just(Token::RBrace))
1682            .map_with_span({
1683                let src = src.clone();
1684                move |((name, type_args), fields), span: Range<usize>| Expr::RecordConstruct {
1685                    name,
1686                    type_args,
1687                    fields,
1688                    span: make_span(&src, span),
1689                }
1690            });
1691
1692        // Closure parameter: `name` or `name: Type`
1693        let closure_param = ident_token_parser(src.clone())
1694            .then(
1695                just(Token::Colon)
1696                    .ignore_then(type_parser(src.clone()))
1697                    .or_not(),
1698            )
1699            .map_with_span({
1700                let src = src.clone();
1701                move |(name, ty), span: Range<usize>| ClosureParam {
1702                    name,
1703                    ty,
1704                    span: make_span(&src, span),
1705                }
1706            });
1707
1708        // Closure expression: |params| body
1709        // Handle both `|| expr` (empty params using Or token) and `|params| expr`
1710        let closure_empty = just(Token::Or).ignore_then(expr.clone()).map_with_span({
1711            let src = src.clone();
1712            move |body, span: Range<usize>| Expr::Closure {
1713                params: vec![],
1714                body: Box::new(body),
1715                span: make_span(&src, span),
1716            }
1717        });
1718
1719        let closure_with_params = just(Token::Pipe)
1720            .ignore_then(
1721                closure_param
1722                    .separated_by(just(Token::Comma))
1723                    .allow_trailing(),
1724            )
1725            .then_ignore(just(Token::Pipe))
1726            .then(expr.clone())
1727            .map_with_span({
1728                let src = src.clone();
1729                move |(params, body), span: Range<usize>| Expr::Closure {
1730                    params,
1731                    body: Box::new(body),
1732                    span: make_span(&src, span),
1733                }
1734            });
1735
1736        let closure = closure_with_params.or(closure_empty);
1737
1738        // Map literal: { key: value, ... } or {}
1739        // This must be distinguished from record construction which has an identifier before the brace
1740        let map_entry = expr
1741            .clone()
1742            .then_ignore(just(Token::Colon))
1743            .then(expr.clone())
1744            .map_with_span({
1745                let src = src.clone();
1746                move |(key, value), span: Range<usize>| MapEntry {
1747                    key,
1748                    value,
1749                    span: make_span(&src, span),
1750                }
1751            });
1752
1753        let map_literal = map_entry
1754            .separated_by(just(Token::Comma))
1755            .allow_trailing()
1756            .delimited_by(just(Token::LBrace), just(Token::RBrace))
1757            .map_with_span({
1758                let src = src.clone();
1759                move |entries, span: Range<usize>| Expr::Map {
1760                    entries,
1761                    span: make_span(&src, span),
1762                }
1763            });
1764
1765        // Enum variant construction: EnumName::Variant or Either::<L, R>::Left(payload)
1766        // Turbofish comes between enum name and ::Variant
1767        let variant_turbofish = just(Token::ColonColon)
1768            .ignore_then(
1769                type_parser(src.clone())
1770                    .separated_by(just(Token::Comma))
1771                    .allow_trailing()
1772                    .delimited_by(just(Token::Lt), just(Token::Gt)),
1773            )
1774            .or_not()
1775            .map(|args| args.unwrap_or_default());
1776
1777        // Parser for enum name: accepts identifiers OR builtin type keywords (Option, Result)
1778        // This allows Option::Some(...) and Result::Ok(...) syntax
1779        let enum_name_parser = {
1780            let src = src.clone();
1781            ident_token_parser(src.clone()).or(just(Token::TyOption)
1782                .or(just(Token::TyResult))
1783                .map_with_span({
1784                    let src = src.clone();
1785                    move |token, span: Range<usize>| {
1786                        let name = match token {
1787                            Token::TyOption => "Option",
1788                            Token::TyResult => "Result",
1789                            _ => unreachable!(),
1790                        };
1791                        Ident {
1792                            name: name.to_string(),
1793                            span: make_span(&src, span),
1794                        }
1795                    }
1796                }))
1797        };
1798
1799        let variant_construct = enum_name_parser
1800            .then(variant_turbofish)
1801            .then_ignore(just(Token::ColonColon))
1802            .then(ident_token_parser(src.clone()))
1803            .then(
1804                expr.clone()
1805                    .delimited_by(just(Token::LParen), just(Token::RParen))
1806                    .or_not(),
1807            )
1808            .map_with_span({
1809                let src = src.clone();
1810                move |(((enum_name, type_args), variant), payload), span: Range<usize>| {
1811                    Expr::VariantConstruct {
1812                        enum_name,
1813                        type_args,
1814                        variant,
1815                        payload: payload.map(Box::new),
1816                        span: make_span(&src, span),
1817                    }
1818                }
1819            });
1820
1821        // Atom: the base expression without binary ops
1822        // Box early to cut type complexity
1823        // Note: record_construct must come before call_expr and var to parse `Name { ... }` correctly
1824        // Note: receive_expr must come before call_expr to avoid being parsed as function call
1825        // Note: closure must come before other expressions to handle `|` tokens correctly
1826        // Note: map_literal must come after record_construct (record has name before brace)
1827        // Note: variant_construct must come before call_expr to parse `EnumName::Variant(...)` correctly
1828        let atom = closure
1829            .or(divine_expr)
1830            .or(summon_expr)
1831            .or(await_expr)
1832            .or(send_expr)
1833            .or(yield_expr)
1834            .or(reply_expr)
1835            .or(receive_expr)
1836            .or(trace_expr)
1837            .or(match_expr)
1838            .or(self_access)
1839            .or(record_construct)
1840            .or(variant_construct)
1841            .or(call_expr)
1842            .or(map_literal)
1843            .or(list)
1844            .or(paren_or_tuple)
1845            .or(literal)
1846            .or(var)
1847            .boxed();
1848
1849        // Postfix access: expr.field, expr.0 (tuple index), or expr.method(args) (tool call)
1850        // We need to distinguish between field access, tuple index, and method call
1851        enum PostfixOp {
1852            Field(Ident),
1853            TupleIndex(usize, Range<usize>),
1854            MethodCall(Ident, Vec<Expr>, Range<usize>), // method name, args, span of closing paren
1855        }
1856
1857        // Parse method call: .ident(args) — uses method_name_parser to allow keywords as method names
1858        let method_call = method_name_parser(src.clone())
1859            .then(
1860                expr.clone()
1861                    .separated_by(just(Token::Comma))
1862                    .allow_trailing()
1863                    .delimited_by(just(Token::LParen), just(Token::RParen)),
1864            )
1865            .map_with_span(|(name, args), span: Range<usize>| {
1866                PostfixOp::MethodCall(name, args, span)
1867            });
1868
1869        let postfix_op = just(Token::Dot).ignore_then(
1870            // Try to parse a tuple index (integer literal)
1871            filter_map({
1872                let src = src.clone();
1873                move |span: Range<usize>, token| match token {
1874                    Token::IntLit => {
1875                        let text = &src[span.start..span.end];
1876                        text.parse::<usize>()
1877                            .map(|idx| PostfixOp::TupleIndex(idx, span.clone()))
1878                            .map_err(|_| Simple::custom(span, "invalid tuple index"))
1879                    }
1880                    _ => Err(Simple::expected_input_found(
1881                        span,
1882                        vec![Some(Token::IntLit)],
1883                        Some(token),
1884                    )),
1885                }
1886            })
1887            // Try method call first, then field access
1888            .or(method_call)
1889            .or(method_name_parser(src.clone()).map(PostfixOp::Field)),
1890        );
1891
1892        let postfix = atom
1893            .then(postfix_op.repeated())
1894            .foldl({
1895                let src = src.clone();
1896                move |object, op| match op {
1897                    PostfixOp::Field(field) => {
1898                        let span = make_span(&src, object.span().start..field.span.end);
1899                        Expr::FieldAccess {
1900                            object: Box::new(object),
1901                            field,
1902                            span,
1903                        }
1904                    }
1905                    PostfixOp::TupleIndex(index, idx_span) => {
1906                        let span = make_span(&src, object.span().start..idx_span.end);
1907                        Expr::TupleIndex {
1908                            tuple: Box::new(object),
1909                            index,
1910                            span,
1911                        }
1912                    }
1913                    PostfixOp::MethodCall(method, args, call_span) => {
1914                        // If object is a Var, this might be a tool call
1915                        // Tool calls look like: Http.get(url)
1916                        if let Expr::Var { name: tool, .. } = &object {
1917                            let span = make_span(&src, object.span().start..call_span.end);
1918                            Expr::ToolCall {
1919                                tool: tool.clone(),
1920                                function: method,
1921                                args,
1922                                span,
1923                            }
1924                        } else {
1925                            // Not a tool call - this is a method call on a value.
1926                            // Produce Apply { callee: FieldAccess, args }
1927                            let span = make_span(&src, object.span().start..call_span.end);
1928                            let callee = Expr::FieldAccess {
1929                                object: Box::new(object),
1930                                field: method,
1931                                span: span.clone(),
1932                            };
1933                            Expr::Apply {
1934                                callee: Box::new(callee),
1935                                args,
1936                                span,
1937                            }
1938                        }
1939                    }
1940                }
1941            })
1942            .boxed();
1943
1944        // Unary expressions
1945        let unary = just(Token::Minus)
1946            .to(UnaryOp::Neg)
1947            .or(just(Token::Bang).to(UnaryOp::Not))
1948            .repeated()
1949            .then(postfix.clone())
1950            .foldr(|op, operand| {
1951                let span = operand.span().clone();
1952                Expr::Unary {
1953                    op,
1954                    operand: Box::new(operand),
1955                    span,
1956                }
1957            })
1958            .boxed();
1959
1960        // RFC-0007: try expression - propagates errors upward
1961        // try expr
1962        let try_expr = just(Token::KwTry)
1963            .ignore_then(postfix.clone())
1964            .map_with_span({
1965                let src = src.clone();
1966                move |inner, span: Range<usize>| Expr::Try {
1967                    expr: Box::new(inner),
1968                    span: make_span(&src, span),
1969                }
1970            })
1971            .boxed();
1972
1973        // fail expression - explicit error raising
1974        // fail "message" or fail Error { ... }
1975        let fail_expr = just(Token::KwFail)
1976            .ignore_then(postfix.clone())
1977            .map_with_span({
1978                let src = src.clone();
1979                move |error, span: Range<usize>| Expr::Fail {
1980                    error: Box::new(error),
1981                    span: make_span(&src, span),
1982                }
1983            })
1984            .boxed();
1985
1986        // retry expression - retry a fallible operation
1987        // retry(count) { body }
1988        // retry(count, delay: ms) { body }
1989        // retry(count, on: [ErrorKind.Network, ...]) { body }
1990        let retry_delay = just(Token::Comma)
1991            .ignore_then(just(Token::KwDelay))
1992            .ignore_then(just(Token::Colon))
1993            .ignore_then(postfix.clone());
1994
1995        let retry_on = just(Token::Comma)
1996            .ignore_then(just(Token::KwOn))
1997            .ignore_then(just(Token::Colon))
1998            .ignore_then(
1999                postfix
2000                    .clone()
2001                    .separated_by(just(Token::Comma))
2002                    .allow_trailing()
2003                    .delimited_by(just(Token::LBracket), just(Token::RBracket)),
2004            );
2005
2006        let retry_expr = just(Token::KwRetry)
2007            .ignore_then(just(Token::LParen))
2008            .ignore_then(postfix.clone())
2009            .then(retry_delay.or_not())
2010            .then(retry_on.or_not())
2011            .then_ignore(just(Token::RParen))
2012            .then(
2013                expr.clone()
2014                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
2015            )
2016            .map_with_span({
2017                let src = src.clone();
2018                move |(((count, delay), on_errors), body), span: Range<usize>| Expr::Retry {
2019                    count: Box::new(count),
2020                    delay: delay.map(Box::new),
2021                    on_errors,
2022                    body: Box::new(body),
2023                    span: make_span(&src, span),
2024                }
2025            })
2026            .boxed();
2027
2028        // Combined unary (including try, fail, and retry)
2029        let unary = retry_expr.or(fail_expr).or(try_expr).or(unary).boxed();
2030
2031        // Binary operators with precedence levels
2032        // Level 7: * / %
2033        let mul_div_op = just(Token::Star)
2034            .to(BinOp::Mul)
2035            .or(just(Token::Slash).to(BinOp::Div))
2036            .or(just(Token::Percent).to(BinOp::Rem));
2037
2038        let mul_div = unary
2039            .clone()
2040            .then(mul_div_op.then(unary.clone()).repeated())
2041            .foldl({
2042                let src = src.clone();
2043                move |left, (op, right)| {
2044                    let span = make_span(&src, left.span().start..right.span().end);
2045                    Expr::Binary {
2046                        op,
2047                        left: Box::new(left),
2048                        right: Box::new(right),
2049                        span,
2050                    }
2051                }
2052            })
2053            .boxed();
2054
2055        // Level 6: + -
2056        let add_sub_op = just(Token::Plus)
2057            .to(BinOp::Add)
2058            .or(just(Token::Minus).to(BinOp::Sub));
2059
2060        let add_sub = mul_div
2061            .clone()
2062            .then(add_sub_op.then(mul_div).repeated())
2063            .foldl({
2064                let src = src.clone();
2065                move |left, (op, right)| {
2066                    let span = make_span(&src, left.span().start..right.span().end);
2067                    Expr::Binary {
2068                        op,
2069                        left: Box::new(left),
2070                        right: Box::new(right),
2071                        span,
2072                    }
2073                }
2074            })
2075            .boxed();
2076
2077        // Level 5: ++
2078        let concat_op = just(Token::PlusPlus).to(BinOp::Concat);
2079
2080        let concat = add_sub
2081            .clone()
2082            .then(concat_op.then(add_sub).repeated())
2083            .foldl({
2084                let src = src.clone();
2085                move |left, (op, right)| {
2086                    let span = make_span(&src, left.span().start..right.span().end);
2087                    Expr::Binary {
2088                        op,
2089                        left: Box::new(left),
2090                        right: Box::new(right),
2091                        span,
2092                    }
2093                }
2094            })
2095            .boxed();
2096
2097        // Level 4: < > <= >=
2098        let cmp_op = choice((
2099            just(Token::Le).to(BinOp::Le),
2100            just(Token::Ge).to(BinOp::Ge),
2101            just(Token::Lt).to(BinOp::Lt),
2102            just(Token::Gt).to(BinOp::Gt),
2103        ));
2104
2105        let comparison = concat
2106            .clone()
2107            .then(cmp_op.then(concat).repeated())
2108            .foldl({
2109                let src = src.clone();
2110                move |left, (op, right)| {
2111                    let span = make_span(&src, left.span().start..right.span().end);
2112                    Expr::Binary {
2113                        op,
2114                        left: Box::new(left),
2115                        right: Box::new(right),
2116                        span,
2117                    }
2118                }
2119            })
2120            .boxed();
2121
2122        // Level 3: == !=
2123        let eq_op = just(Token::EqEq)
2124            .to(BinOp::Eq)
2125            .or(just(Token::Ne).to(BinOp::Ne));
2126
2127        let equality = comparison
2128            .clone()
2129            .then(eq_op.then(comparison).repeated())
2130            .foldl({
2131                let src = src.clone();
2132                move |left, (op, right)| {
2133                    let span = make_span(&src, left.span().start..right.span().end);
2134                    Expr::Binary {
2135                        op,
2136                        left: Box::new(left),
2137                        right: Box::new(right),
2138                        span,
2139                    }
2140                }
2141            })
2142            .boxed();
2143
2144        // Level 2: &&
2145        let and_op = just(Token::And).to(BinOp::And);
2146
2147        let and = equality
2148            .clone()
2149            .then(and_op.then(equality).repeated())
2150            .foldl({
2151                let src = src.clone();
2152                move |left, (op, right)| {
2153                    let span = make_span(&src, left.span().start..right.span().end);
2154                    Expr::Binary {
2155                        op,
2156                        left: Box::new(left),
2157                        right: Box::new(right),
2158                        span,
2159                    }
2160                }
2161            })
2162            .boxed();
2163
2164        // Level 1: ||
2165        let or_op = just(Token::Or).to(BinOp::Or);
2166
2167        let or_expr = and.clone().then(or_op.then(and).repeated()).foldl({
2168            let src = src.clone();
2169            move |left, (op, right)| {
2170                let span = make_span(&src, left.span().start..right.span().end);
2171                Expr::Binary {
2172                    op,
2173                    left: Box::new(left),
2174                    right: Box::new(right),
2175                    span,
2176                }
2177            }
2178        });
2179
2180        // RFC-0007: catch expression (lowest precedence)
2181        // expr catch { recovery } OR expr catch(e) { recovery }
2182        let catch_recovery = just(Token::KwCatch)
2183            .ignore_then(
2184                ident_token_parser(src.clone())
2185                    .delimited_by(just(Token::LParen), just(Token::RParen))
2186                    .or_not(),
2187            )
2188            .then(
2189                expr.clone()
2190                    .delimited_by(just(Token::LBrace), just(Token::RBrace)),
2191            );
2192
2193        or_expr.then(catch_recovery.or_not()).map_with_span({
2194            let src = src.clone();
2195            move |(inner, catch_opt), span: Range<usize>| match catch_opt {
2196                Some((error_bind, recovery)) => Expr::Catch {
2197                    expr: Box::new(inner),
2198                    error_bind,
2199                    recovery: Box::new(recovery),
2200                    span: make_span(&src, span),
2201                },
2202                None => inner,
2203            }
2204        })
2205    })
2206    .boxed()
2207}
2208
2209// =============================================================================
2210// Primitive parsers
2211// =============================================================================
2212
2213/// Create a Span from a Range<usize>.
2214fn make_span(source: &Arc<str>, range: Range<usize>) -> Span {
2215    Span::new(range.start, range.end, Arc::clone(source))
2216}
2217
2218/// Parser for identifier tokens.
2219fn ident_token_parser(source: Arc<str>) -> impl Parser<Token, Ident, Error = ParseError> + Clone {
2220    filter_map(move |span: Range<usize>, token| match token {
2221        Token::Ident => {
2222            let text = &source[span.start..span.end];
2223            Ok(Ident::new(text.to_string(), make_span(&source, span)))
2224        }
2225        _ => Err(Simple::expected_input_found(
2226            span,
2227            vec![Some(Token::Ident)],
2228            Some(token),
2229        )),
2230    })
2231}
2232
2233/// Parser for identifiers in method/field positions where keywords should also be accepted.
2234/// This allows `Shell.run()`, `Fs.read()`, etc. where `run`/`read` might be keywords.
2235fn method_name_parser(
2236    source: Arc<str>,
2237) -> impl Parser<Token, Ident, Error = ParseError> + Clone {
2238    filter_map(move |span: Range<usize>, token| {
2239        let text = &source[span.start..span.end];
2240        match token {
2241            Token::Ident | Token::KwRun | Token::KwUse | Token::KwFn
2242            | Token::KwReturn | Token::KwFor | Token::KwIn | Token::KwIf | Token::KwElse
2243            | Token::KwLet | Token::KwOn | Token::KwAgent | Token::KwSupervisor
2244            | Token::KwTool | Token::KwMock | Token::KwStart | Token::KwStop
2245            | Token::KwError | Token::KwMatch | Token::KwBreak | Token::KwLoop => {
2246                Ok(Ident::new(text.to_string(), make_span(&source, span)))
2247            }
2248            _ => Err(Simple::expected_input_found(
2249                span,
2250                vec![Some(Token::Ident)],
2251                Some(token),
2252            )),
2253        }
2254    })
2255}
2256
2257/// Parser for variable references.
2258fn var_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
2259    ident_token_parser(source.clone()).map_with_span(move |name, span: Range<usize>| Expr::Var {
2260        name,
2261        span: make_span(&source, span),
2262    })
2263}
2264
2265/// Parser for type expressions.
2266fn type_parser(source: Arc<str>) -> impl Parser<Token, TypeExpr, Error = ParseError> + Clone {
2267    recursive(move |ty| {
2268        let src = source.clone();
2269
2270        let primitive = choice((
2271            just(Token::TyInt).to(TypeExpr::Int),
2272            just(Token::TyFloat).to(TypeExpr::Float),
2273            just(Token::TyBool).to(TypeExpr::Bool),
2274            just(Token::TyString).to(TypeExpr::String),
2275            just(Token::TyUnit).to(TypeExpr::Unit),
2276        ));
2277
2278        let list_ty = just(Token::TyList)
2279            .ignore_then(just(Token::Lt))
2280            .ignore_then(ty.clone())
2281            .then_ignore(just(Token::Gt))
2282            .map(|inner| TypeExpr::List(Box::new(inner)));
2283
2284        let option_ty = just(Token::TyOption)
2285            .ignore_then(just(Token::Lt))
2286            .ignore_then(ty.clone())
2287            .then_ignore(just(Token::Gt))
2288            .map(|inner| TypeExpr::Option(Box::new(inner)));
2289
2290        let oracle_ty = just(Token::TyOracle)
2291            .ignore_then(just(Token::Lt))
2292            .ignore_then(ty.clone())
2293            .then_ignore(just(Token::Gt))
2294            .map(|inner| TypeExpr::Oracle(Box::new(inner)));
2295
2296        let agent_ty = just(Token::TyAgent)
2297            .ignore_then(just(Token::Lt))
2298            .ignore_then(ident_token_parser(src.clone()))
2299            .then_ignore(just(Token::Gt))
2300            .map(TypeExpr::Agent);
2301
2302        // Named type with optional type arguments: MyRecord, Pair<Int, String>
2303        let named_ty = ident_token_parser(src.clone())
2304            .then(
2305                ty.clone()
2306                    .separated_by(just(Token::Comma))
2307                    .allow_trailing()
2308                    .delimited_by(just(Token::Lt), just(Token::Gt))
2309                    .or_not(),
2310            )
2311            .map(|(name, type_args)| TypeExpr::Named(name, type_args.unwrap_or_default()));
2312
2313        // Function type: Fn(A, B) -> C
2314        let fn_ty = just(Token::TyFn)
2315            .ignore_then(
2316                ty.clone()
2317                    .separated_by(just(Token::Comma))
2318                    .allow_trailing()
2319                    .delimited_by(just(Token::LParen), just(Token::RParen)),
2320            )
2321            .then_ignore(just(Token::Arrow))
2322            .then(ty.clone())
2323            .map(|(params, ret)| TypeExpr::Fn(params, Box::new(ret)));
2324
2325        // Map type: Map<K, V>
2326        let map_ty = just(Token::TyMap)
2327            .ignore_then(just(Token::Lt))
2328            .ignore_then(ty.clone())
2329            .then_ignore(just(Token::Comma))
2330            .then(ty.clone())
2331            .then_ignore(just(Token::Gt))
2332            .map(|(k, v)| TypeExpr::Map(Box::new(k), Box::new(v)));
2333
2334        // Result type: Result<T, E>
2335        let result_ty = just(Token::TyResult)
2336            .ignore_then(just(Token::Lt))
2337            .ignore_then(ty.clone())
2338            .then_ignore(just(Token::Comma))
2339            .then(ty.clone())
2340            .then_ignore(just(Token::Gt))
2341            .map(|(ok, err)| TypeExpr::Result(Box::new(ok), Box::new(err)));
2342
2343        // Tuple type: (A, B, C) - at least 2 elements
2344        let tuple_ty = ty
2345            .clone()
2346            .separated_by(just(Token::Comma))
2347            .at_least(2)
2348            .allow_trailing()
2349            .delimited_by(just(Token::LParen), just(Token::RParen))
2350            .map(TypeExpr::Tuple);
2351
2352        primitive
2353            .or(list_ty)
2354            .or(option_ty)
2355            .or(oracle_ty)
2356            .or(agent_ty)
2357            .or(fn_ty)
2358            .or(map_ty)
2359            .or(result_ty)
2360            .or(tuple_ty)
2361            .or(named_ty)
2362    })
2363}
2364
2365/// Parser for patterns in for loops.
2366/// Only supports simple bindings (`x`) and tuple patterns (`(k, v)`).
2367fn for_pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
2368    recursive(move |pattern| {
2369        let src = source.clone();
2370        let src2 = source.clone();
2371
2372        // Simple binding pattern: `x`
2373        let binding = ident_token_parser(src.clone()).map_with_span({
2374            let src = src.clone();
2375            move |name, span: Range<usize>| Pattern::Binding {
2376                name,
2377                span: make_span(&src, span),
2378            }
2379        });
2380
2381        // Tuple pattern: `(a, b)` - at least 2 elements
2382        let tuple_pattern = pattern
2383            .clone()
2384            .separated_by(just(Token::Comma))
2385            .at_least(2)
2386            .allow_trailing()
2387            .delimited_by(just(Token::LParen), just(Token::RParen))
2388            .map_with_span({
2389                let src = src2.clone();
2390                move |elements, span: Range<usize>| Pattern::Tuple {
2391                    elements,
2392                    span: make_span(&src, span),
2393                }
2394            });
2395
2396        tuple_pattern.or(binding)
2397    })
2398}
2399
2400/// Parser for patterns in match expressions.
2401fn pattern_parser(source: Arc<str>) -> impl Parser<Token, Pattern, Error = ParseError> + Clone {
2402    recursive(move |pattern| {
2403        let src = source.clone();
2404        let src2 = source.clone();
2405        let src3 = source.clone();
2406        let src4 = source.clone();
2407        let src5 = source.clone();
2408
2409        // Wildcard pattern: `_`
2410        let wildcard = filter_map({
2411            let src = src.clone();
2412            move |span: Range<usize>, token| match &token {
2413                Token::Ident if src[span.start..span.end].eq("_") => Ok(()),
2414                _ => Err(Simple::expected_input_found(span, vec![], Some(token))),
2415            }
2416        })
2417        .map_with_span(move |_, span: Range<usize>| Pattern::Wildcard {
2418            span: make_span(&src2, span),
2419        });
2420
2421        // Literal patterns: 42, "hello", true, false
2422        let lit_int = filter_map({
2423            let src = src3.clone();
2424            move |span: Range<usize>, token| match token {
2425                Token::IntLit => {
2426                    let text = &src[span.start..span.end];
2427                    text.parse::<i64>()
2428                        .map(Literal::Int)
2429                        .map_err(|_| Simple::custom(span, "invalid integer literal"))
2430                }
2431                _ => Err(Simple::expected_input_found(
2432                    span,
2433                    vec![Some(Token::IntLit)],
2434                    Some(token),
2435                )),
2436            }
2437        })
2438        .map_with_span({
2439            let src = src3.clone();
2440            move |value, span: Range<usize>| Pattern::Literal {
2441                value,
2442                span: make_span(&src, span),
2443            }
2444        });
2445
2446        let lit_bool = just(Token::KwTrue)
2447            .to(Literal::Bool(true))
2448            .or(just(Token::KwFalse).to(Literal::Bool(false)))
2449            .map_with_span({
2450                let src = src3.clone();
2451                move |value, span: Range<usize>| Pattern::Literal {
2452                    value,
2453                    span: make_span(&src, span),
2454                }
2455            });
2456
2457        // Tuple pattern: (a, b, c) - at least 2 elements
2458        let tuple_pattern = pattern
2459            .clone()
2460            .separated_by(just(Token::Comma))
2461            .at_least(2)
2462            .allow_trailing()
2463            .delimited_by(just(Token::LParen), just(Token::RParen))
2464            .map_with_span({
2465                let src = src5.clone();
2466                move |elements, span: Range<usize>| Pattern::Tuple {
2467                    elements,
2468                    span: make_span(&src, span),
2469                }
2470            });
2471
2472        // Enum variant with optional payload: `Ok(x)` or `Status::Active`
2473        // Qualified with payload: EnumName::Variant(pattern)
2474        // Parser for enum name: accepts identifiers OR builtin type keywords (Option, Result)
2475        let pattern_enum_name_parser = {
2476            let src = src4.clone();
2477            ident_token_parser(src.clone()).or(just(Token::TyOption)
2478                .or(just(Token::TyResult))
2479                .map_with_span({
2480                    let src = src.clone();
2481                    move |token, span: Range<usize>| {
2482                        let name = match token {
2483                            Token::TyOption => "Option",
2484                            Token::TyResult => "Result",
2485                            _ => unreachable!(),
2486                        };
2487                        Ident {
2488                            name: name.to_string(),
2489                            span: make_span(&src, span),
2490                        }
2491                    }
2492                }))
2493        };
2494        let qualified_variant_with_payload = pattern_enum_name_parser
2495            .then_ignore(just(Token::ColonColon))
2496            .then(ident_token_parser(src4.clone()))
2497            .then(
2498                pattern
2499                    .clone()
2500                    .delimited_by(just(Token::LParen), just(Token::RParen))
2501                    .or_not(),
2502            )
2503            .map_with_span({
2504                let src = src4.clone();
2505                move |((enum_name, variant), payload), span: Range<usize>| Pattern::Variant {
2506                    enum_name: Some(enum_name),
2507                    variant,
2508                    payload: payload.map(Box::new),
2509                    span: make_span(&src, span),
2510                }
2511            });
2512
2513        // Unqualified variant with payload: `Ok(x)` or just `x`
2514        let unqualified_with_payload = ident_token_parser(src4.clone())
2515            .then(
2516                pattern
2517                    .clone()
2518                    .delimited_by(just(Token::LParen), just(Token::RParen))
2519                    .or_not(),
2520            )
2521            .map_with_span({
2522                let src = src4.clone();
2523                move |(name, payload), span: Range<usize>| {
2524                    // If it looks like a variant (starts with uppercase), treat as variant
2525                    // Otherwise treat as binding (only if no payload)
2526                    if name.name.chars().next().is_some_and(|c| c.is_uppercase())
2527                        || payload.is_some()
2528                    {
2529                        Pattern::Variant {
2530                            enum_name: None,
2531                            variant: name,
2532                            payload: payload.map(Box::new),
2533                            span: make_span(&src, span),
2534                        }
2535                    } else {
2536                        Pattern::Binding {
2537                            name,
2538                            span: make_span(&src, span),
2539                        }
2540                    }
2541                }
2542            });
2543
2544        // Order matters: try wildcard first, then tuple pattern, then qualified variant, then literals, then unqualified
2545        wildcard
2546            .or(tuple_pattern)
2547            .or(qualified_variant_with_payload)
2548            .or(lit_int)
2549            .or(lit_bool)
2550            .or(unqualified_with_payload)
2551    })
2552}
2553
2554/// Parser for literals.
2555fn literal_parser(source: Arc<str>) -> impl Parser<Token, Expr, Error = ParseError> + Clone {
2556    let src = source.clone();
2557    let src2 = source.clone();
2558    let src3 = source.clone();
2559    let src4 = source.clone();
2560    let src5 = source.clone();
2561
2562    let int_lit = filter_map(move |span: Range<usize>, token| match token {
2563        Token::IntLit => {
2564            let text = &src[span.start..span.end];
2565            text.parse::<i64>()
2566                .map(Literal::Int)
2567                .map_err(|_| Simple::custom(span, "invalid integer literal"))
2568        }
2569        _ => Err(Simple::expected_input_found(
2570            span,
2571            vec![Some(Token::IntLit)],
2572            Some(token),
2573        )),
2574    })
2575    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
2576        value,
2577        span: make_span(&src2, span),
2578    });
2579
2580    let float_lit = filter_map(move |span: Range<usize>, token| match token {
2581        Token::FloatLit => {
2582            let text = &src3[span.start..span.end];
2583            text.parse::<f64>()
2584                .map(Literal::Float)
2585                .map_err(|_| Simple::custom(span, "invalid float literal"))
2586        }
2587        _ => Err(Simple::expected_input_found(
2588            span,
2589            vec![Some(Token::FloatLit)],
2590            Some(token),
2591        )),
2592    })
2593    .map_with_span(move |value, span: Range<usize>| Expr::Literal {
2594        value,
2595        span: make_span(&src4, span),
2596    });
2597
2598    let src6 = source.clone();
2599    let string_lit = filter_map(move |span: Range<usize>, token| match token {
2600        Token::StringLit => {
2601            let text = &src5[span.start..span.end];
2602            let inner = &text[1..text.len() - 1];
2603            let parts = parse_string_template(inner, &make_span(&src5, span.clone()));
2604            Ok(parts)
2605        }
2606        _ => Err(Simple::expected_input_found(
2607            span,
2608            vec![Some(Token::StringLit)],
2609            Some(token),
2610        )),
2611    })
2612    .map_with_span(move |parts, span: Range<usize>| {
2613        let span = make_span(&src6, span);
2614        // If no interpolations, use a simple string literal
2615        if parts.len() == 1 {
2616            if let StringPart::Literal(s) = &parts[0] {
2617                return Expr::Literal {
2618                    value: Literal::String(s.clone()),
2619                    span,
2620                };
2621            }
2622        }
2623        // Otherwise, use StringInterp
2624        Expr::StringInterp {
2625            template: StringTemplate {
2626                parts,
2627                span: span.clone(),
2628            },
2629            span,
2630        }
2631    });
2632
2633    let bool_lit = just(Token::KwTrue)
2634        .to(Literal::Bool(true))
2635        .or(just(Token::KwFalse).to(Literal::Bool(false)))
2636        .map_with_span(move |value, _span: Range<usize>| Expr::Literal {
2637            value,
2638            span: Span::dummy(), // bool literals don't carry source
2639        });
2640
2641    int_lit.or(float_lit).or(string_lit).or(bool_lit)
2642}
2643
2644/// Parser for string templates (handles interpolation).
2645fn string_template_parser(
2646    source: Arc<str>,
2647) -> impl Parser<Token, StringTemplate, Error = ParseError> + Clone {
2648    filter_map(move |span: Range<usize>, token| match token {
2649        Token::StringLit => {
2650            let text = &source[span.start..span.end];
2651            let inner = &text[1..text.len() - 1];
2652            let parts = parse_string_template(inner, &make_span(&source, span.clone()));
2653            Ok(StringTemplate {
2654                parts,
2655                span: make_span(&source, span),
2656            })
2657        }
2658        _ => Err(Simple::expected_input_found(
2659            span,
2660            vec![Some(Token::StringLit)],
2661            Some(token),
2662        )),
2663    })
2664}
2665
2666/// Parse a string into template parts, handling `{expr}` interpolations.
2667/// Supports arbitrary expressions: `{name}`, `{a + b}`, `{foo(x, y)}`
2668fn parse_string_template(s: &str, span: &Span) -> Vec<StringPart> {
2669    let mut parts = Vec::new();
2670    let mut current = String::new();
2671    let mut chars = s.chars().peekable();
2672
2673    while let Some(ch) = chars.next() {
2674        if ch == '{' {
2675            if !current.is_empty() {
2676                parts.push(StringPart::Literal(std::mem::take(&mut current)));
2677            }
2678
2679            // Collect the full interpolation expression, handling nested braces
2680            let mut expr_str = String::new();
2681            let mut brace_depth = 1;
2682            let mut string_quote: Option<char> = None; // Track which quote type we're in
2683            let mut escape_next = false;
2684
2685            while let Some(&c) = chars.peek() {
2686                if escape_next {
2687                    expr_str.push(c);
2688                    chars.next();
2689                    escape_next = false;
2690                    continue;
2691                }
2692
2693                if c == '\\' && string_quote.is_some() {
2694                    escape_next = true;
2695                    expr_str.push(c);
2696                    chars.next();
2697                    continue;
2698                }
2699
2700                // Handle string delimiters (both " and ')
2701                if c == '"' || c == '\'' {
2702                    match string_quote {
2703                        None => string_quote = Some(c),           // Start of string
2704                        Some(q) if q == c => string_quote = None, // End of string
2705                        Some(_) => {} // Inside different type of string, ignore
2706                    }
2707                }
2708
2709                if string_quote.is_none() {
2710                    if c == '{' {
2711                        brace_depth += 1;
2712                    } else if c == '}' {
2713                        brace_depth -= 1;
2714                        if brace_depth == 0 {
2715                            chars.next();
2716                            break;
2717                        }
2718                    }
2719                }
2720
2721                expr_str.push(c);
2722                chars.next();
2723            }
2724
2725            if !expr_str.is_empty() {
2726                let expr = parse_interp_expr(&expr_str, span);
2727                parts.push(StringPart::Interpolation(Box::new(expr)));
2728            }
2729        } else if ch == '\\' {
2730            if let Some(escaped) = chars.next() {
2731                current.push(match escaped {
2732                    'n' => '\n',
2733                    't' => '\t',
2734                    'r' => '\r',
2735                    '\\' => '\\',
2736                    '"' => '"',
2737                    '{' => '{',
2738                    '}' => '}',
2739                    other => other,
2740                });
2741            }
2742        } else {
2743            current.push(ch);
2744        }
2745    }
2746
2747    if !current.is_empty() {
2748        parts.push(StringPart::Literal(current));
2749    }
2750
2751    if parts.is_empty() {
2752        parts.push(StringPart::Literal(String::new()));
2753    }
2754
2755    parts
2756}
2757
2758/// Parse an interpolation expression string into an Expr AST node.
2759/// Lexes and parses the substring using a mini recursive-descent parser.
2760fn parse_interp_expr(s: &str, span: &Span) -> Expr {
2761    let trimmed = s.trim();
2762    if trimmed.is_empty() {
2763        // Empty interpolation, return a placeholder
2764        return Expr::Literal {
2765            value: Literal::String(String::new()),
2766            span: span.clone(),
2767        };
2768    }
2769
2770    // Lex the expression substring
2771    let lex_result = crate::lex(trimmed);
2772    let (tokens, source) = match lex_result {
2773        Ok(result) => (result.tokens().to_vec(), trimmed.to_string()),
2774        Err(_) => {
2775            // Lexing failed, return the raw string as a variable name (fallback)
2776            return Expr::Var {
2777                name: Ident::new(trimmed.to_string(), span.clone()),
2778                span: span.clone(),
2779            };
2780        }
2781    };
2782
2783    if tokens.is_empty() {
2784        return Expr::Var {
2785            name: Ident::new(trimmed.to_string(), span.clone()),
2786            span: span.clone(),
2787        };
2788    }
2789
2790    // Parse with a simple recursive descent parser
2791    let mut parser = InterpExprParser::new(&tokens, &source, span.clone());
2792    parser.parse_expr()
2793}
2794
2795/// A simple recursive-descent parser for interpolation expressions.
2796/// Supports: variables, field access, tuple index, binary ops, function calls, parens
2797struct InterpExprParser<'a> {
2798    tokens: &'a [crate::Spanned],
2799    source: &'a str,
2800    pos: usize,
2801    span: Span,
2802}
2803
2804impl<'a> InterpExprParser<'a> {
2805    fn new(tokens: &'a [crate::Spanned], source: &'a str, span: Span) -> Self {
2806        Self {
2807            tokens,
2808            source,
2809            pos: 0,
2810            span,
2811        }
2812    }
2813
2814    fn current(&self) -> Option<&Token> {
2815        self.tokens.get(self.pos).map(|s| &s.token)
2816    }
2817
2818    fn current_text(&self) -> Option<&str> {
2819        self.tokens
2820            .get(self.pos)
2821            .map(|s| &self.source[s.start..s.end])
2822    }
2823
2824    fn advance(&mut self) {
2825        if self.pos < self.tokens.len() {
2826            self.pos += 1;
2827        }
2828    }
2829
2830    fn parse_expr(&mut self) -> Expr {
2831        self.parse_or()
2832    }
2833
2834    fn parse_or(&mut self) -> Expr {
2835        let mut left = self.parse_and();
2836        while matches!(self.current(), Some(Token::Or)) {
2837            self.advance();
2838            let right = self.parse_and();
2839            left = Expr::Binary {
2840                left: Box::new(left),
2841                op: BinOp::Or,
2842                right: Box::new(right),
2843                span: self.span.clone(),
2844            };
2845        }
2846        left
2847    }
2848
2849    fn parse_and(&mut self) -> Expr {
2850        let mut left = self.parse_comparison();
2851        while matches!(self.current(), Some(Token::And)) {
2852            self.advance();
2853            let right = self.parse_comparison();
2854            left = Expr::Binary {
2855                left: Box::new(left),
2856                op: BinOp::And,
2857                right: Box::new(right),
2858                span: self.span.clone(),
2859            };
2860        }
2861        left
2862    }
2863
2864    fn parse_comparison(&mut self) -> Expr {
2865        let mut left = self.parse_additive();
2866        loop {
2867            let op = match self.current() {
2868                Some(Token::EqEq) => BinOp::Eq,
2869                Some(Token::Ne) => BinOp::Ne,
2870                Some(Token::Lt) => BinOp::Lt,
2871                Some(Token::Le) => BinOp::Le,
2872                Some(Token::Gt) => BinOp::Gt,
2873                Some(Token::Ge) => BinOp::Ge,
2874                _ => break,
2875            };
2876            self.advance();
2877            let right = self.parse_additive();
2878            left = Expr::Binary {
2879                left: Box::new(left),
2880                op,
2881                right: Box::new(right),
2882                span: self.span.clone(),
2883            };
2884        }
2885        left
2886    }
2887
2888    fn parse_additive(&mut self) -> Expr {
2889        let mut left = self.parse_multiplicative();
2890        loop {
2891            let op = match self.current() {
2892                Some(Token::Plus) => BinOp::Add,
2893                Some(Token::Minus) => BinOp::Sub,
2894                Some(Token::PlusPlus) => BinOp::Concat,
2895                _ => break,
2896            };
2897            self.advance();
2898            let right = self.parse_multiplicative();
2899            left = Expr::Binary {
2900                left: Box::new(left),
2901                op,
2902                right: Box::new(right),
2903                span: self.span.clone(),
2904            };
2905        }
2906        left
2907    }
2908
2909    fn parse_multiplicative(&mut self) -> Expr {
2910        let mut left = self.parse_unary();
2911        loop {
2912            let op = match self.current() {
2913                Some(Token::Star) => BinOp::Mul,
2914                Some(Token::Slash) => BinOp::Div,
2915                Some(Token::Percent) => BinOp::Rem,
2916                _ => break,
2917            };
2918            self.advance();
2919            let right = self.parse_unary();
2920            left = Expr::Binary {
2921                left: Box::new(left),
2922                op,
2923                right: Box::new(right),
2924                span: self.span.clone(),
2925            };
2926        }
2927        left
2928    }
2929
2930    fn parse_unary(&mut self) -> Expr {
2931        match self.current() {
2932            Some(Token::Minus) => {
2933                self.advance();
2934                let operand = self.parse_unary();
2935                Expr::Unary {
2936                    op: UnaryOp::Neg,
2937                    operand: Box::new(operand),
2938                    span: self.span.clone(),
2939                }
2940            }
2941            Some(Token::Bang) => {
2942                self.advance();
2943                let operand = self.parse_unary();
2944                Expr::Unary {
2945                    op: UnaryOp::Not,
2946                    operand: Box::new(operand),
2947                    span: self.span.clone(),
2948                }
2949            }
2950            _ => self.parse_postfix(),
2951        }
2952    }
2953
2954    fn parse_postfix(&mut self) -> Expr {
2955        let mut expr = self.parse_primary();
2956
2957        loop {
2958            match self.current() {
2959                Some(Token::Dot) => {
2960                    self.advance();
2961                    match self.current() {
2962                        Some(Token::IntLit) => {
2963                            let text = self.current_text().unwrap_or("0");
2964                            let index = text.parse::<usize>().unwrap_or(0);
2965                            self.advance();
2966                            expr = Expr::TupleIndex {
2967                                tuple: Box::new(expr),
2968                                index,
2969                                span: self.span.clone(),
2970                            };
2971                        }
2972                        Some(Token::Ident) => {
2973                            let name = self.current_text().unwrap_or("").to_string();
2974                            let field = Ident::new(name, self.span.clone());
2975                            self.advance();
2976                            expr = Expr::FieldAccess {
2977                                object: Box::new(expr),
2978                                field,
2979                                span: self.span.clone(),
2980                            };
2981                        }
2982                        _ => break,
2983                    }
2984                }
2985                Some(Token::ColonColon) if matches!(expr, Expr::Var { .. }) => {
2986                    // Turbofish: name::<Type1, Type2>(args)
2987                    if let Expr::Var { name, .. } = expr {
2988                        self.advance(); // consume ::
2989                        let type_args = self.parse_turbofish();
2990                        // Now expect (
2991                        if matches!(self.current(), Some(Token::LParen)) {
2992                            self.advance();
2993                            let args = self.parse_args();
2994                            expr = Expr::Call {
2995                                name,
2996                                type_args,
2997                                args,
2998                                span: self.span.clone(),
2999                            };
3000                        } else {
3001                            // Turbofish without call - just treat as var with type args
3002                            // This shouldn't normally happen, but handle gracefully
3003                            expr = Expr::Call {
3004                                name,
3005                                type_args,
3006                                args: vec![],
3007                                span: self.span.clone(),
3008                            };
3009                        }
3010                    }
3011                }
3012                Some(Token::LParen) if matches!(expr, Expr::Var { .. }) => {
3013                    // Function call without turbofish
3014                    if let Expr::Var { name, .. } = expr {
3015                        self.advance();
3016                        let args = self.parse_args();
3017                        expr = Expr::Call {
3018                            name,
3019                            type_args: vec![],
3020                            args,
3021                            span: self.span.clone(),
3022                        };
3023                    }
3024                }
3025                _ => break,
3026            }
3027        }
3028
3029        expr
3030    }
3031
3032    fn parse_primary(&mut self) -> Expr {
3033        match self.current() {
3034            Some(Token::IntLit) => {
3035                let text = self.current_text().unwrap_or("0");
3036                let n = text.parse::<i64>().unwrap_or(0);
3037                self.advance();
3038                Expr::Literal {
3039                    value: Literal::Int(n),
3040                    span: self.span.clone(),
3041                }
3042            }
3043            Some(Token::FloatLit) => {
3044                let text = self.current_text().unwrap_or("0.0");
3045                let f = text.parse::<f64>().unwrap_or(0.0);
3046                self.advance();
3047                Expr::Literal {
3048                    value: Literal::Float(f),
3049                    span: self.span.clone(),
3050                }
3051            }
3052            Some(Token::StringLit) => {
3053                let text = self.current_text().unwrap_or("\"\"");
3054                // Remove surrounding quotes
3055                let s = if text.len() >= 2 {
3056                    text[1..text.len() - 1].to_string()
3057                } else {
3058                    String::new()
3059                };
3060                self.advance();
3061                Expr::Literal {
3062                    value: Literal::String(s),
3063                    span: self.span.clone(),
3064                }
3065            }
3066            Some(Token::KwTrue) => {
3067                self.advance();
3068                Expr::Literal {
3069                    value: Literal::Bool(true),
3070                    span: self.span.clone(),
3071                }
3072            }
3073            Some(Token::KwFalse) => {
3074                self.advance();
3075                Expr::Literal {
3076                    value: Literal::Bool(false),
3077                    span: self.span.clone(),
3078                }
3079            }
3080            Some(Token::Ident) => {
3081                let name = self.current_text().unwrap_or("").to_string();
3082                self.advance();
3083                Expr::Var {
3084                    name: Ident::new(name, self.span.clone()),
3085                    span: self.span.clone(),
3086                }
3087            }
3088            Some(Token::KwSelf) => {
3089                self.advance();
3090                // Handle self.field
3091                if matches!(self.current(), Some(Token::Dot)) {
3092                    self.advance();
3093                    if let Some(Token::Ident) = self.current() {
3094                        let field_name = self.current_text().unwrap_or("").to_string();
3095                        let field = Ident::new(field_name, self.span.clone());
3096                        self.advance();
3097                        return Expr::SelfField {
3098                            field,
3099                            span: self.span.clone(),
3100                        };
3101                    }
3102                }
3103                Expr::Var {
3104                    name: Ident::new("self".to_string(), self.span.clone()),
3105                    span: self.span.clone(),
3106                }
3107            }
3108            Some(Token::LParen) => {
3109                self.advance();
3110                let inner = self.parse_expr();
3111                if matches!(self.current(), Some(Token::RParen)) {
3112                    self.advance();
3113                }
3114                Expr::Paren {
3115                    inner: Box::new(inner),
3116                    span: self.span.clone(),
3117                }
3118            }
3119            Some(Token::LBracket) => {
3120                // List literal
3121                self.advance();
3122                let mut elements = Vec::new();
3123                while !matches!(self.current(), Some(Token::RBracket) | None) {
3124                    elements.push(self.parse_expr());
3125                    if matches!(self.current(), Some(Token::Comma)) {
3126                        self.advance();
3127                    } else {
3128                        break;
3129                    }
3130                }
3131                if matches!(self.current(), Some(Token::RBracket)) {
3132                    self.advance();
3133                }
3134                Expr::List {
3135                    elements,
3136                    span: self.span.clone(),
3137                }
3138            }
3139            _ => {
3140                // Unknown token, return empty string literal as fallback
3141                Expr::Literal {
3142                    value: Literal::String(String::new()),
3143                    span: self.span.clone(),
3144                }
3145            }
3146        }
3147    }
3148
3149    fn parse_args(&mut self) -> Vec<Expr> {
3150        let mut args = Vec::new();
3151        while !matches!(self.current(), Some(Token::RParen) | None) {
3152            args.push(self.parse_expr());
3153            if matches!(self.current(), Some(Token::Comma)) {
3154                self.advance();
3155            } else {
3156                break;
3157            }
3158        }
3159        if matches!(self.current(), Some(Token::RParen)) {
3160            self.advance();
3161        }
3162        args
3163    }
3164
3165    /// Parse turbofish type arguments: `<Type1, Type2>`
3166    /// Assumes `::` has already been consumed.
3167    fn parse_turbofish(&mut self) -> Vec<TypeExpr> {
3168        let mut type_args = Vec::new();
3169
3170        // Expect <
3171        if !matches!(self.current(), Some(Token::Lt)) {
3172            return type_args;
3173        }
3174        self.advance();
3175
3176        // Parse comma-separated types until >
3177        loop {
3178            if matches!(self.current(), Some(Token::Gt) | None) {
3179                break;
3180            }
3181
3182            if let Some(ty) = self.parse_type() {
3183                type_args.push(ty);
3184            }
3185
3186            if matches!(self.current(), Some(Token::Comma)) {
3187                self.advance();
3188            } else {
3189                break;
3190            }
3191        }
3192
3193        // Consume >
3194        if matches!(self.current(), Some(Token::Gt)) {
3195            self.advance();
3196        }
3197
3198        type_args
3199    }
3200
3201    /// Parse a type expression: `Int`, `String`, `List<T>`, `Map<K, V>`, etc.
3202    fn parse_type(&mut self) -> Option<TypeExpr> {
3203        let name = match self.current() {
3204            Some(Token::Ident) => self.current_text().unwrap_or("").to_string(),
3205            Some(Token::TyInt) => "Int".to_string(),
3206            Some(Token::TyFloat) => "Float".to_string(),
3207            Some(Token::TyBool) => "Bool".to_string(),
3208            Some(Token::TyString) => "String".to_string(),
3209            Some(Token::TyUnit) => "Unit".to_string(),
3210            _ => return None,
3211        };
3212        self.advance();
3213
3214        // Check for type parameters: <T, U>
3215        if matches!(self.current(), Some(Token::Lt)) {
3216            self.advance();
3217            let mut params = Vec::new();
3218
3219            loop {
3220                if matches!(self.current(), Some(Token::Gt) | None) {
3221                    break;
3222                }
3223
3224                if let Some(param) = self.parse_type() {
3225                    params.push(param);
3226                }
3227
3228                if matches!(self.current(), Some(Token::Comma)) {
3229                    self.advance();
3230                } else {
3231                    break;
3232                }
3233            }
3234
3235            if matches!(self.current(), Some(Token::Gt)) {
3236                self.advance();
3237            }
3238
3239            // Return generic type
3240            Some(match name.as_str() {
3241                "List" => {
3242                    if let Some(elem) = params.into_iter().next() {
3243                        TypeExpr::List(Box::new(elem))
3244                    } else {
3245                        TypeExpr::Named(Ident::new(name, self.span.clone()), vec![])
3246                    }
3247                }
3248                "Map" => {
3249                    let mut iter = params.into_iter();
3250                    if let (Some(k), Some(v)) = (iter.next(), iter.next()) {
3251                        TypeExpr::Map(Box::new(k), Box::new(v))
3252                    } else {
3253                        TypeExpr::Named(Ident::new(name, self.span.clone()), vec![])
3254                    }
3255                }
3256                "Option" => {
3257                    if let Some(inner) = params.into_iter().next() {
3258                        TypeExpr::Option(Box::new(inner))
3259                    } else {
3260                        TypeExpr::Named(Ident::new(name, self.span.clone()), vec![])
3261                    }
3262                }
3263                "Result" => {
3264                    let mut iter = params.into_iter();
3265                    if let (Some(ok), Some(err)) = (iter.next(), iter.next()) {
3266                        TypeExpr::Result(Box::new(ok), Box::new(err))
3267                    } else {
3268                        TypeExpr::Named(Ident::new(name, self.span.clone()), vec![])
3269                    }
3270                }
3271                _ => {
3272                    // Generic named type
3273                    TypeExpr::Named(Ident::new(name, self.span.clone()), params)
3274                }
3275            })
3276        } else {
3277            // Simple type
3278            Some(match name.as_str() {
3279                "Int" => TypeExpr::Int,
3280                "Float" => TypeExpr::Float,
3281                "Bool" => TypeExpr::Bool,
3282                "String" => TypeExpr::String,
3283                "Unit" => TypeExpr::Unit,
3284                _ => TypeExpr::Named(Ident::new(name, self.span.clone()), vec![]),
3285            })
3286        }
3287    }
3288}
3289
3290// =============================================================================
3291// Tests
3292// =============================================================================
3293
3294#[cfg(test)]
3295mod tests {
3296    use super::*;
3297    use crate::lex;
3298
3299    fn parse_str(source: &str) -> (Option<Program>, Vec<ParseError>) {
3300        let lex_result = lex(source).expect("lexing should succeed");
3301        let source_arc: Arc<str> = Arc::from(source);
3302        parse(lex_result.tokens(), source_arc)
3303    }
3304
3305    #[test]
3306    fn parse_minimal_program() {
3307        let source = r#"
3308            agent Main {
3309                on start {
3310                    yield(42);
3311                }
3312            }
3313            run Main;
3314        "#;
3315
3316        let (prog, errors) = parse_str(source);
3317        assert!(errors.is_empty(), "errors: {errors:?}");
3318        let prog = prog.expect("should parse");
3319
3320        assert_eq!(prog.agents.len(), 1);
3321        assert_eq!(prog.agents[0].name.name, "Main");
3322        assert_eq!(prog.run_agent.as_ref().unwrap().name, "Main");
3323    }
3324
3325    #[test]
3326    fn parse_agent_with_beliefs() {
3327        let source = r#"
3328            agent Researcher {
3329                topic: String
3330                max_words: Int
3331
3332                on start {
3333                    yield(self.topic);
3334                }
3335            }
3336            run Researcher;
3337        "#;
3338
3339        let (prog, errors) = parse_str(source);
3340        assert!(errors.is_empty(), "errors: {errors:?}");
3341        let prog = prog.expect("should parse");
3342
3343        assert_eq!(prog.agents[0].beliefs.len(), 2);
3344        assert_eq!(prog.agents[0].beliefs[0].name.name, "topic");
3345        assert_eq!(prog.agents[0].beliefs[1].name.name, "max_words");
3346    }
3347
3348    #[test]
3349    fn parse_multiple_handlers() {
3350        let source = r#"
3351            agent Worker {
3352                on start {
3353                    print("started");
3354                }
3355
3356                on message(msg: String) {
3357                    print(msg);
3358                }
3359
3360                on stop {
3361                    print("stopped");
3362                }
3363            }
3364            run Worker;
3365        "#;
3366
3367        let (prog, errors) = parse_str(source);
3368        assert!(errors.is_empty(), "errors: {errors:?}");
3369        let prog = prog.expect("should parse");
3370
3371        assert_eq!(prog.agents[0].handlers.len(), 3);
3372        assert_eq!(prog.agents[0].handlers[0].event, EventKind::Start);
3373        assert!(matches!(
3374            prog.agents[0].handlers[1].event,
3375            EventKind::Message { .. }
3376        ));
3377        assert_eq!(prog.agents[0].handlers[2].event, EventKind::Stop);
3378    }
3379
3380    #[test]
3381    fn parse_v2_lifecycle_hooks() {
3382        let source = r#"
3383            agent StatefulWorker {
3384                on waking {
3385                    // Load persisted state
3386                    trace("waking up");
3387                }
3388
3389                on start {
3390                    trace("started");
3391                }
3392
3393                on pause {
3394                    // Save state before pause
3395                    trace("pausing");
3396                }
3397
3398                on resume {
3399                    trace("resuming");
3400                }
3401
3402                on resting {
3403                    // Cleanup before shutdown
3404                    trace("resting");
3405                }
3406            }
3407            run StatefulWorker;
3408        "#;
3409
3410        let (prog, errors) = parse_str(source);
3411        assert!(errors.is_empty(), "errors: {errors:?}");
3412        let prog = prog.expect("should parse");
3413
3414        assert_eq!(prog.agents[0].handlers.len(), 5);
3415        assert_eq!(prog.agents[0].handlers[0].event, EventKind::Waking);
3416        assert_eq!(prog.agents[0].handlers[1].event, EventKind::Start);
3417        assert_eq!(prog.agents[0].handlers[2].event, EventKind::Pause);
3418        assert_eq!(prog.agents[0].handlers[3].event, EventKind::Resume);
3419        assert_eq!(prog.agents[0].handlers[4].event, EventKind::Resting);
3420    }
3421
3422    #[test]
3423    fn parse_persistent_beliefs() {
3424        let source = r#"
3425            agent DatabaseSteward {
3426                @persistent schema_version: Int
3427                @persistent migration_log: List<String>
3428                active_connections: Int
3429
3430                on start {
3431                    yield(0);
3432                }
3433            }
3434            run DatabaseSteward;
3435        "#;
3436
3437        let (prog, errors) = parse_str(source);
3438        assert!(errors.is_empty(), "errors: {errors:?}");
3439        let prog = prog.expect("should parse");
3440
3441        assert_eq!(prog.agents[0].beliefs.len(), 3);
3442        assert!(prog.agents[0].beliefs[0].is_persistent);
3443        assert_eq!(prog.agents[0].beliefs[0].name.name, "schema_version");
3444        assert!(prog.agents[0].beliefs[1].is_persistent);
3445        assert_eq!(prog.agents[0].beliefs[1].name.name, "migration_log");
3446        assert!(!prog.agents[0].beliefs[2].is_persistent);
3447        assert_eq!(prog.agents[0].beliefs[2].name.name, "active_connections");
3448    }
3449
3450    #[test]
3451    fn parse_function() {
3452        let source = r#"
3453            fn greet(name: String) -> String {
3454                return "Hello, " ++ name;
3455            }
3456
3457            agent Main {
3458                on start {
3459                    yield(greet("World"));
3460                }
3461            }
3462            run Main;
3463        "#;
3464
3465        let (prog, errors) = parse_str(source);
3466        assert!(errors.is_empty(), "errors: {errors:?}");
3467        let prog = prog.expect("should parse");
3468
3469        assert_eq!(prog.functions.len(), 1);
3470        assert_eq!(prog.functions[0].name.name, "greet");
3471        assert_eq!(prog.functions[0].params.len(), 1);
3472    }
3473
3474    #[test]
3475    fn parse_let_statement() {
3476        let source = r#"
3477            agent Main {
3478                on start {
3479                    let x: Int = 42;
3480                    let y = "hello";
3481                    yield(x);
3482                }
3483            }
3484            run Main;
3485        "#;
3486
3487        let (prog, errors) = parse_str(source);
3488        assert!(errors.is_empty(), "errors: {errors:?}");
3489        let prog = prog.expect("should parse");
3490
3491        let stmts = &prog.agents[0].handlers[0].body.stmts;
3492        assert!(matches!(stmts[0], Stmt::Let { .. }));
3493        assert!(matches!(stmts[1], Stmt::Let { .. }));
3494    }
3495
3496    #[test]
3497    fn parse_if_statement() {
3498        let source = r#"
3499            agent Main {
3500                on start {
3501                    if true {
3502                        yield(1);
3503                    } else {
3504                        yield(2);
3505                    }
3506                }
3507            }
3508            run Main;
3509        "#;
3510
3511        let (prog, errors) = parse_str(source);
3512        assert!(errors.is_empty(), "errors: {errors:?}");
3513        let prog = prog.expect("should parse");
3514
3515        let stmts = &prog.agents[0].handlers[0].body.stmts;
3516        assert!(matches!(stmts[0], Stmt::If { .. }));
3517    }
3518
3519    #[test]
3520    fn parse_for_loop() {
3521        let source = r#"
3522            agent Main {
3523                on start {
3524                    for x in [1, 2, 3] {
3525                        print(x);
3526                    }
3527                    yield(0);
3528                }
3529            }
3530            run Main;
3531        "#;
3532
3533        let (prog, errors) = parse_str(source);
3534        assert!(errors.is_empty(), "errors: {errors:?}");
3535        let prog = prog.expect("should parse");
3536
3537        let stmts = &prog.agents[0].handlers[0].body.stmts;
3538        assert!(matches!(stmts[0], Stmt::For { .. }));
3539    }
3540
3541    #[test]
3542    fn parse_spawn_await() {
3543        let source = r#"
3544            agent Worker {
3545                name: String
3546
3547                on start {
3548                    yield(self.name);
3549                }
3550            }
3551
3552            agent Main {
3553                on start {
3554                    let w = summon Worker { name: "test" };
3555                    let result = await w;
3556                    yield(result);
3557                }
3558            }
3559            run Main;
3560        "#;
3561
3562        let (prog, errors) = parse_str(source);
3563        assert!(errors.is_empty(), "errors: {errors:?}");
3564        prog.expect("should parse");
3565    }
3566
3567    #[test]
3568    fn parse_await_with_timeout() {
3569        let source = r#"
3570            agent Worker {
3571                on start {
3572                    yield("done");
3573                }
3574            }
3575
3576            agent Main {
3577                on start {
3578                    let w = summon Worker {};
3579                    let result = await w timeout(5000);
3580                    yield(result);
3581                }
3582            }
3583            run Main;
3584        "#;
3585
3586        let (prog, errors) = parse_str(source);
3587        assert!(errors.is_empty(), "errors: {errors:?}");
3588        let prog = prog.expect("should parse");
3589
3590        // Find the await statement in Main's on start handler
3591        let main = &prog.agents[1];
3592        let stmts = &main.handlers[0].body.stmts;
3593        // stmts[0] is the let w = spawn...
3594        // stmts[1] is the let result = await w timeout(5000)
3595        if let Stmt::Let { value, .. } = &stmts[1] {
3596            if let Expr::Await { timeout, .. } = value {
3597                assert!(timeout.is_some(), "timeout should be present");
3598            } else {
3599                panic!("expected Await expression");
3600            }
3601        } else {
3602            panic!("expected Let statement with value");
3603        }
3604    }
3605
3606    #[test]
3607    fn parse_divine() {
3608        let source = r#"
3609            agent Main {
3610                on start {
3611                    let result = divine("What is 2+2?");
3612                    yield(result);
3613                }
3614            }
3615            run Main;
3616        "#;
3617
3618        let (prog, errors) = parse_str(source);
3619        assert!(errors.is_empty(), "errors: {errors:?}");
3620        prog.expect("should parse");
3621    }
3622
3623    #[test]
3624    fn parse_binary_precedence() {
3625        let source = r#"
3626            agent Main {
3627                on start {
3628                    let x = 2 + 3 * 4;
3629                    yield(x);
3630                }
3631            }
3632            run Main;
3633        "#;
3634
3635        let (prog, errors) = parse_str(source);
3636        assert!(errors.is_empty(), "errors: {errors:?}");
3637        let prog = prog.expect("should parse");
3638
3639        let stmts = &prog.agents[0].handlers[0].body.stmts;
3640        if let Stmt::Let { value, .. } = &stmts[0] {
3641            if let Expr::Binary { op, .. } = value {
3642                assert_eq!(*op, BinOp::Add);
3643            } else {
3644                panic!("expected binary expression");
3645            }
3646        }
3647    }
3648
3649    #[test]
3650    fn parse_string_interpolation() {
3651        let source = r#"
3652            agent Main {
3653                on start {
3654                    let name = "World";
3655                    let msg = divine("Greet {name}");
3656                    yield(msg);
3657                }
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        let stmts = &prog.agents[0].handlers[0].body.stmts;
3667        if let Stmt::Let { value, .. } = &stmts[1] {
3668            if let Expr::Divine { template, .. } = value {
3669                assert!(template.has_interpolations());
3670            } else {
3671                panic!("expected infer expression");
3672            }
3673        }
3674    }
3675
3676    #[test]
3677    fn parse_single_quoted_string() {
3678        let source = r#"
3679            agent Main {
3680                on start {
3681                    let x = 'hello';
3682                    yield(0);
3683                }
3684            }
3685            run Main;
3686        "#;
3687
3688        let (prog, errors) = parse_str(source);
3689        assert!(errors.is_empty(), "errors: {errors:?}");
3690        let prog = prog.expect("should parse");
3691
3692        let stmts = &prog.agents[0].handlers[0].body.stmts;
3693        if let Stmt::Let { value, .. } = &stmts[0] {
3694            if let Expr::Literal {
3695                value: Literal::String(s),
3696                ..
3697            } = value
3698            {
3699                assert_eq!(s, "hello");
3700            } else {
3701                panic!("expected string literal, got {:?}", value);
3702            }
3703        } else {
3704            panic!("expected let statement");
3705        }
3706    }
3707
3708    #[test]
3709    fn parse_single_quoted_string_in_interpolation() {
3710        // Use single quotes for strings inside interpolations to avoid
3711        // conflicts with the outer double-quoted string
3712        let source = r#"
3713            fn reverse(s: String) -> String {
3714                return s;
3715            }
3716            agent Main {
3717                on start {
3718                    print("Result: {reverse('hello')}");
3719                    print("Concat: {'abc' ++ 'def'}");
3720                    yield(0);
3721                }
3722            }
3723            run Main;
3724        "#;
3725
3726        let (prog, errors) = parse_str(source);
3727        assert!(errors.is_empty(), "errors: {errors:?}");
3728        let prog = prog.expect("should parse");
3729
3730        // Verify the string interpolation was parsed
3731        let stmts = &prog.agents[0].handlers[0].body.stmts;
3732        if let Stmt::Expr {
3733            expr: Expr::Call { args, .. },
3734            ..
3735        } = &stmts[0]
3736        {
3737            if let Expr::StringInterp { template, .. } = &args[0] {
3738                assert!(template.has_interpolations());
3739            } else {
3740                panic!("expected string interpolation");
3741            }
3742        } else {
3743            panic!("expected print call");
3744        }
3745    }
3746
3747    // =========================================================================
3748    // Error recovery tests
3749    // =========================================================================
3750
3751    #[test]
3752    fn recover_from_malformed_agent_continues_to_next() {
3753        // First agent has syntax error (missing type after colon), second is valid
3754        let source = r#"
3755            agent Broken {
3756                x:
3757            }
3758
3759            agent Main {
3760                on start {
3761                    yield(42);
3762                }
3763            }
3764            run Main;
3765        "#;
3766
3767        let (prog, errors) = parse_str(source);
3768        // Should have errors from the broken agent
3769        assert!(!errors.is_empty(), "should have parse errors");
3770        // But should still produce a program with the valid agent
3771        let prog = prog.expect("should produce partial AST");
3772        assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
3773    }
3774
3775    #[test]
3776    fn recover_from_mismatched_braces_in_block() {
3777        let source = r#"
3778            agent Main {
3779                on start {
3780                    let x = [1, 2, 3;
3781                    yield(42);
3782                }
3783            }
3784            run Main;
3785        "#;
3786
3787        let (prog, errors) = parse_str(source);
3788        // Should have errors but still produce an AST
3789        assert!(!errors.is_empty(), "should have parse errors");
3790        assert!(prog.is_some(), "should produce partial AST despite errors");
3791    }
3792
3793    #[test]
3794    fn parse_mod_declaration() {
3795        let source = r#"
3796            mod agents;
3797            pub mod utils;
3798
3799            agent Main {
3800                on start {
3801                    yield(42);
3802                }
3803            }
3804            run Main;
3805        "#;
3806
3807        let (prog, errors) = parse_str(source);
3808        assert!(errors.is_empty(), "errors: {errors:?}");
3809        let prog = prog.expect("should parse");
3810
3811        assert_eq!(prog.mod_decls.len(), 2);
3812        assert!(!prog.mod_decls[0].is_pub);
3813        assert_eq!(prog.mod_decls[0].name.name, "agents");
3814        assert!(prog.mod_decls[1].is_pub);
3815        assert_eq!(prog.mod_decls[1].name.name, "utils");
3816    }
3817
3818    #[test]
3819    fn parse_use_simple() {
3820        let source = r#"
3821            use agents::Researcher;
3822
3823            agent Main {
3824                on start {
3825                    yield(42);
3826                }
3827            }
3828            run Main;
3829        "#;
3830
3831        let (prog, errors) = parse_str(source);
3832        assert!(errors.is_empty(), "errors: {errors:?}");
3833        let prog = prog.expect("should parse");
3834
3835        assert_eq!(prog.use_decls.len(), 1);
3836        assert!(!prog.use_decls[0].is_pub);
3837        assert_eq!(prog.use_decls[0].path.len(), 2);
3838        assert_eq!(prog.use_decls[0].path[0].name, "agents");
3839        assert_eq!(prog.use_decls[0].path[1].name, "Researcher");
3840        assert!(matches!(prog.use_decls[0].kind, UseKind::Simple(None)));
3841    }
3842
3843    #[test]
3844    fn parse_use_with_alias() {
3845        let source = r#"
3846            use agents::Researcher as R;
3847
3848            agent Main {
3849                on start {
3850                    yield(42);
3851                }
3852            }
3853            run Main;
3854        "#;
3855
3856        let (prog, errors) = parse_str(source);
3857        assert!(errors.is_empty(), "errors: {errors:?}");
3858        let prog = prog.expect("should parse");
3859
3860        assert_eq!(prog.use_decls.len(), 1);
3861        if let UseKind::Simple(Some(alias)) = &prog.use_decls[0].kind {
3862            assert_eq!(alias.name, "R");
3863        } else {
3864            panic!("expected Simple with alias");
3865        }
3866    }
3867
3868    #[test]
3869    fn parse_pub_agent() {
3870        let source = r#"
3871            pub agent Worker {
3872                on start {
3873                    yield(42);
3874                }
3875            }
3876
3877            agent Main {
3878                on start {
3879                    yield(0);
3880                }
3881            }
3882            run Main;
3883        "#;
3884
3885        let (prog, errors) = parse_str(source);
3886        assert!(errors.is_empty(), "errors: {errors:?}");
3887        let prog = prog.expect("should parse");
3888
3889        assert_eq!(prog.agents.len(), 2);
3890        assert!(prog.agents[0].is_pub);
3891        assert_eq!(prog.agents[0].name.name, "Worker");
3892        assert!(!prog.agents[1].is_pub);
3893    }
3894
3895    #[test]
3896    fn parse_pub_function() {
3897        let source = r#"
3898            pub fn helper(x: Int) -> Int {
3899                return x;
3900            }
3901
3902            agent Main {
3903                on start {
3904                    yield(helper(42));
3905                }
3906            }
3907            run Main;
3908        "#;
3909
3910        let (prog, errors) = parse_str(source);
3911        assert!(errors.is_empty(), "errors: {errors:?}");
3912        let prog = prog.expect("should parse");
3913
3914        assert_eq!(prog.functions.len(), 1);
3915        assert!(prog.functions[0].is_pub);
3916        assert_eq!(prog.functions[0].name.name, "helper");
3917    }
3918
3919    #[test]
3920    fn parse_library_no_run() {
3921        // A library module has no `run` statement
3922        let source = r#"
3923            pub agent Worker {
3924                on start {
3925                    yield(42);
3926                }
3927            }
3928
3929            pub fn helper(x: Int) -> Int {
3930                return x;
3931            }
3932        "#;
3933
3934        let (prog, errors) = parse_str(source);
3935        assert!(errors.is_empty(), "errors: {errors:?}");
3936        let prog = prog.expect("should parse");
3937
3938        assert!(prog.run_agent.is_none());
3939        assert_eq!(prog.agents.len(), 1);
3940        assert_eq!(prog.functions.len(), 1);
3941    }
3942
3943    #[test]
3944    fn recover_multiple_errors_reported() {
3945        // Multiple errors in different places - incomplete field missing type
3946        let source = r#"
3947            agent A {
3948                x:
3949            }
3950
3951            agent Main {
3952                on start {
3953                    yield(42);
3954                }
3955            }
3956            run Main;
3957        "#;
3958
3959        let (prog, errors) = parse_str(source);
3960        // The malformed field is missing its type after `:` so should cause an error
3961        // However, with recovery the valid agent may still parse
3962        // Check that we either have errors or recovered successfully
3963        if errors.is_empty() {
3964            // Recovery succeeded - should have parsed Main agent
3965            let prog = prog.expect("should have AST with recovery");
3966            assert!(prog.agents.iter().any(|a| a.name.name == "Main"));
3967        }
3968        // Either way, the test passes - we're testing recovery works
3969    }
3970
3971    #[test]
3972    fn parse_record_declaration() {
3973        let source = r#"
3974            record Point {
3975                x: Int,
3976                y: Int,
3977            }
3978
3979            agent Main {
3980                on start {
3981                    yield(0);
3982                }
3983            }
3984            run Main;
3985        "#;
3986
3987        let (prog, errors) = parse_str(source);
3988        assert!(errors.is_empty(), "errors: {errors:?}");
3989        let prog = prog.expect("should parse");
3990
3991        assert_eq!(prog.records.len(), 1);
3992        assert!(!prog.records[0].is_pub);
3993        assert_eq!(prog.records[0].name.name, "Point");
3994        assert_eq!(prog.records[0].fields.len(), 2);
3995        assert_eq!(prog.records[0].fields[0].name.name, "x");
3996        assert_eq!(prog.records[0].fields[1].name.name, "y");
3997    }
3998
3999    #[test]
4000    fn parse_pub_record() {
4001        let source = r#"
4002            pub record Config {
4003                host: String,
4004                port: Int,
4005            }
4006
4007            agent Main {
4008                on start { yield(0); }
4009            }
4010            run Main;
4011        "#;
4012
4013        let (prog, errors) = parse_str(source);
4014        assert!(errors.is_empty(), "errors: {errors:?}");
4015        let prog = prog.expect("should parse");
4016
4017        assert_eq!(prog.records.len(), 1);
4018        assert!(prog.records[0].is_pub);
4019        assert_eq!(prog.records[0].name.name, "Config");
4020    }
4021
4022    #[test]
4023    fn parse_enum_declaration() {
4024        let source = r#"
4025            enum Status {
4026                Active,
4027                Pending,
4028                Done,
4029            }
4030
4031            agent Main {
4032                on start {
4033                    yield(0);
4034                }
4035            }
4036            run Main;
4037        "#;
4038
4039        let (prog, errors) = parse_str(source);
4040        assert!(errors.is_empty(), "errors: {errors:?}");
4041        let prog = prog.expect("should parse");
4042
4043        assert_eq!(prog.enums.len(), 1);
4044        assert!(!prog.enums[0].is_pub);
4045        assert_eq!(prog.enums[0].name.name, "Status");
4046        assert_eq!(prog.enums[0].variants.len(), 3);
4047        assert_eq!(prog.enums[0].variants[0].name.name, "Active");
4048        assert_eq!(prog.enums[0].variants[1].name.name, "Pending");
4049        assert_eq!(prog.enums[0].variants[2].name.name, "Done");
4050    }
4051
4052    #[test]
4053    fn parse_pub_enum() {
4054        let source = r#"
4055            pub enum Priority { High, Medium, Low }
4056
4057            agent Main {
4058                on start { yield(0); }
4059            }
4060            run Main;
4061        "#;
4062
4063        let (prog, errors) = parse_str(source);
4064        assert!(errors.is_empty(), "errors: {errors:?}");
4065        let prog = prog.expect("should parse");
4066
4067        assert_eq!(prog.enums.len(), 1);
4068        assert!(prog.enums[0].is_pub);
4069        assert_eq!(prog.enums[0].name.name, "Priority");
4070    }
4071
4072    #[test]
4073    fn parse_const_declaration() {
4074        let source = r#"
4075            const MAX_RETRIES: Int = 3;
4076
4077            agent Main {
4078                on start {
4079                    yield(0);
4080                }
4081            }
4082            run Main;
4083        "#;
4084
4085        let (prog, errors) = parse_str(source);
4086        assert!(errors.is_empty(), "errors: {errors:?}");
4087        let prog = prog.expect("should parse");
4088
4089        assert_eq!(prog.consts.len(), 1);
4090        assert!(!prog.consts[0].is_pub);
4091        assert_eq!(prog.consts[0].name.name, "MAX_RETRIES");
4092        assert!(matches!(prog.consts[0].ty, TypeExpr::Int));
4093    }
4094
4095    #[test]
4096    fn parse_pub_const() {
4097        let source = r#"
4098            pub const API_URL: String = "https://api.example.com";
4099
4100            agent Main {
4101                on start { yield(0); }
4102            }
4103            run Main;
4104        "#;
4105
4106        let (prog, errors) = parse_str(source);
4107        assert!(errors.is_empty(), "errors: {errors:?}");
4108        let prog = prog.expect("should parse");
4109
4110        assert_eq!(prog.consts.len(), 1);
4111        assert!(prog.consts[0].is_pub);
4112        assert_eq!(prog.consts[0].name.name, "API_URL");
4113    }
4114
4115    #[test]
4116    fn parse_multiple_type_declarations() {
4117        let source = r#"
4118            record Point { x: Int, y: Int }
4119            enum Color { Red, Green, Blue }
4120            const ORIGIN_X: Int = 0;
4121
4122            agent Main {
4123                on start { yield(0); }
4124            }
4125            run Main;
4126        "#;
4127
4128        let (prog, errors) = parse_str(source);
4129        assert!(errors.is_empty(), "errors: {errors:?}");
4130        let prog = prog.expect("should parse");
4131
4132        assert_eq!(prog.records.len(), 1);
4133        assert_eq!(prog.enums.len(), 1);
4134        assert_eq!(prog.consts.len(), 1);
4135    }
4136
4137    #[test]
4138    fn parse_match_expression() {
4139        let source = r#"
4140            enum Status { Active, Pending, Done }
4141
4142            agent Main {
4143                on start {
4144                    let s: Int = match Active {
4145                        Active => 1,
4146                        Pending => 2,
4147                        Done => 3,
4148                    };
4149                    yield(s);
4150                }
4151            }
4152            run Main;
4153        "#;
4154
4155        let (prog, errors) = parse_str(source);
4156        assert!(errors.is_empty(), "errors: {errors:?}");
4157        let prog = prog.expect("should parse");
4158
4159        // Check the agent parsed
4160        assert_eq!(prog.agents.len(), 1);
4161        // Match is in the handler
4162        let handler = &prog.agents[0].handlers[0];
4163        let stmt = &handler.body.stmts[0];
4164        if let Stmt::Let { value, .. } = stmt {
4165            assert!(matches!(value, Expr::Match { .. }));
4166        } else {
4167            panic!("expected let statement with match");
4168        }
4169    }
4170
4171    #[test]
4172    fn parse_match_with_wildcard() {
4173        let source = r#"
4174            agent Main {
4175                on start {
4176                    let x = 5;
4177                    let result = match x {
4178                        1 => 10,
4179                        2 => 20,
4180                        _ => 0,
4181                    };
4182                    yield(result);
4183                }
4184            }
4185            run Main;
4186        "#;
4187
4188        let (prog, errors) = parse_str(source);
4189        assert!(errors.is_empty(), "errors: {errors:?}");
4190        let prog = prog.expect("should parse");
4191
4192        assert_eq!(prog.agents.len(), 1);
4193    }
4194
4195    #[test]
4196    fn parse_record_construction() {
4197        let source = r#"
4198            record Point { x: Int, y: Int }
4199
4200            agent Main {
4201                on start {
4202                    let p = Point { x: 10, y: 20 };
4203                    yield(0);
4204                }
4205            }
4206            run Main;
4207        "#;
4208
4209        let (prog, errors) = parse_str(source);
4210        assert!(errors.is_empty(), "errors: {errors:?}");
4211        let prog = prog.expect("should parse");
4212
4213        assert_eq!(prog.records.len(), 1);
4214        assert_eq!(prog.agents.len(), 1);
4215
4216        // Check the let statement has a record construction
4217        let handler = &prog.agents[0].handlers[0];
4218        let stmt = &handler.body.stmts[0];
4219        if let Stmt::Let { value, .. } = stmt {
4220            if let Expr::RecordConstruct { name, fields, .. } = value {
4221                assert_eq!(name.name, "Point");
4222                assert_eq!(fields.len(), 2);
4223                assert_eq!(fields[0].name.name, "x");
4224                assert_eq!(fields[1].name.name, "y");
4225            } else {
4226                panic!("expected RecordConstruct");
4227            }
4228        } else {
4229            panic!("expected let statement");
4230        }
4231    }
4232
4233    #[test]
4234    fn parse_match_with_qualified_variant() {
4235        let source = r#"
4236            enum Status { Active, Pending }
4237
4238            fn get_status() -> Int {
4239                return 1;
4240            }
4241
4242            agent Main {
4243                on start {
4244                    let s = get_status();
4245                    let result = match s {
4246                        Status::Active => 1,
4247                        Status::Pending => 0,
4248                    };
4249                    yield(result);
4250                }
4251            }
4252            run Main;
4253        "#;
4254
4255        let (prog, errors) = parse_str(source);
4256        assert!(errors.is_empty(), "errors: {errors:?}");
4257        let prog = prog.expect("should parse");
4258
4259        assert_eq!(prog.enums.len(), 1);
4260        assert_eq!(prog.agents.len(), 1);
4261    }
4262
4263    #[test]
4264    fn parse_field_access() {
4265        let source = r#"
4266            record Point { x: Int, y: Int }
4267
4268            agent Main {
4269                on start {
4270                    let p = Point { x: 10, y: 20 };
4271                    let x_val = p.x;
4272                    let y_val = p.y;
4273                    yield(x_val);
4274                }
4275            }
4276            run Main;
4277        "#;
4278
4279        let (prog, errors) = parse_str(source);
4280        assert!(errors.is_empty(), "errors: {errors:?}");
4281        let prog = prog.expect("should parse");
4282
4283        assert_eq!(prog.records.len(), 1);
4284        assert_eq!(prog.agents.len(), 1);
4285
4286        // Check the field access
4287        let handler = &prog.agents[0].handlers[0];
4288        let stmt = &handler.body.stmts[1]; // p.x assignment
4289        if let Stmt::Let { value, .. } = stmt {
4290            if let Expr::FieldAccess { field, .. } = value {
4291                assert_eq!(field.name, "x");
4292            } else {
4293                panic!("expected FieldAccess");
4294            }
4295        } else {
4296            panic!("expected let statement");
4297        }
4298    }
4299
4300    #[test]
4301    fn parse_chained_field_access() {
4302        let source = r#"
4303            record Inner { val: Int }
4304            record Outer { inner: Inner }
4305
4306            agent Main {
4307                on start {
4308                    let inner = Inner { val: 42 };
4309                    let outer = Outer { inner: inner };
4310                    let v = outer.inner.val;
4311                    yield(v);
4312                }
4313            }
4314            run Main;
4315        "#;
4316
4317        let (prog, errors) = parse_str(source);
4318        assert!(errors.is_empty(), "errors: {errors:?}");
4319        let prog = prog.expect("should parse");
4320
4321        assert_eq!(prog.records.len(), 2);
4322        assert_eq!(prog.agents.len(), 1);
4323
4324        // Check the chained field access: outer.inner.val
4325        let handler = &prog.agents[0].handlers[0];
4326        let stmt = &handler.body.stmts[2]; // outer.inner.val assignment
4327        if let Stmt::Let { value, .. } = stmt {
4328            if let Expr::FieldAccess {
4329                object, field: val, ..
4330            } = value
4331            {
4332                assert_eq!(val.name, "val");
4333                // object should be outer.inner
4334                if let Expr::FieldAccess { field: inner, .. } = object.as_ref() {
4335                    assert_eq!(inner.name, "inner");
4336                } else {
4337                    panic!("expected nested FieldAccess");
4338                }
4339            } else {
4340                panic!("expected FieldAccess");
4341            }
4342        } else {
4343            panic!("expected let statement");
4344        }
4345    }
4346
4347    // =========================================================================
4348    // RFC-0006: Message passing tests
4349    // =========================================================================
4350
4351    #[test]
4352    fn parse_loop_break() {
4353        let source = r#"
4354            agent Main {
4355                on start {
4356                    let count = 0;
4357                    loop {
4358                        count = count + 1;
4359                        if count > 5 {
4360                            break;
4361                        }
4362                    }
4363                    yield(count);
4364                }
4365            }
4366            run Main;
4367        "#;
4368
4369        let (prog, errors) = parse_str(source);
4370        assert!(errors.is_empty(), "errors: {errors:?}");
4371        let prog = prog.expect("should parse");
4372
4373        assert_eq!(prog.agents.len(), 1);
4374        let handler = &prog.agents[0].handlers[0];
4375        // Check loop statement exists
4376        let loop_stmt = &handler.body.stmts[1];
4377        assert!(matches!(loop_stmt, Stmt::Loop { .. }));
4378        // Check break is inside the loop
4379        if let Stmt::Loop { body, .. } = loop_stmt {
4380            let if_stmt = &body.stmts[1];
4381            if let Stmt::If { then_block, .. } = if_stmt {
4382                assert!(matches!(then_block.stmts[0], Stmt::Break { .. }));
4383            } else {
4384                panic!("expected if statement");
4385            }
4386        }
4387    }
4388
4389    #[test]
4390    fn parse_agent_receives() {
4391        let source = r#"
4392            enum WorkerMsg {
4393                Task,
4394                Shutdown,
4395            }
4396
4397            agent Worker receives WorkerMsg {
4398                id: Int
4399
4400                on start {
4401                    yield(0);
4402                }
4403            }
4404
4405            agent Main {
4406                on start {
4407                    yield(0);
4408                }
4409            }
4410            run Main;
4411        "#;
4412
4413        let (prog, errors) = parse_str(source);
4414        assert!(errors.is_empty(), "errors: {errors:?}");
4415        let prog = prog.expect("should parse");
4416
4417        assert_eq!(prog.agents.len(), 2);
4418
4419        // Worker should have receives clause
4420        let worker = &prog.agents[0];
4421        assert_eq!(worker.name.name, "Worker");
4422        assert!(worker.receives.is_some());
4423        if let Some(TypeExpr::Named(name, _)) = &worker.receives {
4424            assert_eq!(name.name, "WorkerMsg");
4425        } else {
4426            panic!("expected named type for receives");
4427        }
4428
4429        // Main should not have receives
4430        let main = &prog.agents[1];
4431        assert_eq!(main.name.name, "Main");
4432        assert!(main.receives.is_none());
4433    }
4434
4435    #[test]
4436    fn parse_receive_expression() {
4437        let source = r#"
4438            enum Msg { Ping }
4439
4440            agent Worker receives Msg {
4441                on start {
4442                    let msg = receive();
4443                    yield(0);
4444                }
4445            }
4446
4447            agent Main {
4448                on start { yield(0); }
4449            }
4450            run Main;
4451        "#;
4452
4453        let (prog, errors) = parse_str(source);
4454        assert!(errors.is_empty(), "errors: {errors:?}");
4455        let prog = prog.expect("should parse");
4456
4457        // Find Worker agent
4458        let worker = prog
4459            .agents
4460            .iter()
4461            .find(|a| a.name.name == "Worker")
4462            .unwrap();
4463        let handler = &worker.handlers[0];
4464        let stmt = &handler.body.stmts[0];
4465
4466        if let Stmt::Let { value, .. } = stmt {
4467            assert!(matches!(value, Expr::Receive { .. }));
4468        } else {
4469            panic!("expected let with receive");
4470        }
4471    }
4472
4473    #[test]
4474    fn parse_message_passing_full() {
4475        let source = r#"
4476            enum WorkerMsg {
4477                Task,
4478                Shutdown,
4479            }
4480
4481            agent Worker receives WorkerMsg {
4482                id: Int
4483
4484                on start {
4485                    let msg = receive();
4486                    let result = match msg {
4487                        Task => 1,
4488                        Shutdown => 0,
4489                    };
4490                    yield(result);
4491                }
4492            }
4493
4494            agent Main {
4495                on start {
4496                    let w = summon Worker { id: 1 };
4497                    send(w, Task);
4498                    send(w, Shutdown);
4499                    await w;
4500                    yield(0);
4501                }
4502            }
4503            run Main;
4504        "#;
4505
4506        let (prog, errors) = parse_str(source);
4507        assert!(errors.is_empty(), "errors: {errors:?}");
4508        let prog = prog.expect("should parse");
4509
4510        assert_eq!(prog.enums.len(), 1);
4511        assert_eq!(prog.agents.len(), 2);
4512
4513        // Check Worker has receives
4514        let worker = prog
4515            .agents
4516            .iter()
4517            .find(|a| a.name.name == "Worker")
4518            .unwrap();
4519        assert!(worker.receives.is_some());
4520    }
4521
4522    // =========================================================================
4523    // RFC-0007: Error handling tests
4524    // =========================================================================
4525
4526    #[test]
4527    fn parse_fallible_function() {
4528        let source = r#"
4529            fn get_data(url: String) -> String fails {
4530                return divine("Get data from {url}" -> String);
4531            }
4532
4533            agent Main {
4534                on start { yield(0); }
4535            }
4536            run Main;
4537        "#;
4538
4539        let (prog, errors) = parse_str(source);
4540        assert!(errors.is_empty(), "errors: {errors:?}");
4541        let prog = prog.expect("should parse");
4542
4543        assert_eq!(prog.functions.len(), 1);
4544        assert!(prog.functions[0].is_fallible);
4545    }
4546
4547    #[test]
4548    fn parse_try_expression() {
4549        let source = r#"
4550            fn fallible() -> Int fails { return 42; }
4551
4552            agent Main {
4553                on start {
4554                    let x = try fallible();
4555                    yield(x);
4556                }
4557            }
4558            run Main;
4559        "#;
4560
4561        let (prog, errors) = parse_str(source);
4562        assert!(errors.is_empty(), "errors: {errors:?}");
4563        let prog = prog.expect("should parse");
4564
4565        // Find the let statement and check it contains a Try expression
4566        let handler = &prog.agents[0].handlers[0];
4567        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4568            assert!(matches!(value, Expr::Try { .. }));
4569        } else {
4570            panic!("expected Let statement");
4571        }
4572    }
4573
4574    #[test]
4575    fn parse_catch_expression() {
4576        let source = r#"
4577            fn fallible() -> Int fails { return 42; }
4578
4579            agent Main {
4580                on start {
4581                    let x = fallible() catch { 0 };
4582                    yield(x);
4583                }
4584            }
4585            run Main;
4586        "#;
4587
4588        let (prog, errors) = parse_str(source);
4589        assert!(errors.is_empty(), "errors: {errors:?}");
4590        let prog = prog.expect("should parse");
4591
4592        // Find the let statement and check it contains a Catch expression
4593        let handler = &prog.agents[0].handlers[0];
4594        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4595            if let Expr::Catch { error_bind, .. } = value {
4596                assert!(error_bind.is_none());
4597            } else {
4598                panic!("expected Catch expression");
4599            }
4600        } else {
4601            panic!("expected Let statement");
4602        }
4603    }
4604
4605    #[test]
4606    fn parse_catch_with_error_binding() {
4607        let source = r#"
4608            fn fallible() -> Int fails { return 42; }
4609
4610            agent Main {
4611                on start {
4612                    let x = fallible() catch(e) { 0 };
4613                    yield(x);
4614                }
4615            }
4616            run Main;
4617        "#;
4618
4619        let (prog, errors) = parse_str(source);
4620        assert!(errors.is_empty(), "errors: {errors:?}");
4621        let prog = prog.expect("should parse");
4622
4623        // Find the let statement and check it contains a Catch expression with binding
4624        let handler = &prog.agents[0].handlers[0];
4625        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4626            if let Expr::Catch { error_bind, .. } = value {
4627                assert!(error_bind.is_some());
4628                assert_eq!(error_bind.as_ref().unwrap().name, "e");
4629            } else {
4630                panic!("expected Catch expression");
4631            }
4632        } else {
4633            panic!("expected Let statement");
4634        }
4635    }
4636
4637    #[test]
4638    fn parse_fail_expression() {
4639        let source = r#"
4640            agent Main {
4641                on start {
4642                    fail "something went wrong";
4643                }
4644                on error(e) {
4645                    yield(0);
4646                }
4647            }
4648            run Main;
4649        "#;
4650
4651        let (prog, errors) = parse_str(source);
4652        assert!(errors.is_empty(), "errors: {errors:?}");
4653        let prog = prog.expect("should parse");
4654
4655        // Find the fail statement
4656        let handler = &prog.agents[0].handlers[0];
4657        if let Stmt::Expr { expr, .. } = &handler.body.stmts[0] {
4658            if let Expr::Fail { error, .. } = expr {
4659                assert!(matches!(**error, Expr::Literal { .. }));
4660            } else {
4661                panic!("expected Fail expression, got {expr:?}");
4662            }
4663        } else {
4664            panic!("expected Expr statement");
4665        }
4666    }
4667
4668    #[test]
4669    fn parse_retry_expression() {
4670        let source = r#"
4671            agent Main {
4672                topic: String
4673
4674                on start {
4675                    let result = retry(3) {
4676                        try divine("Summarize: {self.topic}")
4677                    } catch { "fallback" };
4678                    yield(result);
4679                }
4680
4681                on error(e) {
4682                    yield("");
4683                }
4684            }
4685            run Main;
4686        "#;
4687
4688        let (prog, errors) = parse_str(source);
4689        assert!(errors.is_empty(), "errors: {errors:?}");
4690        let prog = prog.expect("should parse");
4691
4692        // Find the let statement with retry
4693        let handler = &prog.agents[0].handlers[0];
4694        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4695            // Should be Catch wrapping Retry
4696            if let Expr::Catch { expr, .. } = value {
4697                if let Expr::Retry { count, delay, .. } = expr.as_ref() {
4698                    assert!(matches!(**count, Expr::Literal { .. }));
4699                    assert!(delay.is_none());
4700                } else {
4701                    panic!("expected Retry expression");
4702                }
4703            } else {
4704                panic!("expected Catch expression");
4705            }
4706        } else {
4707            panic!("expected Let statement");
4708        }
4709    }
4710
4711    #[test]
4712    fn parse_retry_with_delay() {
4713        let source = r#"
4714            agent Main {
4715                on start {
4716                    let result = retry(3, delay: 1000) {
4717                        42
4718                    } catch { 0 };
4719                    yield(result);
4720                }
4721            }
4722            run Main;
4723        "#;
4724
4725        let (prog, errors) = parse_str(source);
4726        assert!(errors.is_empty(), "errors: {errors:?}");
4727        let prog = prog.expect("should parse");
4728
4729        // Find the let statement with retry
4730        let handler = &prog.agents[0].handlers[0];
4731        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4732            if let Expr::Catch { expr, .. } = value {
4733                if let Expr::Retry { delay, .. } = expr.as_ref() {
4734                    assert!(delay.is_some());
4735                } else {
4736                    panic!("expected Retry expression");
4737                }
4738            } else {
4739                panic!("expected Catch expression");
4740            }
4741        } else {
4742            panic!("expected Let statement");
4743        }
4744    }
4745
4746    #[test]
4747    fn parse_on_error_handler() {
4748        let source = r#"
4749            agent Main {
4750                on start {
4751                    yield(0);
4752                }
4753
4754                on error(e) {
4755                    yield(1);
4756                }
4757            }
4758            run Main;
4759        "#;
4760
4761        let (prog, errors) = parse_str(source);
4762        assert!(errors.is_empty(), "errors: {errors:?}");
4763        let prog = prog.expect("should parse");
4764
4765        assert_eq!(prog.agents.len(), 1);
4766        assert_eq!(prog.agents[0].handlers.len(), 2);
4767
4768        // Check the error handler
4769        let error_handler = prog.agents[0]
4770            .handlers
4771            .iter()
4772            .find(|h| matches!(h.event, EventKind::Error { .. }));
4773        assert!(error_handler.is_some());
4774
4775        if let EventKind::Error { param_name } = &error_handler.unwrap().event {
4776            assert_eq!(param_name.name, "e");
4777        } else {
4778            panic!("expected Error event kind");
4779        }
4780    }
4781
4782    // =========================================================================
4783    // RFC-0009: Closures and function types
4784    // =========================================================================
4785
4786    #[test]
4787    fn parse_fn_type() {
4788        let source = r#"
4789            fn apply(f: Fn(Int) -> Int, x: Int) -> Int {
4790                return f(x);
4791            }
4792
4793            agent Main {
4794                on start {
4795                    yield(0);
4796                }
4797            }
4798            run Main;
4799        "#;
4800
4801        let (prog, errors) = parse_str(source);
4802        assert!(errors.is_empty(), "errors: {errors:?}");
4803        let prog = prog.expect("should parse");
4804
4805        assert_eq!(prog.functions.len(), 1);
4806        let func = &prog.functions[0];
4807        assert_eq!(func.name.name, "apply");
4808        assert_eq!(func.params.len(), 2);
4809
4810        // Check first param is Fn(Int) -> Int
4811        if let TypeExpr::Fn(params, ret) = &func.params[0].ty {
4812            assert_eq!(params.len(), 1);
4813            assert!(matches!(params[0], TypeExpr::Int));
4814            assert!(matches!(ret.as_ref(), TypeExpr::Int));
4815        } else {
4816            panic!("expected Fn type for first param");
4817        }
4818    }
4819
4820    #[test]
4821    fn parse_closure_with_params() {
4822        let source = r#"
4823            agent Main {
4824                on start {
4825                    let f = |x: Int| x + 1;
4826                    yield(0);
4827                }
4828            }
4829            run Main;
4830        "#;
4831
4832        let (prog, errors) = parse_str(source);
4833        assert!(errors.is_empty(), "errors: {errors:?}");
4834        let prog = prog.expect("should parse");
4835
4836        // Find the let statement in the on start handler
4837        let handler = &prog.agents[0].handlers[0];
4838        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4839            if let Expr::Closure { params, body, .. } = value {
4840                assert_eq!(params.len(), 1);
4841                assert_eq!(params[0].name.name, "x");
4842                assert!(matches!(&params[0].ty, Some(TypeExpr::Int)));
4843
4844                // Body should be a binary expression
4845                assert!(matches!(body.as_ref(), Expr::Binary { .. }));
4846            } else {
4847                panic!("expected closure expression");
4848            }
4849        } else {
4850            panic!("expected let statement");
4851        }
4852    }
4853
4854    #[test]
4855    fn parse_closure_empty_params() {
4856        let source = r#"
4857            agent Main {
4858                on start {
4859                    let f = || 42;
4860                    yield(0);
4861                }
4862            }
4863            run Main;
4864        "#;
4865
4866        let (prog, errors) = parse_str(source);
4867        assert!(errors.is_empty(), "errors: {errors:?}");
4868        let prog = prog.expect("should parse");
4869
4870        // Find the let statement
4871        let handler = &prog.agents[0].handlers[0];
4872        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4873            if let Expr::Closure { params, body, .. } = value {
4874                assert!(params.is_empty());
4875
4876                // Body should be a literal
4877                assert!(matches!(body.as_ref(), Expr::Literal { .. }));
4878            } else {
4879                panic!("expected closure expression");
4880            }
4881        } else {
4882            panic!("expected let statement");
4883        }
4884    }
4885
4886    #[test]
4887    fn parse_closure_multiple_params() {
4888        let source = r#"
4889            agent Main {
4890                on start {
4891                    let add = |x: Int, y: Int| x + y;
4892                    yield(0);
4893                }
4894            }
4895            run Main;
4896        "#;
4897
4898        let (prog, errors) = parse_str(source);
4899        assert!(errors.is_empty(), "errors: {errors:?}");
4900        let prog = prog.expect("should parse");
4901
4902        let handler = &prog.agents[0].handlers[0];
4903        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4904            if let Expr::Closure { params, .. } = value {
4905                assert_eq!(params.len(), 2);
4906                assert_eq!(params[0].name.name, "x");
4907                assert_eq!(params[1].name.name, "y");
4908            } else {
4909                panic!("expected closure expression");
4910            }
4911        } else {
4912            panic!("expected let statement");
4913        }
4914    }
4915
4916    #[test]
4917    fn parse_fn_type_multiarg() {
4918        let source = r#"
4919            fn fold_left(f: Fn(Int, Int) -> Int, init: Int) -> Int {
4920                return init;
4921            }
4922
4923            agent Main {
4924                on start {
4925                    yield(0);
4926                }
4927            }
4928            run Main;
4929        "#;
4930
4931        let (prog, errors) = parse_str(source);
4932        assert!(errors.is_empty(), "errors: {errors:?}");
4933        let prog = prog.expect("should parse");
4934
4935        // Check Fn(Int, Int) -> Int
4936        if let TypeExpr::Fn(params, ret) = &prog.functions[0].params[0].ty {
4937            assert_eq!(params.len(), 2);
4938            assert!(matches!(params[0], TypeExpr::Int));
4939            assert!(matches!(params[1], TypeExpr::Int));
4940            assert!(matches!(ret.as_ref(), TypeExpr::Int));
4941        } else {
4942            panic!("expected Fn type");
4943        }
4944    }
4945
4946    #[test]
4947    fn parse_tuple_literal() {
4948        let source = r#"
4949            agent Main {
4950                on start {
4951                    let t = (1, 2);
4952                    yield(0);
4953                }
4954            }
4955            run Main;
4956        "#;
4957
4958        let (prog, errors) = parse_str(source);
4959        assert!(errors.is_empty(), "errors: {errors:?}");
4960        let prog = prog.expect("should parse");
4961
4962        let handler = &prog.agents[0].handlers[0];
4963        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
4964            if let Expr::Tuple { elements, .. } = value {
4965                assert_eq!(elements.len(), 2);
4966            } else {
4967                panic!("expected tuple expression, got {:?}", value);
4968            }
4969        } else {
4970            panic!("expected let statement");
4971        }
4972    }
4973
4974    // =========================================================================
4975    // RFC-0011: Tool support tests
4976    // =========================================================================
4977
4978    #[test]
4979    fn parse_tool_declaration() {
4980        let source = r#"
4981            tool Http {
4982                fn get(url: String) -> Result<String, String>
4983                fn post(url: String, body: String) -> Result<String, String>
4984            }
4985            agent Main {
4986                on start { yield(0); }
4987            }
4988            run Main;
4989        "#;
4990
4991        let (prog, errors) = parse_str(source);
4992        assert!(errors.is_empty(), "errors: {errors:?}");
4993        let prog = prog.expect("should parse");
4994
4995        assert_eq!(prog.tools.len(), 1);
4996        assert_eq!(prog.tools[0].name.name, "Http");
4997        assert_eq!(prog.tools[0].functions.len(), 2);
4998        assert_eq!(prog.tools[0].functions[0].name.name, "get");
4999        assert_eq!(prog.tools[0].functions[1].name.name, "post");
5000    }
5001
5002    #[test]
5003    fn parse_pub_tool_declaration() {
5004        let source = r#"
5005            pub tool Database {
5006                fn query(sql: String) -> Result<List<String>, String>
5007            }
5008            agent Main {
5009                on start { yield(0); }
5010            }
5011            run Main;
5012        "#;
5013
5014        let (prog, errors) = parse_str(source);
5015        assert!(errors.is_empty(), "errors: {errors:?}");
5016        let prog = prog.expect("should parse");
5017
5018        assert!(prog.tools[0].is_pub);
5019        assert_eq!(prog.tools[0].name.name, "Database");
5020    }
5021
5022    #[test]
5023    fn parse_agent_with_tool_use() {
5024        let source = r#"
5025            agent Fetcher {
5026                use Http
5027
5028                url: String
5029
5030                on start {
5031                    yield(0);
5032                }
5033            }
5034            run Fetcher;
5035        "#;
5036
5037        let (prog, errors) = parse_str(source);
5038        assert!(errors.is_empty(), "errors: {errors:?}");
5039        let prog = prog.expect("should parse");
5040
5041        assert_eq!(prog.agents[0].tool_uses.len(), 1);
5042        assert_eq!(prog.agents[0].tool_uses[0].name, "Http");
5043        assert_eq!(prog.agents[0].beliefs.len(), 1);
5044    }
5045
5046    #[test]
5047    fn parse_agent_with_multiple_tool_uses() {
5048        let source = r#"
5049            agent Pipeline {
5050                use Http, Fs
5051
5052                on start {
5053                    yield(0);
5054                }
5055            }
5056            run Pipeline;
5057        "#;
5058
5059        let (prog, errors) = parse_str(source);
5060        assert!(errors.is_empty(), "errors: {errors:?}");
5061        let prog = prog.expect("should parse");
5062
5063        assert_eq!(prog.agents[0].tool_uses.len(), 2);
5064        assert_eq!(prog.agents[0].tool_uses[0].name, "Http");
5065        assert_eq!(prog.agents[0].tool_uses[1].name, "Fs");
5066    }
5067
5068    #[test]
5069    fn parse_tool_call_expression() {
5070        let source = r#"
5071            agent Fetcher {
5072                use Http
5073
5074                on start {
5075                    let response = Http.get("https://example.com");
5076                    yield(0);
5077                }
5078            }
5079            run Fetcher;
5080        "#;
5081
5082        let (prog, errors) = parse_str(source);
5083        assert!(errors.is_empty(), "errors: {errors:?}");
5084        let prog = prog.expect("should parse");
5085
5086        let handler = &prog.agents[0].handlers[0];
5087        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
5088            if let Expr::ToolCall {
5089                tool,
5090                function,
5091                args,
5092                ..
5093            } = value
5094            {
5095                assert_eq!(tool.name, "Http");
5096                assert_eq!(function.name, "get");
5097                assert_eq!(args.len(), 1);
5098            } else {
5099                panic!("expected ToolCall expression, got {:?}", value);
5100            }
5101        } else {
5102            panic!("expected let statement");
5103        }
5104    }
5105
5106    #[test]
5107    fn parse_tool_call_with_multiple_args() {
5108        let source = r#"
5109            agent Writer {
5110                use Fs
5111
5112                on start {
5113                    let result = Fs.write("/tmp/test.txt", "hello world");
5114                    yield(0);
5115                }
5116            }
5117            run Writer;
5118        "#;
5119
5120        let (prog, errors) = parse_str(source);
5121        assert!(errors.is_empty(), "errors: {errors:?}");
5122        let prog = prog.expect("should parse");
5123
5124        let handler = &prog.agents[0].handlers[0];
5125        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
5126            if let Expr::ToolCall { args, .. } = value {
5127                assert_eq!(args.len(), 2);
5128            } else {
5129                panic!("expected ToolCall expression, got {:?}", value);
5130            }
5131        } else {
5132            panic!("expected let statement");
5133        }
5134    }
5135
5136    #[test]
5137    fn parse_string_interp_with_field_access() {
5138        let source = r#"
5139            record Person { name: String }
5140            agent Main {
5141                on start {
5142                    let p = Person { name: "Alice" };
5143                    print("Hello, {p.name}!");
5144                    yield(0);
5145                }
5146            }
5147            run Main;
5148        "#;
5149
5150        let (prog, errors) = parse_str(source);
5151        assert!(errors.is_empty(), "errors: {errors:?}");
5152        let prog = prog.expect("should parse");
5153
5154        // Find the print statement with interpolation
5155        let handler = &prog.agents[0].handlers[0];
5156        if let Stmt::Expr { expr, .. } = &handler.body.stmts[1] {
5157            if let Expr::Call { args, .. } = expr {
5158                if let Expr::StringInterp { template, .. } = &args[0] {
5159                    assert!(template.has_interpolations());
5160                    let interps: Vec<_> = template.interpolations().collect();
5161                    assert_eq!(interps.len(), 1);
5162                    // Should be a field access: p.name
5163                    match interps[0] {
5164                        Expr::FieldAccess { object, field, .. } => {
5165                            if let Expr::Var { name, .. } = object.as_ref() {
5166                                assert_eq!(name.name, "p");
5167                            } else {
5168                                panic!("expected Var as base");
5169                            }
5170                            assert_eq!(field.name, "name");
5171                        }
5172                        _ => panic!("expected FieldAccess, got {:?}", interps[0]),
5173                    }
5174                } else {
5175                    panic!("expected StringInterp");
5176                }
5177            } else {
5178                panic!("expected Call");
5179            }
5180        } else {
5181            panic!("expected Expr statement");
5182        }
5183    }
5184
5185    #[test]
5186    fn parse_string_interp_with_tuple_index() {
5187        let source = r#"
5188            agent Main {
5189                on start {
5190                    let pair = (1, 2);
5191                    print("First: {pair.0}");
5192                    yield(0);
5193                }
5194            }
5195            run Main;
5196        "#;
5197
5198        let (prog, errors) = parse_str(source);
5199        assert!(errors.is_empty(), "errors: {errors:?}");
5200        let prog = prog.expect("should parse");
5201
5202        let handler = &prog.agents[0].handlers[0];
5203        if let Stmt::Expr { expr, .. } = &handler.body.stmts[1] {
5204            if let Expr::Call { args, .. } = expr {
5205                if let Expr::StringInterp { template, .. } = &args[0] {
5206                    let interps: Vec<_> = template.interpolations().collect();
5207                    assert_eq!(interps.len(), 1);
5208                    match interps[0] {
5209                        Expr::TupleIndex { tuple, index, .. } => {
5210                            if let Expr::Var { name, .. } = tuple.as_ref() {
5211                                assert_eq!(name.name, "pair");
5212                            } else {
5213                                panic!("expected Var as tuple base");
5214                            }
5215                            assert_eq!(*index, 0);
5216                        }
5217                        _ => panic!("expected TupleIndex, got {:?}", interps[0]),
5218                    }
5219                } else {
5220                    panic!("expected StringInterp");
5221                }
5222            } else {
5223                panic!("expected Call");
5224            }
5225        } else {
5226            panic!("expected Expr statement");
5227        }
5228    }
5229
5230    #[test]
5231    fn parse_mock_tool_with_fail() {
5232        let source = r#"
5233            test "mock tool fail" {
5234                mock tool Http.get -> fail("network error");
5235            }
5236        "#;
5237
5238        let (prog, errors) = parse_str(source);
5239        assert!(errors.is_empty(), "errors: {errors:?}");
5240        let prog = prog.expect("should parse");
5241
5242        let test = &prog.tests[0];
5243        assert_eq!(test.body.stmts.len(), 1);
5244
5245        if let Stmt::MockTool {
5246            tool_name,
5247            fn_name,
5248            value,
5249            ..
5250        } = &test.body.stmts[0]
5251        {
5252            assert_eq!(tool_name.name, "Http");
5253            assert_eq!(fn_name.name, "get");
5254            assert!(
5255                matches!(value, MockValue::Fail(_)),
5256                "expected MockValue::Fail, got {:?}",
5257                value
5258            );
5259        } else {
5260            panic!("expected MockTool statement, got {:?}", test.body.stmts[0]);
5261        }
5262    }
5263
5264    #[test]
5265    fn parse_mock_tool_with_value() {
5266        let source = r#"
5267            test "mock tool value" {
5268                mock tool Http.get -> "response";
5269            }
5270        "#;
5271
5272        let (prog, errors) = parse_str(source);
5273        assert!(errors.is_empty(), "errors: {errors:?}");
5274        let prog = prog.expect("should parse");
5275
5276        let test = &prog.tests[0];
5277        if let Stmt::MockTool { value, .. } = &test.body.stmts[0] {
5278            assert!(
5279                matches!(value, MockValue::Value(_)),
5280                "expected MockValue::Value, got {:?}",
5281                value
5282            );
5283        } else {
5284            panic!("expected MockTool statement");
5285        }
5286    }
5287
5288    // =========================================================================
5289    // RFC-0015: Generics tests
5290    // =========================================================================
5291
5292    #[test]
5293    fn parse_generic_function() {
5294        let source = r#"
5295            fn identity<T>(x: T) -> T {
5296                return x;
5297            }
5298
5299            agent Main {
5300                on start { yield(0); }
5301            }
5302            run Main;
5303        "#;
5304
5305        let (prog, errors) = parse_str(source);
5306        assert!(errors.is_empty(), "errors: {errors:?}");
5307        let prog = prog.expect("should parse");
5308
5309        assert_eq!(prog.functions.len(), 1);
5310        let func = &prog.functions[0];
5311        assert_eq!(func.name.name, "identity");
5312        assert_eq!(func.type_params.len(), 1);
5313        assert_eq!(func.type_params[0].name, "T");
5314    }
5315
5316    #[test]
5317    fn parse_generic_function_multiple_params() {
5318        let source = r#"
5319            fn map<T, U>(list: List<T>, f: Fn(T) -> U) -> List<U> {
5320                return [];
5321            }
5322
5323            agent Main {
5324                on start { yield(0); }
5325            }
5326            run Main;
5327        "#;
5328
5329        let (prog, errors) = parse_str(source);
5330        assert!(errors.is_empty(), "errors: {errors:?}");
5331        let prog = prog.expect("should parse");
5332
5333        let func = &prog.functions[0];
5334        assert_eq!(func.name.name, "map");
5335        assert_eq!(func.type_params.len(), 2);
5336        assert_eq!(func.type_params[0].name, "T");
5337        assert_eq!(func.type_params[1].name, "U");
5338    }
5339
5340    #[test]
5341    fn parse_generic_record() {
5342        let source = r#"
5343            record Pair<A, B> {
5344                first: A,
5345                second: B,
5346            }
5347
5348            agent Main {
5349                on start { yield(0); }
5350            }
5351            run Main;
5352        "#;
5353
5354        let (prog, errors) = parse_str(source);
5355        assert!(errors.is_empty(), "errors: {errors:?}");
5356        let prog = prog.expect("should parse");
5357
5358        assert_eq!(prog.records.len(), 1);
5359        let record = &prog.records[0];
5360        assert_eq!(record.name.name, "Pair");
5361        assert_eq!(record.type_params.len(), 2);
5362        assert_eq!(record.type_params[0].name, "A");
5363        assert_eq!(record.type_params[1].name, "B");
5364    }
5365
5366    #[test]
5367    fn parse_generic_enum() {
5368        let source = r#"
5369            enum Tree<T> {
5370                Leaf(T),
5371                Node(Tree<T>),
5372            }
5373
5374            agent Main {
5375                on start { yield(0); }
5376            }
5377            run Main;
5378        "#;
5379
5380        let (prog, errors) = parse_str(source);
5381        assert!(errors.is_empty(), "errors: {errors:?}");
5382        let prog = prog.expect("should parse");
5383
5384        assert_eq!(prog.enums.len(), 1);
5385        let enumm = &prog.enums[0];
5386        assert_eq!(enumm.name.name, "Tree");
5387        assert_eq!(enumm.type_params.len(), 1);
5388        assert_eq!(enumm.type_params[0].name, "T");
5389    }
5390
5391    #[test]
5392    fn parse_generic_type_argument() {
5393        let source = r#"
5394            record Wrapper<T> {
5395                value: T,
5396            }
5397
5398            fn make_wrapper<T>(value: T) -> Wrapper<T> {
5399                return Wrapper { value: value };
5400            }
5401
5402            agent Main {
5403                on start { yield(0); }
5404            }
5405            run Main;
5406        "#;
5407
5408        let (prog, errors) = parse_str(source);
5409        assert!(errors.is_empty(), "errors: {errors:?}");
5410        let prog = prog.expect("should parse");
5411
5412        let func = &prog.functions[0];
5413        // Check return type is Wrapper<T>
5414        if let TypeExpr::Named(name, type_args) = &func.return_ty {
5415            assert_eq!(name.name, "Wrapper");
5416            assert_eq!(type_args.len(), 1);
5417            if let TypeExpr::Named(inner_name, _) = &type_args[0] {
5418                assert_eq!(inner_name.name, "T");
5419            } else {
5420                panic!("expected Named type argument");
5421            }
5422        } else {
5423            panic!("expected Named return type");
5424        }
5425    }
5426
5427    #[test]
5428    fn parse_turbofish_function_call() {
5429        let source = r#"
5430            fn identity<T>(x: T) -> T {
5431                return x;
5432            }
5433
5434            agent Main {
5435                on start {
5436                    let result = identity::<Int>(42);
5437                    yield(0);
5438                }
5439            }
5440            run Main;
5441        "#;
5442
5443        let (prog, errors) = parse_str(source);
5444        assert!(errors.is_empty(), "errors: {errors:?}");
5445        let prog = prog.expect("should parse");
5446
5447        let handler = &prog.agents[0].handlers[0];
5448        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
5449            if let Expr::Call {
5450                name, type_args, ..
5451            } = value
5452            {
5453                assert_eq!(name.name, "identity");
5454                assert_eq!(type_args.len(), 1);
5455                assert!(matches!(type_args[0], TypeExpr::Int));
5456            } else {
5457                panic!("expected Call expression");
5458            }
5459        } else {
5460            panic!("expected Let statement");
5461        }
5462    }
5463
5464    #[test]
5465    fn parse_turbofish_multiple_type_args() {
5466        let source = r#"
5467            fn make_pair<A, B>(a: A, b: B) -> (A, B) {
5468                return (a, b);
5469            }
5470
5471            agent Main {
5472                on start {
5473                    let pair = make_pair::<Int, String>(42, "hello");
5474                    yield(0);
5475                }
5476            }
5477            run Main;
5478        "#;
5479
5480        let (prog, errors) = parse_str(source);
5481        assert!(errors.is_empty(), "errors: {errors:?}");
5482        let prog = prog.expect("should parse");
5483
5484        let handler = &prog.agents[0].handlers[0];
5485        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
5486            if let Expr::Call {
5487                name, type_args, ..
5488            } = value
5489            {
5490                assert_eq!(name.name, "make_pair");
5491                assert_eq!(type_args.len(), 2);
5492                assert!(matches!(type_args[0], TypeExpr::Int));
5493                assert!(matches!(type_args[1], TypeExpr::String));
5494            } else {
5495                panic!("expected Call expression");
5496            }
5497        } else {
5498            panic!("expected Let statement");
5499        }
5500    }
5501
5502    #[test]
5503    fn parse_turbofish_record_construction() {
5504        let source = r#"
5505            record Pair<A, B> {
5506                first: A,
5507                second: B,
5508            }
5509
5510            agent Main {
5511                on start {
5512                    let p = Pair::<Int, String> { first: 42, second: "hi" };
5513                    yield(0);
5514                }
5515            }
5516            run Main;
5517        "#;
5518
5519        let (prog, errors) = parse_str(source);
5520        assert!(errors.is_empty(), "errors: {errors:?}");
5521        let prog = prog.expect("should parse");
5522
5523        let handler = &prog.agents[0].handlers[0];
5524        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
5525            if let Expr::RecordConstruct {
5526                name, type_args, ..
5527            } = value
5528            {
5529                assert_eq!(name.name, "Pair");
5530                assert_eq!(type_args.len(), 2);
5531                assert!(matches!(type_args[0], TypeExpr::Int));
5532                assert!(matches!(type_args[1], TypeExpr::String));
5533            } else {
5534                panic!("expected RecordConstruct expression");
5535            }
5536        } else {
5537            panic!("expected Let statement");
5538        }
5539    }
5540
5541    #[test]
5542    fn parse_turbofish_variant_construction() {
5543        let source = r#"
5544            enum Either<L, R> {
5545                Left(L),
5546                Right(R),
5547            }
5548
5549            agent Main {
5550                on start {
5551                    let e = Either::<String, Int>::Left("hello");
5552                    yield(0);
5553                }
5554            }
5555            run Main;
5556        "#;
5557
5558        let (prog, errors) = parse_str(source);
5559        assert!(errors.is_empty(), "errors: {errors:?}");
5560        let prog = prog.expect("should parse");
5561
5562        let handler = &prog.agents[0].handlers[0];
5563        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
5564            if let Expr::VariantConstruct {
5565                enum_name,
5566                type_args,
5567                variant,
5568                ..
5569            } = value
5570            {
5571                assert_eq!(enum_name.name, "Either");
5572                assert_eq!(variant.name, "Left");
5573                assert_eq!(type_args.len(), 2);
5574                assert!(matches!(type_args[0], TypeExpr::String));
5575                assert!(matches!(type_args[1], TypeExpr::Int));
5576            } else {
5577                panic!("expected VariantConstruct expression");
5578            }
5579        } else {
5580            panic!("expected Let statement");
5581        }
5582    }
5583
5584    #[test]
5585    fn parse_generic_in_type_annotation() {
5586        let source = r#"
5587            record Page<T> {
5588                items: List<T>,
5589                count: Int,
5590            }
5591
5592            agent Main {
5593                on start {
5594                    let page: Page<String> = Page { items: [], count: 0 };
5595                    yield(0);
5596                }
5597            }
5598            run Main;
5599        "#;
5600
5601        let (prog, errors) = parse_str(source);
5602        assert!(errors.is_empty(), "errors: {errors:?}");
5603        let prog = prog.expect("should parse");
5604
5605        let handler = &prog.agents[0].handlers[0];
5606        if let Stmt::Let { ty: Some(ty), .. } = &handler.body.stmts[0] {
5607            if let TypeExpr::Named(name, type_args) = ty {
5608                assert_eq!(name.name, "Page");
5609                assert_eq!(type_args.len(), 1);
5610                assert!(matches!(type_args[0], TypeExpr::String));
5611            } else {
5612                panic!("expected Named type");
5613            }
5614        } else {
5615            panic!("expected Let statement with type annotation");
5616        }
5617    }
5618
5619    #[test]
5620    fn parse_nested_generic_types() {
5621        let source = r#"
5622            record Nested<T> {
5623                value: Option<List<T>>,
5624            }
5625
5626            agent Main {
5627                on start { yield(0); }
5628            }
5629            run Main;
5630        "#;
5631
5632        let (prog, errors) = parse_str(source);
5633        assert!(errors.is_empty(), "errors: {errors:?}");
5634        let prog = prog.expect("should parse");
5635
5636        let record = &prog.records[0];
5637        let field_ty = &record.fields[0].ty;
5638        // Should be Option<List<T>>
5639        if let TypeExpr::Option(inner) = field_ty {
5640            if let TypeExpr::List(elem) = inner.as_ref() {
5641                if let TypeExpr::Named(name, _) = elem.as_ref() {
5642                    assert_eq!(name.name, "T");
5643                } else {
5644                    panic!("expected Named type T");
5645                }
5646            } else {
5647                panic!("expected List type");
5648            }
5649        } else {
5650            panic!("expected Option type");
5651        }
5652    }
5653
5654    #[test]
5655    fn parse_span_block() {
5656        let source = r#"
5657            agent Main {
5658                on start {
5659                    span "fetch_data" {
5660                        let x = 1;
5661                        let y = 2;
5662                    }
5663                    yield(42);
5664                }
5665            }
5666            run Main;
5667        "#;
5668
5669        let (prog, errors) = parse_str(source);
5670        assert!(errors.is_empty(), "errors: {errors:?}");
5671        let prog = prog.expect("should parse");
5672
5673        let handler = &prog.agents[0].handlers[0];
5674        // First statement should be a SpanBlock
5675        if let Stmt::SpanBlock { name, body, .. } = &handler.body.stmts[0] {
5676            // Name should be a string literal
5677            if let Expr::Literal {
5678                value: Literal::String(s),
5679                ..
5680            } = name
5681            {
5682                assert_eq!(s, "fetch_data");
5683            } else {
5684                panic!("expected string literal for span name");
5685            }
5686            // Body should have 2 statements
5687            assert_eq!(body.stmts.len(), 2);
5688        } else {
5689            panic!("expected SpanBlock statement");
5690        }
5691    }
5692
5693    #[test]
5694    fn parse_turbofish_in_interpolation() {
5695        let source = r#"
5696            fn identity<T>(x: T) -> T { return x; }
5697
5698            agent Main {
5699                on start {
5700                    let msg = "result: {identity::<Int>(42)}";
5701                    yield(0);
5702                }
5703            }
5704            run Main;
5705        "#;
5706
5707        let (prog, errors) = parse_str(source);
5708        assert!(errors.is_empty(), "errors: {errors:?}");
5709        let prog = prog.expect("should parse");
5710
5711        let handler = &prog.agents[0].handlers[0];
5712        if let Stmt::Let { value, .. } = &handler.body.stmts[0] {
5713            if let Expr::StringInterp { template, .. } = value {
5714                // Should have 2 parts: "result: " and the interpolated expression
5715                assert_eq!(template.parts.len(), 2);
5716                if let StringPart::Interpolation(call_expr) = &template.parts[1] {
5717                    if let Expr::Call { name, type_args, args, .. } = call_expr.as_ref() {
5718                        assert_eq!(name.name, "identity");
5719                        assert_eq!(type_args.len(), 1);
5720                        assert!(matches!(type_args[0], TypeExpr::Int));
5721                        assert_eq!(args.len(), 1);
5722                    } else {
5723                        panic!("expected Call expression in interpolation, got {:?}", call_expr);
5724                    }
5725                } else {
5726                    panic!("expected Interpolation part in template");
5727                }
5728            } else {
5729                panic!("expected StringInterp expression");
5730            }
5731        } else {
5732            panic!("expected Let statement");
5733        }
5734    }
5735
5736    // =============================================================================
5737    // Phase 3: Session Types & Algebraic Effects
5738    // =============================================================================
5739
5740    #[test]
5741    fn parse_protocol_declaration() {
5742        let source = r#"
5743            protocol SchemaSync {
5744                DatabaseSteward -> APISteward: SchemaChanged
5745                APISteward -> DatabaseSteward: Acknowledged
5746            }
5747
5748            agent Main {
5749                on start {
5750                    yield(0);
5751                }
5752            }
5753            run Main;
5754        "#;
5755
5756        let (prog, errors) = parse_str(source);
5757        assert!(errors.is_empty(), "errors: {errors:?}");
5758        let prog = prog.expect("should parse");
5759
5760        assert_eq!(prog.protocols.len(), 1);
5761        let proto = &prog.protocols[0];
5762        assert_eq!(proto.name.name, "SchemaSync");
5763        assert!(!proto.is_pub);
5764        assert_eq!(proto.steps.len(), 2);
5765
5766        assert_eq!(proto.steps[0].sender.name, "DatabaseSteward");
5767        assert_eq!(proto.steps[0].receiver.name, "APISteward");
5768        if let TypeExpr::Named(name, _) = &proto.steps[0].message_type {
5769            assert_eq!(name.name, "SchemaChanged");
5770        } else {
5771            panic!("expected Named type");
5772        }
5773
5774        assert_eq!(proto.steps[1].sender.name, "APISteward");
5775        assert_eq!(proto.steps[1].receiver.name, "DatabaseSteward");
5776    }
5777
5778    #[test]
5779    fn parse_public_protocol() {
5780        let source = r#"
5781            pub protocol PingPong {
5782                Pinger -> Ponger: Ping
5783                Ponger -> Pinger: Pong
5784            }
5785
5786            agent Main { on start { yield(0); } }
5787            run Main;
5788        "#;
5789
5790        let (prog, errors) = parse_str(source);
5791        assert!(errors.is_empty(), "errors: {errors:?}");
5792        let prog = prog.expect("should parse");
5793
5794        assert_eq!(prog.protocols.len(), 1);
5795        assert!(prog.protocols[0].is_pub);
5796    }
5797
5798    #[test]
5799    fn parse_agent_follows_protocol() {
5800        let source = r#"
5801            protocol SchemaSync {
5802                A -> B: Msg
5803            }
5804
5805            agent APISteward follows SchemaSync as APISteward {
5806                on start {
5807                    yield(0);
5808                }
5809            }
5810            run APISteward;
5811        "#;
5812
5813        let (prog, errors) = parse_str(source);
5814        assert!(errors.is_empty(), "errors: {errors:?}");
5815        let prog = prog.expect("should parse");
5816
5817        assert_eq!(prog.agents[0].follows.len(), 1);
5818        assert_eq!(prog.agents[0].follows[0].protocol.name, "SchemaSync");
5819        assert_eq!(prog.agents[0].follows[0].role.name, "APISteward");
5820    }
5821
5822    #[test]
5823    fn parse_agent_follows_multiple_protocols() {
5824        let source = r#"
5825            agent MultiProtocolAgent follows Proto1 as RoleA, Proto2 as RoleB {
5826                on start {
5827                    yield(0);
5828                }
5829            }
5830            run MultiProtocolAgent;
5831        "#;
5832
5833        let (prog, errors) = parse_str(source);
5834        assert!(errors.is_empty(), "errors: {errors:?}");
5835        let prog = prog.expect("should parse");
5836
5837        assert_eq!(prog.agents[0].follows.len(), 2);
5838        assert_eq!(prog.agents[0].follows[0].protocol.name, "Proto1");
5839        assert_eq!(prog.agents[0].follows[0].role.name, "RoleA");
5840        assert_eq!(prog.agents[0].follows[1].protocol.name, "Proto2");
5841        assert_eq!(prog.agents[0].follows[1].role.name, "RoleB");
5842    }
5843
5844    #[test]
5845    fn parse_agent_with_receives_and_follows() {
5846        let source = r#"
5847            agent Worker receives Request follows WorkProto as Worker {
5848                on message(msg: Request) {
5849                    reply(Response {});
5850                }
5851            }
5852            run Worker;
5853        "#;
5854
5855        let (prog, errors) = parse_str(source);
5856        assert!(errors.is_empty(), "errors: {errors:?}");
5857        let prog = prog.expect("should parse");
5858
5859        assert!(prog.agents[0].receives.is_some());
5860        assert_eq!(prog.agents[0].follows.len(), 1);
5861    }
5862
5863    #[test]
5864    fn parse_reply_expression() {
5865        let source = r#"
5866            agent Responder {
5867                on message(msg: Request) {
5868                    reply(Response { code: 200 });
5869                }
5870            }
5871            run Responder;
5872        "#;
5873
5874        let (prog, errors) = parse_str(source);
5875        assert!(errors.is_empty(), "errors: {errors:?}");
5876        let prog = prog.expect("should parse");
5877
5878        let handler = &prog.agents[0].handlers[0];
5879        if let Stmt::Expr { expr: Expr::Reply { message, .. }, .. } = &handler.body.stmts[0] {
5880            assert!(matches!(message.as_ref(), Expr::RecordConstruct { .. }));
5881        } else {
5882            panic!("expected Reply expression, got {:?}", handler.body.stmts[0]);
5883        }
5884    }
5885
5886    #[test]
5887    fn parse_effect_handler_declaration() {
5888        let source = r#"
5889            handler DefaultLLM handles Infer {
5890                model: "gpt-4o"
5891                temperature: 0.7
5892                max_tokens: 1024
5893            }
5894
5895            agent Main { on start { yield(0); } }
5896            run Main;
5897        "#;
5898
5899        let (prog, errors) = parse_str(source);
5900        assert!(errors.is_empty(), "errors: {errors:?}");
5901        let prog = prog.expect("should parse");
5902
5903        assert_eq!(prog.effect_handlers.len(), 1);
5904        let handler = &prog.effect_handlers[0];
5905        assert_eq!(handler.name.name, "DefaultLLM");
5906        assert_eq!(handler.effect.name, "Infer");
5907        assert!(!handler.is_pub);
5908        assert_eq!(handler.config.len(), 3);
5909
5910        assert_eq!(handler.config[0].key.name, "model");
5911        assert!(matches!(handler.config[0].value, Literal::String(_)));
5912
5913        assert_eq!(handler.config[1].key.name, "temperature");
5914        assert!(matches!(handler.config[1].value, Literal::Float(_)));
5915
5916        assert_eq!(handler.config[2].key.name, "max_tokens");
5917        assert!(matches!(handler.config[2].value, Literal::Int(1024)));
5918    }
5919
5920    #[test]
5921    fn parse_public_effect_handler() {
5922        let source = r#"
5923            pub handler FastLLM handles Infer {
5924                model: "gpt-4o-mini"
5925            }
5926
5927            agent Main { on start { yield(0); } }
5928            run Main;
5929        "#;
5930
5931        let (prog, errors) = parse_str(source);
5932        assert!(errors.is_empty(), "errors: {errors:?}");
5933        let prog = prog.expect("should parse");
5934
5935        assert!(prog.effect_handlers[0].is_pub);
5936    }
5937
5938    #[test]
5939    fn parse_supervisor_with_handler_assignment() {
5940        let source = r#"
5941            handler DefaultLLM handles Infer {
5942                model: "gpt-4o"
5943            }
5944
5945            agent Worker {
5946                on start {
5947                    let thought = divine("Think!");
5948                    yield(thought);
5949                }
5950            }
5951
5952            supervisor AppSupervisor {
5953                strategy: OneForOne
5954                children {
5955                    Worker {
5956                        restart: Permanent
5957                        handler Infer: DefaultLLM
5958                    }
5959                }
5960            }
5961
5962            run AppSupervisor;
5963        "#;
5964
5965        let (prog, errors) = parse_str(source);
5966        assert!(errors.is_empty(), "errors: {errors:?}");
5967        let prog = prog.expect("should parse");
5968
5969        let child = &prog.supervisors[0].children[0];
5970        assert_eq!(child.agent_name.name, "Worker");
5971        assert_eq!(child.handler_assignments.len(), 1);
5972        assert_eq!(child.handler_assignments[0].effect.name, "Infer");
5973        assert_eq!(child.handler_assignments[0].handler.name, "DefaultLLM");
5974    }
5975
5976    #[test]
5977    fn parse_supervisor_with_multiple_handler_assignments() {
5978        let source = r#"
5979            agent Worker { on start { yield(0); } }
5980
5981            supervisor MultiHandlerSupervisor {
5982                strategy: OneForOne
5983                children {
5984                    Worker {
5985                        restart: Permanent
5986                        handler Infer: FastLLM
5987                        handler Logger: FileLogger
5988                    }
5989                }
5990            }
5991
5992            run MultiHandlerSupervisor;
5993        "#;
5994
5995        let (prog, errors) = parse_str(source);
5996        assert!(errors.is_empty(), "errors: {errors:?}");
5997        let prog = prog.expect("should parse");
5998
5999        let child = &prog.supervisors[0].children[0];
6000        assert_eq!(child.handler_assignments.len(), 2);
6001        assert_eq!(child.handler_assignments[0].effect.name, "Infer");
6002        assert_eq!(child.handler_assignments[0].handler.name, "FastLLM");
6003        assert_eq!(child.handler_assignments[1].effect.name, "Logger");
6004        assert_eq!(child.handler_assignments[1].handler.name, "FileLogger");
6005    }
6006}