Skip to main content

elly_core/
parse.rs

1//! Parsing: Muon tree → Elly AST.
2//!
3//! A program is a single chain (`<expr>`). Chains are split on the first `&`-prefixed
4//! item into an application spine and a trailing abstraction. Comments are dropped.
5//!
6//! Patterns are faithful AST: `&`-headers and `let` LHSs parse into [`Pattern`] nodes
7//! matched directly by the evaluator. Abstractions are `Expr::Abs(Rc<Lambda>)`, where
8//! `&p0 &p1 …` runs collect into multi-parameter [`Lambda`]s. Application spines and
9//! spread calls flatten into `Expr::App(Box<[Expr]>)`.
10
11use alloc::boxed::Box;
12use alloc::rc::Rc;
13use alloc::string::String;
14use alloc::vec::Vec;
15
16use muon::{Chain, Item, Seq};
17use num_bigint::BigInt;
18
19use crate::ast::{Captures, Expr, HomeLeaf, Lambda, MapKey, Pattern, ProtoRef, Text};
20use crate::builtins::Builtin;
21pub use crate::module::ModuleSyntax;
22use crate::module::{ImportDecl, ImportSpec, ModuleData};
23
24/// Copy a source string slice into an owned text leaf.
25/// The AST uses owned `Text` rather than borrowing from the Muon source.
26fn text(s: &str) -> Text {
27    Text::from(s)
28}
29
30// TODO: use thiserror?
31/// A parse failure: the input is a valid Muon tree but not a well-formed Elly
32/// program in this subset.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum ParseError {
35    /// The top-level sequence held no chain (nothing to evaluate).
36    EmptyProgram,
37    /// The top-level sequence held more than one chain; top-level sequencing
38    /// is deferred (a program is a single expression).
39    MultipleExpressions,
40    /// A chain reduced to nothing (e.g. only comments).
41    EmptyChain,
42    /// A `&` binder with no body — `&x` alone is ill-formed (yet).
43    AbsWithoutBody,
44    /// A `&` prefix on something that is not a well-formed binder header (see
45    /// `BadPattern` for the pattern content itself).
46    BadBinder,
47    /// A binder appeared where an atomic expression was expected. Not reachable
48    /// via the split logic, kept so parsing never panics.
49    UnexpectedBinder,
50    /// A `.` sigil on something that is not a single name/number segment (e.g.
51    /// `.(…)`, `.&x`). `.foo` / `.0` are symbols; the record sugar `.(…)` and
52    /// other prefixed items are deferred / ill-formed.
53    BadSymbol,
54    /// A bare `.` used as an atom (`f . g`, a lone `.`). At the Muon layer this
55    /// is a `<punct>`; Elly reserves it for a future `.` / `|>` application
56    /// combinator, which is deferred.
57    CombinatorDeferred,
58    /// `_` used as a reference. `_` is the discard pattern (a binder that binds
59    /// nothing), never a name that can be read back.
60    DiscardReference,
61    /// A `&`-binder tried to introduce a name starting with `__`. Such names are
62    /// reserved for builtins / special forms and may be referenced but not bound.
63    ReservedName,
64    /// A `<sym>` that begins like a number but is not a valid integer literal
65    /// (`1a`, `0xZZ`, `1__2`, `0x`). Since a `<name>` may not start with a digit,
66    /// a digit-leading sym must be a well-formed `<int>` or it is an error.
67    MalformedNumber,
68    /// A stray `:` / `=` (or other) `<punct>` item at atom position. Muon parses
69    /// this punctuation, but outside a `let` binding (`=`) or a map entry (`:`)
70    /// it has no atomic reading. (A `#{…}` map literal and a `#[…]` list value are
71    /// their own atoms; the bare `{…}` / `[…]` forms are rejected, not deferred.)
72    PunctAtAtom,
73    /// A map entry (`<braces>` chain) whose key is missing or malformed: no `:`
74    /// after the key, or a key item that is not a bare atom, a `.`-symbol, a `(…)`
75    /// computed key, or a `#[…]` / `#{…}` literal key (`{ : 1 }`, `{ foo }`).
76    MalformedMapKey,
77    /// A `let` not followed by a binder group — `let 1 e`. An *empty* group is not
78    /// an error: `let () e` binds nothing and is `e` (see `parse_let`).
79    LetMissingBinders,
80    /// A `let (…)` with nothing after the binder group; the body is required.
81    LetMissingBody,
82    /// A `recur` not followed by a binder group — `recur 1 e`. As with `let`, an
83    /// empty group is not an error: `recur () e` is `e`.
84    RecurMissingBinders,
85    /// A `recur (…)` with nothing after the binder group; the body is required.
86    RecurMissingBody,
87    /// A `recur` binding whose name is already in scope. The group's bindings are
88    /// simultaneous, so inside it the name would mean the group's own binding and
89    /// never the outer one — the opposite of what the identical `let` line means.
90    /// Detected by the resolution pass (`resolve.rs`); carries the offending name.
91    RecurShadowsOuter(Text),
92    /// A binding inside a `let (…)` group is not `<pattern> "=" <expr>`: missing
93    /// or repeated top-level `=`, an empty pattern, or an empty value.
94    MalformedBinding,
95    /// A keyword (`let`, `with`) used as a reference or a binder. Keywords are
96    /// syntax, not names: they may be neither read nor bound.
97    ReservedKeyword,
98    /// A malformed pattern: an empty pattern, a comma-grouped `(a, b)` used as a
99    /// single pattern, a misplaced `...rest`, or any item with no pattern reading.
100    BadPattern,
101    /// A comparand after `=` / `<` / `>` that is not a single **atom** (a name or
102    /// a literal). A pattern is a closed grammar — `= f x`, `< (f x)`, `= [a]`
103    /// have no reading; write `= x`, `= 5`, or `.foo`.
104    ComparandNotAtom,
105    /// A bare **matching literal** used as an entire `&`-header or `let` binding
106    /// LHS: a number (`&42`), a symbol (`&.foo`), or a string (`&"foo"`).
107    /// Parenthesize it — `&(42)`, `&(.foo)`, `&("foo")`, `let ((42) = v)` — so the
108    /// "match this literal" intent is explicit (a bare literal reads as a value, or
109    /// as an intended binder). A literal *nested* in `[…]` / `{…}` / `(…)` / an
110    /// or-pattern needs no parens.
111    BareLiteralHeader,
112    /// A `<str>` literal with an **unpaired UTF-16 surrogate** escape (`"\uD83D"`,
113    /// a high or low `\uXXXX` not completing a pair). Muon accepts it lexically,
114    /// but UTF-8 cannot represent it, so Elly's decode rejects it at parse.
115    LoneSurrogate,
116    /// A bare top-level `|` in a `let` binding's LHS: `let ((x=.a) | (x=.b) = v)`.
117    /// Wrap the or-pattern — `let (((x=.a) | (x=.b)) = v) e` — so it does not
118    /// visually compete with the binding's own `=`.
119    LetOrUnparenthesized,
120    /// An `as <Type>` whose `<Type>` is not one of the closed kinds
121    /// `Int Sym List Map Fun`.
122    UnknownType,
123    /// An or-pattern `(p | q)` whose arms bind different sets of names.
124    OrBindersMismatch,
125    /// A `|>` pipe combinator with a missing operand — `|> f`, `x |>`, or
126    /// `x |> |> f`. Both sides of `|>` must be non-empty.
127    MalformedPipe,
128    /// A name reference that resolves to no enclosing `&`-binding — a free
129    /// variable. Detected by the resolution pass (`resolve.rs`) after parsing,
130    /// so it is a *static* error: the program is rejected before it runs, rather
131    /// than raising `.unbound_name` at eval. Carries the offending name (spans
132    /// are still a TODO). `__`-names never reach here — they are resolved to
133    /// [`Expr::Builtin`](crate::Expr) at parse.
134    UnboundName(Text),
135    /// A module (`parse_module`) binding whose left-hand side is not a single bare
136    /// name: a destructuring pattern, a literal, a discard, or a digit-leading
137    /// token. Module top-level keys are plain names in v0. A `__…` or keyword LHS
138    /// reports [`ParseError::ReservedName`] / [`ParseError::ReservedKeyword`] instead.
139    BadModuleItemName,
140    /// A module defines the same item name twice, or an `import`'s local name is
141    /// also an item name. A name means one thing inside a module, so both are
142    /// rejected here. The `Foo = import "spec"` form is the one place a name is
143    /// both an import and an item: it is a single declaration, and it makes them
144    /// the same thing.
145    DuplicateModuleItem,
146    /// An `import` that is neither `import "<spec>" as <Name>` nor
147    /// `<Name> = import "<spec>"`: a missing `as`, a spec that is not a string
148    /// literal, or trailing items. The spec is a literal in a fixed position —
149    /// which is what makes a module's imports a static, auditable list — so there
150    /// is nothing to compute here.
151    MalformedImport,
152    /// An `import` somewhere other than a module's top level — today, inside a
153    /// [`recur`](crate::Expr::Recur) group. An import is a frame slot the const
154    /// stage fills before the module exists, so it can only be written in a
155    /// module's own source. Written anywhere else, `import` is an ordinary keyword
156    /// and reports [`ParseError::ReservedKeyword`].
157    MisplacedImport,
158    /// A `const` declaration that is neither `const <name>`, `const (<name> …)`
159    /// nor `<name> = const`: a declaration that is not a bare name, a group whose
160    /// chains are not bare names, or a **payload** — `const x = 1`, `x = const 1`.
161    /// The payload forms are reserved for const-time expressions and rejected
162    /// until those exist (see `docs/done/2026-08-13_elly-modules-iotas.md`).
163    MalformedConst,
164    /// A `const` somewhere other than a module's top level — today, inside a
165    /// [`recur`](crate::Expr::Recur) group. A group is built while a program runs
166    /// and has no instance of its own to mint against, so an iota can only be
167    /// declared in a module's source. Written anywhere else, `const` is an
168    /// ordinary keyword and reports [`ParseError::ReservedKeyword`].
169    MisplacedConst,
170    /// A binder — a pattern binder, a `let` binding, a `recur` binding — taking a
171    /// name the enclosing module declares as an **import**. A declaration's name
172    /// means one thing over the whole module, so it may not be shadowed from
173    /// inside (see `docs/done/2026-08-13_elly-modules-iotas.md`). Carries the name.
174    ShadowsImport(Text),
175    /// A binder taking a name the enclosing module declares as a `const`
176    /// [iota](ParseError::ShadowsImport). Carries the name.
177    ShadowsIota(Text),
178    /// A `local` declaration that is neither `local <name> = <expr>` nor the
179    /// group `local (<name> = <expr> …)`: a left-hand side that is not a bare
180    /// name, a group chain that is not a binding, or a missing body. The
181    /// head-with-arguments form `local f x = <expr>` lands here too — it is a
182    /// template moditem, left open (see `docs/done/2026-08-13_elly-modules-local.md`).
183    MalformedLocal,
184    /// A `local` somewhere other than a module's top level — today, inside a
185    /// [`recur`](crate::Expr::Recur) group, where every binding is private
186    /// already, so the keyword would say nothing. Written anywhere else, `local`
187    /// is an ordinary keyword and reports [`ParseError::ReservedKeyword`].
188    MisplacedLocal,
189    /// A binder taking a name the enclosing module declares as a
190    /// [`local`](ParseError::ShadowsImport). Carries the name.
191    ShadowsLocal(Text),
192    /// A binder taking a name the enclosing module — or an enclosing
193    /// [`recur`](crate::Expr::Recur) group — declares as an **item**. Carries the
194    /// name. This is the direction opposite to
195    /// [`RecurShadowsOuter`](ParseError::RecurShadowsOuter), which stops a group
196    /// binding from shadowing a name already in scope; together they make a
197    /// declared name unshadowable from either side.
198    ShadowsItem(Text),
199    /// A `__` name the language does not define, in either position it can be
200    /// written: a module top-level left-hand side (`__mian = …`) or a reference
201    /// in an expression (`__mian`). The `__` namespace belongs to the language —
202    /// a source may write the builtins and the **reserved moditems** it defines
203    /// and nothing else — so a misspelling fails here, naming the namespace,
204    /// rather than becoming an item nobody reads or a name nothing binds.
205    /// Carries the name as written.
206    UnknownReserved(Text),
207    /// A module declaring the same reserved moditem twice (two `__main`s). A
208    /// reserved moditem is a single slot, not a table, so there is no second one
209    /// to hold. Carries the name.
210    DuplicateReserved(Text),
211    /// A reserved moditem somewhere other than a module's top level — today,
212    /// inside a [`recur`](crate::Expr::Recur) group. A group is an item table,
213    /// not a module source, and nothing would ever read the slot. Written
214    /// anywhere else, `__main` is an ordinary `__` name and reports
215    /// [`UnknownReserved`](ParseError::UnknownReserved).
216    MisplacedReserved,
217    /// A [home-module leaf](crate::HomeLeaf) — `Self`, `#Self(…)`, `x.Self`, or
218    /// the `Self <pat>` pattern — outside a module body. Each reads the module
219    /// its body is written in, and there is none: the term is not a module's, or
220    /// it is a [`recur`](crate::Expr::Recur) group in a program that is not a
221    /// module. One test covers both, since a group is not a module either.
222    /// Carries the name — `Self`, the one word every form is spelled with.
223    OutsideModule(Text),
224    /// An `as <Proto>` pattern whose reference is a `__` name that is not a
225    /// **prototype**: a builtin that is no module at all (`as __eq`), or `__Err`,
226    /// which is a namespace of operations that no value answers `__proto` with —
227    /// so the check could never hold. Rejected here rather than compiled into a
228    /// pattern that always refutes. Carries the name.
229    NotAPrototype(Text),
230    /// A [`&`](muon::Item::Prefixed)-headed **run** (`&x.0`, `&(a, b).0`) — a
231    /// binder glued to a following atom with no whitespace. A binder is one
232    /// prefixed atom, so the glued tail has no reading; write it spaced
233    /// (`&x .0`) to apply, or restructure. See `docs/todo/elly-parse-runs.md` §C.
234    BinderRunGlued,
235    /// An `<int>`-then-`.<digits>` run — the float-like run `3.14` (Muon groups
236    /// the two atoms, see `docs/done/2026-09-08_muon-runs.md`). Float literals do
237    /// not exist yet; this holds the notation open for them (a real float is a
238    /// later `Num` extension) while giving a clear error now instead of the
239    /// runtime `.not_applicable` a spaced `3 .14` projection raises.
240    FloatLiteralUnsupported,
241    /// A [`#`](muon::Item::Prefixed)-prefixed form other than the three that
242    /// read: `#[…]` (a list), `#{…}` (a map), and `#Self(…)` / `#Self …`
243    /// (construction). The `#` sigil marks a **literal notation**, and the
244    /// remaining shapes — `#<sym>`, `#<str>`, `#(…)`, `#.name` — are reserved
245    /// for future literal kinds with no reading yet. A bare `#Self` lands here
246    /// as well: construction is a form, not a value, so it may only head a
247    /// spine (see `parse_app_spine`). See `docs/todo/elly-syntax-v1.md` §2.
248    ReservedLiteralForm,
249    /// A bare `[…]` where a list literal or list pattern is written. After the
250    /// reshuffle (phase 3), the list literal uses the `#`-prefixed spelling
251    /// exclusively: write `#[…]`. See `docs/todo/elly-syntax-v1.md` § 3–5.
252    BareListLiteral,
253    /// A bare `{…}` carrying a `:`-clause where a map literal or map pattern is
254    /// written. After the reshuffle, the map literal uses the `#`-prefixed
255    /// spelling exclusively: write `#{…}`. A `:`-clause has no reading in a block
256    /// (nothing legal in a block starts `<key> :`), so a brace group with one is
257    /// this error in every position; a brace group *without* a `:`-clause is a
258    /// block (step 6), and the empty `{}` is the unit value.
259    /// See `docs/todo/elly-syntax-v1.md` § 3–5.
260    BareMapLiteral,
261    /// A block `{…}` whose final clause is a binding (`{ …, x = v }`). A block's
262    /// value is its last clause's value and its bindings do not escape, so a
263    /// trailing binding would bind a name nothing can read and leave no value to
264    /// return — as a `let` with no body does. End the block with an expression.
265    /// See `docs/todo/elly-syntax-v1.md` § 5 (review) and step 6.
266    BlockTrailingBinding,
267    /// A `case` with no `{…}` arm block — `case x` or `case` alone, or a trailing
268    /// item that is not a brace group. The block is where the clauses live, so a
269    /// `case` without one has nothing to match against and no reading.
270    CaseMissingArms,
271    /// A `case` subject that is more than one item — `case f x { … }`. The subject
272    /// is exactly one item so the arm block's `{…}` is unambiguous; group a
273    /// compound subject (`case (f x) { … }`) or pipe it in (`f x |> case { … }`).
274    CaseSubjectExtra,
275    /// A `case` arm whose chain is not headed by `&` — a clause that is not a
276    /// binder. Every arm is `& <pattern> … <body>`; a bare expression is not one.
277    ArmNotBinder,
278    /// A `case` arm with a pattern (and any guards) but no body after them —
279    /// `& x` or `& x when (c)` alone. An arm's body is required.
280    ArmMissingBody,
281    /// A `when` guard with no `(…)` condition group — `when` at the end of a
282    /// header, or `when` followed by something other than a parenthesized group.
283    WhenMissingCond,
284}
285
286/// Parse a program (single top-level chain) to an expression.
287pub fn parse_program<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
288    match chain_slices(seq).as_slice() {
289        [] => Err(ParseError::EmptyProgram),
290        [items] => parse_items(items),
291        _ => Err(ParseError::MultipleExpressions),
292    }
293}
294
295/// Parse a module source's top-level sequence into its declarations.
296///
297/// Items are single bare names in v0. Imports are mapped to leading frame slots.
298/// Bodies are unresolved here (done in [`crate::compile_module`]).
299pub fn parse_module<'a>(seq: &Seq<'a>) -> Result<ModuleSyntax, ParseError> {
300    let mut items: Vec<(Text, Expr)> = Vec::new();
301    let mut imports: Vec<ImportDecl> = Vec::new();
302    let mut iotas: Vec<Text> = Vec::new();
303    let mut locals: Vec<(Text, Expr)> = Vec::new();
304    let mut main: Option<Expr> = None;
305    let mut reexported_imports: Vec<Text> = Vec::new();
306    let mut reexported_iotas: Vec<Text> = Vec::new();
307    for ci in chain_slices(seq) {
308        let split = ci.iter().position(|it| matches!(it, Item::Punct("=")));
309        if split.is_none() && head_keyword(ci[0]) == Some("import") {
310            let (name, spec) = parse_private_import(&ci)?;
311            push_import(&mut imports, name, spec)?;
312            continue;
313        }
314        if split.is_none() && head_keyword(ci[0]) == Some("const") {
315            for name in parse_const_decl(&ci)? {
316                push_iota(&mut iotas, name)?;
317            }
318            continue;
319        }
320        if split.is_none() && head_keyword(ci[0]) == Some("local") {
321            for (name, body) in parse_local_group(&ci)? {
322                push_local(&mut locals, name, body)?;
323            }
324            continue;
325        }
326        let eq = split.ok_or(ParseError::MalformedBinding)?;
327        if ci[eq + 1..].iter().any(|it| matches!(it, Item::Punct("="))) {
328            return Err(ParseError::MalformedBinding);
329        }
330        let (head, tail) = (&ci[..eq], &ci[eq + 1..]);
331        if head.is_empty() || tail.is_empty() {
332            return Err(ParseError::MalformedBinding);
333        }
334        if head.len() > 1 && head_keyword(head[0]) == Some("const") {
335            return Err(ParseError::MalformedConst);
336        }
337        if head.len() > 1 && head_keyword(head[0]) == Some("local") {
338            let name = plain_name(&head[1..], ParseError::MalformedLocal)?;
339            push_local(&mut locals, name, parse_items(tail)?)?;
340            continue;
341        }
342        if let [Item::Sym(s)] = head {
343            if let Some(reserved) = reserved_moditem(s) {
344                if main.is_some() {
345                    return Err(ParseError::DuplicateReserved(text(reserved)));
346                }
347                main = Some(parse_items(tail)?);
348                continue;
349            }
350            if s.starts_with("__") {
351                return Err(ParseError::UnknownReserved(text(s)));
352            }
353        }
354        let name = plain_name(head, ParseError::BadModuleItemName)?;
355        if items.iter().any(|(n, _)| n.as_str() == name.as_str()) {
356            return Err(ParseError::DuplicateModuleItem);
357        }
358        if head_keyword(tail[0]) == Some("import") {
359            let spec = parse_import_spec(tail)?;
360            push_import(&mut imports, name.clone(), spec)?;
361            reexported_imports.push(name.clone());
362            items.push((name.clone(), Expr::Name(name)));
363            continue;
364        }
365        if head_keyword(tail[0]) == Some("const") {
366            if tail.len() != 1 {
367                return Err(ParseError::MalformedConst);
368            }
369            push_iota(&mut iotas, name.clone())?;
370            reexported_iotas.push(name.clone());
371            items.push((name.clone(), Expr::Name(name)));
372            continue;
373        }
374        let body = parse_items(tail)?;
375        items.push((name, body));
376    }
377    for imp in &imports {
378        if iotas.iter().any(|n| n.as_str() == imp.name.as_str()) {
379            return Err(ParseError::DuplicateModuleItem);
380        }
381        if reexported_imports
382            .iter()
383            .any(|n| n.as_str() == imp.name.as_str())
384        {
385            continue;
386        }
387        if items.iter().any(|(n, _)| n.as_str() == imp.name.as_str()) {
388            return Err(ParseError::DuplicateModuleItem);
389        }
390    }
391    for iota in &iotas {
392        if reexported_iotas.iter().any(|n| n.as_str() == iota.as_str()) {
393            continue;
394        }
395        if items.iter().any(|(n, _)| n.as_str() == iota.as_str()) {
396            return Err(ParseError::DuplicateModuleItem);
397        }
398    }
399    for (name, _) in &locals {
400        let taken = items.iter().any(|(n, _)| n.as_str() == name.as_str())
401            || iotas.iter().any(|n| n.as_str() == name.as_str())
402            || imports.iter().any(|i| i.name.as_str() == name.as_str());
403        if taken {
404            return Err(ParseError::DuplicateModuleItem);
405        }
406    }
407    Ok(ModuleSyntax {
408        imports,
409        iotas,
410        items,
411        locals,
412        main,
413    })
414}
415
416/// The reserved moditems the language defines, as the names a module top-level
417/// left-hand side may spell in the `__` namespace. One so far; `__test` and
418/// `__doc` are anticipated, and each will be another slot on [`ModuleSyntax`]
419/// with a clause here (see `docs/done/2026-08-14_elly-run.md`).
420///
421/// Answers with the `'static` spelling so the caller reports the *defined* name
422/// rather than echoing the source, and `None` for a name that is not one — which
423/// the caller turns into [`ParseError::UnknownReserved`] rather than an item.
424fn reserved_moditem(name: &str) -> Option<&'static str> {
425    match name {
426        "__main" => Some("__main"),
427        _ => None,
428    }
429}
430
431/// Record one import, rejecting a name a previous import already took.
432fn push_import(
433    imports: &mut Vec<ImportDecl>,
434    name: Text,
435    spec: ImportSpec,
436) -> Result<(), ParseError> {
437    if imports.iter().any(|i| i.name.as_str() == name.as_str()) {
438        return Err(ParseError::DuplicateModuleItem);
439    }
440    imports.push(ImportDecl { name, spec });
441    Ok(())
442}
443
444/// Parse `import "<spec>" as <Name>` — the private form, whose whole chain is the
445/// declaration — to its name and spec. The keyword may be a bare sym or a glued
446/// run head (§D); in the run case only the spec glues into the run (`import"spec"`)
447/// while `as <Name>` stays in the chain after it, so the two are concatenated back
448/// into the one item stream the spaced form already is.
449fn parse_private_import<'a>(ci: &[&Item<'a>]) -> Result<(Text, ImportSpec), ParseError> {
450    // Drop the keyword head. When it is a glued run head (§D) the spec sits inside
451    // the run (`import"spec"`), but `as <Name>` is space-separated and so stays in
452    // the chain after the run — so the run's members and the chain tail concatenate
453    // into one `"spec" as Name` stream, the same one the spaced form is.
454    let rest: Vec<&Item<'a>> = match ci[0] {
455        Item::Run(members) => members[1..]
456            .iter()
457            .map(|it| it as &Item<'a>)
458            .chain(ci[1..].iter().copied())
459            .collect(),
460        _ => ci[1..].to_vec(),
461    };
462    match rest.as_slice() {
463        [Item::Str(spec), Item::Sym("as"), rest @ ..] if !rest.is_empty() => {
464            let name = plain_name(rest, ParseError::BadModuleItemName)?;
465            Ok((name, decode_str(spec)?))
466        }
467        _ => Err(ParseError::MalformedImport),
468    }
469}
470
471/// Record one iota, rejecting a name a previous `const` already declared.
472fn push_iota(iotas: &mut Vec<Text>, name: Text) -> Result<(), ParseError> {
473    if iotas.iter().any(|n| n.as_str() == name.as_str()) {
474        return Err(ParseError::DuplicateModuleItem);
475    }
476    iotas.push(name);
477    Ok(())
478}
479
480/// Parse a private `const` declaration — `const <name>`, or the group form
481/// `const (<name> …)` — to the names it declares.
482///
483/// The group is an ordinary `<parens>` wrapping a `<seq>`, so its names may be
484/// newline- or comma-separated and it may carry comments, exactly as a `let`
485/// group may. An **empty group declares nothing**, the same degenerate reading
486/// `let ()` and `recur ()` take.
487fn parse_const_decl<'a>(ci: &[&Item<'a>]) -> Result<Vec<Text>, ParseError> {
488    // The keyword may be a bare sym or a glued run head (§D); the declared names
489    // are read from what follows it (the run's members in the run case).
490    let rest: Vec<&Item<'a>> = match ci[0] {
491        Item::Run(members) => members[1..].iter().map(|it| it as &Item<'a>).collect(),
492        _ => ci[1..].to_vec(),
493    };
494    match rest.as_slice() {
495        [Item::Parens(seq), ..] => {
496            let mut names = Vec::new();
497            for c in chain_slices(seq) {
498                names.push(plain_name(&c, ParseError::MalformedConst)?);
499            }
500            Ok(names)
501        }
502        rest if !rest.is_empty() => Ok(alloc::vec![plain_name(rest, ParseError::MalformedConst)?]),
503        _ => Err(ParseError::MalformedConst),
504    }
505}
506
507/// Record one local, rejecting a name a previous `local` already took.
508fn push_local(locals: &mut Vec<(Text, Expr)>, name: Text, body: Expr) -> Result<(), ParseError> {
509    if locals.iter().any(|(n, _)| n.as_str() == name.as_str()) {
510        return Err(ParseError::DuplicateModuleItem);
511    }
512    locals.push((name, body));
513    Ok(())
514}
515
516/// Parse group form `local (<name> = <expr> …)` to its bindings.
517///
518/// The group is an ordinary `<parens>` wrapping a `<seq>`. An empty group
519/// declares nothing. The single form `local x = <expr>` is handled separately.
520fn parse_local_group<'a>(ci: &[&Item<'a>]) -> Result<Vec<(Text, Expr)>, ParseError> {
521    let seq = match ci[0] {
522        Item::Run(members) => {
523            let Some(Item::Parens(seq)) = members.get(1) else {
524                return Err(ParseError::MalformedLocal);
525            };
526            seq
527        }
528        _ => {
529            let Some(Item::Parens(seq)) = ci.get(1) else {
530                return Err(ParseError::MalformedLocal);
531            };
532            seq
533        }
534    };
535    let mut bindings = Vec::new();
536    for c in chain_slices(seq) {
537        let eq = c
538            .iter()
539            .position(|it| matches!(it, Item::Punct("=")))
540            .ok_or(ParseError::MalformedLocal)?;
541        let (head, tail) = (&c[..eq], &c[eq + 1..]);
542        if head.is_empty() || tail.is_empty() {
543            return Err(ParseError::MalformedLocal);
544        }
545        if tail.iter().any(|it| matches!(it, Item::Punct("="))) {
546            return Err(ParseError::MalformedLocal);
547        }
548        bindings.push((
549            plain_name(head, ParseError::MalformedLocal)?,
550            parse_items(tail)?,
551        ));
552    }
553    Ok(bindings)
554}
555
556/// Parse the `import "<spec>"` RHS of a re-exporting import to its spec.
557/// Handles both spaced and glued spellings.
558fn parse_import_spec<'a>(tail: &[&Item<'a>]) -> Result<ImportSpec, ParseError> {
559    let spec = match tail {
560        [_import, Item::Str(spec)] => spec,
561        [Item::Run(members)] => match members.as_slice() {
562            [_import, Item::Str(spec)] => spec,
563            _ => return Err(ParseError::MalformedImport),
564        },
565        _ => return Err(ParseError::MalformedImport),
566    };
567    decode_str(spec)
568}
569
570/// Parse a single plain name for a binding position.
571/// Must be exactly one `<sym>` that is not a keyword, `__` name, discard, or number.
572fn plain_name<'a>(items: &[&Item<'a>], bad: ParseError) -> Result<Text, ParseError> {
573    match items {
574        [Item::Sym(s)] if is_keyword(s) => Err(ParseError::ReservedKeyword),
575        [Item::Sym(s)] if s.starts_with("__") => Err(ParseError::ReservedName),
576        [Item::Sym(s)] if reads_as_number(s) || *s == "_" => Err(bad),
577        [Item::Sym(s)] => Ok(text(s)),
578        _ => Err(bad),
579    }
580}
581
582/// Check if a `<sym>` reads as a number (valid integer or digit-leading).
583fn reads_as_number(s: &str) -> bool {
584    parse_int(s).is_some() || s.as_bytes()[0].is_ascii_digit()
585}
586
587/// Parse a comment-free slice of items.
588fn parse_items<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
589    if items.is_empty() {
590        return Err(ParseError::EmptyChain);
591    }
592    // `|>` is the loosest operator: split on the last one to form a left-associative
593    // pipe chain over segments.
594    match items.iter().rposition(|it| matches!(it, Item::Punct("|>"))) {
595        None => parse_segment(items),
596        Some(k) => {
597            let (left, right) = (&items[..k], &items[k + 1..]);
598            if left.is_empty() || right.is_empty() {
599                return Err(ParseError::MalformedPipe);
600            }
601            let l = parse_items(left)?;
602            let r = parse_segment(right)?;
603            Ok(append_arg(r, l))
604        }
605    }
606}
607
608/// Parse one pipe segment (a `|>`-free slice).
609///
610/// This is an application spine whose trailing argument may be a `&`/`let`/`recur`
611/// binder form. Split points have lowest precedence within the segment.
612fn parse_segment<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
613    match items.iter().position(|it| is_split_point(it)) {
614        None => parse_app_spine(items),
615        Some(0) => match head_keyword(items[0]) {
616            Some("let") | Some("recur") => parse_let_or_recur(items[0], items),
617            Some("case") => parse_case(items),
618            _ => {
619                if items.len() == 1 {
620                    return Err(ParseError::AbsWithoutBody);
621                }
622                let inner = match items[0] {
623                    Item::Prefixed { sigil: '&', item } => item.as_ref(),
624                    _ => return Err(ParseError::UnexpectedBinder),
625                };
626                if matches!(items.get(1), Some(it) if is_when_kw(it))
627                    && !matches!(inner, Item::Parens(_))
628                {
629                    if is_bare_literal_header(inner) {
630                        return Err(ParseError::BareLiteralHeader);
631                    }
632                    let base = parse_pattern(&[inner])?;
633                    let (pat, rest) = peel_when(base, &items[1..])?;
634                    if rest.is_empty() {
635                        return Err(ParseError::AbsWithoutBody);
636                    }
637                    let body = parse_segment(rest)?;
638                    return Ok(abs(pat, body));
639                }
640                let body = parse_segment(&items[1..])?;
641                build_binder(inner, body)
642            }
643        },
644        Some(k) => {
645            let f = parse_segment(&items[..k])?;
646            let arg = parse_segment(&items[k..])?;
647            Ok(append_arg(f, arg))
648        }
649    }
650}
651
652/// Parse leading `let`/`recur` form, reading the binder group from a glued
653/// run or a bare symbol.
654fn parse_let_or_recur<'a>(head: &Item<'a>, items: &[&Item<'a>]) -> Result<Expr, ParseError> {
655    let is_recur = head_keyword(head) == Some("recur");
656    let missing = || {
657        if is_recur {
658            ParseError::RecurMissingBinders
659        } else {
660            ParseError::LetMissingBinders
661        }
662    };
663    let (group_item, body_items): (&Item<'_>, &[&Item<'_>]) = match head {
664        Item::Run(members) if members.len() == 2 => (&members[1], &items[1..]),
665        Item::Run(_) => return Err(missing()),
666        _ => (*items.get(1).ok_or_else(missing)?, &items[2..]),
667    };
668    let group = match group_item {
669        Item::Parens(seq) => seq,
670        _ => return Err(missing()),
671    };
672    if is_recur {
673        parse_recur_inner(group, body_items)
674    } else {
675        parse_let_inner(group, body_items)
676    }
677}
678
679/// Parse `let` body: fold group bindings right-to-left over `body_items`.
680///
681/// An empty group binds nothing (`let () e` is `e`).
682fn parse_let_inner<'a>(group: &Seq<'a>, body_items: &[&Item<'a>]) -> Result<Expr, ParseError> {
683    if body_items.is_empty() {
684        return Err(ParseError::LetMissingBody);
685    }
686    let mut clauses = chain_slices(group);
687    for ci in &clauses {
688        if !clause_is_binding(ci) {
689            return Err(ParseError::MalformedBinding);
690        }
691    }
692    clauses.push(body_items.to_vec());
693    build_block(clauses)
694}
695
696/// Parse `recur (b0, b1, …) body`. Bindings are simultaneous and form an item table.
697///
698/// The table is sorted at parse to define the index space for sibling `ModItem`s.
699/// An empty group returns `body` directly.
700fn parse_recur_inner<'a>(group: &Seq<'a>, body_items: &[&Item<'a>]) -> Result<Expr, ParseError> {
701    if body_items.is_empty() {
702        return Err(ParseError::RecurMissingBody);
703    }
704    let syntax = parse_module(group)?;
705    if !syntax.imports.is_empty() {
706        return Err(ParseError::MisplacedImport);
707    }
708    if !syntax.iotas.is_empty() {
709        return Err(ParseError::MisplacedConst);
710    }
711    if !syntax.locals.is_empty() {
712        return Err(ParseError::MisplacedLocal);
713    }
714    if syntax.main.is_some() {
715        return Err(ParseError::MisplacedReserved);
716    }
717    let mut bindings = syntax.items;
718    let body = parse_segment(body_items)?;
719    if bindings.is_empty() {
720        return Ok(body);
721    }
722    bindings.sort_by(|(a, _), (b, _)| a.as_str().cmp(b.as_str()));
723    Ok(Expr::Recur(Rc::new(ModuleData::from_bindings(
724        bindings,
725        Vec::new(),
726        Box::new([]),
727        Box::new([]),
728        Box::new([]),
729        Some(body),
730        None,
731    ))))
732}
733
734/// Parse a binding chain `<pattern> "=" <expr>`.
735/// Separates on the first top-level `=`. Head and tail must be non-empty.
736fn parse_binding<'a>(items: &[&Item<'a>]) -> Result<(Pattern, Expr), ParseError> {
737    let mut eq = None;
738    for (i, it) in items.iter().enumerate() {
739        if matches!(it, Item::Punct("=")) {
740            if eq.is_some() {
741                return Err(ParseError::MalformedBinding);
742            }
743            eq = Some(i);
744        }
745    }
746    let eq = eq.ok_or(ParseError::MalformedBinding)?;
747    let (head, tail) = (&items[..eq], &items[eq + 1..]);
748    if head.is_empty() || tail.is_empty() {
749        return Err(ParseError::MalformedBinding);
750    }
751    if head.iter().any(|it| matches!(it, Item::Punct("|"))) {
752        return Err(ParseError::LetOrUnparenthesized);
753    }
754    if let [it] = head {
755        if is_bare_literal_header(it) {
756            return Err(ParseError::BareLiteralHeader);
757        }
758    }
759    let pat = parse_pattern(head)?;
760    let value = parse_items(tail)?;
761    Ok((pat, value))
762}
763
764/// Parse a single item as an atomic expression.
765fn parse_aexpr<'a>(item: &Item<'a>) -> Result<Expr, ParseError> {
766    match item {
767        Item::Sym(s) => Ok(parse_sym(s)?),
768        Item::Prefixed { sigil: '.', item } => parse_dot_symbol(item),
769        Item::Brackets(_) => Err(ParseError::BareListLiteral),
770        Item::Parens(seq) => parse_group(seq),
771        Item::Str(s) => Ok(Expr::Str(decode_str(s)?)),
772        Item::Braces(seq) => parse_block(seq),
773        Item::Punct(".") => Err(ParseError::CombinatorDeferred),
774        Item::Punct(_) => Err(ParseError::PunctAtAtom),
775        Item::Comm(_) => Err(ParseError::EmptyChain),
776        Item::Prefixed { sigil: '#', item } => match item.as_ref() {
777            Item::Brackets(seq) => parse_list(seq),
778            Item::Braces(seq) => parse_map(seq),
779            _ => Err(ParseError::ReservedLiteralForm),
780        },
781        Item::Prefixed { .. } => Err(ParseError::UnexpectedBinder),
782        Item::Run(members) => parse_run(members),
783    }
784}
785
786/// Lower a run (glued atoms) to the expression its members form as one chain.
787fn parse_run<'a>(members: &[Item<'a>]) -> Result<Expr, ParseError> {
788    check_run_head(members)?;
789    parse_items(
790        members
791            .iter()
792            .map(|it| it as &Item<'_>)
793            .collect::<Vec<_>>()
794            .as_slice(),
795    )
796}
797
798/// Reject `&`-headed runs and float-like runs (`3.14`).
799fn check_run_head(members: &[Item<'_>]) -> Result<(), ParseError> {
800    if matches!(&members[0], Item::Prefixed { sigil: '&', .. }) {
801        return Err(ParseError::BinderRunGlued);
802    }
803    if let (
804        Item::Sym(head),
805        Some(Item::Prefixed {
806            sigil: '.',
807            item: seg,
808        }),
809    ) = (&members[0], members.get(1))
810    {
811        if parse_int(head).is_some() && is_dot_number(seg) {
812            return Err(ParseError::FloatLiteralUnsupported);
813        }
814    }
815    Ok(())
816}
817
818/// Check if a `.`-prefixed segment is a number (float tail).
819fn is_dot_number(item: &Item<'_>) -> bool {
820    match item {
821        Item::Sym(s) => parse_int(s).is_some(),
822        _ => false,
823    }
824}
825
826/// Decode a Muon `<str>` literal into owned UTF-8 `Text`, combining surrogate pairs.
827/// Unpaired surrogates cause [`ParseError::LoneSurrogate`].
828fn decode_str(raw: &str) -> Result<Text, ParseError> {
829    let b = raw.as_bytes();
830    let mut out = String::with_capacity(raw.len());
831    let mut i = 0;
832    let mut chunk = 0;
833    while i < b.len() {
834        if b[i] != b'\\' {
835            i += 1;
836            continue;
837        }
838        out.push_str(&raw[chunk..i]);
839        match b[i + 1] {
840            b'"' => out.push('"'),
841            b'\\' => out.push('\\'),
842            b'/' => out.push('/'),
843            b'n' => out.push('\n'),
844            b't' => out.push('\t'),
845            b'r' => out.push('\r'),
846            b'b' => out.push('\u{8}'),
847            b'f' => out.push('\u{c}'),
848            b'u' => {
849                let hi = hex4(&b[i + 2..i + 6]);
850                i += 6;
851                if (0xD800..=0xDBFF).contains(&hi) {
852                    let lo = match (b.get(i), b.get(i + 1)) {
853                        (Some(b'\\'), Some(b'u')) => hex4(&b[i + 2..i + 6]),
854                        _ => return Err(ParseError::LoneSurrogate),
855                    };
856                    if !(0xDC00..=0xDFFF).contains(&lo) {
857                        return Err(ParseError::LoneSurrogate);
858                    }
859                    let c = 0x1_0000 + ((hi - 0xD800) << 10) + (lo - 0xDC00);
860                    out.push(char::from_u32(c).expect("combined surrogate pair"));
861                    i += 6;
862                } else if (0xDC00..=0xDFFF).contains(&hi) {
863                    return Err(ParseError::LoneSurrogate);
864                } else {
865                    out.push(char::from_u32(hi).expect("non-surrogate BMP scalar"));
866                }
867                chunk = i;
868                continue;
869            }
870            _ => unreachable!("Muon validated the <str> escape grammar"),
871        }
872        i += 2;
873        chunk = i;
874    }
875    out.push_str(&raw[chunk..]);
876    Ok(Text::from(out.as_str()))
877}
878
879/// Read four ASCII hex digits as a `u32`.
880fn hex4(b: &[u8]) -> u32 {
881    b.iter()
882        .fold(0, |v, &c| v * 16 + (c as char).to_digit(16).unwrap())
883}
884
885/// Parse a `.`-prefixed item as a symbol.
886fn parse_dot_symbol<'a>(item: &Item<'a>) -> Result<Expr, ParseError> {
887    match item {
888        Item::Sym(s) => Ok(Expr::Symbol(text(s))),
889        _ => Err(ParseError::BadSymbol),
890    }
891}
892
893/// Classify a `<sym>` as an integer literal, name, or error.
894fn parse_sym(s: &str) -> Result<Expr, ParseError> {
895    if s == "Self" {
896        return Ok(Expr::Home {
897            leaf: HomeLeaf::Module,
898            depth: 0,
899        });
900    }
901    if is_keyword(s) {
902        return Err(ParseError::ReservedKeyword);
903    }
904    if s == "_" {
905        Err(ParseError::DiscardReference)
906    } else if let Some(n) = parse_int(s) {
907        Ok(Expr::Int(n))
908    } else if s.as_bytes()[0].is_ascii_digit() {
909        Err(ParseError::MalformedNumber)
910    } else if s.starts_with("__") {
911        match Builtin::from_name(s) {
912            Some(op) => Ok(Expr::Builtin(op)),
913            None => Err(ParseError::UnknownReserved(text(s))),
914        }
915    } else {
916        Ok(Expr::Name(text(s)))
917    }
918}
919
920/// Parse an integer literal (decimal, hex, or binary) with `_` separators.
921pub(crate) fn parse_int(s: &str) -> Option<BigInt> {
922    let (neg, body) = match s.as_bytes().first()? {
923        b'-' => (true, &s[1..]),
924        b'+' => (false, &s[1..]),
925        _ => (false, s),
926    };
927    let (radix, digits) =
928        if let Some(h) = body.strip_prefix("0x").or_else(|| body.strip_prefix("0X")) {
929            (16u32, h)
930        } else if let Some(b) = body.strip_prefix("0b").or_else(|| body.strip_prefix("0B")) {
931            (2, b)
932        } else {
933            (10, body)
934        };
935    let mut cleaned = String::new();
936    let mut after_sep = true;
937    for &c in digits.as_bytes() {
938        if c == b'_' {
939            if after_sep {
940                return None;
941            }
942            after_sep = true;
943        } else {
944            cleaned.push(c as char);
945            after_sep = false;
946        }
947    }
948    if after_sep || cleaned.is_empty() {
949        return None;
950    }
951    let mag = BigInt::parse_bytes(cleaned.as_bytes(), radix)?;
952    Some(if neg { -mag } else { mag })
953}
954
955/// Parse an application spine, folding left.
956/// `(…)` items are spread into multiple arguments.
957fn parse_app_spine<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
958    let mut args: Vec<Expr> = Vec::new();
959    for (i, it) in items.iter().enumerate() {
960        if i == 0 {
961            match *it {
962                Item::Run(members) => {
963                    check_run_head(members)?;
964                    if is_self_head(members) {
965                        args.push(Expr::Home {
966                            leaf: HomeLeaf::New,
967                            depth: 0,
968                        });
969                        for m in members[1..].iter() {
970                            expand_arg(m, &mut args)?;
971                        }
972                    } else {
973                        for m in members.iter() {
974                            expand_arg(m, &mut args)?;
975                        }
976                    }
977                }
978                Item::Prefixed { sigil: '#', item }
979                    if matches!(item.as_ref(), Item::Sym("Self")) =>
980                {
981                    args.push(Expr::Home {
982                        leaf: HomeLeaf::New,
983                        depth: 0,
984                    });
985                }
986                _ => expand_arg(it, &mut args)?,
987            }
988        } else {
989            expand_arg(it, &mut args)?;
990        }
991    }
992    if matches!(
993        args.as_slice(),
994        [Expr::Home {
995            leaf: HomeLeaf::New,
996            ..
997        }]
998    ) {
999        return Err(ParseError::ReservedLiteralForm);
1000    }
1001    Ok(fold_spine(args))
1002}
1003
1004/// Fold a non-empty argument list: lone element is returned directly, else a flat `App`.
1005fn fold_spine(args: Vec<Expr>) -> Expr {
1006    if args.len() == 1 {
1007        args.into_iter().next().unwrap()
1008    } else {
1009        app(args)
1010    }
1011}
1012
1013/// Check if a run is the glued `#Self(…)` construction.
1014fn is_self_head(members: &[Item<'_>]) -> bool {
1015    matches!(
1016        members.first(),
1017        Some(Item::Prefixed { sigil: '#', item })
1018            if matches!(item.as_ref(), Item::Sym("Self"))
1019    )
1020}
1021
1022/// Expand one chain item into argument expressions. `(…)` groups spread;
1023/// other items are single atoms.
1024fn expand_arg<'a>(item: &'a Item<'a>, out: &mut Vec<Expr>) -> Result<(), ParseError> {
1025    match item {
1026        Item::Parens(seq) => expand_group_args(seq, out),
1027        Item::Prefixed { sigil: '.', item }
1028            if matches!(item.as_ref(), Item::Sym("Self")) && !out.is_empty() =>
1029        {
1030            let acc = fold_spine(core::mem::take(out));
1031            out.push(Expr::App(Box::new([
1032                Expr::Home {
1033                    leaf: HomeLeaf::Value,
1034                    depth: 0,
1035                },
1036                acc,
1037            ])));
1038            Ok(())
1039        }
1040        _ => {
1041            out.push(parse_aexpr(item)?);
1042            Ok(())
1043        }
1044    }
1045}
1046
1047/// Expand `(…)` group chains as arguments (one chain $\to$ one argument).
1048/// Empty groups feed the unit value `()`.
1049fn expand_group_args<'a>(seq: &Seq<'a>, out: &mut Vec<Expr>) -> Result<(), ParseError> {
1050    let mut any = false;
1051    for items in chain_slices(seq) {
1052        out.push(parse_items(&items)?);
1053        any = true;
1054    }
1055    if !any {
1056        out.push(Expr::Unit);
1057    }
1058    Ok(())
1059}
1060
1061/// Parse a `(…)` group as a standalone atom.
1062fn parse_group<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
1063    let mut args: Vec<Expr> = Vec::new();
1064    expand_group_args(seq, &mut args)?;
1065    Ok(fold_spine(args))
1066}
1067
1068/// Parse a `[…]` list into a runtime list literal of *any* arity: `[]` (the
1069/// empty list, not unit), `[e]` (a genuine 1-list, not collapsed), `[e0, e1, …]`.
1070/// Each non-comment chain is one element expression.
1071fn parse_list<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
1072    let mut elems: Vec<Expr> = Vec::new();
1073    for items in chain_slices(seq) {
1074        elems.push(parse_items(&items)?);
1075    }
1076    Ok(Expr::List(elems))
1077}
1078
1079/// Parse a `{…}` brace group as a **block expression** (syntax v1 step 6): a
1080/// sequence of clauses whose value is the last clause's. A block lowers onto the
1081/// same nested-abstraction chain a `let` group produces — `{ a = va, e }` is
1082/// `let (a = va) e`, i.e. `(&a e) va` — so it adds no evaluator node, and a
1083/// block *is* a scope because each binding becomes a lambda parameter that the
1084/// following clauses (and nothing outside) can see.
1085///
1086/// - The empty block `{}` is the unit value `()`, and `{ e }` is `e` — the
1087///   degenerate-case identities.
1088/// - Each **non-final** clause is a binding (`<pat> = <expr>`, a top-level `=`,
1089///   bound over the rest) or a bare expression (evaluated for its effect, its
1090///   value discarded — lowered to a `_ =` binding, today's `_ = io.print(…)`).
1091/// - The **final** clause is the block's value and must be an expression; a
1092///   trailing binding is [`BlockTrailingBinding`](ParseError::BlockTrailingBinding).
1093///
1094/// A `:`-clause (a stale bare-map entry) has no reading in a block and is refused
1095/// with the `#{…}` nudge, as it is in every brace position. Clauses are the
1096/// `<seq>`'s joined non-comment chains, so a block may be laid out multi-line
1097/// with blank lines, comments, and the chain-continuation joins.
1098fn parse_block<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
1099    // A `:`-clause is the one shape a block shares with the old bare map; keep
1100    // the pointed error rather than letting it fall through as a bad expression.
1101    if braces_contain_colon(seq) {
1102        return Err(ParseError::BareMapLiteral);
1103    }
1104    build_block(chain_slices(seq))
1105}
1106
1107/// Build an [`Expr::Block`] from a block's (already joined, comment-free) clause
1108/// chains — shared by the `{…}` block and by `let (…) e` (its group's binding
1109/// chains plus its body as the final clause). Each clause becomes a
1110/// [`Clause::Bind`] (a chain with a top-level `=`) or a [`Clause::Do`] (a bare
1111/// expression). The degenerate blocks collapse rather than build a node: an empty
1112/// clause list is the unit value `()`, and a single expression clause is that
1113/// expression (`{ e }` is `e`). A trailing binding is rejected — a block's value
1114/// is its last clause's, and a binding leaves none (and its name would not
1115/// escape) — so the final clause is always a `Do`.
1116fn build_block<'a>(clauses: Vec<Vec<&Item<'a>>>) -> Result<Expr, ParseError> {
1117    let Some((last, leading)) = clauses.split_last() else {
1118        return Ok(Expr::Unit); // `{}` / `let () e` with no body chain — unit
1119    };
1120    if clause_is_binding(last) {
1121        return Err(ParseError::BlockTrailingBinding);
1122    }
1123    if leading.is_empty() {
1124        return parse_items(last); // `{ e }` is `e`
1125    }
1126    let mut built: Vec<(Option<Pattern>, Expr)> = Vec::with_capacity(leading.len() + 1);
1127    for ci in leading {
1128        let clause = if clause_is_binding(ci) {
1129            let (pat, value) = parse_binding(ci)?;
1130            (Some(pat), value)
1131        } else {
1132            (None, parse_items(ci)?)
1133        };
1134        built.push(clause);
1135    }
1136    built.push((None, parse_items(last)?));
1137    Ok(Expr::Block(built.into_boxed_slice()))
1138}
1139
1140/// Whether a block clause is a **binding** — it carries a top-level `=` punct.
1141/// A nested `=` lives inside a `(…)`/`[…]`/`{…}` item, never as a top-level
1142/// `Item::Punct`, so this is exactly the `<binding>` vs `<chain>` split the
1143/// `let` group makes (and [`parse_binding`] then splits at the first `=`).
1144fn clause_is_binding(items: &[&Item<'_>]) -> bool {
1145    items.iter().any(|it| matches!(it, Item::Punct("=")))
1146}
1147
1148/// Parse a `{…}` block into a map literal: each non-comment chain of the `<seq>`
1149/// is one entry (see `parse_entry`); comment-only chains are skipped (so a map may
1150/// be laid out multi-line with blank lines and comments). Entry order is source
1151/// order — evaluation re-sorts by the value order.
1152fn parse_map<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
1153    let mut entries: Vec<(Expr, Expr)> = Vec::new();
1154    for items in chain_slices(seq) {
1155        entries.push(parse_entry(&items)?);
1156    }
1157    Ok(Expr::Map(entries))
1158}
1159
1160/// Parse one map entry `<key> ":" <value>`. The `:` is a single `<punct>` item at
1161/// position 1; the key is the single head item (`parse_key`) and the value is the
1162/// rest of the chain parsed as an `<expr>`. An *empty* value tail is sugar for the
1163/// unit value `()` (so `{ .foo: }` == `{ .foo: () }`).
1164fn parse_entry<'a>(items: &[&Item<'a>]) -> Result<(Expr, Expr), ParseError> {
1165    if items.len() < 2 || !matches!(items[1], Item::Punct(":")) {
1166        return Err(ParseError::MalformedMapKey);
1167    }
1168    let key = parse_key(items[0])?;
1169    let value_items = &items[2..];
1170    let value = if value_items.is_empty() {
1171        Expr::Unit // empty rest → the unit value ()
1172    } else {
1173        parse_items(value_items)?
1174    };
1175    Ok((key, value))
1176}
1177
1178/// Parse a map-entry key item. A bare *non-digit* atom key is the **symbol** of
1179/// the same spelling — `one` == `.one` — mirroring `.foo` exactly; a key is just
1180/// a token. A **digit-leading** bare key (`0`, `5`) is rejected to avoid the
1181/// number/symbol confusion: write `.0` for the symbol or `(0)` for the integer.
1182/// Wrap a key as a `(…)` **computed** key to evaluate it: `(one)` is the *value*
1183/// of variable `one`, `(0)` the integer `0`; an *empty* `()` is the unit key.
1184/// The remaining forms are a `.`-prefixed symbol (`.one`) and a `#[…]` / `#{…}`
1185/// literal key (`#[]` the empty-list key). A bare `[…]` / `{…}` key routes to the
1186/// same place only to be refused with its `#`-spelling nudge (`BareListLiteral` /
1187/// `BareMapLiteral`); anything else is a `MalformedMapKey`.
1188fn parse_key<'a>(item: &Item<'a>) -> Result<Expr, ParseError> {
1189    match item {
1190        // A bare non-digit `<sym>` key is the symbol of that spelling, read
1191        // exactly as `.<sym>` would be. A digit-leading key is rejected — write
1192        // `.5` (the symbol) or `(5)` (the computed integer key) to disambiguate.
1193        Item::Sym(s) if s.as_bytes()[0].is_ascii_digit() => Err(ParseError::MalformedMapKey),
1194        Item::Sym(s) => Ok(Expr::Symbol(text(s))),
1195        // A bare `{…}` in key position is a block expression at the atom layer,
1196        // which has no reading as a key — refuse it with the `#{…}` nudge rather
1197        // than silently keying on a block value (write `#{…}` for a map key).
1198        Item::Braces(_) => Err(ParseError::BareMapLiteral),
1199        // `.sym` symbol key, `(…)` computed key (empty `()` the unit key), `"…"`
1200        // string key, and the explicit `#[…]` / `#{…}` literal keys — each reads
1201        // exactly as the same item would at atom position. A bare `[…]` routes
1202        // here too, only to be refused with its `#[…]`-spelling nudge.
1203        Item::Prefixed { sigil: '.', .. }
1204        | Item::Prefixed { sigil: '#', .. }
1205        | Item::Brackets(_)
1206        | Item::Parens(_)
1207        | Item::Str(_) => parse_aexpr(item),
1208        // A **path** key (`{ Mod.k: v }`) denotes a concrete value by reference,
1209        // so it is a lookup key alongside the `.sym` / bare-sym forms (§E).
1210        Item::Run(members) => parse_path(members).map_err(|_| ParseError::MalformedMapKey),
1211        _ => Err(ParseError::MalformedMapKey),
1212    }
1213}
1214
1215// ===========================================================================
1216// patterns
1217// ===========================================================================
1218
1219/// `&<pattern> body` — the one binding form every abstraction (and `let`, and a
1220/// `__match` clause) is built from. Greedily **collects** into one multi-parameter
1221/// [`Lambda`]: when `body` is *directly* another abstraction (a run of `&p0 &p1 …`,
1222/// or a curried binder group), `pat` is prepended to its head so the whole run
1223/// shares one code node with a known arity. Collection stops at the first
1224/// non-abstraction body (a `&` nested inside an application does not extend the
1225/// arity) and at 255 parameters — a head that would exceed the `applied: u8`
1226/// counter starts a fresh nested `Lambda` instead of overflowing.
1227fn abs(pat: Pattern, body: Expr) -> Expr {
1228    match body {
1229        Expr::Abs(lam) if lam.head.len() < 255 => {
1230            // Prepend `pat` to the (freshly built, so uniquely owned) inner head.
1231            let lam = Rc::try_unwrap(lam).unwrap_or_else(|rc| (*rc).clone());
1232            let mut head = Vec::with_capacity(lam.head.len() + 1);
1233            head.push(pat);
1234            head.extend(Vec::from(lam.head));
1235            Expr::Abs(Rc::new(Lambda {
1236                head: head.into_boxed_slice(),
1237                body: lam.body,
1238                // Free variables are a resolution artifact: the parser leaves the
1239                // capture plan unresolved and `resolve.rs` fills it in.
1240                captures: Captures::Unresolved,
1241            }))
1242        }
1243        _ => Expr::Abs(Rc::new(Lambda {
1244            head: Box::new([pat]),
1245            body,
1246            captures: Captures::Unresolved,
1247        })),
1248    }
1249}
1250
1251/// Build an application node from a flat call spine `[callee, arg0, …]` (≥ 2
1252/// elements). A single-element `items` is *not* an application — the caller
1253/// returns that element directly.
1254fn app(items: Vec<Expr>) -> Expr {
1255    debug_assert!(
1256        items.len() >= 2,
1257        "an application has a callee and ≥1 argument"
1258    );
1259    Expr::App(items.into_boxed_slice())
1260}
1261
1262/// Apply `f` to one more `arg`, extending `f`'s call spine when it already is one
1263/// so `g x &y …` and `x |> f a` stay single flattened `App` nodes (a partial
1264/// applied to a trailing abstraction / pipe operand). Currying makes this
1265/// equivalent to a fresh binary application, and it re-renders identically.
1266fn append_arg(f: Expr, arg: Expr) -> Expr {
1267    match f {
1268        Expr::App(items) => {
1269            let mut v = Vec::from(items);
1270            v.push(arg);
1271            Expr::App(v.into_boxed_slice())
1272        }
1273        _ => Expr::App(Box::new([f, arg])),
1274    }
1275}
1276
1277/// Whether `item` is a bare **matching literal** — a number, a `.`-symbol, or a
1278/// string — used where a whole `&`-header or `let` LHS is expected. These are
1279/// equality patterns, not binders, so a bare one reads as a stray value or an
1280/// intended binding; Elly requires the wrap (`&(42)`, `&(.foo)`, `&("s")`; see
1281/// `BareLiteralHeader`). Nested in `[…]`/`{…}`/`(…)`/an or-pattern they are fine.
1282fn is_bare_literal_header(item: &Item<'_>) -> bool {
1283    match item {
1284        Item::Sym(s) => parse_int(s).is_some(),
1285        Item::Prefixed { sigil: '.', .. } => true,
1286        Item::Str(_) => true,
1287        _ => false,
1288    }
1289}
1290
1291/// Build the abstraction(s) an `&`-header `&<inner>` introduces over `body`. A
1292/// `(…)` inner is a **binder group** whose commas curry (`&(a, b)` → two params);
1293/// any other inner is a single-parameter pattern.
1294fn build_binder<'a>(inner: &Item<'a>, body: Expr) -> Result<Expr, ParseError> {
1295    match inner {
1296        Item::Parens(seq) => build_group(seq, body),
1297        // A bare matching literal (number `&42`, symbol `&.foo`, string `&"s"`) as
1298        // the whole header must be parenthesized: `&(42)`, `&(.foo)`, `&("s")` (see
1299        // `BareLiteralHeader`). Nested literals reach `parse_atom_item` unaffected.
1300        it if is_bare_literal_header(it) => Err(ParseError::BareLiteralHeader),
1301        other => {
1302            let pat = parse_pattern(&[other])?;
1303            Ok(abs(pat, body))
1304        }
1305    }
1306}
1307
1308/// Build a curried binder group `&(p0, p1, …)`: each non-comment chain is one
1309/// parameter pattern, curried left-to-right (leftmost outermost). An empty group
1310/// `&()` is the nullary marker — one param, the unit pattern that matches `()`.
1311fn build_group<'a>(seq: &Seq<'a>, body: Expr) -> Result<Expr, ParseError> {
1312    let params = chain_slices(seq);
1313    if params.is_empty() {
1314        // `&()` → the unit pattern: assert the argument is unit `()`.
1315        return Ok(abs(Pattern::Unit, body));
1316    }
1317    let mut acc = body;
1318    for chain in params.iter().rev() {
1319        let pat = parse_pattern(chain)?;
1320        acc = abs(pat, acc);
1321    }
1322    Ok(acc)
1323}
1324
1325/// Parse `case <subject>? { arms }`. Subjectless cases are lowered to
1326/// `&__arg (case __arg { ... })`.
1327fn parse_case<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
1328    let mut rest: Vec<&Item<'a>> = Vec::new();
1329    if let Item::Run(members) = items[0] {
1330        for m in &members[1..] {
1331            rest.push(m);
1332        }
1333    }
1334    rest.extend_from_slice(&items[1..]);
1335    let (last, subject_items) = rest.split_last().ok_or(ParseError::CaseMissingArms)?;
1336    let arms_seq = match last {
1337        Item::Braces(seq) => seq,
1338        _ => return Err(ParseError::CaseMissingArms),
1339    };
1340    let cases = parse_arms(arms_seq)?;
1341    match subject_items {
1342        [] => {
1343            let subject = Rc::new(Expr::Name(text("__arg")));
1344            Ok(abs(
1345                Pattern::Bind(text("__arg")),
1346                Expr::Case { subject, cases },
1347            ))
1348        }
1349        [subj] => Ok(Expr::Case {
1350            subject: Rc::new(parse_aexpr(subj)?),
1351            cases,
1352        }),
1353        _ => Err(ParseError::CaseSubjectExtra),
1354    }
1355}
1356
1357/// Parse `case` arms from a block, one per non-comment chain.
1358fn parse_arms<'a>(seq: &Seq<'a>) -> Result<Box<[(Pattern, Expr)]>, ParseError> {
1359    let chains = chain_slices(seq);
1360    if chains.is_empty() {
1361        return Err(ParseError::CaseMissingArms);
1362    }
1363    let mut arms = Vec::with_capacity(chains.len());
1364    for chain in &chains {
1365        arms.push(parse_arm(chain)?);
1366    }
1367    Ok(arms.into_boxed_slice())
1368}
1369
1370/// Parse an arm: `& <pattern> (when (<cond>))* <body>`.
1371fn parse_arm<'a>(items: &[&Item<'a>]) -> Result<(Pattern, Expr), ParseError> {
1372    let inner = match items.first() {
1373        Some(Item::Prefixed { sigil: '&', item }) => item.as_ref(),
1374        _ => return Err(ParseError::ArmNotBinder),
1375    };
1376    if is_bare_literal_header(inner) {
1377        return Err(ParseError::BareLiteralHeader);
1378    }
1379    let base = parse_pattern(&[inner])?;
1380    let (pat, rest) = peel_when(base, &items[1..])?;
1381    if rest.is_empty() {
1382        return Err(ParseError::ArmMissingBody);
1383    }
1384    let body = parse_items(rest)?;
1385    Ok((pat, body))
1386}
1387
1388/// Fold `when (<cond>)` guards onto a pattern.
1389fn peel_when<'a, 'b>(
1390    mut base: Pattern,
1391    mut items: &'b [&'b Item<'a>],
1392) -> Result<(Pattern, &'b [&'b Item<'a>]), ParseError> {
1393    while !items.is_empty() && is_when_kw(items[0]) {
1394        let (group, next): (&Seq<'a>, &[&Item<'a>]) = match items[0] {
1395            Item::Run(members) if members.len() == 2 => match &members[1] {
1396                Item::Parens(seq) => (seq, &items[1..]),
1397                _ => return Err(ParseError::WhenMissingCond),
1398            },
1399            Item::Run(_) => return Err(ParseError::WhenMissingCond),
1400            _ => match items.get(1) {
1401                Some(Item::Parens(seq)) => (seq, &items[2..]),
1402                _ => return Err(ParseError::WhenMissingCond),
1403            },
1404        };
1405        let cond = parse_when_cond(group)?;
1406        base = Pattern::When {
1407            inner: Box::new(base),
1408            when: cond,
1409        };
1410        items = next;
1411    }
1412    Ok((base, items))
1413}
1414
1415/// Check if an item is the `when` guard keyword.
1416fn is_when_kw(item: &Item) -> bool {
1417    head_keyword(item) == Some("when")
1418}
1419
1420/// Parse a `when` condition (plain or pattern guard).
1421fn parse_when_cond<'a>(seq: &Seq<'a>) -> Result<(Option<Box<Pattern>>, Expr), ParseError> {
1422    let chain = single_chain(seq).ok_or(ParseError::WhenMissingCond)?;
1423    if chain.is_empty() {
1424        return Err(ParseError::WhenMissingCond);
1425    }
1426    if chain.iter().any(|it| matches!(it, Item::Punct("="))) {
1427        let (pat, expr) = parse_binding(&chain)?;
1428        Ok((Some(Box::new(pat)), expr))
1429    } else {
1430        Ok((None, parse_items(&chain)?))
1431    }
1432}
1433
1434/// Returns non-comment chains of a `<seq>` with [chain-joining](join_chains) applied.
1435fn chain_slices<'a, 'b>(seq: &'b Seq<'a>) -> Vec<Vec<&'b Item<'a>>> {
1436    let chains = seq
1437        .0
1438        .iter()
1439        .filter(|c| !is_comment_only(c))
1440        .map(|c| {
1441            c.0.iter()
1442                .filter(|it| !matches!(it, Item::Comm(_)))
1443                .collect()
1444        })
1445        .collect();
1446    join_chains(chains)
1447}
1448
1449/// Returns true if a `<braces>` seq contains a chain whose second non-comment item is `:`.
1450/// Used by [`parse_block`] to reject bare-map spellings.
1451fn braces_contain_colon(seq: &Seq<'_>) -> bool {
1452    for chain in seq.0.iter().filter(|c| !is_comment_only(c)) {
1453        let items: Vec<&Item<'_>> = chain
1454            .0
1455            .iter()
1456            .filter(|it| !matches!(it, Item::Comm(_)))
1457            .collect();
1458        if items.len() >= 2 && matches!(items[1], Item::Punct(":")) {
1459            return true;
1460        }
1461    }
1462    false
1463}
1464
1465/// Joins adjacent chains if they form a single continued expression.
1466/// A chain joins if the predecessor ends with a continuation (e.g., `&`, `=`, `|>`, `when`)
1467/// or if the current chain starts with one (e.g., `|>`, `elif`, `else`, `catch`).
1468fn join_chains<'a, 'b>(chains: Vec<Vec<&'b Item<'a>>>) -> Vec<Vec<&'b Item<'a>>> {
1469    let mut out: Vec<Vec<&'b Item<'a>>> = Vec::with_capacity(chains.len());
1470    for chain in chains {
1471        let join = matches!(out.last(), Some(prev) if trailing_join(prev) || leading_join(&chain));
1472        if join {
1473            out.last_mut().unwrap().extend(chain);
1474        } else {
1475            out.push(chain);
1476        }
1477    }
1478    out
1479}
1480
1481/// Whether a chain ends with a trailing continuation.
1482fn trailing_join(chain: &[&Item]) -> bool {
1483    match chain.last() {
1484        Some(Item::Prefixed { sigil: '&', .. }) => true,
1485        Some(Item::Punct("=")) | Some(Item::Punct("|>")) => true,
1486        _ => {
1487            let n = chain.len();
1488            n >= 2
1489                && matches!(chain[n - 2], Item::Sym("when"))
1490                && matches!(chain[n - 1], Item::Parens(_))
1491        }
1492    }
1493}
1494
1495/// Whether a chain opens with a leading continuation.
1496fn leading_join(chain: &[&Item]) -> bool {
1497    match chain.first() {
1498        Some(Item::Punct("|>")) => true,
1499        Some(first) => matches!(head_keyword(first), Some("elif" | "else" | "catch")),
1500        None => false,
1501    }
1502}
1503
1504/// Returns the single non-comment chain of a `<seq>`, if it contains exactly one.
1505fn single_chain<'a, 'b>(seq: &'b Seq<'a>) -> Option<Vec<&'b Item<'a>>> {
1506    let mut chains = chain_slices(seq).into_iter();
1507    let first = chains.next()?;
1508    if chains.next().is_some() {
1509        return None;
1510    }
1511    Some(first)
1512}
1513
1514/// Parse a pattern from a chain of items. Splits on `|` (or-pattern),
1515/// then `=` (equality / at-pattern), otherwise defers to `as` / atom levels.
1516fn parse_pattern<'a>(items: &[&Item<'a>]) -> Result<Pattern, ParseError> {
1517    if items.is_empty() {
1518        return Err(ParseError::BadPattern);
1519    }
1520    if let Some(idx) = items.iter().position(|it| matches!(it, Item::Punct("|"))) {
1521        let left = parse_pattern(&items[..idx])?;
1522        let right = parse_pattern(&items[idx + 1..])?;
1523        return Ok(Pattern::Or(Box::new(left), Box::new(right)));
1524    }
1525    match items[0] {
1526        Item::Punct("<") => return Ok(Pattern::Less(parse_comparand(&items[1..])?)),
1527        Item::Punct(">") => return Ok(Pattern::Greater(parse_comparand(&items[1..])?)),
1528        _ => {}
1529    }
1530    if let Some(idx) = items.iter().position(|it| matches!(it, Item::Punct("="))) {
1531        if idx == 0 {
1532            return Ok(Pattern::Equal(parse_comparand(&items[1..])?));
1533        }
1534        let name = at_name(&items[..idx])?;
1535        let inner = parse_pattern(&items[idx + 1..])?;
1536        return Ok(Pattern::At(name, Box::new(inner)));
1537    }
1538    parse_as(items)
1539}
1540
1541/// Parse the RHS of an `=` / `<` / `>` comparand: exactly one atom.
1542/// A pattern is a closed grammar; comparands never recurse into `parse_items`.
1543fn parse_comparand<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
1544    match items {
1545        [it] => match it {
1546            Item::Sym(s) => parse_sym(s),
1547            Item::Prefixed { sigil: '.', item } => parse_dot_symbol(item),
1548            Item::Str(s) => Ok(Expr::Str(decode_str(s)?)),
1549            Item::Run(members) => parse_path(members),
1550            _ => Err(ParseError::ComparandNotAtom),
1551        },
1552        _ => Err(ParseError::ComparandNotAtom),
1553    }
1554}
1555
1556/// The bound name of an at-pattern's LHS: one plain name.
1557fn at_name<'a>(items: &[&Item<'a>]) -> Result<Text, ParseError> {
1558    plain_name(items, ParseError::BadPattern)
1559}
1560
1561/// Parse the narrowing level: `Self <pat>`, `(as <Proto>) <pat>`, `<pat> as <Proto>`, or a bare atom.
1562fn parse_as<'a>(items: &[&Item<'a>]) -> Result<Pattern, ParseError> {
1563    if let Item::Sym("Self") = items[0] {
1564        if items.len() < 2 {
1565            return Err(ParseError::BadPattern);
1566        }
1567        let inner = parse_pattern(&items[1..])?;
1568        return Ok(Pattern::Unwrap {
1569            depth: 0,
1570            inner: Box::new(inner),
1571        });
1572    }
1573    if let Item::Parens(seq) = items[0] {
1574        if let Some(chain) = single_chain(seq) {
1575            if !chain.is_empty() && matches!(chain[0], Item::Sym("as")) {
1576                let proto = parse_proto_ref(&chain[1..])?;
1577                let inner = parse_pattern(&items[1..])?;
1578                return Ok(Pattern::Type(proto, Box::new(inner)));
1579            }
1580        }
1581    }
1582    if let Some(idx) = items.iter().position(|it| matches!(it, Item::Sym("as"))) {
1583        let proto = parse_proto_ref(&items[idx + 1..])?;
1584        let inner = parse_atom(&items[..idx])?;
1585        return Ok(Pattern::Type(proto, Box::new(inner)));
1586    }
1587    parse_atom(items)
1588}
1589
1590/// Parse a prototype for `as <Proto>`: exactly one name (a module reference).
1591fn parse_proto_ref(items: &[&Item<'_>]) -> Result<ProtoRef, ParseError> {
1592    let e = match items {
1593        [Item::Sym(s)] => parse_sym(s)?,
1594        [Item::Run(members)] => parse_path(members)?,
1595        _ => return Err(ParseError::UnknownType),
1596    };
1597    match e {
1598        e @ (Expr::Name(_) | Expr::Builtin(_) | Expr::Home { .. } | Expr::App(_)) => {
1599            Ok(ProtoRef::Ref(e))
1600        }
1601        _ => Err(ParseError::UnknownType),
1602    }
1603}
1604
1605/// Parse a path: a run starting with an identifier followed by `.`-prefixed segments.
1606/// Lowers to a left fold of the head applied to symbol literals.
1607fn parse_path<'a>(members: &[Item<'a>]) -> Result<Expr, ParseError> {
1608    if members.len() < 2 {
1609        return Err(ParseError::ComparandNotAtom);
1610    }
1611    let head = match &members[0] {
1612        Item::Sym(s) => parse_sym(s)?,
1613        _ => return Err(ParseError::ComparandNotAtom),
1614    };
1615    for m in &members[1..] {
1616        if !matches!(m, Item::Prefixed { sigil: '.', .. }) {
1617            return Err(ParseError::ComparandNotAtom);
1618        }
1619    }
1620    let mut acc = head;
1621    for m in &members[1..] {
1622        if let Item::Prefixed { sigil: '.', item } = m {
1623            acc = Expr::App(Box::new([acc, parse_dot_symbol(item)?]));
1624        }
1625    }
1626    Ok(acc)
1627}
1628
1629/// Parse an atomic pattern: a single item or a `(…)` grouping of one pattern.
1630fn parse_atom<'a>(items: &[&Item<'a>]) -> Result<Pattern, ParseError> {
1631    match items {
1632        [it] => parse_atom_item(it),
1633        _ => Err(ParseError::BadPattern),
1634    }
1635}
1636
1637/// Parse a single atomic-pattern item.
1638fn parse_atom_item<'a>(item: &Item<'a>) -> Result<Pattern, ParseError> {
1639    match item {
1640        Item::Sym("_") => Ok(Pattern::Discard),
1641        Item::Sym(s) if is_keyword(s) => Err(ParseError::ReservedKeyword),
1642        Item::Sym(s) if s.starts_with("__") => Err(ParseError::ReservedName),
1643        Item::Sym(s) => match parse_int(s) {
1644            Some(n) => Ok(Pattern::Equal(Expr::Int(n))),
1645            None if s.as_bytes()[0].is_ascii_digit() => Err(ParseError::MalformedNumber),
1646            None => Ok(Pattern::Bind(text(s))),
1647        },
1648        Item::Prefixed { sigil: '.', item } => Ok(Pattern::Equal(parse_dot_symbol(item)?)),
1649        Item::Str(s) => Ok(Pattern::Equal(Expr::Str(decode_str(s)?))),
1650        Item::Brackets(_) => Err(ParseError::BareListLiteral),
1651        Item::Parens(seq) => match single_chain(seq) {
1652            Some(chain) if !chain.is_empty() => parse_pattern(&chain),
1653            _ => Err(ParseError::BadPattern),
1654        },
1655        Item::Braces(_) => Err(ParseError::BareMapLiteral),
1656        Item::Prefixed { sigil: '#', item } => match item.as_ref() {
1657            Item::Brackets(seq) => parse_list_pattern(seq),
1658            Item::Braces(seq) => parse_map_pattern(seq),
1659            _ => Err(ParseError::ReservedLiteralForm),
1660        },
1661        _ => Err(ParseError::BadPattern),
1662    }
1663}
1664
1665/// Parse a `{ k0: p0, … }` map pattern.
1666/// Keys are patterns; literals and equality forms are lookups, others are captures.
1667fn parse_map_pattern<'a>(seq: &Seq<'a>) -> Result<Pattern, ParseError> {
1668    let chains = chain_slices(seq);
1669    let n = chains.len();
1670    let mut entries: Vec<(MapKey, Pattern)> = Vec::new();
1671    let mut rest: Option<Box<Pattern>> = None;
1672    for (i, chain) in chains.iter().enumerate() {
1673        if let Some(r) = as_rest(chain) {
1674            if i != n - 1 {
1675                return Err(ParseError::BadPattern);
1676            }
1677            rest = Some(r);
1678        } else {
1679            let colon = chain
1680                .iter()
1681                .position(|it| matches!(it, Item::Punct(":")))
1682                .ok_or(ParseError::MalformedMapKey)?;
1683            let val_items = &chain[colon + 1..];
1684            if colon == 0 || val_items.is_empty() {
1685                return Err(ParseError::MalformedMapKey);
1686            }
1687            let key_items = &chain[..colon];
1688            let bare_sym = match key_items {
1689                [Item::Sym(s)] => Some(*s),
1690                _ => None,
1691            };
1692            let key = if let Some(s) = bare_sym {
1693                if s.as_bytes()[0].is_ascii_digit() {
1694                    return Err(ParseError::MalformedMapKey);
1695                }
1696                MapKey::Lookup(Expr::Symbol(text(s)))
1697            } else if let [Item::Run(members)] = key_items {
1698                MapKey::Lookup(parse_path(members)?)
1699            } else {
1700                match parse_pattern(key_items)? {
1701                    Pattern::Equal(expr) => MapKey::Lookup(expr),
1702                    kpat => MapKey::Capture(Box::new(kpat)),
1703                }
1704            };
1705            let valpat = parse_pattern(val_items)?;
1706            entries.push((key, valpat));
1707        }
1708    }
1709    Ok(Pattern::Map { entries, rest })
1710}
1711
1712/// Parse a `[…]` list pattern with an optional trailing rest.
1713fn parse_list_pattern<'a>(seq: &Seq<'a>) -> Result<Pattern, ParseError> {
1714    let chains = chain_slices(seq);
1715    let n = chains.len();
1716    let mut elems: Vec<Pattern> = Vec::new();
1717    let mut rest: Option<Box<Pattern>> = None;
1718    for (i, chain) in chains.iter().enumerate() {
1719        if let Some(r) = as_rest(chain) {
1720            if i != n - 1 {
1721                return Err(ParseError::BadPattern);
1722            }
1723            rest = Some(r);
1724        } else {
1725            elems.push(parse_pattern(chain)?);
1726        }
1727    }
1728    Ok(Pattern::List { elems, rest })
1729}
1730
1731/// Recognize a trailing-rest element chain (`...rest` or `...`).
1732fn as_rest<'a>(chain: &[&Item<'a>]) -> Option<Box<Pattern>> {
1733    if chain.len() != 1 {
1734        return None;
1735    }
1736    if let Item::Prefixed {
1737        sigil: '.',
1738        item: a,
1739    } = chain[0]
1740    {
1741        if let Item::Prefixed {
1742            sigil: '.',
1743            item: b,
1744        } = a.as_ref()
1745        {
1746            match b.as_ref() {
1747                Item::Prefixed {
1748                    sigil: '.',
1749                    item: c,
1750                } => {
1751                    if let Item::Sym(name) = c.as_ref() {
1752                        return Some(Box::new(Pattern::Bind(text(name))));
1753                    }
1754                }
1755                Item::Punct(".") => return Some(Box::new(Pattern::Discard)),
1756                _ => {}
1757            }
1758        }
1759    }
1760    None
1761}
1762
1763/// Whether a chain holds only comments.
1764fn is_comment_only(chain: &Chain) -> bool {
1765    chain.0.iter().all(|it| matches!(it, Item::Comm(_)))
1766}
1767
1768/// Whether `s` is an Elly keyword.
1769fn is_keyword(s: &str) -> bool {
1770    matches!(
1771        s,
1772        "Self"
1773            | "let"
1774            | "recur"
1775            | "import"
1776            | "const"
1777            | "local"
1778            | "with"
1779            | "as"
1780            | "when"
1781            | "match"
1782            | "case"
1783            | "of"
1784            | "if"
1785            | "elif"
1786            | "else"
1787            | "catch"
1788    )
1789}
1790
1791/// Whether `name` can be bound and referenced from source as a plain name.
1792pub fn is_bindable_name(name: &str) -> bool {
1793    muon::is_sym(name)
1794        && !is_keyword(name)
1795        && !name.starts_with("__")
1796        && name != "_"
1797        && !reads_as_number(name)
1798}
1799
1800/// Whether this item is a chain split point (e.g., `&`-binder or `let`/`recur`/`case`).
1801fn is_split_point(item: &Item) -> bool {
1802    is_binder(item)
1803        || matches!(
1804            head_keyword(item),
1805            Some("let") | Some("recur") | Some("case")
1806        )
1807}
1808
1809/// Whether this item is a `&`-prefixed binder.
1810fn is_binder(item: &Item) -> bool {
1811    matches!(item, Item::Prefixed { sigil: '&', .. })
1812}
1813
1814/// The keyword head of an item, if any (bare keyword or head of a keyword-headed run).
1815fn head_keyword<'a>(item: &Item<'a>) -> Option<&'a str> {
1816    match item {
1817        Item::Sym(s) if is_keyword(s) => Some(s),
1818        Item::Run(members) => match members.first() {
1819            Some(Item::Sym(s)) if is_keyword(s) => Some(s),
1820            _ => None,
1821        },
1822        _ => None,
1823    }
1824}