Skip to main content

panproto_expr_parser/
parser.rs

1//! Chumsky parser producing `panproto_expr::Expr` from the token stream.
2//!
3//! Implements the grammar defined in `notes/POLY_IMPLEMENTATION_PLAN.md`.
4//! Uses Pratt parsing for operator precedence and recursive descent for
5//! the rest. Layout tokens (`Indent`/`Dedent`/`Newline`) from the lexer
6//! are consumed directly as delimiters for layout-sensitive blocks.
7
8use std::sync::Arc;
9
10use chumsky::input::{Input as _, Stream, ValueInput};
11use chumsky::pratt::{infix, left, prefix, right};
12use chumsky::prelude::*;
13use chumsky::span::SimpleSpan;
14
15use panproto_expr::{BuiltinOp, Expr, Literal, Pattern};
16
17use crate::token::Token;
18
19/// A parse error.
20pub type ParseError = Rich<'static, Token, SimpleSpan>;
21
22/// Parse a token stream into an `Expr`.
23///
24/// The input should come from [`crate::tokenize`].
25///
26/// # Errors
27///
28/// Returns parse errors with source spans on failure.
29pub fn parse(tokens: &[crate::Spanned]) -> Result<Expr, Vec<ParseError>> {
30    let mapped: Vec<(Token, SimpleSpan)> = tokens
31        .iter()
32        .filter(|s| s.token != Token::Eof)
33        .map(|s| (s.token.clone(), SimpleSpan::new(s.span.start, s.span.end)))
34        .collect();
35    let eoi = tokens.last().map_or_else(
36        || SimpleSpan::new(0, 0),
37        |s| SimpleSpan::new(s.span.start, s.span.end),
38    );
39    let stream = Stream::from_iter(mapped).map(eoi, |(tok, span)| (tok, span));
40    expr_parser().parse(stream).into_result().map_err(|errs| {
41        errs.into_iter()
42            .map(chumsky::error::Rich::into_owned)
43            .collect()
44    })
45}
46
47// ── Token matchers ──────────────────────────────────────────────────
48
49/// Match an identifier and return its name.
50fn ident<'t, 'src: 't, I>()
51-> impl Parser<'t, I, Arc<str>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
52where
53    I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
54{
55    select! { Token::Ident(s) => Arc::from(s.as_str()) }.labelled("identifier")
56}
57
58/// Match an upper-case identifier.
59fn upper_ident<'t, 'src: 't, I>()
60-> impl Parser<'t, I, Arc<str>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
61where
62    I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
63{
64    select! { Token::UpperIdent(s) => Arc::from(s.as_str()) }.labelled("constructor")
65}
66
67// ── Layout blocks ───────────────────────────────────────────────────
68
69/// Parse a layout block: either `{ item ; item ; ... }` or
70/// `INDENT item NEWLINE item ... DEDENT`.
71fn layout_block<'t, 'src: 't, I, T: 't>(
72    item: impl Parser<'t, I, T, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone,
73) -> impl Parser<'t, I, Vec<T>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
74where
75    I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
76{
77    let explicit = item
78        .clone()
79        .separated_by(just(Token::Newline).or(just(Token::Comma)))
80        .allow_trailing()
81        .collect::<Vec<_>>()
82        .delimited_by(just(Token::LBrace), just(Token::RBrace));
83
84    let implicit = item
85        .separated_by(just(Token::Newline))
86        .allow_trailing()
87        .collect::<Vec<_>>()
88        .delimited_by(just(Token::Indent), just(Token::Dedent));
89
90    explicit.or(implicit)
91}
92
93// ── Pattern parser ──────────────────────────────────────────────────
94
95/// Parse a pattern.
96fn pattern_parser<'t, 'src: 't, I>()
97-> impl Parser<'t, I, Pattern, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
98where
99    I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
100{
101    recursive(|pat| {
102        let wildcard = select! { Token::Ident(s) if s == "_" => Pattern::Wildcard };
103
104        let var = ident().map(Pattern::Var);
105
106        let literal_pat = literal_parser().map(Pattern::Lit);
107
108        let paren = pat
109            .clone()
110            .delimited_by(just(Token::LParen), just(Token::RParen));
111
112        let list_pat = pat
113            .clone()
114            .separated_by(just(Token::Comma))
115            .collect::<Vec<_>>()
116            .delimited_by(just(Token::LBracket), just(Token::RBracket))
117            .map(Pattern::List);
118
119        let field_pat = ident()
120            .then(just(Token::Eq).ignore_then(pat.clone()).or_not())
121            .map(|(name, maybe_pat): (Arc<str>, Option<Pattern>)| {
122                let p = maybe_pat.unwrap_or_else(|| Pattern::Var(name.clone()));
123                (name, p)
124            });
125
126        let record_pat = field_pat
127            .separated_by(just(Token::Comma))
128            .collect::<Vec<_>>()
129            .delimited_by(just(Token::LBrace), just(Token::RBrace))
130            .map(Pattern::Record);
131
132        let constructor = upper_ident()
133            .then(pat.clone().repeated().collect::<Vec<_>>())
134            .map(|(name, args): (Arc<str>, Vec<Pattern>)| Pattern::Constructor(name, args));
135
136        choice((
137            wildcard,
138            literal_pat,
139            paren,
140            list_pat,
141            record_pat,
142            constructor,
143            var,
144        ))
145    })
146}
147
148// ── Literal parser ──────────────────────────────────────────────────
149
150/// Parse a literal value.
151fn literal_parser<'t, 'src: 't, I>()
152-> impl Parser<'t, I, Literal, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
153where
154    I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
155{
156    select! {
157        Token::Int(n) => Literal::Int(n),
158        Token::Float(f) => Literal::Float(f),
159        Token::Str(s) => Literal::Str(s),
160        Token::True => Literal::Bool(true),
161        Token::False => Literal::Bool(false),
162        Token::Nothing => Literal::Null,
163    }
164    .labelled("literal")
165}
166
167// ── Builtin name → op mapping ───────────────────────────────────────
168
169/// Resolve a lowercase identifier to a builtin op, if any.
170fn resolve_builtin(name: &str) -> Option<BuiltinOp> {
171    match name {
172        "add" => Some(BuiltinOp::Add),
173        "sub" => Some(BuiltinOp::Sub),
174        "mul" => Some(BuiltinOp::Mul),
175        "abs" => Some(BuiltinOp::Abs),
176        "floor" => Some(BuiltinOp::Floor),
177        "ceil" => Some(BuiltinOp::Ceil),
178        "round" => Some(BuiltinOp::Round),
179        "concat" => Some(BuiltinOp::Concat),
180        "len" => Some(BuiltinOp::Len),
181        "slice" => Some(BuiltinOp::Slice),
182        "upper" => Some(BuiltinOp::Upper),
183        "lower" => Some(BuiltinOp::Lower),
184        "trim" => Some(BuiltinOp::Trim),
185        "split" => Some(BuiltinOp::Split),
186        "join" => Some(BuiltinOp::Join),
187        "replace" => Some(BuiltinOp::Replace),
188        "contains" => Some(BuiltinOp::Contains),
189        "map" => Some(BuiltinOp::Map),
190        "filter" => Some(BuiltinOp::Filter),
191        "fold" => Some(BuiltinOp::Fold),
192        "append" => Some(BuiltinOp::Append),
193        "head" => Some(BuiltinOp::Head),
194        "tail" => Some(BuiltinOp::Tail),
195        "reverse" => Some(BuiltinOp::Reverse),
196        "flat_map" | "flatMap" => Some(BuiltinOp::FlatMap),
197        "length" => Some(BuiltinOp::Length),
198        "merge" | "merge_records" => Some(BuiltinOp::MergeRecords),
199        "keys" => Some(BuiltinOp::Keys),
200        "values" => Some(BuiltinOp::Values),
201        "has_field" | "hasField" => Some(BuiltinOp::HasField),
202        "default" | "default_val" | "defaultVal" => Some(BuiltinOp::DefaultVal),
203        "clamp" => Some(BuiltinOp::Clamp),
204        "truncate_str" | "truncateStr" => Some(BuiltinOp::TruncateStr),
205        "int_to_float" | "intToFloat" => Some(BuiltinOp::IntToFloat),
206        "float_to_int" | "floatToInt" => Some(BuiltinOp::FloatToInt),
207        "int_to_str" | "intToStr" => Some(BuiltinOp::IntToStr),
208        "float_to_str" | "floatToStr" => Some(BuiltinOp::FloatToStr),
209        "str_to_int" | "strToInt" => Some(BuiltinOp::StrToInt),
210        "str_to_float" | "strToFloat" => Some(BuiltinOp::StrToFloat),
211        "type_of" | "typeOf" => Some(BuiltinOp::TypeOf),
212        "is_null" | "isNull" => Some(BuiltinOp::IsNull),
213        "is_list" | "isList" => Some(BuiltinOp::IsList),
214        "edge" => Some(BuiltinOp::Edge),
215        "children" => Some(BuiltinOp::Children),
216        "has_edge" | "hasEdge" => Some(BuiltinOp::HasEdge),
217        "edge_count" | "edgeCount" => Some(BuiltinOp::EdgeCount),
218        "anchor" => Some(BuiltinOp::Anchor),
219        _ => None,
220    }
221}
222
223// ── Expression parser ───────────────────────────────────────────────
224
225/// Top-level expression parser.
226///
227/// This is a single `chumsky::recursive` combinator defining the entire
228/// expression grammar: literals, variables, list literals/comprehensions,
229/// function application, the pratt operator table, lambdas, let, if/case,
230/// do-notation, and where-clauses. Extracting individual sub-parsers
231/// would require forwarding the recursive `expr` parser by reference
232/// with a verbose `impl Parser<…> + Clone` bound at each call site, which
233/// would hurt rather than help readability. The grammar is kept inline
234/// and the length lint is silenced locally.
235#[allow(clippy::too_many_lines)]
236fn expr_parser<'t, 'src: 't, I>()
237-> impl Parser<'t, I, Expr, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
238where
239    I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
240{
241    recursive(|expr| {
242        let pattern = pattern_parser();
243
244        // ── Atoms ───────────────────────────────────────────
245
246        let lit = literal_parser().map(Expr::Lit);
247
248        let var_or_builtin = ident().map(Expr::Var);
249
250        let constructor = upper_ident().map(Expr::Var);
251
252        let paren_expr = expr
253            .clone()
254            .delimited_by(just(Token::LParen), just(Token::RParen));
255
256        // List literal or comprehension
257        let list_expr = {
258            let plain_list = expr
259                .clone()
260                .separated_by(just(Token::Comma))
261                .collect::<Vec<_>>()
262                .map(Expr::List);
263
264            // List comprehension: [e | x <- xs, pred]
265            let comprehension = expr
266                .clone()
267                .then_ignore(just(Token::Pipe))
268                .then(
269                    ident()
270                        .then_ignore(just(Token::LeftArrow))
271                        .then(expr.clone())
272                        .map(|(n, e): (Arc<str>, Expr)| Qual::Generator(n, e))
273                        .or(expr.clone().map(Qual::Guard))
274                        .separated_by(just(Token::Comma))
275                        .at_least(1)
276                        .collect::<Vec<Qual>>(),
277                )
278                .map(|(body, quals): (Expr, Vec<Qual>)| desugar_comprehension(body, &quals));
279
280            // Range: [1..10] or [1..]
281            let range = expr
282                .clone()
283                .then_ignore(just(Token::DotDot))
284                .then(expr.clone().or_not())
285                .map(|(start, end): (Expr, Option<Expr>)| match end {
286                    Some(stop) => Expr::Builtin(
287                        BuiltinOp::Map,
288                        vec![
289                            Expr::Lam(
290                                Arc::from("_i"),
291                                Box::new(Expr::Builtin(
292                                    BuiltinOp::Add,
293                                    vec![start.clone(), Expr::Var(Arc::from("_i"))],
294                                )),
295                            ),
296                            Expr::Builtin(
297                                BuiltinOp::Sub,
298                                vec![
299                                    Expr::Builtin(
300                                        BuiltinOp::Add,
301                                        vec![stop, Expr::Lit(Literal::Int(1))],
302                                    ),
303                                    start,
304                                ],
305                            ),
306                        ],
307                    ),
308                    None => Expr::List(vec![start]),
309                });
310
311            choice((comprehension, range, plain_list))
312                .delimited_by(just(Token::LBracket), just(Token::RBracket))
313        };
314
315        // Record literal
316        let record_expr = {
317            let field_bind = ident()
318                .then(just(Token::Eq).ignore_then(expr.clone()).or_not())
319                .map(|(name, val): (Arc<str>, Option<Expr>)| {
320                    let v = val.unwrap_or_else(|| Expr::Var(name.clone()));
321                    (name, v)
322                });
323
324            field_bind
325                .separated_by(just(Token::Comma))
326                .allow_trailing()
327                .collect::<Vec<_>>()
328                .delimited_by(just(Token::LBrace), just(Token::RBrace))
329                .map(Expr::Record)
330        };
331
332        let atom = choice((
333            lit,
334            paren_expr,
335            list_expr,
336            record_expr,
337            constructor,
338            var_or_builtin,
339        ));
340
341        // ── Postfix: field access (.field) and edge traversal (->edge) ──
342
343        let postfix_chain = atom.foldl(
344            choice((
345                just(Token::Dot).ignore_then(ident()).map(PostfixOp::Field),
346                just(Token::Arrow).ignore_then(ident()).map(PostfixOp::Edge),
347            ))
348            .repeated(),
349            |expr, postfix| match postfix {
350                PostfixOp::Field(name) => Expr::Field(Box::new(expr), name),
351                PostfixOp::Edge(edge) => Expr::Builtin(
352                    BuiltinOp::Edge,
353                    vec![expr, Expr::Lit(Literal::Str(edge.to_string()))],
354                ),
355            },
356        );
357
358        // ── Application (juxtaposition) ─────────────────────
359
360        let app = postfix_chain
361            .clone()
362            .foldl(postfix_chain.repeated(), resolve_application);
363
364        // ── Pratt parser for infix/prefix operators ─────────
365
366        let pratt = app.pratt((
367            // Precedence 1: pipe (&)
368            infix(left(1), just(Token::Ampersand), |l, _, r, _| {
369                Expr::App(Box::new(r), Box::new(l))
370            }),
371            // Precedence 3: logical or
372            infix(left(3), just(Token::OrOr), |l, _, r, _| {
373                Expr::Builtin(BuiltinOp::Or, vec![l, r])
374            }),
375            // Precedence 4: logical and
376            infix(left(4), just(Token::AndAnd), |l, _, r, _| {
377                Expr::Builtin(BuiltinOp::And, vec![l, r])
378            }),
379            // Precedence 5: comparison
380            infix(right(5), just(Token::EqEq), |l, _, r, _| {
381                Expr::Builtin(BuiltinOp::Eq, vec![l, r])
382            }),
383            infix(right(5), just(Token::Neq), |l, _, r, _| {
384                Expr::Builtin(BuiltinOp::Neq, vec![l, r])
385            }),
386            infix(right(5), just(Token::Lt), |l, _, r, _| {
387                Expr::Builtin(BuiltinOp::Lt, vec![l, r])
388            }),
389            infix(right(5), just(Token::Lte), |l, _, r, _| {
390                Expr::Builtin(BuiltinOp::Lte, vec![l, r])
391            }),
392            infix(right(5), just(Token::Gt), |l, _, r, _| {
393                Expr::Builtin(BuiltinOp::Gt, vec![l, r])
394            }),
395            infix(right(5), just(Token::Gte), |l, _, r, _| {
396                Expr::Builtin(BuiltinOp::Gte, vec![l, r])
397            }),
398            // Precedence 6: string concat
399            infix(right(6), just(Token::PlusPlus), |l, _, r, _| {
400                Expr::Builtin(BuiltinOp::Concat, vec![l, r])
401            }),
402            // Precedence 7: addition/subtraction
403            infix(left(7), just(Token::Plus), |l, _, r, _| {
404                Expr::Builtin(BuiltinOp::Add, vec![l, r])
405            }),
406            infix(left(7), just(Token::Minus), |l, _, r, _| {
407                Expr::Builtin(BuiltinOp::Sub, vec![l, r])
408            }),
409            // Precedence 8: multiplication/division
410            infix(left(8), just(Token::Star), |l, _, r, _| {
411                Expr::Builtin(BuiltinOp::Mul, vec![l, r])
412            }),
413            infix(left(8), just(Token::Slash), |l, _, r, _| {
414                Expr::Builtin(BuiltinOp::Div, vec![l, r])
415            }),
416            infix(left(8), just(Token::Percent), |l, _, r, _| {
417                Expr::Builtin(BuiltinOp::Mod, vec![l, r])
418            }),
419            infix(left(8), just(Token::ModKw), |l, _, r, _| {
420                Expr::Builtin(BuiltinOp::Mod, vec![l, r])
421            }),
422            infix(left(8), just(Token::DivKw), |l, _, r, _| {
423                Expr::Builtin(BuiltinOp::Div, vec![l, r])
424            }),
425            // Precedence 9: unary prefix
426            prefix(9, just(Token::Minus), |_, rhs, _| {
427                Expr::Builtin(BuiltinOp::Neg, vec![rhs])
428            }),
429            prefix(9, just(Token::Not), |_, rhs, _| {
430                Expr::Builtin(BuiltinOp::Not, vec![rhs])
431            }),
432        ));
433
434        // ── Compound expressions ────────────────────────────
435
436        // Lambda: \x y -> body
437        let lambda = just(Token::Backslash)
438            .ignore_then(
439                pattern
440                    .clone()
441                    .repeated()
442                    .at_least(1)
443                    .collect::<Vec<Pattern>>(),
444            )
445            .then_ignore(just(Token::Arrow))
446            .then(expr.clone())
447            .map(|(params, body): (Vec<Pattern>, Expr)| desugar_lambda(&params, body));
448
449        // Let binding
450        let let_bind = ident()
451            .then(pattern.clone().repeated().collect::<Vec<Pattern>>())
452            .then_ignore(just(Token::Eq))
453            .then(expr.clone())
454            .map(|((name, params), val): ((Arc<str>, Vec<Pattern>), Expr)| {
455                if params.is_empty() {
456                    (name, val)
457                } else {
458                    (name, desugar_lambda(&params, val))
459                }
460            });
461
462        let let_expr = just(Token::Let)
463            .ignore_then(layout_block(let_bind.clone()).or(let_bind.clone().map(|b| vec![b])))
464            .then_ignore(just(Token::In))
465            .then(expr.clone())
466            .map(|(binds, body)| desugar_let_binds(binds, body));
467
468        // If-then-else
469        let if_expr = just(Token::If)
470            .ignore_then(expr.clone())
471            .then_ignore(just(Token::Then))
472            .then(expr.clone())
473            .then_ignore(just(Token::Else))
474            .then(expr.clone())
475            .map(|((cond, then_branch), else_branch)| Expr::Match {
476                scrutinee: Box::new(cond),
477                arms: vec![
478                    (Pattern::Lit(Literal::Bool(true)), then_branch),
479                    (Pattern::Wildcard, else_branch),
480                ],
481            });
482
483        // Case-of
484        let case_arm = pattern
485            .clone()
486            .then_ignore(just(Token::Arrow))
487            .then(expr.clone());
488
489        let case_expr = just(Token::Case)
490            .ignore_then(expr.clone())
491            .then_ignore(just(Token::Of))
492            .then(layout_block(case_arm))
493            .map(|(scrutinee, arms)| Expr::Match {
494                scrutinee: Box::new(scrutinee),
495                arms,
496            });
497
498        // Do-notation
499        let do_stmt = choice((
500            ident()
501                .then_ignore(just(Token::LeftArrow))
502                .then(expr.clone())
503                .map(|(name, e): (Arc<str>, Expr)| DoStmt::Bind(name, e)),
504            just(Token::Let)
505                .ignore_then(let_bind.clone())
506                .map(|(name, val)| DoStmt::Let(name, val)),
507            expr.clone().map(DoStmt::Expr),
508        ));
509
510        let do_expr = just(Token::Do)
511            .ignore_then(layout_block(do_stmt))
512            .map(desugar_do);
513
514        // ── Combine all expression forms ────────────────────
515
516        let full_expr = choice((do_expr, let_expr, if_expr, case_expr, lambda, pratt));
517
518        // Where clause as postfix
519        let where_bind = ident()
520            .then(pattern.repeated().collect::<Vec<Pattern>>())
521            .then_ignore(just(Token::Eq))
522            .then(expr.clone())
523            .map(|((name, params), val): ((Arc<str>, Vec<Pattern>), Expr)| {
524                if params.is_empty() {
525                    (name, val)
526                } else {
527                    (name, desugar_lambda(&params, val))
528                }
529            });
530
531        let where_clause = just(Token::Where)
532            .ignore_then(layout_block(where_bind.clone()).or(where_bind.map(|b| vec![b])));
533
534        full_expr
535            .then(where_clause.or_not())
536            .map(|(body, where_binds)| match where_binds {
537                Some(binds) => desugar_let_binds(binds, body),
538                None => body,
539            })
540    })
541}
542
543// ── Helper types ────────────────────────────────────────────────────
544
545/// Postfix operation.
546#[derive(Debug, Clone)]
547enum PostfixOp {
548    /// `.field`
549    Field(Arc<str>),
550    /// `->edge`
551    Edge(Arc<str>),
552}
553
554/// List comprehension qualifier.
555#[derive(Debug, Clone)]
556enum Qual {
557    /// `x <- xs`
558    Generator(Arc<str>, Expr),
559    /// Predicate.
560    Guard(Expr),
561}
562
563/// Do-notation statement.
564#[derive(Debug, Clone)]
565enum DoStmt {
566    /// `x <- e`
567    Bind(Arc<str>, Expr),
568    /// `let x = e`
569    Let(Arc<str>, Expr),
570    /// Bare expression.
571    Expr(Expr),
572}
573
574// ── Desugaring helpers ──────────────────────────────────────────────
575
576/// Desugar `\p1 p2 ... -> body` into nested lambdas.
577fn desugar_lambda(params: &[Pattern], body: Expr) -> Expr {
578    params.iter().rev().fold(body, |acc, pat| match pat {
579        Pattern::Var(name) => Expr::Lam(name.clone(), Box::new(acc)),
580        Pattern::Wildcard => Expr::Lam(Arc::from("_"), Box::new(acc)),
581        other => {
582            let fresh: Arc<str> = Arc::from("_arg");
583            Expr::Lam(
584                fresh.clone(),
585                Box::new(Expr::Match {
586                    scrutinee: Box::new(Expr::Var(fresh)),
587                    arms: vec![(other.clone(), acc)],
588                }),
589            )
590        }
591    })
592}
593
594/// Desugar `let a = e1; b = e2 in body` into nested `Let`.
595fn desugar_let_binds(binds: Vec<(Arc<str>, Expr)>, body: Expr) -> Expr {
596    binds
597        .into_iter()
598        .rev()
599        .fold(body, |acc, (name, val)| Expr::Let {
600            name,
601            value: Box::new(val),
602            body: Box::new(acc),
603        })
604}
605
606/// Desugar list comprehension `[e | quals]` into `flatMap`/guard.
607fn desugar_comprehension(body: Expr, quals: &[Qual]) -> Expr {
608    quals
609        .iter()
610        .rev()
611        .fold(Expr::List(vec![body]), |acc, qual| match qual {
612            Qual::Generator(name, source) => Expr::Builtin(
613                BuiltinOp::FlatMap,
614                vec![source.clone(), Expr::Lam(name.clone(), Box::new(acc))],
615            ),
616            Qual::Guard(pred) => Expr::Match {
617                scrutinee: Box::new(pred.clone()),
618                arms: vec![
619                    (Pattern::Lit(Literal::Bool(true)), acc),
620                    (Pattern::Wildcard, Expr::List(vec![])),
621                ],
622            },
623        })
624}
625
626/// Desugar do-notation into nested `flatMap`/`let`.
627fn desugar_do(stmts: Vec<DoStmt>) -> Expr {
628    if stmts.is_empty() {
629        return Expr::List(vec![]);
630    }
631    let mut iter = stmts.into_iter().rev();
632    // Safety: we checked `is_empty()` above, so `next()` always returns `Some`.
633    let Some(last) = iter.next() else {
634        return Expr::List(vec![]);
635    };
636    let init = match last {
637        DoStmt::Expr(e) | DoStmt::Bind(_, e) => e,
638        DoStmt::Let(name, val) => Expr::Let {
639            name,
640            value: Box::new(val),
641            body: Box::new(Expr::List(vec![])),
642        },
643    };
644    iter.fold(init, |acc, stmt| match stmt {
645        DoStmt::Bind(name, source) => Expr::Builtin(
646            BuiltinOp::FlatMap,
647            vec![source, Expr::Lam(name, Box::new(acc))],
648        ),
649        DoStmt::Let(name, val) => Expr::Let {
650            name,
651            value: Box::new(val),
652            body: Box::new(acc),
653        },
654        DoStmt::Expr(e) => Expr::Builtin(
655            BuiltinOp::FlatMap,
656            vec![e, Expr::Lam(Arc::from("_"), Box::new(acc))],
657        ),
658    })
659}
660
661/// Resolve function application, detecting builtin names.
662fn resolve_application(func: Expr, arg: Expr) -> Expr {
663    match &func {
664        Expr::Var(name) => {
665            if let Some(op) = resolve_builtin(name) {
666                Expr::Builtin(op, vec![arg])
667            } else {
668                Expr::App(Box::new(func), Box::new(arg))
669            }
670        }
671        Expr::Builtin(op, args) if args.len() < op.arity() => {
672            let mut new_args = args.clone();
673            new_args.push(arg);
674            Expr::Builtin(*op, new_args)
675        }
676        _ => Expr::App(Box::new(func), Box::new(arg)),
677    }
678}
679
680#[cfg(test)]
681mod tests {
682    use super::*;
683    use crate::tokenize;
684
685    fn parse_ok(input: &str) -> Expr {
686        let tokens = tokenize(input).unwrap_or_else(|e| panic!("lex failed: {e}"));
687        parse(&tokens).unwrap_or_else(|e| panic!("parse failed: {e:?}"))
688    }
689
690    #[test]
691    fn parse_literal_int() {
692        assert_eq!(parse_ok("42"), Expr::Lit(Literal::Int(42)));
693    }
694
695    #[test]
696    fn parse_literal_string() {
697        assert_eq!(
698            parse_ok(r#""hello""#),
699            Expr::Lit(Literal::Str("hello".into()))
700        );
701    }
702
703    #[test]
704    fn parse_literal_bool() {
705        assert_eq!(parse_ok("True"), Expr::Lit(Literal::Bool(true)));
706        assert_eq!(parse_ok("False"), Expr::Lit(Literal::Bool(false)));
707    }
708
709    #[test]
710    fn parse_nothing() {
711        assert_eq!(parse_ok("Nothing"), Expr::Lit(Literal::Null));
712    }
713
714    #[test]
715    fn parse_variable() {
716        assert_eq!(parse_ok("x"), Expr::Var(Arc::from("x")));
717    }
718
719    #[test]
720    fn parse_arithmetic() {
721        assert_eq!(
722            parse_ok("1 + 2"),
723            Expr::Builtin(
724                BuiltinOp::Add,
725                vec![Expr::Lit(Literal::Int(1)), Expr::Lit(Literal::Int(2))]
726            )
727        );
728    }
729
730    #[test]
731    fn parse_precedence() {
732        assert_eq!(
733            parse_ok("1 + 2 * 3"),
734            Expr::Builtin(
735                BuiltinOp::Add,
736                vec![
737                    Expr::Lit(Literal::Int(1)),
738                    Expr::Builtin(
739                        BuiltinOp::Mul,
740                        vec![Expr::Lit(Literal::Int(2)), Expr::Lit(Literal::Int(3))]
741                    ),
742                ]
743            )
744        );
745    }
746
747    #[test]
748    fn parse_comparison() {
749        assert_eq!(
750            parse_ok("x == 1"),
751            Expr::Builtin(
752                BuiltinOp::Eq,
753                vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
754            )
755        );
756    }
757
758    #[test]
759    fn parse_logical() {
760        assert_eq!(
761            parse_ok("a && b || c"),
762            Expr::Builtin(
763                BuiltinOp::Or,
764                vec![
765                    Expr::Builtin(
766                        BuiltinOp::And,
767                        vec![Expr::Var(Arc::from("a")), Expr::Var(Arc::from("b"))]
768                    ),
769                    Expr::Var(Arc::from("c")),
770                ]
771            )
772        );
773    }
774
775    #[test]
776    fn parse_negation() {
777        assert_eq!(
778            parse_ok("-x"),
779            Expr::Builtin(BuiltinOp::Neg, vec![Expr::Var(Arc::from("x"))])
780        );
781    }
782
783    #[test]
784    fn parse_not() {
785        assert_eq!(
786            parse_ok("not True"),
787            Expr::Builtin(BuiltinOp::Not, vec![Expr::Lit(Literal::Bool(true))])
788        );
789    }
790
791    #[test]
792    fn parse_field_access() {
793        assert_eq!(
794            parse_ok("x.name"),
795            Expr::Field(Box::new(Expr::Var(Arc::from("x"))), Arc::from("name"))
796        );
797    }
798
799    #[test]
800    fn parse_edge_traversal() {
801        assert_eq!(
802            parse_ok("doc -> layers"),
803            Expr::Builtin(
804                BuiltinOp::Edge,
805                vec![
806                    Expr::Var(Arc::from("doc")),
807                    Expr::Lit(Literal::Str("layers".into())),
808                ]
809            )
810        );
811    }
812
813    #[test]
814    fn parse_lambda() {
815        assert_eq!(
816            parse_ok("\\x -> x + 1"),
817            Expr::Lam(
818                Arc::from("x"),
819                Box::new(Expr::Builtin(
820                    BuiltinOp::Add,
821                    vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
822                ))
823            )
824        );
825    }
826
827    #[test]
828    fn parse_multi_param_lambda() {
829        let e = parse_ok("\\x y -> x + y");
830        match &e {
831            Expr::Lam(x, inner) => {
832                assert_eq!(&**x, "x");
833                assert!(matches!(&**inner, Expr::Lam(y, _) if &**y == "y"));
834            }
835            _ => panic!("expected nested Lam, got {e:?}"),
836        }
837    }
838
839    #[test]
840    fn parse_let_in() {
841        assert_eq!(
842            parse_ok("let x = 1 in x + 1"),
843            Expr::Let {
844                name: Arc::from("x"),
845                value: Box::new(Expr::Lit(Literal::Int(1))),
846                body: Box::new(Expr::Builtin(
847                    BuiltinOp::Add,
848                    vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
849                )),
850            }
851        );
852    }
853
854    #[test]
855    fn parse_if_then_else() {
856        let e = parse_ok("if True then 1 else 0");
857        assert!(matches!(e, Expr::Match { .. }));
858    }
859
860    #[test]
861    fn parse_case_of() {
862        let e = parse_ok("case x of\n  True -> 1\n  False -> 0");
863        match e {
864            Expr::Match { arms, .. } => assert_eq!(arms.len(), 2),
865            _ => panic!("expected Match"),
866        }
867    }
868
869    #[test]
870    fn parse_list() {
871        assert_eq!(
872            parse_ok("[1, 2, 3]"),
873            Expr::List(vec![
874                Expr::Lit(Literal::Int(1)),
875                Expr::Lit(Literal::Int(2)),
876                Expr::Lit(Literal::Int(3)),
877            ])
878        );
879    }
880
881    #[test]
882    fn parse_empty_list() {
883        assert_eq!(parse_ok("[]"), Expr::List(vec![]));
884    }
885
886    #[test]
887    fn parse_record() {
888        assert_eq!(
889            parse_ok("{ name = x, age = 30 }"),
890            Expr::Record(vec![
891                (Arc::from("name"), Expr::Var(Arc::from("x"))),
892                (Arc::from("age"), Expr::Lit(Literal::Int(30))),
893            ])
894        );
895    }
896
897    #[test]
898    fn parse_record_punning() {
899        assert_eq!(
900            parse_ok("{ name, age }"),
901            Expr::Record(vec![
902                (Arc::from("name"), Expr::Var(Arc::from("name"))),
903                (Arc::from("age"), Expr::Var(Arc::from("age"))),
904            ])
905        );
906    }
907
908    #[test]
909    fn parse_builtin_application() {
910        assert_eq!(
911            parse_ok("map f xs"),
912            Expr::Builtin(
913                BuiltinOp::Map,
914                vec![Expr::Var(Arc::from("f")), Expr::Var(Arc::from("xs"))]
915            )
916        );
917    }
918
919    #[test]
920    fn parse_string_concat() {
921        assert_eq!(
922            parse_ok(r#""hello" ++ " world""#),
923            Expr::Builtin(
924                BuiltinOp::Concat,
925                vec![
926                    Expr::Lit(Literal::Str("hello".into())),
927                    Expr::Lit(Literal::Str(" world".into())),
928                ]
929            )
930        );
931    }
932
933    #[test]
934    fn parse_pipe() {
935        assert_eq!(
936            parse_ok("x & f"),
937            Expr::App(
938                Box::new(Expr::Var(Arc::from("f"))),
939                Box::new(Expr::Var(Arc::from("x"))),
940            )
941        );
942    }
943
944    #[test]
945    fn parse_chained_field_access() {
946        assert_eq!(
947            parse_ok("x.a.b"),
948            Expr::Field(
949                Box::new(Expr::Field(
950                    Box::new(Expr::Var(Arc::from("x"))),
951                    Arc::from("a"),
952                )),
953                Arc::from("b"),
954            )
955        );
956    }
957
958    #[test]
959    fn parse_comprehension() {
960        let e = parse_ok("[ x + 1 | x <- xs ]");
961        assert!(matches!(e, Expr::Builtin(BuiltinOp::FlatMap, _)));
962    }
963}