Skip to main content

elly_core/
parse.rs

1//! Parsing: Muon tree → Elly chunk, resolving names as it goes.
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//! The parser emits straight into a [`Builder`](crate::code::Builder): each
7//! method pushes the nodes below its own and returns that node unpushed (see
8//! [`code`](crate::code)). It resolves every name as it reaches it, against
9//! a [`Scope`] that mirrors the environment the evaluator will build, so no
10//! tree is built and no pass runs after the parse. A module is read in two
11//! walks over its chains: the declarations first, which give every member its
12//! index, then the bodies (see `docs/done/2026-10-11_elly-ctx-chunks.md`).
13//!
14//! Patterns are emitted as [`Pat`]s matched directly by the evaluator.
15//! `&p0 &p1 …` runs collect into one multi-parameter lambda, and application
16//! spines and spread calls flatten into one `App` node.
17
18use alloc::vec::Vec;
19
20use muon::{Chain, Group, Item, Seq};
21use num_bigint::BigInt;
22
23use crate::builtins::Builtin;
24use crate::code::{len32, Builder, ChunkId, HomeLeaf, Member, Node, NodeId, Pat, PatId, Run, Text};
25use crate::module::{ImportSpec, ModuleName};
26use crate::resolve::{Barrier, Ref, Scope};
27use crate::source::{report, ChunkSpan, DocSource, Pos};
28use crate::sym::Sym;
29use crate::Ctx;
30
31/// Copy a source string slice into an owned text leaf.
32fn text(s: &str) -> Text {
33    Text::from(s)
34}
35
36// TODO: use thiserror?
37/// A failure at the syntax or parse stage.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum Error {
40    /// The syntax layer (Muon) rejected the input.
41    Syntax(muon::ParseError),
42    /// The input is a valid Muon tree but not a valid Elly program (this
43    /// subset), with the span of the source the error is about.
44    Parse(ParseError, muon::Span),
45}
46
47impl Error {
48    /// The span of the source the error is about; a syntax error is at an
49    /// offset, so its span is empty.
50    pub fn span(&self) -> muon::Span {
51        match self {
52            Error::Syntax(e) => muon::Span {
53                offset: e.offset as Pos,
54                length: 0,
55            },
56            Error::Parse(_, span) => *span,
57        }
58    }
59
60    /// Reports the error in the document `name`, whose text is `text`, as
61    /// `name:line:column: message` and the line, the span underlined (see
62    /// [`source::report`](crate::source::report)).
63    pub fn report(&self, name: &str, text: &str) -> alloc::string::String {
64        let message = match self {
65            Error::Syntax(e) => alloc::format!("syntax error: {:?}", e.kind),
66            Error::Parse(e, _) => alloc::format!("parse error: {e:?}"),
67        };
68        report(name, text, self.span(), &message)
69    }
70}
71
72// TODO: use thiserror?
73/// A parse failure: the input is a valid Muon tree but not a well-formed Elly
74/// program in this subset.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum ParseError {
77    /// The top-level sequence held no chain (nothing to evaluate).
78    EmptyProgram,
79    /// The top-level sequence held more than one chain; top-level sequencing
80    /// is deferred (a program is a single expression).
81    MultipleExpressions,
82    /// A chain reduced to nothing (e.g. only comments).
83    EmptyChain,
84    /// A `&` binder with no body — `&x` alone is ill-formed (yet).
85    AbsWithoutBody,
86    /// A `&` prefix on something that is not a well-formed binder header (see
87    /// `BadPattern` for the pattern content itself).
88    BadBinder,
89    /// A binder appeared where an atomic expression was expected. Not reachable
90    /// via the split logic, kept so parsing never panics.
91    UnexpectedBinder,
92    /// A `.` sigil on something that is not a single name/number segment (e.g.
93    /// `.(…)`, `.&x`). `.foo` / `.0` are symbols; the record sugar `.(…)` and
94    /// other prefixed items are deferred / ill-formed.
95    BadSymbol,
96    /// A bare `.` used as an atom (`f . g`, a lone `.`). At the Muon layer this
97    /// is a `<punct>`; Elly reserves it for a future `.` / `|>` application
98    /// combinator, which is deferred.
99    CombinatorDeferred,
100    /// `_` used as a reference. `_` is the discard pattern (a binder that binds
101    /// nothing), never a name that can be read back.
102    DiscardReference,
103    /// A `&`-binder tried to introduce a name starting with `__`. Such names are
104    /// reserved for builtins / special forms and may be referenced but not bound.
105    ReservedName,
106    /// A `<sym>` that begins like a number but is not a valid integer literal
107    /// (`1a`, `0xZZ`, `1__2`, `0x`). Since a `<name>` may not start with a digit,
108    /// a digit-leading sym must be a well-formed `<int>` or it is an error.
109    MalformedNumber,
110    /// A stray `:` / `=` (or other) `<punct>` item at atom position. Muon parses
111    /// this punctuation, but outside a `let` binding (`=`) or a map entry (`:`)
112    /// it has no atomic reading. (A `#{…}` map literal and a `#[…]` list value are
113    /// their own atoms; the bare `{…}` / `[…]` forms are rejected, not deferred.)
114    PunctAtAtom,
115    /// A map entry (`<braces>` chain) whose key is missing or malformed: no `:`
116    /// after the key, or a key item that is not a bare atom, a `.`-symbol, a `(…)`
117    /// computed key, or a `#[…]` / `#{…}` literal key (`{ : 1 }`, `{ foo }`).
118    MalformedMapKey,
119    /// A `let` not followed by a binder group — `let 1 e`. An *empty* group is not
120    /// an error: `let () e` binds nothing and is `e` (see `Parser::let_block`).
121    LetMissingBinders,
122    /// A `let (…)` with nothing after the binder group; the body is required.
123    LetMissingBody,
124    /// A `recur` not followed by a binder group — `recur 1 e`. As with `let`, an
125    /// empty group is not an error: `recur () e` is `e`.
126    RecurMissingBinders,
127    /// A `recur (…)` with nothing after the binder group; the body is required.
128    RecurMissingBody,
129    /// A `recur` binding whose name is already in scope. The group's bindings are
130    /// simultaneous, so inside it the name would mean the group's own binding and
131    /// never the outer one — the opposite of what the identical `let` line means.
132    /// Detected by resolution (`resolve.rs`); carries the offending name.
133    RecurShadowsOuter(Text),
134    /// A binding inside a `let (…)` group is not `<pattern> "=" <expr>`: missing
135    /// or repeated top-level `=`, an empty pattern, or an empty value.
136    MalformedBinding,
137    /// A keyword (`let`, `with`) used as a reference or a binder. Keywords are
138    /// syntax, not names: they may be neither read nor bound.
139    ReservedKeyword,
140    /// A malformed pattern: an empty pattern, a comma-grouped `(a, b)` used as a
141    /// single pattern, a misplaced `...rest`, or any item with no pattern reading.
142    BadPattern,
143    /// A comparand after `=` / `<` / `>` that is not a single **atom** (a name or
144    /// a literal). A pattern is a closed grammar — `= f x`, `< (f x)`, `= [a]`
145    /// have no reading; write `= x`, `= 5`, or `.foo`.
146    ComparandNotAtom,
147    /// A bare **matching literal** used as an entire `&`-header or `let` binding
148    /// LHS: a number (`&42`), a symbol (`&.foo`), or a string (`&"foo"`).
149    /// Parenthesize it — `&(42)`, `&(.foo)`, `&("foo")`, `let ((42) = v)` — so the
150    /// "match this literal" intent is explicit (a bare literal reads as a value, or
151    /// as an intended binder). A literal *nested* in `[…]` / `{…}` / `(…)` / an
152    /// or-pattern needs no parens.
153    BareLiteralHeader,
154    /// A `<str>` literal with an **unpaired UTF-16 surrogate** escape (`"\uD83D"`,
155    /// a high or low `\uXXXX` not completing a pair). Muon accepts it lexically,
156    /// but UTF-8 cannot represent it, so Elly's decode rejects it at parse.
157    LoneSurrogate,
158    /// A bare top-level `|` in a `let` binding's LHS: `let ((x=.a) | (x=.b) = v)`.
159    /// Wrap the or-pattern — `let (((x=.a) | (x=.b)) = v) e` — so it does not
160    /// visually compete with the binding's own `=`.
161    LetOrUnparenthesized,
162    /// An `as <Type>` whose `<Type>` is not one of the closed kinds
163    /// `Int Sym List Map Fun`.
164    UnknownType,
165    /// An or-pattern `(p | q)` whose arms bind different sets of names.
166    OrBindersMismatch,
167    /// A `|>` pipe combinator with a missing operand — `|> f`, `x |>`, or
168    /// `x |> |> f`. Both sides of `|>` must be non-empty.
169    MalformedPipe,
170    /// A name reference that resolves to no enclosing `&`-binding — a free
171    /// variable. Detected by resolution (`resolve.rs`) while parsing, so it is
172    /// a *static* error: the program is rejected before it runs, rather
173    /// than raising `.unbound_name` at eval. Carries the offending name (spans
174    /// are still a TODO). `__`-names never reach here — they are resolved to
175    /// [`Node::Builtin`] at parse.
176    UnboundName(Text),
177    /// A module binding whose left-hand side is not a single bare
178    /// name: a destructuring pattern, a literal, a discard, or a digit-leading
179    /// token. Module top-level keys are plain names in v0. A `__…` or keyword LHS
180    /// reports [`ParseError::ReservedName`] / [`ParseError::ReservedKeyword`] instead.
181    BadModuleItemName,
182    /// A module defines the same item name twice, or an `import`'s local name is
183    /// also an item name. A name means one thing inside a module, so both are
184    /// rejected here. The `Foo = import "spec"` form is the one place a name is
185    /// both an import and an item: it is a single declaration, and it makes them
186    /// the same thing.
187    DuplicateModuleItem,
188    /// An `import` that is neither `import "<spec>" as <Name>` nor
189    /// `<Name> = import "<spec>"`: a missing `as`, a spec that is not a string
190    /// literal, or trailing items. The spec is a literal in a fixed position —
191    /// which is what makes a module's imports a static, auditable list — so there
192    /// is nothing to compute here.
193    MalformedImport,
194    /// An `import` somewhere other than a module's top level — today, inside a
195    /// `recur` group. An import is a frame slot the const
196    /// stage fills before the module exists, so it can only be written in a
197    /// module's own source. Written anywhere else, `import` is an ordinary keyword
198    /// and reports [`ParseError::ReservedKeyword`].
199    MisplacedImport,
200    /// A `const` declaration that is neither `const <name>`, `const (<name> …)`
201    /// nor `<name> = const`: a declaration that is not a bare name, a group whose
202    /// chains are not bare names, or a **payload** — `const x = 1`, `x = const 1`.
203    /// The payload forms are reserved for const-time expressions and rejected
204    /// until those exist (see `docs/done/2026-08-13_elly-modules-iotas.md`).
205    MalformedConst,
206    /// A `const` somewhere other than a module's top level — today, inside a
207    /// `recur` group. A group is built while a program runs
208    /// and has no instance of its own to mint against, so an iota can only be
209    /// declared in a module's source. Written anywhere else, `const` is an
210    /// ordinary keyword and reports [`ParseError::ReservedKeyword`].
211    MisplacedConst,
212    /// A binder — a pattern binder, a `let` binding, a `recur` binding — taking a
213    /// name the enclosing module declares as an **import**. A declaration's name
214    /// means one thing over the whole module, so it may not be shadowed from
215    /// inside (see `docs/done/2026-08-13_elly-modules-iotas.md`). Carries the name.
216    ShadowsImport(Text),
217    /// A binder taking a name the enclosing module declares as a `const`
218    /// [iota](ParseError::ShadowsImport). Carries the name.
219    ShadowsIota(Text),
220    /// A `local` declaration that is neither `local <name> = <expr>` nor the
221    /// group `local (<name> = <expr> …)`: a left-hand side that is not a bare
222    /// name, a group chain that is not a binding, or a missing body. The
223    /// head-with-arguments form `local f x = <expr>` lands here too — it is a
224    /// template moditem, left open (see `docs/done/2026-08-13_elly-modules-local.md`).
225    MalformedLocal,
226    /// A `local` somewhere other than a module's top level — today, inside a
227    /// `recur` group, where every binding is private
228    /// already, so the keyword would say nothing. Written anywhere else, `local`
229    /// is an ordinary keyword and reports [`ParseError::ReservedKeyword`].
230    MisplacedLocal,
231    /// A binder taking a name the enclosing module declares as a
232    /// [`local`](ParseError::ShadowsImport). Carries the name.
233    ShadowsLocal(Text),
234    /// A binder taking a name the enclosing module — or an enclosing
235    /// `recur` group — declares as an **item**. Carries the
236    /// name. This is the direction opposite to
237    /// [`RecurShadowsOuter`](ParseError::RecurShadowsOuter), which stops a group
238    /// binding from shadowing a name already in scope; together they make a
239    /// declared name unshadowable from either side.
240    ShadowsItem(Text),
241    /// A `__` name the language does not define, in either position it can be
242    /// written: a module top-level left-hand side (`__mian = …`) or a reference
243    /// in an expression (`__mian`). The `__` namespace belongs to the language —
244    /// a source may write the builtins and the **reserved moditems** it defines
245    /// and nothing else — so a misspelling fails here, naming the namespace,
246    /// rather than becoming an item nobody reads or a name nothing binds.
247    /// Carries the name as written.
248    UnknownReserved(Text),
249    /// A module declaring the same reserved moditem twice (two `__main`s). A
250    /// reserved moditem is a single slot, not a table, so there is no second one
251    /// to hold. Carries the name.
252    DuplicateReserved(Text),
253    /// A reserved moditem somewhere other than a module's top level — today,
254    /// inside a `recur` group. A group is an item table,
255    /// not a module source, and nothing would ever read the slot. Written
256    /// anywhere else, `__main` is an ordinary `__` name and reports
257    /// [`UnknownReserved`](ParseError::UnknownReserved).
258    MisplacedReserved,
259    /// A [home-module leaf](crate::HomeLeaf) — `Self`, `#Self(…)`, `x.Self`, or
260    /// the `Self <pat>` pattern — outside a module body. Each reads the module
261    /// its body is written in, and there is none: the term is not a module's, or
262    /// it is a `recur` group in a program that is not a
263    /// module. One test covers both, since a group is not a module either.
264    /// Carries the name — `Self`, the one word every form is spelled with.
265    OutsideModule(Text),
266    /// An `as <Proto>` pattern whose reference is a `__` name that is not a
267    /// **prototype**: a builtin that is no module at all (`as __eq`), or `__Err`,
268    /// which is a namespace of operations that no value answers `__proto` with —
269    /// so the check could never hold. Rejected here rather than compiled into a
270    /// pattern that always refutes. Carries the name.
271    NotAPrototype(Text),
272    /// A [`&`](muon::Item::Prefixed)-headed **run** (`&x.0`, `&(a, b).0`) — a
273    /// binder glued to a following atom with no whitespace. A binder is one
274    /// prefixed atom, so the glued tail has no reading; write it spaced
275    /// (`&x .0`) to apply, or restructure. See `docs/done/2026-09-09_elly-parse-runs.md` §C.
276    BinderRunGlued,
277    /// An `<int>`-then-`.<digits>` run — the float-like run `3.14` (Muon groups
278    /// the two atoms, see `docs/done/2026-09-08_muon-runs.md`). Float literals do
279    /// not exist yet; this holds the notation open for them (a real float is a
280    /// later `Num` extension) while giving a clear error now instead of the
281    /// runtime `.not_applicable` a spaced `3 .14` projection raises.
282    FloatLiteralUnsupported,
283    /// A [`#`](muon::Item::Prefixed)-prefixed form other than the three that
284    /// read: `#[…]` (a list), `#{…}` (a map), and `#Self(…)` / `#Self …`
285    /// (construction). The `#` sigil marks a **literal notation**, and the
286    /// remaining shapes — `#<sym>`, `#<str>`, `#(…)`, `#.name` — are reserved
287    /// for future literal kinds with no reading yet. A bare `#Self` lands here
288    /// as well: construction is a form, not a value, so it may only head a
289    /// spine (see `Parser::app_spine`). See `docs/todo/elly-syntax-v1.md` §2.
290    ReservedLiteralForm,
291    /// A bare `[…]` where a list literal or list pattern is written. After the
292    /// reshuffle (phase 3), the list literal uses the `#`-prefixed spelling
293    /// exclusively: write `#[…]`. See `docs/todo/elly-syntax-v1.md` § 3–5.
294    BareListLiteral,
295    /// A bare `{…}` carrying a `:`-clause where a map literal or map pattern is
296    /// written. After the reshuffle, the map literal uses the `#`-prefixed
297    /// spelling exclusively: write `#{…}`. A `:`-clause has no reading in a block
298    /// (nothing legal in a block starts `<key> :`), so a brace group with one is
299    /// this error in every position; a brace group *without* a `:`-clause is a
300    /// block (step 6), and the empty `{}` is the unit value.
301    /// See `docs/todo/elly-syntax-v1.md` § 3–5.
302    BareMapLiteral,
303    /// A block `{…}` whose final clause is a binding (`{ …, x = v }`). A block's
304    /// value is its last clause's value and its bindings do not escape, so a
305    /// trailing binding would bind a name nothing can read and leave no value to
306    /// return — as a `let` with no body does. End the block with an expression.
307    /// See `docs/todo/elly-syntax-v1.md` § 5 (review) and step 6.
308    BlockTrailingBinding,
309    /// A `case` with no `{…}` arm block — `case x` or `case` alone, or a trailing
310    /// item that is not a brace group. The block is where the clauses live, so a
311    /// `case` without one has nothing to match against and no reading.
312    CaseMissingArms,
313    /// A `case` subject that is more than one item — `case f x { … }`. The subject
314    /// is exactly one item so the arm block's `{…}` is unambiguous; group a
315    /// compound subject (`case (f x) { … }`) or pipe it in (`f x |> case { … }`).
316    CaseSubjectExtra,
317    /// A `case` arm whose chain is not headed by `&` — a clause that is not a
318    /// binder. Every arm is `& <pattern> … <body>`; a bare expression is not one.
319    ArmNotBinder,
320    /// A `case` arm with a pattern (and any guards) but no body after them —
321    /// `& x` or `& x when (c)` alone. An arm's body is required.
322    ArmMissingBody,
323    /// A `when` guard with no `(…)` condition group — `when` at the end of a
324    /// header, or `when` followed by something other than a parenthesized group.
325    WhenMissingCond,
326}
327
328/// A parse error unwinding, with the span of the source it is about, or
329/// `NONE` until a caller that knows what it was parsing gives one: an error
330/// is placed at the innermost item, chain or declaration that places it.
331#[derive(Debug)]
332struct Failure(ParseError, ChunkSpan);
333
334/// A parser result.
335type Parsed<T> = Result<T, Failure>;
336
337impl From<ParseError> for Failure {
338    fn from(error: ParseError) -> Self {
339        Failure(error, ChunkSpan::NONE)
340    }
341}
342
343impl Failure {
344    /// The failure, placed at `span()` if it has no span yet.
345    fn or_at(self, span: impl FnOnce() -> ChunkSpan) -> Self {
346        match self.1.get() {
347            Some(_) => self,
348            None => Failure(self.0, span()),
349        }
350    }
351
352    /// The error a compile entry point reports, its span in the document
353    /// `src`. An error nothing placed is at the document's start.
354    fn into_error(self, src: Source<'_>) -> Error {
355        let span = self.1.in_doc(src.base).unwrap_or_default();
356        Error::Parse(self.0, span)
357    }
358}
359
360// ===========================================================================
361// entry points
362// ===========================================================================
363
364/// How an expression source resolves its free names.
365pub(crate) enum Root<'p> {
366    /// A free name is [`ParseError::UnboundName`].
367    Closed,
368    /// A free name is bound into the root frame, as if given: an open term.
369    Open,
370    /// The root frame holds `prelude`, and any other free name is unbound.
371    Prelude(&'p [Text]),
372}
373
374impl Ctx {
375    /// Parse expression source `src` into a new chunk named `__eval_N`: a top
376    /// module with only a body.
377    pub(crate) fn compile_expr(&self, src: &str, root: Root<'_>) -> Result<ChunkId, Error> {
378        let seq = muon::parse(src).map_err(Error::Syntax)?;
379        let (names, auto_bind) = match root {
380            Root::Closed => (Vec::new(), false),
381            Root::Open => (Vec::new(), true),
382            Root::Prelude(prelude) => (prelude.iter().map(|n| self.intern(n)).collect(), false),
383        };
384        let scope = Scope::new(self, names, 0, auto_bind, Vec::new());
385        let mut b = Builder::new();
386        let src = Source {
387            text: src,
388            base: b.add_doc(None, src),
389        };
390        let mut p = Parser::new(self, scope, b, src);
391        let top = p.b.enter_module();
392        let (body, span) = p.program(&seq).map_err(|f| f.into_error(src))?;
393        let body = p.b.push(body, span);
394        let mark = p.b.member_scratch.len();
395        p.b.leave_module(top, None, mark, 0, 0, &[], Vec::new(), body);
396        Ok(self.add_built(p.b, None, "__eval_"))
397    }
398
399    /// Compile module source `src` against a frame of ordered names into a
400    /// new chunk named `name`, or `__module_N` if it has none.
401    ///
402    /// References to frame names resolve to de Bruijn locals. Lookup order:
403    /// enclosing binders $\to$ imports $\to$ module members (iotas $\to$ items
404    /// $\to$ locals) $\to$ frame $\to$ builtin aliases. Module imports occupy
405    /// the leading frame slots.
406    /// `doc` is where the source came from, `None` for an anonymous source.
407    pub(crate) fn compile_source(
408        &self,
409        src: &str,
410        frame: &[Text],
411        name: Option<ModuleName>,
412        doc: Option<DocSource>,
413    ) -> Result<ChunkId, Error> {
414        let seq = muon::parse(src).map_err(Error::Syntax)?;
415        let b = self.emit_module(&seq, src, doc, frame, name.clone())?;
416        Ok(self.add_built(b, name, "__module_"))
417    }
418
419    fn emit_module(
420        &self,
421        seq: &Seq<'_>,
422        src: &str,
423        doc: Option<DocSource>,
424        frame: &[Text],
425        name: Option<ModuleName>,
426    ) -> Result<Builder, Error> {
427        let mut b = Builder::new();
428        let source = Source {
429            text: src,
430            base: b.add_doc(doc, src),
431        };
432        let chains = chain_slices(seq);
433        let decls = declarations(&chains, self, source).map_err(|f| f.into_error(source))?;
434        let items = sorted(&decls.items);
435        let locals = sorted(&decls.locals);
436        let iotas = sorted(&decls.iotas);
437        // The imports are the leading frame slots, searched before the
438        // members; the host's frame names follow them.
439        let mut slots: Vec<Sym> = decls.imports.iter().map(|(n, _)| *n).collect();
440        slots.extend(frame.iter().map(|n| self.intern(n)));
441        let members = Barrier::Module {
442            items: items.clone(),
443            locals: locals.clone(),
444            iotas: iotas.clone(),
445        };
446        let scope = Scope::new(
447            self,
448            slots.clone(),
449            decls.imports.len(),
450            false,
451            alloc::vec![members],
452        );
453        let mut p = Parser::new(self, scope, b, source);
454        let top = p.b.enter_module();
455        let (mark, body) = p
456            .members(&decls, &items, &locals, &iotas)
457            .map_err(|f| f.into_error(source))?;
458        let specs = decls.imports.into_iter().map(|(_, spec)| spec).collect();
459        let (n_items, n_locals) = (len32(items.len()), len32(locals.len()));
460        p.b.leave_module(top, name, mark, n_items, n_locals, &slots, specs, body);
461        Ok(p.b)
462    }
463}
464
465/// The text a chunk's document was parsed from, and its first position in
466/// the chunk: what turns the Muon tree's items into chunk spans.
467#[derive(Clone, Copy)]
468struct Source<'s> {
469    text: &'s str,
470    base: Pos,
471}
472
473impl Source<'_> {
474    /// The span of `item`, which was parsed from this source.
475    fn item(self, item: &Item<'_>) -> ChunkSpan {
476        let span = item.span(self.text);
477        ChunkSpan::new(self.base + span.offset, span.length)
478    }
479
480    /// The span from the first of `items` to the last, which were parsed from
481    /// this source in order.
482    fn items(self, items: &[&Item<'_>]) -> ChunkSpan {
483        match items {
484            [] => ChunkSpan::NONE,
485            [item] => self.item(item),
486            [first, .., last] => self.item(first).join(self.item(last)),
487        }
488    }
489
490    /// The span of a bracket group, its brackets included.
491    fn group(self, group: &Group<'_>) -> ChunkSpan {
492        ChunkSpan::new(self.base + group.span.offset, group.span.length)
493    }
494}
495
496/// `names`, sorted by `Sym`: the order of a member index space.
497fn sorted(names: &[Sym]) -> Vec<Sym> {
498    let mut names = names.to_vec();
499    names.sort();
500    names
501}
502
503// ===========================================================================
504// module declarations
505// ===========================================================================
506
507/// One body a module source declares, read in the declaration walk and
508/// emitted in the second.
509enum Decl<'b, 'a> {
510    /// `name = <body>`.
511    Item(Sym, Vec<&'b Item<'a>>),
512    /// `Name = import "…"` or `name = const`: an item that forwards the
513    /// import or the iota of the same name.
514    Forward(Sym),
515    /// `local name = <body>`.
516    Local(Sym, Vec<&'b Item<'a>>),
517    /// `__main = <body>`, the reserved moditem.
518    Main(Vec<&'b Item<'a>>),
519}
520
521/// A module source's left-hand sides, read before any body.
522struct Decls<'b, 'a> {
523    /// What each body is, in source order, with its declaration's span.
524    bodies: Vec<(Decl<'b, 'a>, ChunkSpan)>,
525    /// The span of every member's declaration, sorted by name (see
526    /// [`span`](Self::span)).
527    spans: Vec<(Sym, ChunkSpan)>,
528    /// The item names, in source order.
529    items: Vec<Sym>,
530    /// The `local` names, in source order.
531    locals: Vec<Sym>,
532    /// The names declared by `const`, in source order — the module's iotas.
533    iotas: Vec<Sym>,
534    /// The `import` declarations, in source order, which is frame-slot order.
535    imports: Vec<(Sym, ImportSpec)>,
536    has_main: bool,
537}
538
539impl Decls<'_, '_> {
540    /// The span of member `name`'s declaration: `name = body`, or the name of
541    /// an iota a group declares. An item and an iota of one name are one
542    /// declaration, `name = const`.
543    fn span(&self, name: Sym) -> ChunkSpan {
544        match self.spans.binary_search_by(|(n, _)| n.cmp(&name)) {
545            Ok(i) => self.spans[i].1,
546            Err(_) => ChunkSpan::NONE,
547        }
548    }
549}
550
551/// Read a module source's (or a `recur` group's) declarations: the items,
552/// locals, iotas, imports and `__main`, with their bodies unparsed, rejecting
553/// a name declared twice.
554///
555/// Items are single bare names in v0. An import is both a frame slot and, in
556/// its re-exporting form `Foo = import "spec"`, an item.
557fn declarations<'b, 'a>(
558    chains: &[Vec<&'b Item<'a>>],
559    ctx: &Ctx,
560    src: Source<'_>,
561) -> Parsed<Decls<'b, 'a>> {
562    let mut d = Decls {
563        bodies: Vec::new(),
564        spans: Vec::new(),
565        items: Vec::new(),
566        locals: Vec::new(),
567        iotas: Vec::new(),
568        imports: Vec::new(),
569        has_main: false,
570    };
571    let mut re = Reexports::default();
572    for ci in chains {
573        declaration(ci, ctx, src, &mut d, &mut re).map_err(|f| f.or_at(|| src.items(ci)))?;
574    }
575    // A name declared twice in two kinds is placed at its later declaration
576    // that has a span: a private import records none.
577    let dup = |name: &Sym| {
578        let span = d.spans.iter().rev().find(|(n, _)| n == name);
579        Err(Failure(
580            ParseError::DuplicateModuleItem,
581            span.map_or(ChunkSpan::NONE, |(_, span)| *span),
582        ))
583    };
584    for (name, _) in &d.imports {
585        if d.iotas.contains(name) || !re.imports.contains(name) && d.items.contains(name) {
586            return dup(name);
587        }
588    }
589    for iota in &d.iotas {
590        if !re.iotas.contains(iota) && d.items.contains(iota) {
591            return dup(iota);
592        }
593    }
594    for name in &d.locals {
595        let taken = d.items.contains(name)
596            || d.iotas.contains(name)
597            || d.imports.iter().any(|(n, _)| n == name);
598        if taken {
599            return dup(name);
600        }
601    }
602    // Stable, so that of an item and an iota of one name the first stays.
603    d.spans.sort_by_key(|(name, _)| *name);
604    Ok(d)
605}
606
607/// The imports and iotas a module's items re-export, by name.
608#[derive(Default)]
609struct Reexports {
610    imports: Vec<Sym>,
611    iotas: Vec<Sym>,
612}
613
614/// Read one declaration, the chain `ci`, into `d`.
615fn declaration<'b, 'a>(
616    ci: &[&'b Item<'a>],
617    ctx: &Ctx,
618    src: Source<'_>,
619    d: &mut Decls<'b, 'a>,
620    re: &mut Reexports,
621) -> Parsed<()> {
622    let span = src.items(ci);
623    let split = ci.iter().position(|it| matches!(it, Item::Punct("=")));
624    if split.is_none() && head_keyword(ci[0]) == Some("import") {
625        let (name, spec) = parse_private_import(ci, ctx, src)?;
626        push_import(&mut d.imports, name, spec)?;
627        return Ok(());
628    }
629    if split.is_none() && head_keyword(ci[0]) == Some("const") {
630        for (name, span) in parse_const_decl(ci, ctx, src)? {
631            push_name(&mut d.iotas, name)?;
632            d.spans.push((name, span));
633        }
634        return Ok(());
635    }
636    if split.is_none() && head_keyword(ci[0]) == Some("local") {
637        for (name, body, span) in parse_local_group(ci, ctx, src)? {
638            push_name(&mut d.locals, name)?;
639            d.spans.push((name, span));
640            d.bodies.push((Decl::Local(name, body), span));
641        }
642        return Ok(());
643    }
644    let eq = split.ok_or(ParseError::MalformedBinding)?;
645    if ci[eq + 1..].iter().any(|it| matches!(it, Item::Punct("="))) {
646        return Err(ParseError::MalformedBinding.into());
647    }
648    let (head, tail) = (&ci[..eq], &ci[eq + 1..]);
649    if head.is_empty() || tail.is_empty() {
650        return Err(ParseError::MalformedBinding.into());
651    }
652    if head.len() > 1 && head_keyword(head[0]) == Some("const") {
653        return Err(ParseError::MalformedConst.into());
654    }
655    if head.len() > 1 && head_keyword(head[0]) == Some("local") {
656        let name = plain_name(&head[1..], ParseError::MalformedLocal, ctx, src)?;
657        push_name(&mut d.locals, name)?;
658        d.spans.push((name, span));
659        d.bodies.push((Decl::Local(name, tail.to_vec()), span));
660        return Ok(());
661    }
662    if let [Item::Sym(s)] = head {
663        if let Some(reserved) = reserved_moditem(s) {
664            if d.has_main {
665                let error = ParseError::DuplicateReserved(text(reserved));
666                return Err(Failure(error, src.items(head)));
667            }
668            d.has_main = true;
669            d.bodies.push((Decl::Main(tail.to_vec()), span));
670            return Ok(());
671        }
672        if s.starts_with("__") {
673            let error = ParseError::UnknownReserved(text(s));
674            return Err(Failure(error, src.items(head)));
675        }
676    }
677    let name = plain_name(head, ParseError::BadModuleItemName, ctx, src)?;
678    if d.items.contains(&name) {
679        return Err(ParseError::DuplicateModuleItem.into());
680    }
681    if head_keyword(tail[0]) == Some("import") {
682        let spec = parse_import_spec(tail)?;
683        push_import(&mut d.imports, name, spec)?;
684        re.imports.push(name);
685        d.items.push(name);
686        d.spans.push((name, span));
687        d.bodies.push((Decl::Forward(name), span));
688        return Ok(());
689    }
690    if head_keyword(tail[0]) == Some("const") {
691        if tail.len() != 1 {
692            return Err(ParseError::MalformedConst.into());
693        }
694        push_name(&mut d.iotas, name)?;
695        re.iotas.push(name);
696        d.items.push(name);
697        d.spans.push((name, span));
698        d.bodies.push((Decl::Forward(name), span));
699        return Ok(());
700    }
701    d.items.push(name);
702    d.spans.push((name, span));
703    d.bodies.push((Decl::Item(name, tail.to_vec()), span));
704    Ok(())
705}
706
707/// The reserved moditems the language defines, as the names a module top-level
708/// left-hand side may spell in the `__` namespace. One so far; `__test` and
709/// `__doc` are anticipated, and each will be another slot on a module with a
710/// clause here (see `docs/done/2026-08-14_elly-run.md`).
711///
712/// Answers with the `'static` spelling so the caller reports the *defined* name
713/// rather than echoing the source, and `None` for a name that is not one — which
714/// the caller turns into [`ParseError::UnknownReserved`] rather than an item.
715fn reserved_moditem(name: &str) -> Option<&'static str> {
716    match name {
717        "__main" => Some("__main"),
718        _ => None,
719    }
720}
721
722/// Record one import, rejecting a name a previous import already took.
723fn push_import(imports: &mut Vec<(Sym, ImportSpec)>, name: Sym, spec: ImportSpec) -> Parsed<()> {
724    if imports.iter().any(|(n, _)| *n == name) {
725        return Err(ParseError::DuplicateModuleItem.into());
726    }
727    imports.push((name, spec));
728    Ok(())
729}
730
731/// Record one iota or local, rejecting a name a previous one already took.
732fn push_name(names: &mut Vec<Sym>, name: Sym) -> Parsed<()> {
733    if names.contains(&name) {
734        return Err(ParseError::DuplicateModuleItem.into());
735    }
736    names.push(name);
737    Ok(())
738}
739
740/// Parse `import "<spec>" as <Name>` — the private form, whose whole chain is the
741/// declaration — to its name and spec. The keyword may be a bare sym or a glued
742/// run head (§D); in the run case only the spec glues into the run (`import"spec"`)
743/// while `as <Name>` stays in the chain after it, so the two are concatenated back
744/// into the one item stream the spaced form already is.
745fn parse_private_import<'a>(
746    ci: &[&Item<'a>],
747    ctx: &Ctx,
748    src: Source<'_>,
749) -> Parsed<(Sym, ImportSpec)> {
750    let rest: Vec<&Item<'a>> = match ci[0] {
751        Item::Run(members) => members[1..]
752            .iter()
753            .map(|it| it as &Item<'a>)
754            .chain(ci[1..].iter().copied())
755            .collect(),
756        _ => ci[1..].to_vec(),
757    };
758    match rest.as_slice() {
759        [Item::Str(spec), Item::Sym("as"), rest @ ..] if !rest.is_empty() => {
760            let name = plain_name(rest, ParseError::BadModuleItemName, ctx, src)?;
761            Ok((name, decode_str(spec)?))
762        }
763        _ => Err(ParseError::MalformedImport.into()),
764    }
765}
766
767/// Parse a private `const` declaration — `const <name>`, or the group form
768/// `const (<name> …)` — to the names it declares.
769///
770/// The group is an ordinary `<parens>` wrapping a `<seq>`, so its names may be
771/// newline- or comma-separated and it may carry comments, exactly as a `let`
772/// group may. An **empty group declares nothing**, the same degenerate reading
773/// `let ()` and `recur ()` take.
774fn parse_const_decl<'a>(
775    ci: &[&Item<'a>],
776    ctx: &Ctx,
777    src: Source<'_>,
778) -> Parsed<Vec<(Sym, ChunkSpan)>> {
779    // The keyword may be a bare sym or a glued run head (§D); the declared names
780    // are read from what follows it (the run's members in the run case).
781    let rest: Vec<&Item<'a>> = match ci[0] {
782        Item::Run(members) => members[1..].iter().map(|it| it as &Item<'a>).collect(),
783        _ => ci[1..].to_vec(),
784    };
785    match rest.as_slice() {
786        [Item::Parens(Group { seq, .. }), ..] => {
787            let mut names = Vec::new();
788            for c in chain_slices(seq) {
789                let name = plain_name(&c, ParseError::MalformedConst, ctx, src)?;
790                names.push((name, src.items(&c)));
791            }
792            Ok(names)
793        }
794        rest if !rest.is_empty() => {
795            let name = plain_name(rest, ParseError::MalformedConst, ctx, src)?;
796            Ok(alloc::vec![(name, src.items(rest))])
797        }
798        _ => Err(ParseError::MalformedConst.into()),
799    }
800}
801
802/// Parse group form `local (<name> = <expr> …)` to its names and unparsed bodies.
803///
804/// The group is an ordinary `<parens>` wrapping a `<seq>`. An empty group
805/// declares nothing. The single form `local x = <expr>` is handled separately.
806#[allow(clippy::type_complexity)]
807fn parse_local_group<'b, 'a>(
808    ci: &[&'b Item<'a>],
809    ctx: &Ctx,
810    src: Source<'_>,
811) -> Parsed<Vec<(Sym, Vec<&'b Item<'a>>, ChunkSpan)>> {
812    let group = match ci[0] {
813        Item::Run(members) => members.get(1),
814        _ => ci.get(1).copied(),
815    };
816    let Some(Item::Parens(Group { seq, .. })) = group else {
817        return Err(ParseError::MalformedLocal.into());
818    };
819    let mut bindings = Vec::new();
820    for c in chain_slices(seq) {
821        let malformed = || Failure(ParseError::MalformedLocal, src.items(&c));
822        let eq = c
823            .iter()
824            .position(|it| matches!(it, Item::Punct("=")))
825            .ok_or_else(malformed)?;
826        let (head, tail) = (&c[..eq], &c[eq + 1..]);
827        if head.is_empty() || tail.is_empty() {
828            return Err(malformed());
829        }
830        if tail.iter().any(|it| matches!(it, Item::Punct("="))) {
831            return Err(malformed());
832        }
833        bindings.push((
834            plain_name(head, ParseError::MalformedLocal, ctx, src)?,
835            tail.to_vec(),
836            src.items(&c),
837        ));
838    }
839    Ok(bindings)
840}
841
842/// Parse the `import "<spec>"` RHS of a re-exporting import to its spec.
843/// Handles both spaced and glued spellings.
844fn parse_import_spec(tail: &[&Item<'_>]) -> Parsed<ImportSpec> {
845    let spec = match tail {
846        [_import, Item::Str(spec)] => spec,
847        [Item::Run(members)] => match members.as_slice() {
848            [_import, Item::Str(spec)] => spec,
849            _ => return Err(ParseError::MalformedImport.into()),
850        },
851        _ => return Err(ParseError::MalformedImport.into()),
852    };
853    decode_str(spec)
854}
855
856/// Parse a single plain name for a binding position.
857/// Must be exactly one `<sym>` that is not a keyword, `__` name, discard, or number.
858fn plain_name(items: &[&Item<'_>], bad: ParseError, ctx: &Ctx, src: Source<'_>) -> Parsed<Sym> {
859    plain_name_at(items, bad, ctx).map_err(|f| f.or_at(|| src.items(items)))
860}
861
862/// [`plain_name`], unplaced.
863fn plain_name_at(items: &[&Item<'_>], bad: ParseError, ctx: &Ctx) -> Parsed<Sym> {
864    match items {
865        [Item::Sym(s)] if is_keyword(s) => Err(ParseError::ReservedKeyword.into()),
866        [Item::Sym(s)] if s.starts_with("__") => Err(ParseError::ReservedName.into()),
867        [Item::Sym(s)] if reads_as_number(s) || *s == "_" => Err(bad.into()),
868        [Item::Sym(s)] => Ok(ctx.intern(s)),
869        _ => Err(bad.into()),
870    }
871}
872
873/// Check if a `<sym>` reads as a number (valid integer or digit-leading).
874fn reads_as_number(s: &str) -> bool {
875    parse_int(s).is_some() || s.as_bytes()[0].is_ascii_digit()
876}
877
878// ===========================================================================
879// the parser
880// ===========================================================================
881
882/// Where an expression lands, which decides what an `&` there becomes.
883///
884/// A slot passes through the forms that are their inner expression — a
885/// one-chain `( … )`, `{ e }`, `let () e`, `recur () e`, a pipe's right-hand
886/// side — and every other form parses its parts in slots of their own.
887#[derive(Debug, Clone, Copy, PartialEq, Eq)]
888enum Slot {
889    /// Anywhere else: an `&` captures a fresh frame.
890    Value,
891    /// Applied where it is made — a callee, a builtin's thunk argument: an
892    /// `&` shares the enclosing environment ([`Plan::Chain`](crate::code::Plan)).
893    Applied,
894    /// A list argument a builtin applies elementwise: its elements are
895    /// [`Applied`](Slot::Applied).
896    AppliedElems,
897    /// Directly the body of a lambda whose head is still open: an `&` extends
898    /// the head, so `&a &b body` is one lambda of two parameters.
899    Body,
900}
901
902/// A node and its span.
903type Spanned = (Node, ChunkSpan);
904
905/// A parsed expression, its descendants pushed.
906enum Built {
907    /// Its own node, unpushed, and its span.
908    Node(Node, ChunkSpan),
909    /// An application spine still open on the scratch stack, so a trailing
910    /// argument or a pipe operand can extend it.
911    Spine(Spine),
912}
913
914/// An application spine on the builder's scratch stack: the callee at
915/// `mark`, then the arguments so far.
916struct Spine {
917    mark: usize,
918    /// The callee, if it is a builtin: it decides its arguments' slots.
919    callee: Option<Builtin>,
920    /// The span of the application so far.
921    span: ChunkSpan,
922}
923
924/// A lambda whose head is being parsed: its parameters are
925/// `pat_scratch[pats..]`.
926struct Head {
927    pats: usize,
928    /// For a [`Chain`](crate::code::Plan::Chain) lambda, the enclosing
929    /// activation's mark to unbind its parameters to; `None` for one that
930    /// opened an activation of its own.
931    chain: Option<usize>,
932}
933
934/// A name or literal as written, before it is resolved.
935enum SymRef {
936    Home,
937    Int(BigInt),
938    Builtin(Builtin),
939    Name(Sym),
940}
941
942/// The most parameters one lambda takes: a closure counts its applied
943/// arguments in a `u8`.
944const MAX_HEAD: usize = u8::MAX as usize;
945
946/// Emits one chunk from the Muon tree, resolving as it goes.
947struct Parser<'c, 's> {
948    ctx: &'c Ctx,
949    b: Builder,
950    scope: Scope<'c>,
951    /// The lambdas whose heads are open, innermost last.
952    heads: Vec<Head>,
953    /// The source being parsed, which gives the spans.
954    src: Source<'s>,
955}
956
957impl<'c, 's> Parser<'c, 's> {
958    /// A parser emitting into `b`, which has recorded `src` as a document.
959    fn new(ctx: &'c Ctx, scope: Scope<'c>, b: Builder, src: Source<'s>) -> Self {
960        Parser {
961            ctx,
962            b,
963            scope,
964            heads: Vec::new(),
965            src,
966        }
967    }
968
969    /// A program: a single top-level chain.
970    fn program(&mut self, seq: &Seq<'_>) -> Parsed<Spanned> {
971        match chain_slices(seq).as_slice() {
972            [] => Err(ParseError::EmptyProgram.into()),
973            [items] => {
974                let built = self.expr(items, Slot::Value)?;
975                Ok(self.close(built))
976            }
977            [_, second, ..] => Err(Failure(
978                ParseError::MultipleExpressions,
979                self.src.items(second),
980            )),
981        }
982    }
983
984    /// Emits the bodies `decls` declares into the member slots of the module
985    /// being emitted, whose sorted names are `items`, `locals` and `iotas`.
986    /// Returns the members' scratch mark and the `__main` body, or `NONE`.
987    ///
988    /// The bodies are emitted in source order, so errors come in source
989    /// order, and their members land in sorted order.
990    fn members(
991        &mut self,
992        decls: &Decls<'_, '_>,
993        items: &[Sym],
994        locals: &[Sym],
995        iotas: &[Sym],
996    ) -> Parsed<(usize, NodeId)> {
997        let mark = self.b.member_scratch.len();
998        for &name in items.iter().chain(locals).chain(iotas) {
999            let member = Member {
1000                name,
1001                node: NodeId::NONE,
1002            };
1003            self.b.member_scratch.push((member, decls.span(name)));
1004        }
1005        let slot = |name: Sym, names: &[Sym]| {
1006            names
1007                .binary_search(&name)
1008                .expect("a declared name has a slot")
1009        };
1010        let mut main = NodeId::NONE;
1011        for (decl, span) in &decls.bodies {
1012            let (index, node) = match decl {
1013                Decl::Item(name, body) => (slot(*name, items), self.body(body)?),
1014                Decl::Forward(name) => {
1015                    let node = self.name_ref(*name).map_err(|f| f.or_at(|| *span))?;
1016                    (slot(*name, items), self.b.push(node, *span))
1017                }
1018                Decl::Local(name, body) => (items.len() + slot(*name, locals), self.body(body)?),
1019                Decl::Main(body) => {
1020                    main = self.body(body)?;
1021                    continue;
1022                }
1023            };
1024            self.b.member_scratch[mark + index].0.node = node;
1025        }
1026        Ok((mark, main))
1027    }
1028
1029    /// A member's body, pushed.
1030    fn body(&mut self, items: &[&Item<'_>]) -> Parsed<NodeId> {
1031        let built = self.expr(items, Slot::Value)?;
1032        Ok(self.child(built))
1033    }
1034
1035    // -----------------------------------------------------------------------
1036    // spines
1037    // -----------------------------------------------------------------------
1038
1039    /// The node of `built` and its span, closing an open spine.
1040    fn close(&mut self, built: Built) -> Spanned {
1041        match built {
1042            Built::Node(node, span) => (node, span),
1043            Built::Spine(spine) => self.close_spine(spine),
1044        }
1045    }
1046
1047    /// `built`, pushed.
1048    fn child(&mut self, built: Built) -> NodeId {
1049        let (node, span) = self.close(built);
1050        self.b.push(node, span)
1051    }
1052
1053    /// An application of the spine's run, or its one element.
1054    fn close_spine(&mut self, spine: Spine) -> Spanned {
1055        let len = self.b.scratch.len() - spine.mark;
1056        if len == 1 {
1057            return self.b.scratch.pop().expect("a spine has a callee");
1058        }
1059        let app = Node::App {
1060            len: len32(len),
1061            args: self.b.push_run(Run::App, spine.mark),
1062        };
1063        (app, spine.span)
1064    }
1065
1066    /// A spine with `callee` and no arguments yet.
1067    fn start(&mut self, callee: Spanned) -> Spine {
1068        let mark = self.b.scratch.len();
1069        self.b.scratch.push(callee);
1070        Spine {
1071            mark,
1072            callee: builtin_of(callee.0),
1073            span: callee.1,
1074        }
1075    }
1076
1077    /// A spine that `built` heads, to apply to one more argument: an open
1078    /// spine extends, and so does a closed application, whose run is copied
1079    /// (`(f x) &y b` is `f` applied to `x` and the lambda).
1080    fn open(&mut self, built: Built) -> Spine {
1081        match built {
1082            Built::Spine(spine) => spine,
1083            Built::Node(Node::App { len, args }, span) => {
1084                let mark = self.b.scratch.len();
1085                for i in 0..len {
1086                    let node = self.b.node(args.nth(i));
1087                    self.b.scratch.push(node);
1088                }
1089                Spine {
1090                    mark,
1091                    callee: builtin_of(self.b.scratch[mark].0),
1092                    span,
1093                }
1094            }
1095            Built::Node(node, span) => self.start((node, span)),
1096        }
1097    }
1098
1099    /// The slot of `spine`'s next argument.
1100    fn arg_slot(&self, spine: &Spine) -> Slot {
1101        let i = self.b.scratch.len() - spine.mark - 1;
1102        match spine.callee {
1103            Some(op) if op.applies_arg(i) => Slot::Applied,
1104            Some(op) if op.applies_elements_of(i) => Slot::AppliedElems,
1105            _ => Slot::Value,
1106        }
1107    }
1108
1109    /// The slot of the next element of a spine being built: the callee of a
1110    /// spine of several elements is applied where it is made.
1111    fn next_slot(&self, spine: &Option<Spine>) -> Slot {
1112        match spine {
1113            None => Slot::Applied,
1114            Some(spine) => self.arg_slot(spine),
1115        }
1116    }
1117
1118    /// Applies `spine` to one more argument. A builtin module applied to the
1119    /// symbol of one of its members folds to the member's own leaf
1120    /// (`__Int .add` is `__Int.add`), so a written member costs no lookup.
1121    fn extend(&mut self, spine: &mut Spine, arg: Spanned) {
1122        spine.span = spine.span.join(arg.1);
1123        if self.b.scratch.len() - spine.mark == 1 {
1124            if let (Some(ns), Node::Symbol(m)) = (spine.callee, arg.0) {
1125                if let Some(op) = ns.member(m) {
1126                    self.b.scratch[spine.mark] = (Node::Builtin(op), spine.span);
1127                    spine.callee = Some(op);
1128                    return;
1129                }
1130            }
1131        }
1132        self.b.scratch.push(arg);
1133    }
1134
1135    /// Adds `arg` to the spine being built, starting it if it is the first.
1136    fn push_arg(&mut self, spine: &mut Option<Spine>, arg: Spanned) {
1137        match spine {
1138            None => *spine = Some(self.start(arg)),
1139            Some(spine) => self.extend(spine, arg),
1140        }
1141    }
1142
1143    // -----------------------------------------------------------------------
1144    // expressions
1145    // -----------------------------------------------------------------------
1146
1147    /// A comment-free slice of items. An error in it that nothing below
1148    /// placed is placed at the slice, as is each of the methods below that
1149    /// parse an item or a chain.
1150    fn expr(&mut self, items: &[&Item<'_>], slot: Slot) -> Parsed<Built> {
1151        let src = self.src;
1152        self.pipeline(items, slot)
1153            .map_err(|f| f.or_at(|| src.items(items)))
1154    }
1155
1156    /// [`expr`](Self::expr), unplaced.
1157    fn pipeline(&mut self, items: &[&Item<'_>], slot: Slot) -> Parsed<Built> {
1158        if items.is_empty() {
1159            return Err(ParseError::EmptyChain.into());
1160        }
1161        // `|>` is the loosest operator: split on the last one to form a
1162        // left-associative pipe chain over segments. The right side is parsed
1163        // first, so the left knows the slot it lands in.
1164        match items.iter().rposition(|it| matches!(it, Item::Punct("|>"))) {
1165            None => self.segment(items, slot),
1166            Some(k) => {
1167                let (left, right) = (&items[..k], &items[k + 1..]);
1168                if left.is_empty() || right.is_empty() {
1169                    let pipe = self.src.item(items[k]);
1170                    return Err(Failure(ParseError::MalformedPipe, pipe));
1171                }
1172                let r = self.segment(right, Slot::Applied)?;
1173                let mut spine = self.open(r);
1174                let slot = self.arg_slot(&spine);
1175                let l = self.expr(left, slot)?;
1176                let l = self.close(l);
1177                self.extend(&mut spine, l);
1178                Ok(Built::Spine(spine))
1179            }
1180        }
1181    }
1182
1183    /// One pipe segment (a `|>`-free slice).
1184    ///
1185    /// This is an application spine whose trailing argument may be a `&`/`let`/`recur`
1186    /// binder form. Split points have lowest precedence within the segment.
1187    fn segment(&mut self, items: &[&Item<'_>], slot: Slot) -> Parsed<Built> {
1188        match items.iter().position(|it| is_split_point(it)) {
1189            None => self.app_spine(items, slot),
1190            Some(0) => match head_keyword(items[0]) {
1191                Some("let") | Some("recur") => self.let_or_recur(items[0], items, slot),
1192                Some("case") => self.case(items, slot),
1193                _ => self.binder(items, slot),
1194            },
1195            Some(k) => {
1196                let f = self.segment(&items[..k], Slot::Applied)?;
1197                let mut spine = self.open(f);
1198                let slot = self.arg_slot(&spine);
1199                let arg = self.segment(&items[k..], slot)?;
1200                let arg = self.close(arg);
1201                self.extend(&mut spine, arg);
1202                Ok(Built::Spine(spine))
1203            }
1204        }
1205    }
1206
1207    /// An application spine, folding left. `(…)` items are spread into
1208    /// multiple arguments. A spine of one element is that element, in the
1209    /// slot the spine is in.
1210    fn app_spine(&mut self, items: &[&Item<'_>], slot: Slot) -> Parsed<Built> {
1211        if let [item] = items {
1212            match item {
1213                Item::Parens(group) => return self.group(group, slot),
1214                Item::Run(_) => {}
1215                _ => return self.aexpr(item, slot),
1216            }
1217        }
1218        let mut spine: Option<Spine> = None;
1219        for (i, it) in items.iter().enumerate() {
1220            match *it {
1221                Item::Run(members) if i == 0 => {
1222                    let src = self.src;
1223                    check_run_head(members).map_err(|f| f.or_at(|| src.item(it)))?;
1224                    let members = if is_self_head(members) {
1225                        let new = self.home(HomeLeaf::New)?;
1226                        let span = self.src.item(&members[0]);
1227                        self.push_arg(&mut spine, (new, span));
1228                        &members[1..]
1229                    } else {
1230                        &members[..]
1231                    };
1232                    for m in members {
1233                        self.expand_arg(m, &mut spine)?;
1234                    }
1235                }
1236                Item::Prefixed { sigil: '#', item }
1237                    if i == 0 && matches!(item.as_ref(), Item::Sym("Self")) =>
1238                {
1239                    let new = self.home(HomeLeaf::New)?;
1240                    let span = self.src.item(it);
1241                    self.push_arg(&mut spine, (new, span));
1242                }
1243                _ => self.expand_arg(it, &mut spine)?,
1244            }
1245        }
1246        let spine = spine.expect("a spine has at least one item");
1247        let len = self.b.scratch.len() - spine.mark;
1248        if len == 1 {
1249            if let Node::Home {
1250                leaf: HomeLeaf::New,
1251                ..
1252            } = self.b.scratch[spine.mark].0
1253            {
1254                let span = self.b.scratch[spine.mark].1;
1255                return Err(Failure(ParseError::ReservedLiteralForm, span));
1256            }
1257            let (node, span) = self.close_spine(spine);
1258            return Ok(Built::Node(node, span));
1259        }
1260        Ok(Built::Spine(spine))
1261    }
1262
1263    /// Expand one chain item into arguments of the spine being built. `(…)`
1264    /// groups spread; `.Self` reads the payload of everything before it;
1265    /// other items are single atoms.
1266    fn expand_arg(&mut self, item: &Item<'_>, spine: &mut Option<Spine>) -> Parsed<()> {
1267        match item {
1268            Item::Parens(group) => {
1269                let chains = chain_slices(&group.seq);
1270                if chains.is_empty() {
1271                    let span = self.src.group(group);
1272                    self.push_arg(spine, (Node::Unit, span));
1273                }
1274                for chain in &chains {
1275                    let slot = self.next_slot(spine);
1276                    let arg = self.expr(chain, slot)?;
1277                    let arg = self.close(arg);
1278                    self.push_arg(spine, arg);
1279                }
1280                // The application covers the group's brackets too.
1281                if let Some(spine) = spine {
1282                    spine.span = spine.span.join(self.src.group(group));
1283                }
1284                Ok(())
1285            }
1286            Item::Prefixed { sigil: '.', item }
1287                if matches!(item.as_ref(), Item::Sym("Self")) && spine.is_some() =>
1288            {
1289                let acc = self.close_spine(spine.take().expect("checked"));
1290                let value = self.home(HomeLeaf::Value)?;
1291                let value_span = self.src.item(item);
1292                let span = acc.1.join(value_span);
1293                let mark = self.b.scratch.len();
1294                self.b.scratch.push((value, value_span));
1295                self.b.scratch.push(acc);
1296                let read = Node::App {
1297                    len: 2,
1298                    args: self.b.push_run(Run::App, mark),
1299                };
1300                *spine = Some(self.start((read, span)));
1301                Ok(())
1302            }
1303            _ => {
1304                let slot = self.next_slot(spine);
1305                let arg = self.aexpr(item, slot)?;
1306                let arg = self.close(arg);
1307                self.push_arg(spine, arg);
1308                Ok(())
1309            }
1310        }
1311    }
1312
1313    /// A `(…)` group as an atom: `()` is unit, `(e)` is `e`, and `(a, b, …)`
1314    /// a spread spine.
1315    fn group(&mut self, group: &Group<'_>, slot: Slot) -> Parsed<Built> {
1316        let chains = chain_slices(&group.seq);
1317        match chains.as_slice() {
1318            [] => Ok(Built::Node(Node::Unit, self.src.group(group))),
1319            [chain] => self.expr(chain, slot),
1320            chains => {
1321                let mut spine = None;
1322                for chain in chains {
1323                    let slot = self.next_slot(&spine);
1324                    let arg = self.expr(chain, slot)?;
1325                    let arg = self.close(arg);
1326                    self.push_arg(&mut spine, arg);
1327                }
1328                let mut spine = spine.expect("a group of several chains");
1329                spine.span = self.src.group(group);
1330                Ok(Built::Spine(spine))
1331            }
1332        }
1333    }
1334
1335    /// A single item as an atomic expression.
1336    fn aexpr(&mut self, item: &Item<'_>, slot: Slot) -> Parsed<Built> {
1337        let src = self.src;
1338        self.atom_expr(item, slot)
1339            .map_err(|f| f.or_at(|| src.item(item)))
1340    }
1341
1342    /// [`aexpr`](Self::aexpr), unplaced.
1343    fn atom_expr(&mut self, item: &Item<'_>, slot: Slot) -> Parsed<Built> {
1344        let node = match item {
1345            Item::Sym(s) => self.sym(s)?,
1346            Item::Prefixed { sigil: '.', item } => self.dot_symbol(item)?,
1347            Item::Brackets(_) => return Err(ParseError::BareListLiteral.into()),
1348            Item::Parens(group) => return self.group(group, slot),
1349            Item::Str(s) => self.b.str(decode_str(s)?),
1350            Item::Braces(group) => return self.block(group, slot),
1351            Item::Punct(".") => return Err(ParseError::CombinatorDeferred.into()),
1352            Item::Punct(_) => return Err(ParseError::PunctAtAtom.into()),
1353            Item::Comm(_) => return Err(ParseError::EmptyChain.into()),
1354            Item::Prefixed { sigil: '#', item } => match item.as_ref() {
1355                Item::Brackets(Group { seq, .. }) => self.list(seq, slot)?,
1356                Item::Braces(Group { seq, .. }) => self.map(seq)?,
1357                _ => return Err(ParseError::ReservedLiteralForm.into()),
1358            },
1359            Item::Prefixed { .. } => return Err(ParseError::UnexpectedBinder.into()),
1360            Item::Run(members) => {
1361                check_run_head(members)?;
1362                let items: Vec<&Item<'_>> = members.iter().collect();
1363                return self.expr(&items, slot);
1364            }
1365        };
1366        Ok(Built::Node(node, self.src.item(item)))
1367    }
1368
1369    /// A `<sym>`: an integer literal or a resolved name.
1370    fn sym(&mut self, s: &str) -> Parsed<Node> {
1371        let r = classify(s, self.ctx)?;
1372        self.resolve_sym(r)
1373    }
1374
1375    fn resolve_sym(&mut self, r: SymRef) -> Parsed<Node> {
1376        match r {
1377            SymRef::Home => self.home(HomeLeaf::Module),
1378            SymRef::Int(n) => Ok(self.b.int(n)),
1379            SymRef::Builtin(op) => Ok(Node::Builtin(op)),
1380            SymRef::Name(name) => self.name_ref(name),
1381        }
1382    }
1383
1384    /// A reference to `name`, resolved.
1385    fn name_ref(&mut self, name: Sym) -> Parsed<Node> {
1386        Ok(match self.scope.reference(name)? {
1387            Ref::Local(index) => Node::Local { name, index },
1388            Ref::Item { index, depth } => Node::ModItem { index, depth, name },
1389            Ref::Builtin(op) => Node::Builtin(op),
1390        })
1391    }
1392
1393    /// A home-module leaf, resolved to the nearest module.
1394    fn home(&mut self, leaf: HomeLeaf) -> Parsed<Node> {
1395        let depth = self
1396            .scope
1397            .home_depth()
1398            .ok_or_else(|| ParseError::OutsideModule(text(leaf.name())))?;
1399        Ok(Node::Home { leaf, depth })
1400    }
1401
1402    /// A `.`-prefixed item as a symbol.
1403    fn dot_symbol(&mut self, item: &Item<'_>) -> Parsed<Node> {
1404        match item {
1405            Item::Sym(s) => Ok(Node::Symbol(self.ctx.intern(s))),
1406            _ => Err(ParseError::BadSymbol.into()),
1407        }
1408    }
1409
1410    /// A `#[…]` list into a runtime list literal of *any* arity: `#[]` (the
1411    /// empty list, not unit), `#[e]` (a genuine 1-list, not collapsed),
1412    /// `#[e0, e1, …]`. Each non-comment chain is one element expression.
1413    fn list(&mut self, seq: &Seq<'_>, slot: Slot) -> Parsed<Node> {
1414        let elem_slot = match slot {
1415            Slot::AppliedElems => Slot::Applied,
1416            _ => Slot::Value,
1417        };
1418        let chains = chain_slices(seq);
1419        let mark = self.b.scratch.len();
1420        for chain in &chains {
1421            let elem = self.expr(chain, elem_slot)?;
1422            let elem = self.close(elem);
1423            self.b.scratch.push(elem);
1424        }
1425        Ok(Node::List {
1426            len: len32(chains.len()),
1427            elems: self.b.push_run(Run::List, mark),
1428        })
1429    }
1430
1431    /// A `#{…}` map literal: each non-comment chain of the `<seq>` is one
1432    /// entry (see [`entry`](Self::entry)). Entry order is source order —
1433    /// evaluation re-sorts by the value order. The run holds the keys, then
1434    /// the values.
1435    fn map(&mut self, seq: &Seq<'_>) -> Parsed<Node> {
1436        let chains = chain_slices(seq);
1437        let n = chains.len();
1438        let mark = self.b.scratch.len();
1439        self.b
1440            .scratch
1441            .resize(mark + 2 * n, (Node::Unit, ChunkSpan::NONE));
1442        for (i, chain) in chains.iter().enumerate() {
1443            let src = self.src;
1444            let (key, value) = self
1445                .entry(chain)
1446                .map_err(|f| f.or_at(|| src.items(chain)))?;
1447            self.b.scratch[mark + i] = key;
1448            self.b.scratch[mark + n + i] = value;
1449        }
1450        Ok(Node::Map {
1451            len: len32(n),
1452            entries: self.b.push_run(Run::Map, mark),
1453        })
1454    }
1455
1456    /// One map entry `<key> ":" <value>`. The `:` is a single `<punct>` item at
1457    /// position 1; the key is the single head item ([`map_key`](Self::map_key))
1458    /// and the value is the rest of the chain parsed as an `<expr>`. An *empty*
1459    /// value tail is sugar for the unit value `()` (so `#{ .foo: }` is
1460    /// `#{ .foo: () }`).
1461    fn entry(&mut self, items: &[&Item<'_>]) -> Parsed<(Spanned, Spanned)> {
1462        if items.len() < 2 || !matches!(items[1], Item::Punct(":")) {
1463            return Err(ParseError::MalformedMapKey.into());
1464        }
1465        let src = self.src;
1466        let key = self
1467            .map_key(items[0])
1468            .map_err(|f| f.or_at(|| src.item(items[0])))?;
1469        let value_items = &items[2..];
1470        let value = if value_items.is_empty() {
1471            // The unit an empty value stands for is written as the `:`.
1472            (Node::Unit, self.src.item(items[1]))
1473        } else {
1474            let value = self.expr(value_items, Slot::Value)?;
1475            self.close(value)
1476        };
1477        Ok((key, value))
1478    }
1479
1480    /// A map-entry key item. A bare *non-digit* atom key is the **symbol** of
1481    /// the same spelling — `one` == `.one`. A **digit-leading** bare key (`0`,
1482    /// `5`) is rejected to avoid the number/symbol confusion: write `.0` for
1483    /// the symbol or `(0)` for the integer. Wrap a key as a `(…)` **computed**
1484    /// key to evaluate it: `(one)` is the *value* of variable `one`; an *empty*
1485    /// `()` is the unit key. The remaining forms are a `.`-prefixed symbol, a
1486    /// string, a path (`Mod.k`), and a `#[…]` / `#{…}` literal key. A bare
1487    /// `[…]` / `{…}` key is refused with its `#`-spelling nudge.
1488    fn map_key(&mut self, item: &Item<'_>) -> Parsed<Spanned> {
1489        match item {
1490            Item::Sym(s) if s.as_bytes()[0].is_ascii_digit() => {
1491                Err(ParseError::MalformedMapKey.into())
1492            }
1493            Item::Sym(s) => Ok((Node::Symbol(self.ctx.intern(s)), self.src.item(item))),
1494            // A bare `{…}` in key position is a block expression at the atom
1495            // layer, which has no reading as a key.
1496            Item::Braces(_) => Err(ParseError::BareMapLiteral.into()),
1497            Item::Prefixed { sigil: '.', .. }
1498            | Item::Prefixed { sigil: '#', .. }
1499            | Item::Brackets(_)
1500            | Item::Parens(_)
1501            | Item::Str(_) => {
1502                let key = self.aexpr(item, Slot::Value)?;
1503                Ok(self.close(key))
1504            }
1505            // A **path** key (`#{ Mod.k: v }`) denotes a concrete value by
1506            // reference, so it is a lookup key alongside the `.sym` /
1507            // bare-sym forms (§E).
1508            Item::Run(members) => {
1509                let head =
1510                    check_path(members, self.ctx).map_err(|_| ParseError::MalformedMapKey)?;
1511                self.path(head, members)
1512            }
1513            _ => Err(ParseError::MalformedMapKey.into()),
1514        }
1515    }
1516
1517    /// A `{…}` brace group as a **block expression** (syntax v1 step 6): a
1518    /// sequence of clauses whose value is the last clause's (see
1519    /// [`build_block`](Self::build_block)). A `:`-clause (a stale bare-map
1520    /// entry) has no reading in a block and is refused with the `#{…}` nudge.
1521    fn block(&mut self, group: &Group<'_>, slot: Slot) -> Parsed<Built> {
1522        if braces_contain_colon(&group.seq) {
1523            return Err(ParseError::BareMapLiteral.into());
1524        }
1525        let span = self.src.group(group);
1526        self.build_block(chain_slices(&group.seq), slot, span)
1527    }
1528
1529    /// A block from its (already joined, comment-free) clause chains — shared
1530    /// by the `{…}` block and by `let (…) e` (its group's binding chains plus
1531    /// its body as the final clause).
1532    ///
1533    /// - The empty block `{}` is the unit value `()`, and `{ e }` is `e` — the
1534    ///   degenerate-case identities.
1535    /// - Each **non-final** clause is a binding (`<pat> = <expr>`, a top-level
1536    ///   `=`, bound over the rest; its value resolves before its pattern binds)
1537    ///   or a bare expression, evaluated for its effect.
1538    /// - The **final** clause is the block's value and must be an expression; a
1539    ///   trailing binding is [`BlockTrailingBinding`](ParseError::BlockTrailingBinding).
1540    ///
1541    /// A block is a scope: its bindings are unbound when it ends. Its span is
1542    /// `span`, the whole form.
1543    fn build_block(
1544        &mut self,
1545        clauses: Vec<Vec<&Item<'_>>>,
1546        slot: Slot,
1547        span: ChunkSpan,
1548    ) -> Parsed<Built> {
1549        let Some((last, leading)) = clauses.split_last() else {
1550            return Ok(Built::Node(Node::Unit, span));
1551        };
1552        if clause_is_binding(last) {
1553            let span = self.src.items(last);
1554            return Err(Failure(ParseError::BlockTrailingBinding, span));
1555        }
1556        if leading.is_empty() {
1557            return self.expr(last, slot);
1558        }
1559        let (mark, pat_mark) = (self.b.scratch.len(), self.b.pat_scratch.len());
1560        let locals = self.scope.mark();
1561        for ci in leading {
1562            let (pat, value) = if clause_is_binding(ci) {
1563                let src = self.src;
1564                let (head, tail) = split_binding(ci).map_err(|f| f.or_at(|| src.items(ci)))?;
1565                let value = self.expr(tail, Slot::Value)?;
1566                let value = self.close(value);
1567                ((self.pattern(head)?, self.src.items(head)), value)
1568            } else {
1569                let value = self.expr(ci, Slot::Value)?;
1570                ((Pat::None, ChunkSpan::NONE), self.close(value))
1571            };
1572            self.b.pat_scratch.push(pat);
1573            self.b.scratch.push(value);
1574        }
1575        let value = self.expr(last, Slot::Value)?;
1576        let value = self.close(value);
1577        self.b.pat_scratch.push((Pat::None, ChunkSpan::NONE));
1578        self.b.scratch.push(value);
1579        self.scope.truncate(locals);
1580        let block = Node::Block {
1581            len: len32(clauses.len()),
1582            binds: self.b.push_pat_run(Run::Block, pat_mark),
1583            exprs: self.b.push_run(Run::Block, mark),
1584        };
1585        Ok(Built::Node(block, span))
1586    }
1587
1588    /// A leading `let`/`recur` form, reading the binder group from a glued
1589    /// run or a bare symbol.
1590    fn let_or_recur(&mut self, head: &Item<'_>, items: &[&Item<'_>], slot: Slot) -> Parsed<Built> {
1591        let is_recur = head_keyword(head) == Some("recur");
1592        // Placed at the keyword and what follows it.
1593        let at = self.src.items(&items[..items.len().min(2)]);
1594        let missing = || {
1595            let error = if is_recur {
1596                ParseError::RecurMissingBinders
1597            } else {
1598                ParseError::LetMissingBinders
1599            };
1600            Failure(error, at)
1601        };
1602        let (group_item, body_items): (&Item<'_>, &[&Item<'_>]) = match head {
1603            Item::Run(members) if members.len() == 2 => (&members[1], &items[1..]),
1604            Item::Run(_) => return Err(missing()),
1605            _ => (*items.get(1).ok_or_else(missing)?, &items[2..]),
1606        };
1607        let group = match group_item {
1608            Item::Parens(Group { seq, .. }) => seq,
1609            _ => return Err(missing()),
1610        };
1611        let span = self.src.items(items);
1612        if is_recur {
1613            self.recur(group, body_items, slot, span)
1614        } else {
1615            self.let_block(group, body_items, slot, span)
1616        }
1617    }
1618
1619    /// `let (b0, b1, …) body`: a block of the group's bindings and the body.
1620    /// An empty group binds nothing (`let () e` is `e`).
1621    fn let_block(
1622        &mut self,
1623        group: &Seq<'_>,
1624        body_items: &[&Item<'_>],
1625        slot: Slot,
1626        span: ChunkSpan,
1627    ) -> Parsed<Built> {
1628        if body_items.is_empty() {
1629            return Err(ParseError::LetMissingBody.into());
1630        }
1631        let mut clauses = chain_slices(group);
1632        for ci in &clauses {
1633            if !clause_is_binding(ci) {
1634                return Err(Failure(ParseError::MalformedBinding, self.src.items(ci)));
1635            }
1636        }
1637        clauses.push(body_items.to_vec());
1638        self.build_block(clauses, slot, span)
1639    }
1640
1641    /// `recur (b0, b1, …) body`: simultaneous bindings, a nested module whose
1642    /// items they are and whose body is `body`. Its names are read before any
1643    /// of its bodies, as a module's are, and sorted to give each its index.
1644    /// An empty group is `body`.
1645    fn recur(
1646        &mut self,
1647        group: &Seq<'_>,
1648        body_items: &[&Item<'_>],
1649        slot: Slot,
1650        span: ChunkSpan,
1651    ) -> Parsed<Built> {
1652        if body_items.is_empty() {
1653            return Err(ParseError::RecurMissingBody.into());
1654        }
1655        let chains = chain_slices(group);
1656        let decls = declarations(&chains, self.ctx, self.src)?;
1657        if !decls.imports.is_empty() {
1658            return Err(ParseError::MisplacedImport.into());
1659        }
1660        if let Some(&iota) = decls.iotas.first() {
1661            return Err(Failure(ParseError::MisplacedConst, decls.span(iota)));
1662        }
1663        if let Some(&local) = decls.locals.first() {
1664            return Err(Failure(ParseError::MisplacedLocal, decls.span(local)));
1665        }
1666        if let Some((_, span)) = decls
1667            .bodies
1668            .iter()
1669            .find(|(d, _)| matches!(d, Decl::Main(_)))
1670        {
1671            return Err(Failure(ParseError::MisplacedReserved, *span));
1672        }
1673        if decls.items.is_empty() {
1674            return self.segment(body_items, slot);
1675        }
1676        let items = sorted(&decls.items);
1677        self.scope.push_group(items.clone()).map_err(|e| {
1678            let at = match &e {
1679                ParseError::RecurShadowsOuter(name) => decls.span(self.ctx.intern(name)),
1680                _ => span,
1681            };
1682            Failure(e, at)
1683        })?;
1684        let id = self.b.enter_module();
1685        let (mark, _) = self.members(&decls, &items, &[], &[])?;
1686        let body = self.segment(body_items, Slot::Value)?;
1687        let body = self.child(body);
1688        self.scope.pop_group();
1689        let n = len32(items.len());
1690        self.b
1691            .leave_module(id, None, mark, n, 0, &[], Vec::new(), body);
1692        Ok(Built::Node(Node::Recur(id), span))
1693    }
1694
1695    /// `case <subject>? { arms }`. The subject resolves first and is placed
1696    /// after the arm bodies. A subjectless case is `&__arg (case __arg { … })`.
1697    fn case(&mut self, items: &[&Item<'_>], slot: Slot) -> Parsed<Built> {
1698        let span = self.src.items(items);
1699        let missing = || Failure(ParseError::CaseMissingArms, span);
1700        let mut rest: Vec<&Item<'_>> = Vec::new();
1701        if let Item::Run(members) = items[0] {
1702            rest.extend(members[1..].iter());
1703        }
1704        rest.extend_from_slice(&items[1..]);
1705        let (last, subject_items) = rest.split_last().ok_or_else(missing)?;
1706        let arms = match last {
1707            Item::Braces(Group { seq, .. }) => chain_slices(seq),
1708            _ => return Err(missing()),
1709        };
1710        if arms.is_empty() {
1711            return Err(missing());
1712        }
1713        match subject_items {
1714            [] => {
1715                let arg = self.ctx.intern("__arg");
1716                // The parameter and the subject it binds are the case's own.
1717                let depth = self.begin_abs(slot);
1718                self.param(span, |p| {
1719                    p.scope.bind(arg)?;
1720                    Ok(Pat::Bind(arg))
1721                })?;
1722                let subject = self.name_ref(arg)?;
1723                let body = self.case_node((subject, span), &arms)?;
1724                self.end_abs(depth, Built::Node(body, span), span)
1725            }
1726            [subject] => {
1727                let subject = self.aexpr(subject, Slot::Value)?;
1728                let subject = self.close(subject);
1729                Ok(Built::Node(self.case_node(subject, &arms)?, span))
1730            }
1731            _ => Err(Failure(
1732                ParseError::CaseSubjectExtra,
1733                self.src.items(subject_items),
1734            )),
1735        }
1736    }
1737
1738    /// The arms of a case over `subject`, each `& <pattern> (when (<cond>))* <body>`.
1739    fn case_node(&mut self, subject: Spanned, arms: &[Vec<&Item<'_>>]) -> Parsed<Node> {
1740        let (mark, pat_mark) = (self.b.scratch.len(), self.b.pat_scratch.len());
1741        for items in arms {
1742            let inner = match items.first() {
1743                Some(Item::Prefixed { sigil: '&', item }) => item.as_ref(),
1744                _ => return Err(Failure(ParseError::ArmNotBinder, self.src.items(items))),
1745            };
1746            if is_bare_literal_header(inner) {
1747                let span = self.src.item(inner);
1748                return Err(Failure(ParseError::BareLiteralHeader, span));
1749            }
1750            let locals = self.scope.mark();
1751            let base = (self.pattern(&[inner])?, self.src.item(inner));
1752            let (pat, rest) = self.guarded(base, &items[1..])?;
1753            if rest.is_empty() {
1754                return Err(Failure(ParseError::ArmMissingBody, self.src.items(items)));
1755            }
1756            let body = self.expr(rest, Slot::Value)?;
1757            let body = self.close(body);
1758            self.scope.truncate(locals);
1759            self.b.pat_scratch.push(pat);
1760            self.b.scratch.push(body);
1761        }
1762        self.b.scratch.push(subject);
1763        Ok(Node::Case {
1764            len: len32(arms.len()),
1765            conds: self.b.push_pat_run(Run::Case, pat_mark),
1766            exprs: self.b.push_run(Run::Case, mark),
1767        })
1768    }
1769
1770    // -----------------------------------------------------------------------
1771    // abstractions
1772    // -----------------------------------------------------------------------
1773
1774    /// `&<pattern> body` — the one binding form every abstraction is built
1775    /// from. A run of `&p0 &p1 …`, or a curried binder group `&(a, b)`,
1776    /// collects into one lambda: the body is parsed in [`Slot::Body`], where
1777    /// an `&` extends the open head. A head that would exceed
1778    /// [`MAX_HEAD`] parameters starts a fresh nested lambda instead.
1779    fn binder(&mut self, items: &[&Item<'_>], slot: Slot) -> Parsed<Built> {
1780        if items.len() == 1 {
1781            return Err(ParseError::AbsWithoutBody.into());
1782        }
1783        let inner = match items[0] {
1784            Item::Prefixed { sigil: '&', item } => item.as_ref(),
1785            _ => return Err(ParseError::UnexpectedBinder.into()),
1786        };
1787        let span = self.src.items(items);
1788        let depth = self.begin_abs(slot);
1789        let rest = if matches!(items.get(1), Some(it) if is_when_kw(it))
1790            && !matches!(inner, Item::Parens(_))
1791        {
1792            if is_bare_literal_header(inner) {
1793                let span = self.src.item(inner);
1794                return Err(Failure(ParseError::BareLiteralHeader, span));
1795            }
1796            let mut rest: &[&Item<'_>] = &[];
1797            let base_span = self.src.item(inner);
1798            self.param_spanned(|p| {
1799                let base = (p.pattern(&[inner])?, base_span);
1800                let (pat, after) = p.guarded(base, &items[1..])?;
1801                rest = after;
1802                Ok(pat)
1803            })?;
1804            if rest.is_empty() {
1805                return Err(ParseError::AbsWithoutBody.into());
1806            }
1807            rest
1808        } else {
1809            match inner {
1810                // A `(…)` inner is a binder group whose commas curry
1811                // (`&(a, b)` is two parameters); `&()` is the nullary marker,
1812                // one parameter that matches unit.
1813                Item::Parens(group) => {
1814                    let params = chain_slices(&group.seq);
1815                    if params.is_empty() {
1816                        self.param(self.src.group(group), |_| Ok(Pat::Unit))?;
1817                    }
1818                    for chain in &params {
1819                        self.param(self.src.items(chain), |p| p.pattern(chain))?;
1820                    }
1821                }
1822                // A bare matching literal (number `&42`, symbol `&.foo`,
1823                // string `&"s"`) as the whole header must be parenthesized:
1824                // `&(42)`, `&(.foo)`, `&("s")` (see `BareLiteralHeader`).
1825                it if is_bare_literal_header(it) => {
1826                    let span = self.src.item(it);
1827                    return Err(Failure(ParseError::BareLiteralHeader, span));
1828                }
1829                other => self.param(self.src.item(other), |p| p.pattern(&[other]))?,
1830            }
1831            &items[1..]
1832        };
1833        let body = self.segment(rest, Slot::Body)?;
1834        self.end_abs(depth, body, span)
1835    }
1836
1837    /// Starts an abstraction in `slot`: opens a lambda, or in
1838    /// [`Slot::Body`] extends the open one. Returns the depth of the open
1839    /// heads to close back to in [`end_abs`](Self::end_abs).
1840    fn begin_abs(&mut self, slot: Slot) -> usize {
1841        let depth = self.heads.len();
1842        if slot != Slot::Body {
1843            self.open_head(slot == Slot::Applied);
1844        }
1845        depth
1846    }
1847
1848    /// Opens a lambda: a [`Chain`](crate::code::Plan::Chain) one binds in
1849    /// the enclosing activation, any other opens an activation of its own.
1850    fn open_head(&mut self, chain: bool) {
1851        let pats = self.b.pat_scratch.len();
1852        let chain = if chain {
1853            Some(self.scope.mark())
1854        } else {
1855            self.scope.push_level();
1856            None
1857        };
1858        self.heads.push(Head { pats, chain });
1859    }
1860
1861    /// Adds the parameter `parse` emits, and binds, to the innermost open
1862    /// head, opening a nested lambda first if the head is full. The
1863    /// parameter's span is `span`.
1864    fn param(
1865        &mut self,
1866        span: ChunkSpan,
1867        parse: impl FnOnce(&mut Self) -> Parsed<Pat>,
1868    ) -> Parsed<()> {
1869        self.param_spanned(|p| Ok((parse(p)?, span)))
1870    }
1871
1872    /// [`param`](Self::param) for a parameter whose span `parse` gives.
1873    fn param_spanned(
1874        &mut self,
1875        parse: impl FnOnce(&mut Self) -> Parsed<(Pat, ChunkSpan)>,
1876    ) -> Parsed<()> {
1877        let head = self.heads.last().expect("a parameter has a head");
1878        if self.b.pat_scratch.len() - head.pats == MAX_HEAD {
1879            self.open_head(false);
1880        }
1881        let pat = parse(self)?;
1882        self.b.pat_scratch.push(pat);
1883        Ok(())
1884    }
1885
1886    /// Closes the heads opened since `depth` around `body`, innermost first;
1887    /// each lambda's span is `span`, the whole abstraction. An abstraction
1888    /// that only extended an open head returns its body.
1889    fn end_abs(&mut self, depth: usize, body: Built, span: ChunkSpan) -> Parsed<Built> {
1890        let mut body = self.close(body);
1891        while self.heads.len() > depth {
1892            let head = self.heads.pop().expect("an open head");
1893            let plan = match head.chain {
1894                Some(mark) => {
1895                    self.scope.truncate(mark);
1896                    None
1897                }
1898                None => Some(self.scope.pop_level()),
1899            };
1900            body = (self.b.lambda(head.pats, body, plan.as_deref(), span), span);
1901        }
1902        Ok(Built::Node(body.0, body.1))
1903    }
1904
1905    /// Folds `when (<cond>)` guards onto `base`, returning the guarded pattern
1906    /// and the items after the guards. Each condition resolves after the
1907    /// pattern it guards binds, and a pattern guard's pattern after its
1908    /// condition.
1909    fn guarded<'i, 'a>(
1910        &mut self,
1911        mut pat: (Pat, ChunkSpan),
1912        mut items: &'i [&'i Item<'a>],
1913    ) -> Parsed<((Pat, ChunkSpan), &'i [&'i Item<'a>])> {
1914        while !items.is_empty() && is_when_kw(items[0]) {
1915            // Placed at the `when` and what follows it.
1916            let missing = Failure(
1917                ParseError::WhenMissingCond,
1918                self.src.items(&items[..items.len().min(2)]),
1919            );
1920            let (group, next): (&Seq<'a>, &[&Item<'a>]) = match items[0] {
1921                Item::Run(members) if members.len() == 2 => match &members[1] {
1922                    Item::Parens(Group { seq, .. }) => (seq, &items[1..]),
1923                    _ => return Err(missing),
1924                },
1925                Item::Run(_) => return Err(missing),
1926                _ => match items.get(1) {
1927                    Some(Item::Parens(Group { seq, .. })) => (seq, &items[2..]),
1928                    _ => return Err(missing),
1929                },
1930            };
1931            let Some(chain) = single_chain(group).filter(|chain| !chain.is_empty()) else {
1932                return Err(missing);
1933            };
1934            let inner = self.b.push_pat(pat.0, pat.1);
1935            let (gpat, cond) = if clause_is_binding(&chain) {
1936                let src = self.src;
1937                let (head, tail) =
1938                    split_binding(&chain).map_err(|f| f.or_at(|| src.items(&chain)))?;
1939                let cond = self.expr(tail, Slot::Value)?;
1940                let cond = self.child(cond);
1941                let gpat = self.pattern(head)?;
1942                (self.b.push_pat(gpat, self.src.items(head)), cond)
1943            } else {
1944                let cond = self.expr(&chain, Slot::Value)?;
1945                (PatId::NONE, self.child(cond))
1946            };
1947            let guard = self.src.items(&items[..items.len() - next.len()]);
1948            pat = (Pat::When { inner, gpat, cond }, pat.1.join(guard));
1949            items = next;
1950        }
1951        Ok((pat, items))
1952    }
1953
1954    // -----------------------------------------------------------------------
1955    // patterns
1956    // -----------------------------------------------------------------------
1957
1958    /// A pattern from a chain of items, binding its names. Splits on `|`
1959    /// (or-pattern), then `=` (equality / at-pattern), otherwise defers to
1960    /// `as` / atom levels.
1961    fn pattern(&mut self, items: &[&Item<'_>]) -> Parsed<Pat> {
1962        let src = self.src;
1963        self.or_pattern(items)
1964            .map_err(|f| f.or_at(|| src.items(items)))
1965    }
1966
1967    /// [`pattern`](Self::pattern), unplaced.
1968    fn or_pattern(&mut self, items: &[&Item<'_>]) -> Parsed<Pat> {
1969        if items.is_empty() {
1970            return Err(ParseError::BadPattern.into());
1971        }
1972        if let Some(idx) = items.iter().position(|it| matches!(it, Item::Punct("|"))) {
1973            let mark = self.scope.mark();
1974            let left = self.pattern(&items[..idx])?;
1975            let left_names: Vec<Sym> = self.scope.bound_since(mark).to_vec();
1976            self.scope.truncate(mark);
1977            let right = self.pattern(&items[idx + 1..])?;
1978            if self.scope.bound_since(mark) != left_names.as_slice() {
1979                return Err(ParseError::OrBindersMismatch.into());
1980            }
1981            let left = self.b.push_pat(left, self.src.items(&items[..idx]));
1982            let right = self.b.push_pat(right, self.src.items(&items[idx + 1..]));
1983            return Ok(Pat::Or(left, right));
1984        }
1985        match items[0] {
1986            Item::Punct("<") => return Ok(Pat::Less(self.comparand(&items[1..])?)),
1987            Item::Punct(">") => return Ok(Pat::Greater(self.comparand(&items[1..])?)),
1988            _ => {}
1989        }
1990        if let Some(idx) = items.iter().position(|it| matches!(it, Item::Punct("="))) {
1991            if idx == 0 {
1992                return Ok(Pat::Equal(self.comparand(&items[1..])?));
1993            }
1994            let name = plain_name(&items[..idx], ParseError::BadPattern, self.ctx, self.src)?;
1995            self.scope.bind(name)?;
1996            let inner = self.pattern(&items[idx + 1..])?;
1997            let span = self.src.items(&items[idx + 1..]);
1998            return Ok(Pat::At(name, self.b.push_pat(inner, span)));
1999        }
2000        self.narrowed(items)
2001    }
2002
2003    /// The RHS of an `=` / `<` / `>` comparand: exactly one atom, pushed.
2004    /// A pattern is a closed grammar; comparands never recurse into `expr`.
2005    fn comparand(&mut self, items: &[&Item<'_>]) -> Parsed<NodeId> {
2006        let node = match items {
2007            [it] => match it {
2008                Item::Sym(s) => self.sym(s)?,
2009                Item::Prefixed { sigil: '.', item } => self.dot_symbol(item)?,
2010                Item::Str(s) => self.b.str(decode_str(s)?),
2011                Item::Run(members) => {
2012                    let head = check_path(members, self.ctx)?;
2013                    self.path(head, members)?.0
2014                }
2015                _ => return Err(Failure(ParseError::ComparandNotAtom, self.src.items(items))),
2016            },
2017            _ => return Err(Failure(ParseError::ComparandNotAtom, self.src.items(items))),
2018        };
2019        Ok(self.b.push(node, self.src.items(items)))
2020    }
2021
2022    /// The narrowing level: `Self <pat>`, `(as <Proto>) <pat>`, `<pat> as <Proto>`, or a bare atom.
2023    /// A prototype resolves before the pattern it narrows binds.
2024    fn narrowed(&mut self, items: &[&Item<'_>]) -> Parsed<Pat> {
2025        if let Item::Sym("Self") = items[0] {
2026            if items.len() < 2 {
2027                return Err(ParseError::BadPattern.into());
2028            }
2029            let depth = self
2030                .scope
2031                .home_depth()
2032                .ok_or_else(|| ParseError::OutsideModule(text("Self")))?;
2033            let inner = self.pattern(&items[1..])?;
2034            return Ok(Pat::Unwrap {
2035                depth,
2036                inner: self.b.push_pat(inner, self.src.items(&items[1..])),
2037            });
2038        }
2039        if let Item::Parens(Group { seq, .. }) = items[0] {
2040            if let Some(chain) = single_chain(seq) {
2041                if !chain.is_empty() && matches!(chain[0], Item::Sym("as")) {
2042                    let proto = self.proto(&chain[1..])?;
2043                    let inner = self.pattern(&items[1..])?;
2044                    return Ok(self.typed(proto, (inner, self.src.items(&items[1..]))));
2045                }
2046            }
2047        }
2048        if let Some(idx) = items.iter().position(|it| matches!(it, Item::Sym("as"))) {
2049            let proto = self.proto(&items[idx + 1..])?;
2050            let inner = self.atom(&items[..idx])?;
2051            return Ok(self.typed(proto, (inner, self.src.items(&items[..idx]))));
2052        }
2053        self.atom(items)
2054    }
2055
2056    /// The prototype of `as <Proto>`: exactly one name or path, a module
2057    /// reference. A reference to a builtin prototype folds to its kind.
2058    fn proto(&mut self, items: &[&Item<'_>]) -> Parsed<Proto> {
2059        let src = self.src;
2060        self.proto_ref(items)
2061            .map_err(|f| f.or_at(|| src.items(items)))
2062    }
2063
2064    /// [`proto`](Self::proto), unplaced.
2065    fn proto_ref(&mut self, items: &[&Item<'_>]) -> Parsed<Proto> {
2066        let node = match items {
2067            [Item::Sym(s)] => match classify(s, self.ctx)? {
2068                SymRef::Int(_) => return Err(ParseError::UnknownType.into()),
2069                r => self.resolve_sym(r)?,
2070            },
2071            [Item::Run(members)] => {
2072                let head = check_path(members, self.ctx)?;
2073                self.path(head, members)?.0
2074            }
2075            _ => return Err(ParseError::UnknownType.into()),
2076        };
2077        match node {
2078            Node::Builtin(op) => match op.proto_kind() {
2079                Some(kind) => Ok(Proto::Kind(kind)),
2080                None => Err(ParseError::NotAPrototype(text(op.name())).into()),
2081            },
2082            node => Ok(Proto::Ref(self.b.push(node, self.src.items(items)))),
2083        }
2084    }
2085
2086    /// `inner as proto`.
2087    fn typed(&mut self, proto: Proto, inner: (Pat, ChunkSpan)) -> Pat {
2088        let inner = self.b.push_pat(inner.0, inner.1);
2089        match proto {
2090            Proto::Kind(kind) => Pat::TypeKind(kind, inner),
2091            Proto::Ref(node) => Pat::TypeRef(node, inner),
2092        }
2093    }
2094
2095    /// A path: a run of a name followed by `.`-prefixed segments, checked by
2096    /// [`check_path`] to have head `head`. Folds left into the head applied
2097    /// to symbol literals, each a binary application, and a builtin module's
2098    /// member to its leaf.
2099    fn path(&mut self, head: SymRef, members: &[Item<'_>]) -> Parsed<Spanned> {
2100        let mut acc = (self.resolve_sym(head)?, self.src.item(&members[0]));
2101        for m in &members[1..] {
2102            let Item::Prefixed { sigil: '.', item } = m else {
2103                unreachable!("checked by check_path")
2104            };
2105            let symbol = self.dot_symbol(item)?;
2106            let Node::Symbol(name) = symbol else {
2107                unreachable!("a dotted segment is a symbol")
2108            };
2109            let span = self.src.item(m);
2110            if let Some(op) = builtin_of(acc.0).and_then(|ns| ns.member(name)) {
2111                acc = (Node::Builtin(op), acc.1.join(span));
2112                continue;
2113            }
2114            let mark = self.b.scratch.len();
2115            let joined = acc.1.join(span);
2116            self.b.scratch.push(acc);
2117            self.b.scratch.push((symbol, span));
2118            let app = Node::App {
2119                len: 2,
2120                args: self.b.push_run(Run::App, mark),
2121            };
2122            acc = (app, joined);
2123        }
2124        Ok(acc)
2125    }
2126
2127    /// An atomic pattern: a single item or a `(…)` grouping of one pattern.
2128    fn atom(&mut self, items: &[&Item<'_>]) -> Parsed<Pat> {
2129        match items {
2130            [it] => self.atom_item(it),
2131            _ => Err(ParseError::BadPattern.into()),
2132        }
2133    }
2134
2135    /// A single atomic-pattern item.
2136    fn atom_item(&mut self, item: &Item<'_>) -> Parsed<Pat> {
2137        let src = self.src;
2138        self.atom_pattern(item)
2139            .map_err(|f| f.or_at(|| src.item(item)))
2140    }
2141
2142    /// [`atom_item`](Self::atom_item), unplaced.
2143    fn atom_pattern(&mut self, item: &Item<'_>) -> Parsed<Pat> {
2144        match item {
2145            Item::Sym("_") => Ok(Pat::Discard),
2146            Item::Sym(s) if is_keyword(s) => Err(ParseError::ReservedKeyword.into()),
2147            Item::Sym(s) if s.starts_with("__") => Err(ParseError::ReservedName.into()),
2148            Item::Sym(s) => match parse_int(s) {
2149                Some(n) => {
2150                    let node = self.b.int(n);
2151                    Ok(Pat::Equal(self.b.push(node, self.src.item(item))))
2152                }
2153                None if s.as_bytes()[0].is_ascii_digit() => Err(ParseError::MalformedNumber.into()),
2154                None => {
2155                    let name = self.ctx.intern(s);
2156                    self.scope.bind(name)?;
2157                    Ok(Pat::Bind(name))
2158                }
2159            },
2160            Item::Prefixed {
2161                sigil: '.',
2162                item: inner,
2163            } => {
2164                let node = self.dot_symbol(inner)?;
2165                Ok(Pat::Equal(self.b.push(node, self.src.item(item))))
2166            }
2167            Item::Str(s) => {
2168                let node = self.b.str(decode_str(s)?);
2169                Ok(Pat::Equal(self.b.push(node, self.src.item(item))))
2170            }
2171            Item::Brackets(_) => Err(ParseError::BareListLiteral.into()),
2172            Item::Parens(Group { seq, .. }) => match single_chain(seq) {
2173                Some(chain) if !chain.is_empty() => self.pattern(&chain),
2174                _ => Err(ParseError::BadPattern.into()),
2175            },
2176            Item::Braces(_) => Err(ParseError::BareMapLiteral.into()),
2177            Item::Prefixed { sigil: '#', item } => match item.as_ref() {
2178                Item::Brackets(Group { seq, .. }) => self.list_pattern(seq),
2179                Item::Braces(Group { seq, .. }) => self.map_pattern(seq),
2180                _ => Err(ParseError::ReservedLiteralForm.into()),
2181            },
2182            _ => Err(ParseError::BadPattern.into()),
2183        }
2184    }
2185
2186    /// A trailing rest (`...name` or `...`) written at `span`, binding its name.
2187    fn rest(&mut self, rest: Option<&str>, span: ChunkSpan) -> Parsed<PatId> {
2188        let pat = match rest {
2189            Some(name) => {
2190                let name = self.ctx.intern(name);
2191                self.scope.bind(name)?;
2192                Pat::Bind(name)
2193            }
2194            None => Pat::Discard,
2195        };
2196        Ok(self.b.push_pat(pat, span))
2197    }
2198
2199    /// A `#[…]` list pattern with an optional trailing rest, which binds
2200    /// before the elements.
2201    fn list_pattern(&mut self, seq: &Seq<'_>) -> Parsed<Pat> {
2202        let chains = chain_slices(seq);
2203        let (elems, rest) = split_rest(&chains)?;
2204        let rest = match rest {
2205            Some(rest) => self.rest(rest, self.rest_span(&chains))?,
2206            None => PatId::NONE,
2207        };
2208        let mark = self.b.pat_scratch.len();
2209        for chain in elems {
2210            let pat = self.pattern(chain)?;
2211            self.b.pat_scratch.push((pat, self.src.items(chain)));
2212        }
2213        Ok(Pat::List {
2214            len: len32(elems.len()),
2215            elems: self.b.push_pat_run(Run::PatList, mark),
2216            rest,
2217        })
2218    }
2219
2220    /// A `#{ k0: p0, … }` map pattern. A key is a lookup — a bare symbol, a
2221    /// path, or a pattern that is an equality — or else a pattern that
2222    /// captures the key. The lookup keys resolve first, then their values
2223    /// bind, then each captured key and its value, then the rest: the order
2224    /// the matcher binds in.
2225    fn map_pattern(&mut self, seq: &Seq<'_>) -> Parsed<Pat> {
2226        let chains = chain_slices(seq);
2227        let (entries, rest) = split_rest(&chains)?;
2228        let mut keys = Vec::with_capacity(entries.len());
2229        for chain in entries {
2230            let malformed = || Failure(ParseError::MalformedMapKey, self.src.items(chain));
2231            let colon = chain
2232                .iter()
2233                .position(|it| matches!(it, Item::Punct(":")))
2234                .ok_or_else(malformed)?;
2235            if colon == 0 || colon + 1 == chain.len() {
2236                return Err(malformed());
2237            }
2238            let key = match &chain[..colon] {
2239                [Item::Sym(s)] if s.as_bytes()[0].is_ascii_digit() => return Err(malformed()),
2240                [Item::Sym(_)] | [Item::Run(_)] => true,
2241                key => is_lookup(key),
2242            };
2243            keys.push((colon, key));
2244        }
2245        let n = entries.len();
2246        let mark = self.b.pat_scratch.len();
2247        self.b
2248            .pat_scratch
2249            .resize(mark + 2 * n, (Pat::None, ChunkSpan::NONE));
2250        for (i, (chain, &(colon, lookup))) in entries.iter().zip(&keys).enumerate() {
2251            if lookup {
2252                let node = match &chain[..colon] {
2253                    [Item::Sym(s)] => Node::Symbol(self.ctx.intern(s)),
2254                    [Item::Run(members)] => {
2255                        let head = check_path(members, self.ctx)?;
2256                        self.path(head, members)?.0
2257                    }
2258                    key => self.lookup_expr(key)?,
2259                };
2260                let span = self.src.items(&chain[..colon]);
2261                self.b.pat_scratch[mark + i] = (Pat::Lookup(self.b.push(node, span)), span);
2262            }
2263        }
2264        for (i, (chain, &(colon, lookup))) in entries.iter().zip(&keys).enumerate() {
2265            if lookup {
2266                let value = self.pattern(&chain[colon + 1..])?;
2267                let span = self.src.items(&chain[colon + 1..]);
2268                self.b.pat_scratch[mark + n + i] = (value, span);
2269            }
2270        }
2271        for (i, (chain, &(colon, lookup))) in entries.iter().zip(&keys).enumerate() {
2272            if !lookup {
2273                let key = self.pattern(&chain[..colon])?;
2274                self.b.pat_scratch[mark + i] = (key, self.src.items(&chain[..colon]));
2275                let value = self.pattern(&chain[colon + 1..])?;
2276                let span = self.src.items(&chain[colon + 1..]);
2277                self.b.pat_scratch[mark + n + i] = (value, span);
2278            }
2279        }
2280        let rest = match rest {
2281            Some(rest) => self.rest(rest, self.rest_span(&chains))?,
2282            None => PatId::NONE,
2283        };
2284        Ok(Pat::Map {
2285            len: len32(n),
2286            entries: self.b.push_pat_run(Run::PatMap, mark),
2287            rest,
2288        })
2289    }
2290
2291    /// The span of a list or map pattern's rest, its last chain.
2292    fn rest_span(&self, chains: &[Vec<&Item<'_>>]) -> ChunkSpan {
2293        chains
2294            .last()
2295            .map_or(ChunkSpan::NONE, |chain| self.src.items(chain))
2296    }
2297
2298    /// The expression of a lookup key, one [`is_lookup`] accepts.
2299    fn lookup_expr(&mut self, items: &[&Item<'_>]) -> Parsed<Node> {
2300        if let Some(Item::Punct("=")) = items.first() {
2301            let node = self.comparand(&items[1..])?;
2302            return Ok(self.b.node(node).0);
2303        }
2304        match items {
2305            [Item::Parens(Group { seq, .. })] => {
2306                let chain = single_chain(seq).expect("checked by is_lookup");
2307                self.lookup_expr(&chain)
2308            }
2309            [Item::Sym(s)] => Ok(self.b.int(parse_int(s).expect("checked by is_lookup"))),
2310            [Item::Prefixed { sigil: '.', item }] => self.dot_symbol(item),
2311            [Item::Str(s)] => Ok(self.b.str(decode_str(s)?)),
2312            _ => unreachable!("checked by is_lookup"),
2313        }
2314    }
2315}
2316
2317/// A prototype in `as <Proto>`.
2318enum Proto {
2319    /// A builtin prototype, compiled to a discriminant test.
2320    Kind(crate::builtins::TyKind),
2321    /// Any other, evaluated at match time and required to be a module.
2322    Ref(NodeId),
2323}
2324
2325/// The builtin `node` is, if it is one.
2326fn builtin_of(node: Node) -> Option<Builtin> {
2327    match node {
2328        Node::Builtin(op) => Some(op),
2329        _ => None,
2330    }
2331}
2332
2333/// Classify a `<sym>` as a home leaf, an integer literal, a builtin or a
2334/// name, rejecting what reads as none of them.
2335fn classify(s: &str, ctx: &Ctx) -> Parsed<SymRef> {
2336    if s == "Self" {
2337        return Ok(SymRef::Home);
2338    }
2339    if is_keyword(s) {
2340        return Err(ParseError::ReservedKeyword.into());
2341    }
2342    if s == "_" {
2343        Err(ParseError::DiscardReference.into())
2344    } else if let Some(n) = parse_int(s) {
2345        Ok(SymRef::Int(n))
2346    } else if s.as_bytes()[0].is_ascii_digit() {
2347        Err(ParseError::MalformedNumber.into())
2348    } else if s.starts_with("__") {
2349        match Builtin::from_name(s) {
2350            Some(op) => Ok(SymRef::Builtin(op)),
2351            None => Err(ParseError::UnknownReserved(text(s)).into()),
2352        }
2353    } else {
2354        Ok(SymRef::Name(ctx.intern(s)))
2355    }
2356}
2357
2358/// Check a path — a run starting with an identifier followed by
2359/// `.`-prefixed segments — and classify its head.
2360fn check_path(members: &[Item<'_>], ctx: &Ctx) -> Parsed<SymRef> {
2361    if members.len() < 2 {
2362        return Err(ParseError::ComparandNotAtom.into());
2363    }
2364    let head = match &members[0] {
2365        Item::Sym(s) => classify(s, ctx)?,
2366        _ => return Err(ParseError::ComparandNotAtom.into()),
2367    };
2368    for m in &members[1..] {
2369        if !matches!(m, Item::Prefixed { sigil: '.', .. }) {
2370            return Err(ParseError::ComparandNotAtom.into());
2371        }
2372    }
2373    for m in &members[1..] {
2374        if let Item::Prefixed { item, .. } = m {
2375            if !matches!(item.as_ref(), Item::Sym(_)) {
2376                return Err(ParseError::BadSymbol.into());
2377            }
2378        }
2379    }
2380    Ok(head)
2381}
2382
2383/// Whether a map pattern's key chain is an equality pattern, and so a lookup
2384/// key: the shape [`Parser::pattern`] reads as [`Pat::Equal`].
2385fn is_lookup(items: &[&Item<'_>]) -> bool {
2386    if items.is_empty() || items.iter().any(|it| matches!(it, Item::Punct("|"))) {
2387        return false;
2388    }
2389    if matches!(items[0], Item::Punct("<") | Item::Punct(">")) {
2390        return false;
2391    }
2392    if let Some(idx) = items.iter().position(|it| matches!(it, Item::Punct("="))) {
2393        return idx == 0;
2394    }
2395    if matches!(items[0], Item::Sym("Self")) || items.iter().any(|it| matches!(it, Item::Sym("as")))
2396    {
2397        return false;
2398    }
2399    match items {
2400        [Item::Sym(s)] => !is_keyword(s) && !s.starts_with("__") && parse_int(s).is_some(),
2401        [Item::Prefixed { sigil: '.', .. }] | [Item::Str(_)] => true,
2402        [Item::Parens(Group { seq, .. })] => match single_chain(seq) {
2403            Some(chain) => is_lookup(&chain) && !starts_with_as(&chain),
2404            None => false,
2405        },
2406        _ => false,
2407    }
2408}
2409
2410/// Whether a chain is the `as <Proto>` of a `(as <Proto>) <pat>` pattern.
2411fn starts_with_as(chain: &[&Item<'_>]) -> bool {
2412    matches!(chain.first(), Some(Item::Sym("as")))
2413}
2414
2415/// Split a list or map pattern's chains into its entries and its trailing
2416/// rest, if any: `Some(Some(name))` for `...name`, `Some(None)` for `...`.
2417/// A rest anywhere but last is [`ParseError::BadPattern`].
2418#[allow(clippy::type_complexity)]
2419fn split_rest<'c, 'b, 'a>(
2420    chains: &'c [Vec<&'b Item<'a>>],
2421) -> Parsed<(&'c [Vec<&'b Item<'a>>], Option<Option<&'a str>>)> {
2422    let n = chains.len();
2423    for (i, chain) in chains.iter().enumerate() {
2424        if as_rest(chain).is_some() && i != n - 1 {
2425            return Err(ParseError::BadPattern.into());
2426        }
2427    }
2428    match chains.split_last() {
2429        Some((last, entries)) => match as_rest(last) {
2430            Some(rest) => Ok((entries, Some(rest))),
2431            None => Ok((chains, None)),
2432        },
2433        None => Ok((chains, None)),
2434    }
2435}
2436
2437/// Recognize a trailing-rest element chain (`...rest` or `...`).
2438fn as_rest<'a>(chain: &[&Item<'a>]) -> Option<Option<&'a str>> {
2439    let [Item::Prefixed {
2440        sigil: '.',
2441        item: a,
2442    }] = chain
2443    else {
2444        return None;
2445    };
2446    let Item::Prefixed {
2447        sigil: '.',
2448        item: b,
2449    } = a.as_ref()
2450    else {
2451        return None;
2452    };
2453    match b.as_ref() {
2454        Item::Prefixed {
2455            sigil: '.',
2456            item: c,
2457        } => match c.as_ref() {
2458            Item::Sym(name) => Some(Some(name)),
2459            _ => None,
2460        },
2461        Item::Punct(".") => Some(None),
2462        _ => None,
2463    }
2464}
2465
2466/// Split a binding chain `<pattern> "=" <expr>` on its one top-level `=`.
2467/// Head and tail must be non-empty.
2468#[allow(clippy::type_complexity)]
2469fn split_binding<'i, 'a>(
2470    items: &'i [&'i Item<'a>],
2471) -> Parsed<(&'i [&'i Item<'a>], &'i [&'i Item<'a>])> {
2472    let mut eq = None;
2473    for (i, it) in items.iter().enumerate() {
2474        if matches!(it, Item::Punct("=")) {
2475            if eq.is_some() {
2476                return Err(ParseError::MalformedBinding.into());
2477            }
2478            eq = Some(i);
2479        }
2480    }
2481    let eq = eq.ok_or(ParseError::MalformedBinding)?;
2482    let (head, tail) = (&items[..eq], &items[eq + 1..]);
2483    if head.is_empty() || tail.is_empty() {
2484        return Err(ParseError::MalformedBinding.into());
2485    }
2486    if head.iter().any(|it| matches!(it, Item::Punct("|"))) {
2487        return Err(ParseError::LetOrUnparenthesized.into());
2488    }
2489    if let [it] = head {
2490        if is_bare_literal_header(it) {
2491            return Err(ParseError::BareLiteralHeader.into());
2492        }
2493    }
2494    Ok((head, tail))
2495}
2496
2497/// Reject `&`-headed runs and float-like runs (`3.14`).
2498fn check_run_head(members: &[Item<'_>]) -> Parsed<()> {
2499    if matches!(&members[0], Item::Prefixed { sigil: '&', .. }) {
2500        return Err(ParseError::BinderRunGlued.into());
2501    }
2502    if let (
2503        Item::Sym(head),
2504        Some(Item::Prefixed {
2505            sigil: '.',
2506            item: seg,
2507        }),
2508    ) = (&members[0], members.get(1))
2509    {
2510        if parse_int(head).is_some() && is_dot_number(seg) {
2511            return Err(ParseError::FloatLiteralUnsupported.into());
2512        }
2513    }
2514    Ok(())
2515}
2516
2517/// Check if a `.`-prefixed segment is a number (float tail).
2518fn is_dot_number(item: &Item<'_>) -> bool {
2519    match item {
2520        Item::Sym(s) => parse_int(s).is_some(),
2521        _ => false,
2522    }
2523}
2524
2525/// Check if a run is the glued `#Self(…)` construction.
2526fn is_self_head(members: &[Item<'_>]) -> bool {
2527    matches!(
2528        members.first(),
2529        Some(Item::Prefixed { sigil: '#', item })
2530            if matches!(item.as_ref(), Item::Sym("Self"))
2531    )
2532}
2533
2534/// Decode a Muon `<str>` literal into owned UTF-8 `Text`, combining surrogate pairs.
2535/// Unpaired surrogates cause [`ParseError::LoneSurrogate`].
2536fn decode_str(raw: &str) -> Parsed<Text> {
2537    let b = raw.as_bytes();
2538    let mut out = alloc::string::String::with_capacity(raw.len());
2539    let mut i = 0;
2540    let mut chunk = 0;
2541    while i < b.len() {
2542        if b[i] != b'\\' {
2543            i += 1;
2544            continue;
2545        }
2546        out.push_str(&raw[chunk..i]);
2547        match b[i + 1] {
2548            b'"' => out.push('"'),
2549            b'\\' => out.push('\\'),
2550            b'/' => out.push('/'),
2551            b'n' => out.push('\n'),
2552            b't' => out.push('\t'),
2553            b'r' => out.push('\r'),
2554            b'b' => out.push('\u{8}'),
2555            b'f' => out.push('\u{c}'),
2556            b'u' => {
2557                let hi = hex4(&b[i + 2..i + 6]);
2558                i += 6;
2559                if (0xD800..=0xDBFF).contains(&hi) {
2560                    let lo = match (b.get(i), b.get(i + 1)) {
2561                        (Some(b'\\'), Some(b'u')) => hex4(&b[i + 2..i + 6]),
2562                        _ => return Err(ParseError::LoneSurrogate.into()),
2563                    };
2564                    if !(0xDC00..=0xDFFF).contains(&lo) {
2565                        return Err(ParseError::LoneSurrogate.into());
2566                    }
2567                    let c = 0x1_0000 + ((hi - 0xD800) << 10) + (lo - 0xDC00);
2568                    out.push(char::from_u32(c).expect("combined surrogate pair"));
2569                    i += 6;
2570                } else if (0xDC00..=0xDFFF).contains(&hi) {
2571                    return Err(ParseError::LoneSurrogate.into());
2572                } else {
2573                    out.push(char::from_u32(hi).expect("non-surrogate BMP scalar"));
2574                }
2575                chunk = i;
2576                continue;
2577            }
2578            _ => unreachable!("Muon validated the <str> escape grammar"),
2579        }
2580        i += 2;
2581        chunk = i;
2582    }
2583    out.push_str(&raw[chunk..]);
2584    Ok(Text::from(out.as_str()))
2585}
2586
2587/// Read four ASCII hex digits as a `u32`.
2588fn hex4(b: &[u8]) -> u32 {
2589    b.iter()
2590        .fold(0, |v, &c| v * 16 + (c as char).to_digit(16).unwrap())
2591}
2592
2593/// Parse an integer literal (decimal, hex, or binary) with `_` separators.
2594pub(crate) fn parse_int(s: &str) -> Option<BigInt> {
2595    let (neg, body) = match s.as_bytes().first()? {
2596        b'-' => (true, &s[1..]),
2597        b'+' => (false, &s[1..]),
2598        _ => (false, s),
2599    };
2600    let (radix, digits) =
2601        if let Some(h) = body.strip_prefix("0x").or_else(|| body.strip_prefix("0X")) {
2602            (16u32, h)
2603        } else if let Some(b) = body.strip_prefix("0b").or_else(|| body.strip_prefix("0B")) {
2604            (2, b)
2605        } else {
2606            (10, body)
2607        };
2608    let mut cleaned = alloc::string::String::new();
2609    let mut after_sep = true;
2610    for &c in digits.as_bytes() {
2611        if c == b'_' {
2612            if after_sep {
2613                return None;
2614            }
2615            after_sep = true;
2616        } else {
2617            cleaned.push(c as char);
2618            after_sep = false;
2619        }
2620    }
2621    if after_sep || cleaned.is_empty() {
2622        return None;
2623    }
2624    let mag = BigInt::parse_bytes(cleaned.as_bytes(), radix)?;
2625    Some(if neg { -mag } else { mag })
2626}
2627
2628/// Whether a block clause is a **binding** — it carries a top-level `=` punct.
2629/// A nested `=` lives inside a `(…)`/`[…]`/`{…}` item, never as a top-level
2630/// `Item::Punct`, so this is exactly the `<binding>` vs `<chain>` split the
2631/// `let` group makes (and [`split_binding`] then splits at the first `=`).
2632fn clause_is_binding(items: &[&Item<'_>]) -> bool {
2633    items.iter().any(|it| matches!(it, Item::Punct("=")))
2634}
2635
2636/// Whether `item` is a bare **matching literal** — a number, a `.`-symbol, or a
2637/// string — used where a whole `&`-header or `let` LHS is expected. These are
2638/// equality patterns, not binders, so a bare one reads as a stray value or an
2639/// intended binding; Elly requires the wrap (`&(42)`, `&(.foo)`, `&("s")`; see
2640/// `BareLiteralHeader`). Nested in `[…]`/`{…}`/`(…)`/an or-pattern they are fine.
2641fn is_bare_literal_header(item: &Item<'_>) -> bool {
2642    match item {
2643        Item::Sym(s) => parse_int(s).is_some(),
2644        Item::Prefixed { sigil: '.', .. } => true,
2645        Item::Str(_) => true,
2646        _ => false,
2647    }
2648}
2649
2650/// Check if an item is the `when` guard keyword.
2651fn is_when_kw(item: &Item) -> bool {
2652    head_keyword(item) == Some("when")
2653}
2654
2655/// Returns non-comment chains of a `<seq>` with [chain-joining](join_chains) applied.
2656fn chain_slices<'a, 'b>(seq: &'b Seq<'a>) -> Vec<Vec<&'b Item<'a>>> {
2657    let chains = seq
2658        .0
2659        .iter()
2660        .filter(|c| !is_comment_only(c))
2661        .map(|c| {
2662            c.0.iter()
2663                .filter(|it| !matches!(it, Item::Comm(_)))
2664                .collect()
2665        })
2666        .collect();
2667    join_chains(chains)
2668}
2669
2670/// Returns true if a `<braces>` seq contains a chain whose second non-comment item is `:`.
2671/// Used by [`Parser::block`] to reject bare-map spellings.
2672fn braces_contain_colon(seq: &Seq<'_>) -> bool {
2673    for chain in seq.0.iter().filter(|c| !is_comment_only(c)) {
2674        let items: Vec<&Item<'_>> = chain
2675            .0
2676            .iter()
2677            .filter(|it| !matches!(it, Item::Comm(_)))
2678            .collect();
2679        if items.len() >= 2 && matches!(items[1], Item::Punct(":")) {
2680            return true;
2681        }
2682    }
2683    false
2684}
2685
2686/// Joins adjacent chains if they form a single continued expression.
2687/// A chain joins if the predecessor ends with a continuation (e.g., `&`, `=`, `|>`, `when`)
2688/// or if the current chain starts with one (e.g., `|>`, `elif`, `else`, `catch`).
2689fn join_chains<'a, 'b>(chains: Vec<Vec<&'b Item<'a>>>) -> Vec<Vec<&'b Item<'a>>> {
2690    let mut out: Vec<Vec<&'b Item<'a>>> = Vec::with_capacity(chains.len());
2691    for chain in chains {
2692        let join = matches!(out.last(), Some(prev) if trailing_join(prev) || leading_join(&chain));
2693        if join {
2694            out.last_mut().unwrap().extend(chain);
2695        } else {
2696            out.push(chain);
2697        }
2698    }
2699    out
2700}
2701
2702/// Whether a chain ends with a trailing continuation.
2703fn trailing_join(chain: &[&Item]) -> bool {
2704    match chain.last() {
2705        Some(Item::Prefixed { sigil: '&', .. }) => true,
2706        Some(Item::Punct("=")) | Some(Item::Punct("|>")) => true,
2707        _ => {
2708            let n = chain.len();
2709            n >= 2
2710                && matches!(chain[n - 2], Item::Sym("when"))
2711                && matches!(chain[n - 1], Item::Parens(_))
2712        }
2713    }
2714}
2715
2716/// Whether a chain opens with a leading continuation.
2717fn leading_join(chain: &[&Item]) -> bool {
2718    match chain.first() {
2719        Some(Item::Punct("|>")) => true,
2720        Some(first) => matches!(head_keyword(first), Some("elif" | "else" | "catch")),
2721        None => false,
2722    }
2723}
2724
2725/// Returns the single non-comment chain of a `<seq>`, if it contains exactly one.
2726fn single_chain<'a, 'b>(seq: &'b Seq<'a>) -> Option<Vec<&'b Item<'a>>> {
2727    let mut chains = chain_slices(seq).into_iter();
2728    let first = chains.next()?;
2729    if chains.next().is_some() {
2730        return None;
2731    }
2732    Some(first)
2733}
2734
2735/// Whether a chain holds only comments.
2736fn is_comment_only(chain: &Chain) -> bool {
2737    chain.0.iter().all(|it| matches!(it, Item::Comm(_)))
2738}
2739
2740/// Whether `s` is an Elly keyword.
2741fn is_keyword(s: &str) -> bool {
2742    matches!(
2743        s,
2744        "Self"
2745            | "let"
2746            | "recur"
2747            | "import"
2748            | "const"
2749            | "local"
2750            | "with"
2751            | "as"
2752            | "when"
2753            | "match"
2754            | "case"
2755            | "of"
2756            | "if"
2757            | "elif"
2758            | "else"
2759            | "catch"
2760    )
2761}
2762
2763/// Whether `name` can be bound and referenced from source as a plain name.
2764pub fn is_bindable_name(name: &str) -> bool {
2765    muon::is_sym(name)
2766        && !is_keyword(name)
2767        && !name.starts_with("__")
2768        && name != "_"
2769        && !reads_as_number(name)
2770}
2771
2772/// Whether this item is a chain split point (e.g., `&`-binder or `let`/`recur`/`case`).
2773fn is_split_point(item: &Item) -> bool {
2774    is_binder(item)
2775        || matches!(
2776            head_keyword(item),
2777            Some("let") | Some("recur") | Some("case")
2778        )
2779}
2780
2781/// Whether this item is a `&`-prefixed binder.
2782fn is_binder(item: &Item) -> bool {
2783    matches!(item, Item::Prefixed { sigil: '&', .. })
2784}
2785
2786/// The keyword head of an item, if any (bare keyword or head of a keyword-headed run).
2787fn head_keyword<'a>(item: &Item<'a>) -> Option<&'a str> {
2788    match item {
2789        Item::Sym(s) if is_keyword(s) => Some(s),
2790        Item::Run(members) => match members.first() {
2791            Some(Item::Sym(s)) if is_keyword(s) => Some(s),
2792            _ => None,
2793        },
2794        _ => None,
2795    }
2796}