Skip to main content

sage_parser/
parser.rs

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