elly_core/parse.rs
1//! Parsing: Muon tree → Elly AST.
2//!
3//! A program is a single chain (`<expr>`). A chain's items are split on the
4//! first `&`-prefixed item into an application spine and a trailing
5//! abstraction, per `docs/elly-spec.md` and `docs/done/2026-07-06_elly-crate.md`.
6//! Comments are dropped.
7//!
8//! **Patterns** (see `docs/done/2026-07-26_elly-patterns-native.md`) are
9//! *faithful AST*: a `&`-header or a `let` binding's left-hand side parses into
10//! a [`Pattern`] node that the evaluator matches directly. Parsing does not compile patterns
11//! into eliminators — it only rephrases the Muon syntax into Elly's own
12//! structures. Every abstraction is an `Expr::Abs(Rc<Lambda>)`, a run of
13//! `&p0 &p1 …` collected into one multi-parameter [`Lambda`]; applying it matches
14//! the arguments against the head (refuting via `NoMatch` on a miss). Source
15//! application spines and spread calls flatten into one `Expr::App(Box<[Expr]>)`.
16
17use alloc::boxed::Box;
18use alloc::rc::Rc;
19use alloc::string::String;
20use alloc::vec::Vec;
21
22use muon::{Chain, Item, Seq};
23use num_bigint::BigInt;
24
25use crate::ast::{Captures, Clause, Expr, HomeLeaf, Lambda, MapKey, Pattern, ProtoRef, Text};
26use crate::eval::{Builtin, ModuleData};
27
28/// Copy a source string slice into an owned text leaf. The AST no longer borrows
29/// the Muon source (see `docs/todo/elly-impl-expr-repr-v1.md`), so every name /
30/// symbol / bound name captured at parse time is copied here (inline for short
31/// spellings, else a shared `Rc`). Takes `&str` so a `&&str` binding from a
32/// slice pattern deref-coerces at the call site.
33fn text(s: &str) -> Text {
34 Text::from(s)
35}
36
37// TODO: use thiserror?
38/// A parse failure: the input is a valid Muon tree but not a well-formed Elly
39/// program in this subset.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum ParseError {
42 /// The top-level sequence held no chain (nothing to evaluate).
43 EmptyProgram,
44 /// The top-level sequence held more than one chain; top-level sequencing
45 /// is deferred (a program is a single expression).
46 MultipleExpressions,
47 /// A chain reduced to nothing (e.g. only comments).
48 EmptyChain,
49 /// A `&` binder with no body — `&x` alone is ill-formed (yet).
50 AbsWithoutBody,
51 /// A `&` prefix on something that is not a well-formed binder header (see
52 /// `BadPattern` for the pattern content itself).
53 BadBinder,
54 /// A binder appeared where an atomic expression was expected. Not reachable
55 /// via the split logic, kept so parsing never panics.
56 UnexpectedBinder,
57 /// A `.` sigil on something that is not a single name/number segment (e.g.
58 /// `.(…)`, `.&x`). `.foo` / `.0` are symbols; the record sugar `.(…)` and
59 /// other prefixed items are deferred / ill-formed.
60 BadSymbol,
61 /// A bare `.` used as an atom (`f . g`, a lone `.`). At the Muon layer this
62 /// is a `<punct>`; Elly reserves it for a future `.` / `|>` application
63 /// combinator, which is deferred.
64 CombinatorDeferred,
65 /// `_` used as a reference. `_` is the discard pattern (a binder that binds
66 /// nothing), never a name that can be read back.
67 DiscardReference,
68 /// A `&`-binder tried to introduce a name starting with `__`. Such names are
69 /// reserved for builtins / special forms and may be referenced but not bound.
70 ReservedName,
71 /// A `<sym>` that begins like a number but is not a valid integer literal
72 /// (`1a`, `0xZZ`, `1__2`, `0x`). Since a `<name>` may not start with a digit,
73 /// a digit-leading sym must be a well-formed `<int>` or it is an error.
74 MalformedNumber,
75 /// A stray `:` / `=` (or other) `<punct>` item at atom position. Muon parses
76 /// this punctuation, but outside a `let` binding (`=`) or a map entry (`:`)
77 /// it has no atomic reading. (A `#{…}` map literal and a `#[…]` list value are
78 /// their own atoms; the bare `{…}` / `[…]` forms are rejected, not deferred.)
79 PunctAtAtom,
80 /// A map entry (`<braces>` chain) whose key is missing or malformed: no `:`
81 /// after the key, or a key item that is not a bare atom, a `.`-symbol, a `(…)`
82 /// computed key, or a `#[…]` / `#{…}` literal key (`{ : 1 }`, `{ foo }`).
83 MalformedMapKey,
84 /// A `let` not followed by a binder group — `let 1 e`. An *empty* group is not
85 /// an error: `let () e` binds nothing and is `e` (see `parse_let`).
86 LetMissingBinders,
87 /// A `let (…)` with nothing after the binder group; the body is required.
88 LetMissingBody,
89 /// A `recur` not followed by a binder group — `recur 1 e`. As with `let`, an
90 /// empty group is not an error: `recur () e` is `e`.
91 RecurMissingBinders,
92 /// A `recur (…)` with nothing after the binder group; the body is required.
93 RecurMissingBody,
94 /// A `recur` binding whose name is already in scope. The group's bindings are
95 /// simultaneous, so inside it the name would mean the group's own binding and
96 /// never the outer one — the opposite of what the identical `let` line means.
97 /// Detected by the resolution pass (`resolve.rs`); carries the offending name.
98 RecurShadowsOuter(Text),
99 /// A binding inside a `let (…)` group is not `<pattern> "=" <expr>`: missing
100 /// or repeated top-level `=`, an empty pattern, or an empty value.
101 MalformedBinding,
102 /// A keyword (`let`, `with`) used as a reference or a binder. Keywords are
103 /// syntax, not names: they may be neither read nor bound.
104 ReservedKeyword,
105 /// A malformed pattern: an empty pattern, a comma-grouped `(a, b)` used as a
106 /// single pattern, a misplaced `...rest`, or any item with no pattern reading.
107 BadPattern,
108 /// A comparand after `=` / `<` / `>` that is not a single **atom** (a name or
109 /// a literal). A pattern is a closed grammar — `= f x`, `< (f x)`, `= [a]`
110 /// have no reading; write `= x`, `= 5`, or `.foo`.
111 ComparandNotAtom,
112 /// A bare **matching literal** used as an entire `&`-header or `let` binding
113 /// LHS: a number (`&42`), a symbol (`&.foo`), or a string (`&"foo"`).
114 /// Parenthesize it — `&(42)`, `&(.foo)`, `&("foo")`, `let ((42) = v)` — so the
115 /// "match this literal" intent is explicit (a bare literal reads as a value, or
116 /// as an intended binder). A literal *nested* in `[…]` / `{…}` / `(…)` / an
117 /// or-pattern needs no parens.
118 BareLiteralHeader,
119 /// A `<str>` literal with an **unpaired UTF-16 surrogate** escape (`"\uD83D"`,
120 /// a high or low `\uXXXX` not completing a pair). Muon accepts it lexically,
121 /// but UTF-8 cannot represent it, so Elly's decode rejects it at parse.
122 LoneSurrogate,
123 /// A bare top-level `|` in a `let` binding's LHS: `let ((x=.a) | (x=.b) = v)`.
124 /// Wrap the or-pattern — `let (((x=.a) | (x=.b)) = v) e` — so it does not
125 /// visually compete with the binding's own `=`.
126 LetOrUnparenthesized,
127 /// An `as <Type>` whose `<Type>` is not one of the closed kinds
128 /// `Int Sym List Map Fun`.
129 UnknownType,
130 /// An or-pattern `(p | q)` whose arms bind different sets of names.
131 OrBindersMismatch,
132 /// A `|>` pipe combinator with a missing operand — `|> f`, `x |>`, or
133 /// `x |> |> f`. Both sides of `|>` must be non-empty.
134 MalformedPipe,
135 /// A name reference that resolves to no enclosing `&`-binding — a free
136 /// variable. Detected by the resolution pass (`resolve.rs`) after parsing,
137 /// so it is a *static* error: the program is rejected before it runs, rather
138 /// than raising `.unbound_name` at eval. Carries the offending name (spans
139 /// are still a TODO). `__`-names never reach here — they are resolved to
140 /// [`Expr::Builtin`](crate::Expr) at parse.
141 UnboundName(Text),
142 /// A module (`parse_module`) binding whose left-hand side is not a single bare
143 /// name: a destructuring pattern, a literal, a discard, or a digit-leading
144 /// token. Module top-level keys are plain names in v0. A `__…` or keyword LHS
145 /// reports [`ParseError::ReservedName`] / [`ParseError::ReservedKeyword`] instead.
146 BadModuleItemName,
147 /// A module defines the same item name twice, or an `import`'s local name is
148 /// also an item name. A name means one thing inside a module, so both are
149 /// rejected here. The `Foo = import "spec"` form is the one place a name is
150 /// both an import and an item: it is a single declaration, and it makes them
151 /// the same thing.
152 DuplicateModuleItem,
153 /// An `import` that is neither `import "<spec>" as <Name>` nor
154 /// `<Name> = import "<spec>"`: a missing `as`, a spec that is not a string
155 /// literal, or trailing items. The spec is a literal in a fixed position —
156 /// which is what makes a module's imports a static, auditable list — so there
157 /// is nothing to compute here.
158 MalformedImport,
159 /// An `import` somewhere other than a module's top level — today, inside a
160 /// [`recur`](crate::Expr::Recur) group. An import is a frame slot the const
161 /// stage fills before the module exists, so it can only be written in a
162 /// module's own source. Written anywhere else, `import` is an ordinary keyword
163 /// and reports [`ParseError::ReservedKeyword`].
164 MisplacedImport,
165 /// A `const` declaration that is neither `const <name>`, `const (<name> …)`
166 /// nor `<name> = const`: a declaration that is not a bare name, a group whose
167 /// chains are not bare names, or a **payload** — `const x = 1`, `x = const 1`.
168 /// The payload forms are reserved for const-time expressions and rejected
169 /// until those exist (see `docs/done/2026-08-13_elly-modules-iotas.md`).
170 MalformedConst,
171 /// A `const` somewhere other than a module's top level — today, inside a
172 /// [`recur`](crate::Expr::Recur) group. A group is built while a program runs
173 /// and has no instance of its own to mint against, so an iota can only be
174 /// declared in a module's source. Written anywhere else, `const` is an
175 /// ordinary keyword and reports [`ParseError::ReservedKeyword`].
176 MisplacedConst,
177 /// A binder — a pattern binder, a `let` binding, a `recur` binding — taking a
178 /// name the enclosing module declares as an **import**. A declaration's name
179 /// means one thing over the whole module, so it may not be shadowed from
180 /// inside (see `docs/done/2026-08-13_elly-modules-iotas.md`). Carries the name.
181 ShadowsImport(Text),
182 /// A binder taking a name the enclosing module declares as a `const`
183 /// [iota](ParseError::ShadowsImport). Carries the name.
184 ShadowsIota(Text),
185 /// A `local` declaration that is neither `local <name> = <expr>` nor the
186 /// group `local (<name> = <expr> …)`: a left-hand side that is not a bare
187 /// name, a group chain that is not a binding, or a missing body. The
188 /// head-with-arguments form `local f x = <expr>` lands here too — it is a
189 /// template moditem, left open (see `docs/done/2026-08-13_elly-modules-local.md`).
190 MalformedLocal,
191 /// A `local` somewhere other than a module's top level — today, inside a
192 /// [`recur`](crate::Expr::Recur) group, where every binding is private
193 /// already, so the keyword would say nothing. Written anywhere else, `local`
194 /// is an ordinary keyword and reports [`ParseError::ReservedKeyword`].
195 MisplacedLocal,
196 /// A binder taking a name the enclosing module declares as a
197 /// [`local`](ParseError::ShadowsImport). Carries the name.
198 ShadowsLocal(Text),
199 /// A binder taking a name the enclosing module — or an enclosing
200 /// [`recur`](crate::Expr::Recur) group — declares as an **item**. Carries the
201 /// name. This is the direction opposite to
202 /// [`RecurShadowsOuter`](ParseError::RecurShadowsOuter), which stops a group
203 /// binding from shadowing a name already in scope; together they make a
204 /// declared name unshadowable from either side.
205 ShadowsItem(Text),
206 /// A `__` name the language does not define, in either position it can be
207 /// written: a module top-level left-hand side (`__mian = …`) or a reference
208 /// in an expression (`__mian`). The `__` namespace belongs to the language —
209 /// a source may write the builtins and the **reserved moditems** it defines
210 /// and nothing else — so a misspelling fails here, naming the namespace,
211 /// rather than becoming an item nobody reads or a name nothing binds.
212 /// Carries the name as written.
213 UnknownReserved(Text),
214 /// A module declaring the same reserved moditem twice (two `__main`s). A
215 /// reserved moditem is a single slot, not a table, so there is no second one
216 /// to hold. Carries the name.
217 DuplicateReserved(Text),
218 /// A reserved moditem somewhere other than a module's top level — today,
219 /// inside a [`recur`](crate::Expr::Recur) group. A group is an item table,
220 /// not a module source, and nothing would ever read the slot. Written
221 /// anywhere else, `__main` is an ordinary `__` name and reports
222 /// [`UnknownReserved`](ParseError::UnknownReserved).
223 MisplacedReserved,
224 /// A [home-module leaf](crate::HomeLeaf) — `Self`, `#Self(…)`, `x.Self`, or
225 /// the `Self <pat>` pattern — outside a module body. Each reads the module
226 /// its body is written in, and there is none: the term is not a module's, or
227 /// it is a [`recur`](crate::Expr::Recur) group in a program that is not a
228 /// module. One test covers both, since a group is not a module either.
229 /// Carries the name — `Self`, the one word every form is spelled with.
230 OutsideModule(Text),
231 /// An `as <Proto>` pattern whose reference is a `__` name that is not a
232 /// **prototype**: a builtin that is no module at all (`as __eq`), or `__Err`,
233 /// which is a namespace of operations that no value answers `__proto` with —
234 /// so the check could never hold. Rejected here rather than compiled into a
235 /// pattern that always refutes. Carries the name.
236 NotAPrototype(Text),
237 /// A [`&`](muon::Item::Prefixed)-headed **run** (`&x.0`, `&(a, b).0`) — a
238 /// binder glued to a following atom with no whitespace. A binder is one
239 /// prefixed atom, so the glued tail has no reading; write it spaced
240 /// (`&x .0`) to apply, or restructure. See `docs/todo/elly-parse-runs.md` §C.
241 BinderRunGlued,
242 /// An `<int>`-then-`.<digits>` run — the float-like run `3.14` (Muon groups
243 /// the two atoms, see `docs/done/2026-09-08_muon-runs.md`). Float literals do
244 /// not exist yet; this holds the notation open for them (a real float is a
245 /// later `Num` extension) while giving a clear error now instead of the
246 /// runtime `.not_applicable` a spaced `3 .14` projection raises.
247 FloatLiteralUnsupported,
248 /// A [`#`](muon::Item::Prefixed)-prefixed form other than the three that
249 /// read: `#[…]` (a list), `#{…}` (a map), and `#Self(…)` / `#Self …`
250 /// (construction). The `#` sigil marks a **literal notation**, and the
251 /// remaining shapes — `#<sym>`, `#<str>`, `#(…)`, `#.name` — are reserved
252 /// for future literal kinds with no reading yet. A bare `#Self` lands here
253 /// as well: construction is a form, not a value, so it may only head a
254 /// spine (see `parse_app_spine`). See `docs/todo/elly-syntax-v1.md` §2.
255 ReservedLiteralForm,
256 /// A bare `[…]` where a list literal or list pattern is written. After the
257 /// reshuffle (phase 3), the list literal uses the `#`-prefixed spelling
258 /// exclusively: write `#[…]`. See `docs/todo/elly-syntax-v1.md` § 3–5.
259 BareListLiteral,
260 /// A bare `{…}` carrying a `:`-clause where a map literal or map pattern is
261 /// written. After the reshuffle, the map literal uses the `#`-prefixed
262 /// spelling exclusively: write `#{…}`. A `:`-clause has no reading in a block
263 /// (nothing legal in a block starts `<key> :`), so a brace group with one is
264 /// this error in every position; a brace group *without* a `:`-clause is a
265 /// block (step 6), and the empty `{}` is the unit value.
266 /// See `docs/todo/elly-syntax-v1.md` § 3–5.
267 BareMapLiteral,
268 /// A block `{…}` whose final clause is a binding (`{ …, x = v }`). A block's
269 /// value is its last clause's value and its bindings do not escape, so a
270 /// trailing binding would bind a name nothing can read and leave no value to
271 /// return — as a `let` with no body does. End the block with an expression.
272 /// See `docs/todo/elly-syntax-v1.md` § 5 (review) and step 6.
273 BlockTrailingBinding,
274 /// A `case` with no `{…}` arm block — `case x` or `case` alone, or a trailing
275 /// item that is not a brace group. The block is where the clauses live, so a
276 /// `case` without one has nothing to match against and no reading.
277 CaseMissingArms,
278 /// A `case` subject that is more than one item — `case f x { … }`. The subject
279 /// is exactly one item so the arm block's `{…}` is unambiguous; group a
280 /// compound subject (`case (f x) { … }`) or pipe it in (`f x |> case { … }`).
281 CaseSubjectExtra,
282 /// A `case` arm whose chain is not headed by `&` — a clause that is not a
283 /// binder. Every arm is `& <pattern> … <body>`; a bare expression is not one.
284 ArmNotBinder,
285 /// A `case` arm with a pattern (and any guards) but no body after them —
286 /// `& x` or `& x when (c)` alone. An arm's body is required.
287 ArmMissingBody,
288 /// A `when` guard with no `(…)` condition group — `when` at the end of a
289 /// header, or `when` followed by something other than a parenthesized group.
290 WhenMissingCond,
291}
292
293/// What a module's source writes to name an import — a path, a package-qualified
294/// name. Its grammar belongs to the host resolver, not to Elly.
295pub type ImportSpec = Text;
296
297/// What a resolver answers a spec with: one name per module, however many specs
298/// reach it. Module instances are deduplicated by this name, so a resolver that
299/// returns the spec unchanged would hand back two instances of one module for
300/// `"./foo"` and `"foo"` (see [`crate::load_module`]).
301pub type ModuleName = Text;
302
303/// One `import` declaration: the name the importing module reaches the instance
304/// by, and the spec its host resolver is handed.
305///
306/// The two written forms differ in whether the module also **re-exports** the
307/// instance. `import "spec" as Foo` is a private frame slot; `Foo = import "spec"`
308/// is the same slot plus an ordinary item `Foo` that forwards it. Both arrive here
309/// as one `ImportDecl` — the re-exporting form has additionally pushed its
310/// forwarding item into [`ModuleSyntax::items`] (see
311/// `docs/done/2026-08-11_elly-modules-import.md`).
312#[derive(Debug, Clone, PartialEq, Eq)]
313pub struct ImportDecl {
314 /// The name the importing module's bodies read the instance by.
315 pub name: Text,
316 /// What the host resolver is handed.
317 pub spec: ImportSpec,
318}
319
320/// A module source's top-level declarations: its imports and its `const`
321/// declarations, in source order, and its items. The imports are the module's
322/// first frame slots — the const stage resolves, compiles and instantiates each
323/// one, then instantiates this module against the results (see
324/// [`crate::load_module`]). The iotas take no slot at all: an iota's identity is
325/// its instance plus its position, derived on reference (see
326/// `docs/done/2026-08-13_elly-modules-iotas.md`).
327#[derive(Debug, Clone, PartialEq, Eq)]
328pub struct ModuleSyntax {
329 /// The `import` declarations, in source order, which is frame-slot order.
330 pub imports: Vec<ImportDecl>,
331 /// The names declared by `const`, in source order — the module's **iotas**.
332 /// Sorted before they reach [`ModuleData`](crate::ModuleData), where the
333 /// order is the address space a member reference counts against.
334 pub iotas: Vec<Text>,
335 /// The `name = body` items, in source order, bodies unresolved.
336 pub items: Vec<(Text, Expr)>,
337 /// The `local name = body` declarations, in source order, bodies unresolved
338 /// — the module's **private** bindings. They are members like the items, and
339 /// differ only in not being searched by name from outside (see
340 /// `docs/done/2026-08-13_elly-modules-local.md`).
341 pub locals: Vec<(Text, Expr)>,
342 /// What the module declared as `__main`, if anything — the **reserved
343 /// moditem** a runner evaluates and applies to the root capability.
344 ///
345 /// It is a slot rather than an entry in [`items`](Self::items), and that is
346 /// what makes it unnameable: the member index space it stays out of is the
347 /// only thing a body's reference or an outside `M.name` access can address.
348 /// See `docs/done/2026-08-14_elly-run.md`.
349 pub main: Option<Expr>,
350}
351
352/// Parse a whole parsed program (one top-level chain) to an expression.
353pub fn parse_program<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
354 // Chain joining runs first, so a program written as `&x` ⏎ `body` (or any
355 // other continuation) is one expression, not `MultipleExpressions`.
356 match chain_slices(seq).as_slice() {
357 [] => Err(ParseError::EmptyProgram),
358 [items] => parse_items(items),
359 _ => Err(ParseError::MultipleExpressions),
360 }
361}
362
363/// Parse a **module** source's top-level sequence into its declarations: one
364/// `name = body` item or one `import` per non-comment chain, in source order.
365/// Unlike [`parse_program`] a module is a *multi-chain* sequence, and a body is
366/// not resolved here — [`crate::compile_module`] runs
367/// [`crate::resolve::resolve_module_bodies`] afterwards so a body may refer to any
368/// sibling regardless of order. An item's left-hand side is a single bare name in
369/// v0 (no destructuring at module top level); duplicate names and non-name LHSs
370/// are rejected. See `docs/done/2026-08-07_elly-modules-v0.md`.
371///
372/// An `import` chain has no top-level `=` in its private form, so it is recognized
373/// before the `=` split rather than reported as a `MalformedBinding`. Its two
374/// forms are described on [`ImportDecl`]; `Foo = import "spec"` also pushes the
375/// item `Foo` that forwards the slot, since it is written as an item and should
376/// read as one from outside.
377///
378/// A `const` declaration is read the same way and follows the same privacy
379/// convention: `const false` and `const (false, true)` are private, `false =
380/// const` declares the iota *and* the item that exports it.
381///
382/// A `local` declaration is the ordinary binding under the same convention:
383/// `local x = <expr>` and the group `local (x = <expr> …)` bind a member the
384/// module's own bodies reach and nothing outside can, where the bare
385/// `x = <expr>` beside them is the export form.
386pub fn parse_module<'a>(seq: &Seq<'a>) -> Result<ModuleSyntax, ParseError> {
387 let mut items: Vec<(Text, Expr)> = Vec::new();
388 let mut imports: Vec<ImportDecl> = Vec::new();
389 let mut iotas: Vec<Text> = Vec::new();
390 let mut locals: Vec<(Text, Expr)> = Vec::new();
391 let mut main: Option<Expr> = None;
392 // The declarations that are also items, kept apart by kind, so the collision
393 // check below knows which shared names were written as one declaration and
394 // are therefore fine. A name shared by two *declarations* never is.
395 let mut reexported_imports: Vec<Text> = Vec::new();
396 let mut reexported_iotas: Vec<Text> = Vec::new();
397 for ci in chain_slices(seq) {
398 // `chain_slices` drops comment-only chains and applies chain joining, so
399 // `ci` is a non-empty, already-joined declaration chain.
400 // Split on the first top-level `=`, exactly as `parse_binding` does; a
401 // second top-level `=` is malformed (a nested `=` lives inside `(…)`/…).
402 let split = ci.iter().position(|it| matches!(it, Item::Punct("=")));
403 // `import "spec" as Foo` — a private slot, and the one module top-level
404 // chain with no `=` of its own. A chain that *has* one is a binding
405 // whatever it starts with, so `import = 3` still reports the keyword.
406 if split.is_none() && head_keyword(ci[0]) == Some("import") {
407 let (name, spec) = parse_private_import(&ci)?;
408 push_import(&mut imports, name, spec)?;
409 continue;
410 }
411 // `const false` / `const (false, true)` — a private declaration, and like
412 // a private import the whole chain is the declaration.
413 if split.is_none() && head_keyword(ci[0]) == Some("const") {
414 for name in parse_const_decl(&ci)? {
415 push_iota(&mut iotas, name)?;
416 }
417 continue;
418 }
419 // `local (x = 1, y = 2)` — the group form, whose bindings' `=`s all sit
420 // inside the tuple, so the chain has none of its own. The single form
421 // `local x = 1` does, and is read below with the other bindings.
422 if split.is_none() && head_keyword(ci[0]) == Some("local") {
423 for (name, body) in parse_local_group(&ci)? {
424 push_local(&mut locals, name, body)?;
425 }
426 continue;
427 }
428 let eq = split.ok_or(ParseError::MalformedBinding)?;
429 if ci[eq + 1..].iter().any(|it| matches!(it, Item::Punct("="))) {
430 return Err(ParseError::MalformedBinding);
431 }
432 let (head, tail) = (&ci[..eq], &ci[eq + 1..]);
433 if head.is_empty() || tail.is_empty() {
434 return Err(ParseError::MalformedBinding);
435 }
436 // `const x = <expr>`, the private const-time expression form the grammar
437 // reserves and nothing implements. A bare `const = 3` is not this — it
438 // falls through to `plain_name`, which reports the keyword.
439 if head.len() > 1 && head_keyword(head[0]) == Some("const") {
440 return Err(ParseError::MalformedConst);
441 }
442 // `local x = <expr>` — the single private binding. A bare `local = 3` is
443 // not this: it falls through to `plain_name`, which reports the keyword.
444 if head.len() > 1 && head_keyword(head[0]) == Some("local") {
445 let name = plain_name(&head[1..], ParseError::MalformedLocal)?;
446 push_local(&mut locals, name, parse_items(tail)?)?;
447 continue;
448 }
449 // `__main = <expr>` — a **reserved moditem**, recognized here because
450 // `plain_name` would otherwise stop at the `__` and report the name as
451 // reserved. It is not an item: it goes to its own slot, outside the
452 // member index space, so nothing can name it (see `ModuleSyntax::main`).
453 if let [Item::Sym(s)] = head {
454 if let Some(reserved) = reserved_moditem(s) {
455 if main.is_some() {
456 return Err(ParseError::DuplicateReserved(text(reserved)));
457 }
458 main = Some(parse_items(tail)?);
459 continue;
460 }
461 if s.starts_with("__") {
462 return Err(ParseError::UnknownReserved(text(s)));
463 }
464 }
465 let name = plain_name(head, ParseError::BadModuleItemName)?;
466 if items.iter().any(|(n, _)| n.as_str() == name.as_str()) {
467 return Err(ParseError::DuplicateModuleItem);
468 }
469 // `Foo = import "spec"` — the same slot, re-exported. The item's body is
470 // the name itself: resolution looks imports up before items, so it reads
471 // the slot rather than looping back through this forwarding item.
472 if head_keyword(tail[0]) == Some("import") {
473 let spec = parse_import_spec(tail)?;
474 push_import(&mut imports, name.clone(), spec)?;
475 reexported_imports.push(name.clone());
476 items.push((name.clone(), Expr::Name(name)));
477 continue;
478 }
479 // `false = const` — the iota plus the item exporting it. There is nothing
480 // to the right of the keyword until const-time expressions exist, and the
481 // item's body is the name itself: a sibling reference finds the iota,
482 // since members are searched iotas-first (see `resolve_module_bodies`).
483 if head_keyword(tail[0]) == Some("const") {
484 if tail.len() != 1 {
485 return Err(ParseError::MalformedConst);
486 }
487 push_iota(&mut iotas, name.clone())?;
488 reexported_iotas.push(name.clone());
489 items.push((name.clone(), Expr::Name(name)));
490 continue;
491 }
492 let body = parse_items(tail)?;
493 items.push((name, body));
494 }
495 // A private declaration may not share a name with an item — inside the module
496 // the name would have to mean both. A re-exported one shares by construction.
497 // Two *declarations* may never share one, re-exported or not: neither form
498 // writes an import and an iota at once.
499 for imp in &imports {
500 if iotas.iter().any(|n| n.as_str() == imp.name.as_str()) {
501 return Err(ParseError::DuplicateModuleItem);
502 }
503 if reexported_imports
504 .iter()
505 .any(|n| n.as_str() == imp.name.as_str())
506 {
507 continue;
508 }
509 if items.iter().any(|(n, _)| n.as_str() == imp.name.as_str()) {
510 return Err(ParseError::DuplicateModuleItem);
511 }
512 }
513 for iota in &iotas {
514 if reexported_iotas.iter().any(|n| n.as_str() == iota.as_str()) {
515 continue;
516 }
517 if items.iter().any(|(n, _)| n.as_str() == iota.as_str()) {
518 return Err(ParseError::DuplicateModuleItem);
519 }
520 }
521 // A local shares with nothing: it is a declaration, and there is no form
522 // writing a local and something else at once.
523 for (name, _) in &locals {
524 let taken = items.iter().any(|(n, _)| n.as_str() == name.as_str())
525 || iotas.iter().any(|n| n.as_str() == name.as_str())
526 || imports.iter().any(|i| i.name.as_str() == name.as_str());
527 if taken {
528 return Err(ParseError::DuplicateModuleItem);
529 }
530 }
531 Ok(ModuleSyntax {
532 imports,
533 iotas,
534 items,
535 locals,
536 main,
537 })
538}
539
540/// The reserved moditems the language defines, as the names a module top-level
541/// left-hand side may spell in the `__` namespace. One so far; `__test` and
542/// `__doc` are anticipated, and each will be another slot on [`ModuleSyntax`]
543/// with a clause here (see `docs/done/2026-08-14_elly-run.md`).
544///
545/// Answers with the `'static` spelling so the caller reports the *defined* name
546/// rather than echoing the source, and `None` for a name that is not one — which
547/// the caller turns into [`ParseError::UnknownReserved`] rather than an item.
548fn reserved_moditem(name: &str) -> Option<&'static str> {
549 match name {
550 "__main" => Some("__main"),
551 _ => None,
552 }
553}
554
555/// Record one import, rejecting a name a previous import already took.
556fn push_import(
557 imports: &mut Vec<ImportDecl>,
558 name: Text,
559 spec: ImportSpec,
560) -> Result<(), ParseError> {
561 if imports.iter().any(|i| i.name.as_str() == name.as_str()) {
562 return Err(ParseError::DuplicateModuleItem);
563 }
564 imports.push(ImportDecl { name, spec });
565 Ok(())
566}
567
568/// Parse `import "<spec>" as <Name>` — the private form, whose whole chain is the
569/// declaration — to its name and spec. The keyword may be a bare sym or a glued
570/// run head (§D); in the run case only the spec glues into the run (`import"spec"`)
571/// while `as <Name>` stays in the chain after it, so the two are concatenated back
572/// into the one item stream the spaced form already is.
573fn parse_private_import<'a>(ci: &[&Item<'a>]) -> Result<(Text, ImportSpec), ParseError> {
574 // Drop the keyword head. When it is a glued run head (§D) the spec sits inside
575 // the run (`import"spec"`), but `as <Name>` is space-separated and so stays in
576 // the chain after the run — so the run's members and the chain tail concatenate
577 // into one `"spec" as Name` stream, the same one the spaced form is.
578 let rest: Vec<&Item<'a>> = match ci[0] {
579 Item::Run(members) => members[1..]
580 .iter()
581 .map(|it| it as &Item<'a>)
582 .chain(ci[1..].iter().copied())
583 .collect(),
584 _ => ci[1..].to_vec(),
585 };
586 match rest.as_slice() {
587 [Item::Str(spec), Item::Sym("as"), rest @ ..] if !rest.is_empty() => {
588 let name = plain_name(rest, ParseError::BadModuleItemName)?;
589 Ok((name, decode_str(spec)?))
590 }
591 _ => Err(ParseError::MalformedImport),
592 }
593}
594
595/// Record one iota, rejecting a name a previous `const` already declared.
596fn push_iota(iotas: &mut Vec<Text>, name: Text) -> Result<(), ParseError> {
597 if iotas.iter().any(|n| n.as_str() == name.as_str()) {
598 return Err(ParseError::DuplicateModuleItem);
599 }
600 iotas.push(name);
601 Ok(())
602}
603
604/// Parse a private `const` declaration — `const <name>`, or the group form
605/// `const (<name> …)` — to the names it declares.
606///
607/// The group is an ordinary `<parens>` wrapping a `<seq>`, so its names may be
608/// newline- or comma-separated and it may carry comments, exactly as a `let`
609/// group may. An **empty group declares nothing**, the same degenerate reading
610/// `let ()` and `recur ()` take.
611fn parse_const_decl<'a>(ci: &[&Item<'a>]) -> Result<Vec<Text>, ParseError> {
612 // The keyword may be a bare sym or a glued run head (§D); the declared names
613 // are read from what follows it (the run's members in the run case).
614 let rest: Vec<&Item<'a>> = match ci[0] {
615 Item::Run(members) => members[1..].iter().map(|it| it as &Item<'a>).collect(),
616 _ => ci[1..].to_vec(),
617 };
618 match rest.as_slice() {
619 [Item::Parens(seq), ..] => {
620 let mut names = Vec::new();
621 for c in chain_slices(seq) {
622 names.push(plain_name(&c, ParseError::MalformedConst)?);
623 }
624 Ok(names)
625 }
626 rest if !rest.is_empty() => Ok(alloc::vec![plain_name(rest, ParseError::MalformedConst)?]),
627 _ => Err(ParseError::MalformedConst),
628 }
629}
630
631/// Record one local, rejecting a name a previous `local` already took.
632fn push_local(locals: &mut Vec<(Text, Expr)>, name: Text, body: Expr) -> Result<(), ParseError> {
633 if locals.iter().any(|(n, _)| n.as_str() == name.as_str()) {
634 return Err(ParseError::DuplicateModuleItem);
635 }
636 locals.push((name, body));
637 Ok(())
638}
639
640/// Parse the group form `local (<name> = <expr> …)` to its bindings.
641///
642/// The group is an ordinary `<parens>` wrapping a `<seq>`, so its bindings may be
643/// newline- or comma-separated and it may carry comments, exactly as a `let`
644/// group may — and an **empty group declares nothing**, the degenerate reading
645/// `const ()` and `let ()` take. Anything else after the keyword is a malformed
646/// declaration: the single form `local x = <expr>` has a top-level `=` and never
647/// reaches here.
648fn parse_local_group<'a>(ci: &[&Item<'a>]) -> Result<Vec<(Text, Expr)>, ParseError> {
649 // The `local` keyword may be a bare sym or a glued run head (§D); the group is
650 // the item after it (the run's second member in the run case).
651 // The group is the item after the keyword: the run's second member in the run
652 // case, the next chain item otherwise.
653 let seq = match ci[0] {
654 Item::Run(members) => {
655 let Some(Item::Parens(seq)) = members.get(1) else {
656 return Err(ParseError::MalformedLocal);
657 };
658 seq
659 }
660 _ => {
661 let Some(Item::Parens(seq)) = ci.get(1) else {
662 return Err(ParseError::MalformedLocal);
663 };
664 seq
665 }
666 };
667 let mut bindings = Vec::new();
668 for c in chain_slices(seq) {
669 let eq = c
670 .iter()
671 .position(|it| matches!(it, Item::Punct("=")))
672 .ok_or(ParseError::MalformedLocal)?;
673 let (head, tail) = (&c[..eq], &c[eq + 1..]);
674 if head.is_empty() || tail.is_empty() {
675 return Err(ParseError::MalformedLocal);
676 }
677 if tail.iter().any(|it| matches!(it, Item::Punct("="))) {
678 return Err(ParseError::MalformedLocal);
679 }
680 bindings.push((
681 plain_name(head, ParseError::MalformedLocal)?,
682 parse_items(tail)?,
683 ));
684 }
685 Ok(bindings)
686}
687
688/// Parse the `import "<spec>"` right-hand side of a re-exporting import to its
689/// spec. There is no `as` here: the item's own left-hand side named it. The
690/// keyword may be spaced from the spec (two chain items) or glued to it (§D — one
691/// run whose second member is the spec), the two spellings meaning the same.
692fn parse_import_spec<'a>(tail: &[&Item<'a>]) -> Result<ImportSpec, ParseError> {
693 let spec = match tail {
694 [_import, Item::Str(spec)] => spec,
695 [Item::Run(members)] => match members.as_slice() {
696 [_import, Item::Str(spec)] => spec,
697 _ => return Err(ParseError::MalformedImport),
698 },
699 _ => return Err(ParseError::MalformedImport),
700 };
701 decode_str(spec)
702}
703
704/// The single plain name a binding position may hold: exactly one `<sym>` that is
705/// not a keyword, a `__` name, a discard, or a number. A reserved token gets its
706/// own error; every other shape (a destructuring pattern, a literal, several
707/// items) gets the caller's `bad`, which names the position —
708/// [`ParseError::BadPattern`] for an at-pattern's LHS,
709/// [`ParseError::BadModuleItemName`] for a module item's.
710fn plain_name<'a>(items: &[&Item<'a>], bad: ParseError) -> Result<Text, ParseError> {
711 match items {
712 [Item::Sym(s)] if is_keyword(s) => Err(ParseError::ReservedKeyword),
713 [Item::Sym(s)] if s.starts_with("__") => Err(ParseError::ReservedName),
714 [Item::Sym(s)] if reads_as_number(s) || *s == "_" => Err(bad),
715 [Item::Sym(s)] => Ok(text(s)),
716 _ => Err(bad),
717 }
718}
719
720/// Would this non-empty `<sym>` read as a number rather than a reference? Either
721/// it is a valid integer literal — including a signed one, since `-`/`+` are
722/// symchars, so `-1` is one `<sym>` — or it is digit-leading and therefore a
723/// *malformed* number. A reference is neither (see the atom path above), so a
724/// binding may not take such a name: it could never be read back.
725fn reads_as_number(s: &str) -> bool {
726 parse_int(s).is_some() || s.as_bytes()[0].is_ascii_digit()
727}
728
729/// Parse a comment-free slice of items (a chain, or a suffix of one).
730fn parse_items<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
731 if items.is_empty() {
732 return Err(ParseError::EmptyChain);
733 }
734 // `|>` is the **loosest** operator: split on the last one so the chain is
735 // a left-associative pipe chain over **segments**, each parsed by
736 // `parse_segment`. A `&`/`let`/`recur` binder form can therefore never
737 // capture a pipe — `&x x |> f` is `f (&x x)`, and a pipe-bodied lambda is
738 // written `&x (x |> f)` — and a pipe's right operand is a whole segment,
739 // which may itself end in a trailing binder form (`x |> &y body`).
740 match items.iter().rposition(|it| matches!(it, Item::Punct("|>"))) {
741 None => parse_segment(items),
742 Some(k) => {
743 let (left, right) = (&items[..k], &items[k + 1..]);
744 if left.is_empty() || right.is_empty() {
745 return Err(ParseError::MalformedPipe);
746 }
747 let l = parse_items(left)?;
748 let r = parse_segment(right)?;
749 // Thread the left operand in as `r`'s final argument, extending its
750 // spine so `x |> f a b` stays one flattened call `f a b x`.
751 Ok(append_arg(r, l))
752 }
753 }
754}
755
756/// Parse one pipe segment — a `|>`-free item slice: an application spine whose
757/// trailing argument may be a `&`/`let`/`recur` binder form. A split point is a
758/// `&`-binder or the `let`/`recur` keyword (or a `let`/`recur` **run head**, §D):
759/// it has the lowest precedence within the segment and captures the rest of the
760/// segment as its body, so the spine before the first one is a left-folded
761/// application whose trailing argument is the abstraction / let / recur that
762/// follows. With no split point this is exactly `parse_app_spine`.
763fn parse_segment<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
764 match items.iter().position(|it| is_split_point(it)) {
765 // No split point: a plain application spine (with `()` spread).
766 None => parse_app_spine(items),
767 // Leading split point: a `let (…) body`, a `recur (…) body`, or
768 // `&<header> <rest>` abstracting a pattern over the rest.
769 Some(0) => {
770 match head_keyword(items[0]) {
771 // A `let`/`recur` written glued to its group reads the group from
772 // inside the run (§D): `let(x = 1) e` == `let (x = 1) e`.
773 Some("let") | Some("recur") => parse_let_or_recur(items[0], items),
774 // `case <subject>? { arms }`
775 Some("case") => parse_case(items),
776 _ => {
777 // A `&` binder (never `&`-headed-run, which is a run, not a
778 // `Prefixed`).
779 if items.len() == 1 {
780 return Err(ParseError::AbsWithoutBody);
781 }
782 let inner = match items[0] {
783 Item::Prefixed { sigil: '&', item } => item.as_ref(),
784 _ => return Err(ParseError::UnexpectedBinder),
785 };
786 // A `&<pat> when (…) …` binder carries a guard on its pattern,
787 // the same guard a `case` arm takes (see `peel_when`); the body
788 // is what remains after the guards. Guards attach only to a
789 // single (non-group) pattern header — a `&(a, b)` curried group
790 // with a `when` is not supported and falls through to
791 // `build_binder`, where the `when` lands in the body and errors.
792 if matches!(items.get(1), Some(it) if is_when_kw(it))
793 && !matches!(inner, Item::Parens(_))
794 {
795 if is_bare_literal_header(inner) {
796 return Err(ParseError::BareLiteralHeader);
797 }
798 let base = parse_pattern(&[inner])?;
799 let (pat, rest) = peel_when(base, &items[1..])?;
800 if rest.is_empty() {
801 return Err(ParseError::AbsWithoutBody);
802 }
803 let body = parse_segment(rest)?;
804 return Ok(abs(pat, body));
805 }
806 let body = parse_segment(&items[1..])?;
807 build_binder(inner, body)
808 }
809 }
810 }
811 // Split point in the middle: the abstraction / let is the trailing
812 // argument of the application spine that precedes it.
813 Some(k) => {
814 let f = parse_segment(&items[..k])?;
815 let arg = parse_segment(&items[k..])?;
816 Ok(append_arg(f, arg))
817 }
818 }
819}
820
821/// Parse a leading `let`/`recur` form, reading the binder group from the item's
822/// **second member** when the keyword is a glued run (§D) or from the next chain
823/// item when it is a bare sym. `let(x = 1) e` and `let (x = 1) e` are the same.
824/// The group must be a `(…)` tuple; a keyword with no group, or a glued run with
825/// more than the keyword and one group member (`let(a)(b)`), is `…MissingBinders`.
826fn parse_let_or_recur<'a>(head: &Item<'a>, items: &[&Item<'a>]) -> Result<Expr, ParseError> {
827 // A keyword is `let` or `recur` (the only heads that reach here).
828 let is_recur = head_keyword(head) == Some("recur");
829 let missing = || {
830 if is_recur {
831 ParseError::RecurMissingBinders
832 } else {
833 ParseError::LetMissingBinders
834 }
835 };
836 let (group_item, body_items): (&Item<'_>, &[&Item<'_>]) = match head {
837 // Glued run: the group is the run's lone second member (a longer run is
838 // malformed); the body is the chain after the run.
839 Item::Run(members) if members.len() == 2 => (&members[1], &items[1..]),
840 Item::Run(_) => return Err(missing()),
841 // Bare keyword: the group is the next chain item, the body what follows it.
842 _ => (*items.get(1).ok_or_else(missing)?, &items[2..]),
843 };
844 let group = match group_item {
845 Item::Parens(seq) => seq,
846 _ => return Err(missing()),
847 };
848 if is_recur {
849 parse_recur_inner(group, body_items)
850 } else {
851 parse_let_inner(group, body_items)
852 }
853}
854
855/// The body of a `let`: fold the group's **sequential** bindings
856/// (`let (a = va, b = vb) e` desugars to the nested `(&a ((&b e) vb)) va`)
857/// right-to-left over `body_items`, leaving the leftmost binding outermost.
858/// Reached through [`parse_let_or_recur`], which reads the group from the chain
859/// (a bare `let (…)` keyword) or from inside a glued run (`let(…)`).
860///
861/// An **empty group binds nothing**, so `let () e` is `e` — the fold over zero
862/// bindings, and the unit of the form. It is not an error: a group is a list, and
863/// a generated or commented-out one may legitimately come out empty.
864fn parse_let_inner<'a>(group: &Seq<'a>, body_items: &[&Item<'a>]) -> Result<Expr, ParseError> {
865 if body_items.is_empty() {
866 return Err(ParseError::LetMissingBody);
867 }
868 // `let (a = va, b = vb) e` **is** the block `{ a = va, b = vb, e }`: the group's
869 // (joined, comment-free) binding chains followed by the body as the final
870 // clause. `build_block` produces the shared `Expr::Block`, collapsing an empty
871 // group (`let () e` is `e`) and a lone body the same way a `{…}` block does.
872 let mut clauses = chain_slices(group);
873 // A `let` group holds **bindings only** — a chain without a top-level `=` is a
874 // malformed binding (unlike a block, whose non-final clauses may be bare
875 // expressions). Enforce it here so `let (x) x` stays `MalformedBinding`.
876 for ci in &clauses {
877 if !clause_is_binding(ci) {
878 return Err(ParseError::MalformedBinding);
879 }
880 }
881 clauses.push(body_items.to_vec());
882 build_block(clauses)
883}
884
885/// Parse a `recur (b0, b1, …) body`. Shaped like [`parse_let`] — `items[0]` is the
886/// keyword, `items[1]` the binder-group tuple, `items[2..]` the body — and unlike
887/// it in what the group means: the bindings are **simultaneous**, so they cannot
888/// desugar to nested abstractions and instead become a module item table that the
889/// bodies reach through a terminal.
890///
891/// The left-hand sides are plain names, not patterns. The group *is* an item table,
892/// so it follows [`parse_module`]'s rule (which is also what rejects a duplicate)
893/// rather than `let`'s, which does allow a destructuring pattern. That divergence
894/// falls out of the mechanism rather than being a choice.
895///
896/// The table is sorted here, at parse: the sort order *is* the index space a
897/// sibling [`Expr::ModItem`] counts against, and the resolver assigns those indices
898/// against the frozen [`ModuleData`].
899///
900/// As with `let`, an empty group is `body` — with the terminal dropped too, since a
901/// group with no items has nothing to reach through it.
902fn parse_recur_inner<'a>(group: &Seq<'a>, body_items: &[&Item<'a>]) -> Result<Expr, ParseError> {
903 if body_items.is_empty() {
904 return Err(ParseError::RecurMissingBody);
905 }
906 let syntax = parse_module(group)?;
907 if !syntax.imports.is_empty() {
908 // A group is an item table, not a module source: it has no frame of its
909 // own for the const stage to fill, and it is built while a program runs.
910 return Err(ParseError::MisplacedImport);
911 }
912 if !syntax.iotas.is_empty() {
913 // Nor an instance of its own to mint iotas against.
914 return Err(ParseError::MisplacedConst);
915 }
916 if !syntax.locals.is_empty() {
917 // And every binding of a group is private already — it is reachable from
918 // the group's own bodies and its body, and from nowhere else — so the
919 // keyword would mark nothing.
920 return Err(ParseError::MisplacedLocal);
921 }
922 if syntax.main.is_some() {
923 // A reserved moditem is read by a tool that was pointed at a *source* —
924 // a runner, a test runner — and a group is not one, so the slot would
925 // never be looked at.
926 return Err(ParseError::MisplacedReserved);
927 }
928 let mut bindings = syntax.items;
929 let body = parse_segment(body_items)?;
930 if bindings.is_empty() {
931 return Ok(body);
932 }
933 bindings.sort_by(|(a, _), (b, _)| a.as_str().cmp(b.as_str()));
934 // No frame, no imports, and no name: a group's bindings are written where
935 // they are used, so there is no source of its own for a host to name. The
936 // body goes in the module's own body slot — the same one a source fills with
937 // `__main`, differing only in who evaluates it and when.
938 Ok(Expr::Recur(Rc::new(ModuleData::from_bindings(
939 bindings,
940 Vec::new(),
941 Box::new([]),
942 Box::new([]),
943 Box::new([]),
944 Some(body),
945 None,
946 ))))
947}
948
949/// Parse one binding chain `<pattern> "=" <expr>` to its pattern and value. The
950/// separator is the **first** top-level `=` (`<punct>`); a pattern may carry its
951/// own `=` only inside `(…)`/`[…]`/`{…}` (a nested item, never top-level), so a
952/// second top-level `=` is malformed. Both the head (pattern) and tail (value)
953/// must be non-empty.
954fn parse_binding<'a>(items: &[&Item<'a>]) -> Result<(Pattern, Expr), ParseError> {
955 let mut eq = None;
956 for (i, it) in items.iter().enumerate() {
957 if matches!(it, Item::Punct("=")) {
958 if eq.is_some() {
959 return Err(ParseError::MalformedBinding); // a second top-level `=`
960 }
961 eq = Some(i);
962 }
963 }
964 let eq = eq.ok_or(ParseError::MalformedBinding)?;
965 let (head, tail) = (&items[..eq], &items[eq + 1..]);
966 if head.is_empty() || tail.is_empty() {
967 return Err(ParseError::MalformedBinding);
968 }
969 // A top-level `|` in the LHS must be parenthesized (an `&`-header can't reach
970 // this, since `&` prefixes one item), so the or-`|` and the binding `=` do
971 // not compete: `let (((x=.a) | (x=.b)) = v) e`.
972 if head.iter().any(|it| matches!(it, Item::Punct("|"))) {
973 return Err(ParseError::LetOrUnparenthesized);
974 }
975 // A bare matching literal as the whole LHS needs parens: `let ((42) = v)`,
976 // `let ((.foo) = v)`, `let (("s") = v)` (see `BareLiteralHeader`).
977 if let [it] = head {
978 if is_bare_literal_header(it) {
979 return Err(ParseError::BareLiteralHeader);
980 }
981 }
982 let pat = parse_pattern(head)?;
983 let value = parse_items(tail)?;
984 Ok((pat, value))
985}
986
987/// Parse a single item as an atomic expression (`<aexpr>`).
988fn parse_aexpr<'a>(item: &Item<'a>) -> Result<Expr, ParseError> {
989 match item {
990 Item::Sym(s) => Ok(parse_sym(s)?),
991 // A `.`-prefixed item is a symbol (`.foo`, `.0`); see parse_dot_symbol.
992 Item::Prefixed { sigil: '.', item } => parse_dot_symbol(item),
993 // A `[…]` list is a runtime list value (any arity, no 1-element
994 // collapse); a `(…)` group is grouping / spread folded into one expr.
995 // After the reshuffle (phase 3), bare `[…]` is rejected: write `#[…]`.
996 Item::Brackets(_) => Err(ParseError::BareListLiteral),
997 Item::Parens(seq) => parse_group(seq),
998 // A `<str>` literal, decoded at parse into an owned `Expr::Str`.
999 Item::Str(s) => Ok(Expr::Str(decode_str(s)?)),
1000 // A `{…}` brace group is a **block expression** (step 6), never a map:
1001 // the map literal is `#{…}` exclusively. `parse_block` reads the empty
1002 // `{}` as unit and refuses a stale `:`-clause with the `#{…}` nudge.
1003 Item::Braces(seq) => parse_block(seq),
1004 // A bare `.` (composition combinator) is deferred; any other stray punct
1005 // (`:`, `=`) has no atomic reading outside a map entry / `let` binding.
1006 Item::Punct(".") => Err(ParseError::CombinatorDeferred),
1007 Item::Punct(_) => Err(ParseError::PunctAtAtom),
1008 // Filtered before we get here.
1009 Item::Comm(_) => Err(ParseError::EmptyChain),
1010 // `#[…]` / `#{…}` are the explicit spellings of the list / map literals,
1011 // reading exactly as the bare `[…]` / `{…}` forms (reshuffle phase 1);
1012 // any other `#`-prefixed shape — including a bare `#Self`, which is a
1013 // form, not a value (see `parse_app_spine`) — is a reserved literal
1014 // notation (§2).
1015 Item::Prefixed { sigil: '#', item } => match item.as_ref() {
1016 Item::Brackets(seq) => parse_list(seq),
1017 Item::Braces(seq) => parse_map(seq),
1018 _ => Err(ParseError::ReservedLiteralForm),
1019 },
1020 // A `&`-binder is never fed to parse_aexpr by the split logic above.
1021 Item::Prefixed { .. } => Err(ParseError::UnexpectedBinder),
1022 // A **run** of glued atoms is one chain item; lower it to the single
1023 // expression its members would form (see `parse_run`).
1024 Item::Run(members) => parse_run(members),
1025 }
1026}
1027
1028/// Lower a **run** — two or more atoms written with no whitespace — to the single
1029/// expression its members would form as one chain. The members are all atoms (a
1030/// `<punct>` or `<comm>` ends a run, never joins one), so they are exactly a
1031/// `<chain>`: parse them with the chain parser.
1032///
1033/// Two members that glue are either a **path** (a reference head followed by
1034/// `.`-segments) or an application (`f(x)`, `Main .A.S`). Both are the same
1035/// lowering — the members as a left-folded chain — so one `parse_items` call
1036/// serves both. A `&`-headed run is a binder slip and is refused outright (§C),
1037/// and a float-like run (`3.14`) is a rejected literal (§G).
1038fn parse_run<'a>(members: &[Item<'a>]) -> Result<Expr, ParseError> {
1039 check_run_head(members)?;
1040 parse_items(
1041 members
1042 .iter()
1043 .map(|it| it as &Item<'_>)
1044 .collect::<Vec<_>>()
1045 .as_slice(),
1046 )
1047}
1048
1049/// Reject the two run shapes that have no reading in **any** position, so the
1050/// argument lowering ([`parse_run`]) and the spine-head flatten ([`parse_app_spine`])
1051/// refuse them alike:
1052///
1053/// - §C — a `&`-headed run is a binder glued to a following atom (`&x.0` is one
1054/// prefixed atom with a tail, and the tail has no binder reading);
1055/// - §G — an `<int>` immediately followed by a `.<digits>` segment is the float
1056/// shape (`3.14`), refused rather than read as the projection `3 .14` applied,
1057/// until a float literal exists.
1058///
1059/// A run always has ≥ 2 members (Muon never wraps a lone atom), so indexing the
1060/// head is sound.
1061fn check_run_head(members: &[Item<'_>]) -> Result<(), ParseError> {
1062 if matches!(&members[0], Item::Prefixed { sigil: '&', .. }) {
1063 return Err(ParseError::BinderRunGlued);
1064 }
1065 if let (
1066 Item::Sym(head),
1067 Some(Item::Prefixed {
1068 sigil: '.',
1069 item: seg,
1070 }),
1071 ) = (&members[0], members.get(1))
1072 {
1073 if parse_int(head).is_some() && is_dot_number(seg) {
1074 return Err(ParseError::FloatLiteralUnsupported);
1075 }
1076 }
1077 Ok(())
1078}
1079
1080/// Is this a `.`-prefixed **number** segment (the second half of a float run)?
1081/// `3.14` is `Run[3, .14]`; a `.name` segment (`.foo`) is a path member, not a
1082/// float tail.
1083fn is_dot_number(item: &Item<'_>) -> bool {
1084 match item {
1085 Item::Sym(s) => parse_int(s).is_some(),
1086 _ => false,
1087 }
1088}
1089
1090/// Decode a Muon `<str>`'s raw inner text (the bytes between the quotes, escapes
1091/// unresolved) into an owned UTF-8 [`Text`]. Muon has already validated the escape
1092/// *grammar* (only `\" \n \t \r \\ \/ \b \f` and four-hex-digit `\uXXXX`, no raw
1093/// newline), so this resolves the escapes and — the higher-layer job Muon defers —
1094/// **combines `\uXXXX` surrogate pairs** into one scalar. The one failure is an
1095/// **unpaired surrogate** (a high not followed by a low, or a lone low), which
1096/// UTF-8 cannot represent: [`ParseError::LoneSurrogate`].
1097fn decode_str(raw: &str) -> Result<Text, ParseError> {
1098 let b = raw.as_bytes();
1099 let mut out = String::with_capacity(raw.len());
1100 let mut i = 0;
1101 let mut chunk = 0; // start of the current verbatim (unescaped) run
1102 while i < b.len() {
1103 if b[i] != b'\\' {
1104 i += 1; // a non-escape byte (incl. any UTF-8 continuation) copies as-is
1105 continue;
1106 }
1107 out.push_str(&raw[chunk..i]); // flush the verbatim run before this escape
1108 match b[i + 1] {
1109 b'"' => out.push('"'),
1110 b'\\' => out.push('\\'),
1111 b'/' => out.push('/'),
1112 b'n' => out.push('\n'),
1113 b't' => out.push('\t'),
1114 b'r' => out.push('\r'),
1115 b'b' => out.push('\u{8}'),
1116 b'f' => out.push('\u{c}'),
1117 b'u' => {
1118 let hi = hex4(&b[i + 2..i + 6]);
1119 i += 6;
1120 if (0xD800..=0xDBFF).contains(&hi) {
1121 // A high surrogate must be completed by a `\uXXXX` low surrogate.
1122 let lo = match (b.get(i), b.get(i + 1)) {
1123 (Some(b'\\'), Some(b'u')) => hex4(&b[i + 2..i + 6]),
1124 _ => return Err(ParseError::LoneSurrogate),
1125 };
1126 if !(0xDC00..=0xDFFF).contains(&lo) {
1127 return Err(ParseError::LoneSurrogate);
1128 }
1129 let c = 0x1_0000 + ((hi - 0xD800) << 10) + (lo - 0xDC00);
1130 out.push(char::from_u32(c).expect("combined surrogate pair"));
1131 i += 6;
1132 } else if (0xDC00..=0xDFFF).contains(&hi) {
1133 return Err(ParseError::LoneSurrogate); // a lone low surrogate
1134 } else {
1135 out.push(char::from_u32(hi).expect("non-surrogate BMP scalar"));
1136 }
1137 chunk = i;
1138 continue;
1139 }
1140 _ => unreachable!("Muon validated the <str> escape grammar"),
1141 }
1142 i += 2;
1143 chunk = i;
1144 }
1145 out.push_str(&raw[chunk..]);
1146 Ok(Text::from(out.as_str()))
1147}
1148
1149/// Read exactly four ASCII hex-digit bytes (Muon-validated) as a `u32`.
1150fn hex4(b: &[u8]) -> u32 {
1151 b.iter()
1152 .fold(0, |v, &c| v * 16 + (c as char).to_digit(16).unwrap())
1153}
1154
1155/// Parse a `.`-prefixed item into a symbol. The inner item must be a single
1156/// name or number segment (`.foo`, `.0`); a `.` glued to anything else — the
1157/// record sugar `.(…)`, a nested sigil, a string — is deferred / ill-formed.
1158fn parse_dot_symbol<'a>(item: &Item<'a>) -> Result<Expr, ParseError> {
1159 match item {
1160 // Stored *without* the leading dot; the dot is implied by Expr::Symbol
1161 // and re-added when displayed / dumped.
1162 Item::Sym(s) => Ok(Expr::Symbol(text(s))),
1163 _ => Err(ParseError::BadSymbol),
1164 }
1165}
1166
1167/// Classify a `<sym>`: an integer literal, a name, or an error. Symbols are no
1168/// longer `<sym>`s (a `.` is a Muon sigil now) — they arrive as `<prefixed>`
1169/// items and are handled by parse_dot_symbol.
1170fn parse_sym(s: &str) -> Result<Expr, ParseError> {
1171 // `Self` — the home module. It is a keyword (un-bindable, and the `Self <pat>`
1172 // pattern form needs a reserved head), but a *reference* to it is the module
1173 // leaf, so the short-circuit sits before the keyword guard. `as Self` reaches
1174 // here through `parse_proto_ref`, so the nominal check is the same leaf.
1175 if s == "Self" {
1176 return Ok(Expr::Home {
1177 leaf: HomeLeaf::Module,
1178 depth: 0,
1179 });
1180 }
1181 if is_keyword(s) {
1182 // A keyword is syntax, never a reference. `let` at a chain head is
1183 // handled before we get here; anywhere else it (and `with`) is an error.
1184 return Err(ParseError::ReservedKeyword);
1185 }
1186 if s == "_" {
1187 // `_` is the discard pattern (valid only as a binder), never a reference.
1188 Err(ParseError::DiscardReference)
1189 } else if let Some(n) = parse_int(s) {
1190 Ok(Expr::Int(n))
1191 } else if s.as_bytes()[0].is_ascii_digit() {
1192 // Digit-leading but not a valid int, and a name can't start with a digit.
1193 Err(ParseError::MalformedNumber)
1194 } else if s.starts_with("__") {
1195 // A `__` name is un-bindable, so resolve a known builtin here (a direct
1196 // dispatch node) rather than leaving it to a failing env walk plus a name
1197 // match on every call. The namespace is the language's, and closed, so an
1198 // unknown one is rejected here too — a misspelled `__mian` is a compile
1199 // error naming the namespace, not a name that happens to bind to nothing.
1200 // A reserved moditem (`__main`) is not a name at all and lands here as
1201 // well: it is a slot, and nothing reads it by reference. The former
1202 // home-module leaves are retired spellings like any other misspelling
1203 // (`Self` / `#Self` / `x.Self` are the readings now, see
1204 // `docs/done/2026-09-11_elly-v1-self.md`), so they land here too.
1205 match Builtin::from_name(s) {
1206 Some(op) => Ok(Expr::Builtin(op)),
1207 None => Err(ParseError::UnknownReserved(text(s))),
1208 }
1209 } else {
1210 Ok(Expr::Name(text(s)))
1211 }
1212}
1213
1214/// Parse an integer literal per the *integers* grammar: optional `-`/`+` sign,
1215/// then decimal / `0x` hex / `0b` binary digits, with `_` separating digit
1216/// groups (never leading, trailing, or doubled). `None` if `s` is not a literal.
1217pub(crate) fn parse_int(s: &str) -> Option<BigInt> {
1218 let (neg, body) = match s.as_bytes().first()? {
1219 b'-' => (true, &s[1..]),
1220 b'+' => (false, &s[1..]),
1221 _ => (false, s),
1222 };
1223 let (radix, digits) =
1224 if let Some(h) = body.strip_prefix("0x").or_else(|| body.strip_prefix("0X")) {
1225 (16u32, h)
1226 } else if let Some(b) = body.strip_prefix("0b").or_else(|| body.strip_prefix("0B")) {
1227 (2, b)
1228 } else {
1229 (10, body)
1230 };
1231 // Strip `_` separators while rejecting leading/trailing/doubled ones.
1232 let mut cleaned = String::new();
1233 let mut after_sep = true; // start "after a separator" → forbids a leading `_`
1234 for &c in digits.as_bytes() {
1235 if c == b'_' {
1236 if after_sep {
1237 return None;
1238 }
1239 after_sep = true;
1240 } else {
1241 cleaned.push(c as char);
1242 after_sep = false;
1243 }
1244 }
1245 if after_sep || cleaned.is_empty() {
1246 return None; // trailing `_`, or no digits at all (e.g. `0x`, `+`)
1247 }
1248 let mag = BigInt::parse_bytes(cleaned.as_bytes(), radix)?;
1249 Some(if neg { -mag } else { mag })
1250}
1251
1252/// Parse an application spine (a chain slice with no `&`/`let` split point and no
1253/// top-level `|>`), folding left. A `(…)` item does not contribute one argument —
1254/// it **spreads** (see `expand_arg`): `f (a, b)` folds to `((f a) b)`.
1255fn parse_app_spine<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
1256 let mut args: Vec<Expr> = Vec::new();
1257 for (i, it) in items.iter().enumerate() {
1258 // The **head** run flattens into the spine (§B): `f(x) y` keeps the one
1259 // flat `App` node `f x y` it is today, not `((f x) y)`. A run anywhere but
1260 // the head never does — that is where the run is one grouped argument (§A).
1261 if i == 0 {
1262 match *it {
1263 Item::Run(members) => {
1264 // The run still has no reading if it is `&`-headed or float-like
1265 // (§C / §G) — flattening the head must not smuggle it past the
1266 // guard the argument lowering applies.
1267 check_run_head(members)?;
1268 // `#Self(x)` — construction glued to its payload: the run's
1269 // head member is the `#`-prefixed keyword (`Run[#Self, (x)]`),
1270 // so it contributes the constructor leaf and the members
1271 // after it the payload. Reads as the spaced `#Self x` below.
1272 if is_self_head(members) {
1273 args.push(Expr::Home {
1274 leaf: HomeLeaf::New,
1275 depth: 0,
1276 });
1277 for m in members[1..].iter() {
1278 expand_arg(m, &mut args)?;
1279 }
1280 } else {
1281 for m in members.iter() {
1282 expand_arg(m, &mut args)?;
1283 }
1284 }
1285 }
1286 // `#Self x` — construction heading the spine: the constructor leaf
1287 // becomes the callee and the payload(s) follow as ordinary spine
1288 // items. A `#Self` anywhere else is `ReservedLiteralForm` (via
1289 // `parse_aexpr`), and a bare one — the loop's single item, refused
1290 // below — is too: construction is a form, not a value, so it may
1291 // only be applied, never bound or handed out. A module that means
1292 // to give construction away writes the eta-expansion
1293 // `mint = &n #Self(n)`.
1294 Item::Prefixed { sigil: '#', item }
1295 if matches!(item.as_ref(), Item::Sym("Self")) =>
1296 {
1297 args.push(Expr::Home {
1298 leaf: HomeLeaf::New,
1299 depth: 0,
1300 });
1301 }
1302 _ => expand_arg(it, &mut args)?,
1303 }
1304 } else {
1305 expand_arg(it, &mut args)?;
1306 }
1307 }
1308 // `#Self` with nothing after it — the constructor leaf alone, which the
1309 // grammar above never leaves standing as a value.
1310 if matches!(
1311 args.as_slice(),
1312 [Expr::Home {
1313 leaf: HomeLeaf::New,
1314 ..
1315 }]
1316 ) {
1317 return Err(ParseError::ReservedLiteralForm);
1318 }
1319 // `items` is non-empty and every item yields at least one arg (a `()` group
1320 // yields the unit), so `args` is non-empty. A single arg is not an
1321 // application; two or more flatten into one spine (callee then arguments).
1322 Ok(fold_spine(args))
1323}
1324
1325/// Fold a non-empty argument list into an expression: the lone element itself if
1326/// there is one, else a flattened `App` spine (element 0 the callee).
1327fn fold_spine(args: Vec<Expr>) -> Expr {
1328 if args.len() == 1 {
1329 args.into_iter().next().unwrap()
1330 } else {
1331 app(args)
1332 }
1333}
1334
1335/// Is this run the glued `#Self(…)` construction — i.e. headed by the
1336/// `#`-prefixed `Self` keyword as its first member? (`#Self(5)` fuses into
1337/// `Run[#Self, (5)]`; the spaced `#Self 5` is two chain items and never a run.)
1338fn is_self_head(members: &[Item<'_>]) -> bool {
1339 matches!(
1340 members.first(),
1341 Some(Item::Prefixed { sigil: '#', item })
1342 if matches!(item.as_ref(), Item::Sym("Self"))
1343 )
1344}
1345
1346/// Expand one chain item into argument expressions. A `(…)` group spreads its
1347/// chains (see `expand_group_args`); any other item is a single atom.
1348fn expand_arg<'a>(item: &'a Item<'a>, out: &mut Vec<Expr>) -> Result<(), ParseError> {
1349 match item {
1350 Item::Parens(seq) => expand_group_args(seq, out),
1351 // `.Self` — the payload read, a postfix over everything applied so far:
1352 // `f x .Self` is `(f x).Self`, the same left-associative step a `.N`
1353 // projection is (so a member reached by dot can no longer be named
1354 // `Self`). Bare — nothing accumulated before it — it stays the symbol
1355 // literal `.Self`: `parse_dot_symbol` never consults the keywords.
1356 Item::Prefixed { sigil: '.', item }
1357 if matches!(item.as_ref(), Item::Sym("Self")) && !out.is_empty() =>
1358 {
1359 let acc = fold_spine(core::mem::take(out));
1360 out.push(Expr::App(Box::new([
1361 Expr::Home {
1362 leaf: HomeLeaf::Value,
1363 depth: 0,
1364 },
1365 acc,
1366 ])));
1367 Ok(())
1368 }
1369 _ => {
1370 out.push(parse_aexpr(item)?);
1371 Ok(())
1372 }
1373 }
1374}
1375
1376/// Expand a `(…)` group's chains as arguments, on the rule **one chain → one
1377/// argument** (grouping is the one-chain case). An *empty* group is the nullary
1378/// marker: it feeds one unit value `()` (an empty call still calls). Comment-only
1379/// chains are skipped and do not count toward emptiness.
1380fn expand_group_args<'a>(seq: &Seq<'a>, out: &mut Vec<Expr>) -> Result<(), ParseError> {
1381 let mut any = false;
1382 for items in chain_slices(seq) {
1383 out.push(parse_items(&items)?);
1384 any = true;
1385 }
1386 if !any {
1387 out.push(Expr::Unit); // nullary marker → the unit value ()
1388 }
1389 Ok(())
1390}
1391
1392/// Parse a `(…)` group appearing as a standalone atom: expand it (grouping /
1393/// spread / nullary) and fold the pieces into one expression.
1394fn parse_group<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
1395 let mut args: Vec<Expr> = Vec::new();
1396 expand_group_args(seq, &mut args)?;
1397 // `expand_group_args` always yields ≥1 arg (an empty group feeds unit `()`).
1398 Ok(fold_spine(args))
1399}
1400
1401/// Parse a `[…]` list into a runtime list literal of *any* arity: `[]` (the
1402/// empty list, not unit), `[e]` (a genuine 1-list, not collapsed), `[e0, e1, …]`.
1403/// Each non-comment chain is one element expression.
1404fn parse_list<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
1405 let mut elems: Vec<Expr> = Vec::new();
1406 for items in chain_slices(seq) {
1407 elems.push(parse_items(&items)?);
1408 }
1409 Ok(Expr::List(elems))
1410}
1411
1412/// Parse a `{…}` brace group as a **block expression** (syntax v1 step 6): a
1413/// sequence of clauses whose value is the last clause's. A block lowers onto the
1414/// same nested-abstraction chain a `let` group produces — `{ a = va, e }` is
1415/// `let (a = va) e`, i.e. `(&a e) va` — so it adds no evaluator node, and a
1416/// block *is* a scope because each binding becomes a lambda parameter that the
1417/// following clauses (and nothing outside) can see.
1418///
1419/// - The empty block `{}` is the unit value `()`, and `{ e }` is `e` — the
1420/// degenerate-case identities.
1421/// - Each **non-final** clause is a binding (`<pat> = <expr>`, a top-level `=`,
1422/// bound over the rest) or a bare expression (evaluated for its effect, its
1423/// value discarded — lowered to a `_ =` binding, today's `_ = io.print(…)`).
1424/// - The **final** clause is the block's value and must be an expression; a
1425/// trailing binding is [`BlockTrailingBinding`](ParseError::BlockTrailingBinding).
1426///
1427/// A `:`-clause (a stale bare-map entry) has no reading in a block and is refused
1428/// with the `#{…}` nudge, as it is in every brace position. Clauses are the
1429/// `<seq>`'s joined non-comment chains, so a block may be laid out multi-line
1430/// with blank lines, comments, and the chain-continuation joins.
1431fn parse_block<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
1432 // A `:`-clause is the one shape a block shares with the old bare map; keep
1433 // the pointed error rather than letting it fall through as a bad expression.
1434 if braces_contain_colon(seq) {
1435 return Err(ParseError::BareMapLiteral);
1436 }
1437 build_block(chain_slices(seq))
1438}
1439
1440/// Build an [`Expr::Block`] from a block's (already joined, comment-free) clause
1441/// chains — shared by the `{…}` block and by `let (…) e` (its group's binding
1442/// chains plus its body as the final clause). Each clause becomes a
1443/// [`Clause::Bind`] (a chain with a top-level `=`) or a [`Clause::Do`] (a bare
1444/// expression). The degenerate blocks collapse rather than build a node: an empty
1445/// clause list is the unit value `()`, and a single expression clause is that
1446/// expression (`{ e }` is `e`). A trailing binding is rejected — a block's value
1447/// is its last clause's, and a binding leaves none (and its name would not
1448/// escape) — so the final clause is always a `Do`.
1449fn build_block<'a>(clauses: Vec<Vec<&Item<'a>>>) -> Result<Expr, ParseError> {
1450 let Some((last, leading)) = clauses.split_last() else {
1451 return Ok(Expr::Unit); // `{}` / `let () e` with no body chain — unit
1452 };
1453 if clause_is_binding(last) {
1454 return Err(ParseError::BlockTrailingBinding);
1455 }
1456 if leading.is_empty() {
1457 return parse_items(last); // `{ e }` is `e`
1458 }
1459 let mut built: Vec<Clause> = Vec::with_capacity(leading.len() + 1);
1460 for ci in leading {
1461 if clause_is_binding(ci) {
1462 let (pat, value) = parse_binding(ci)?;
1463 built.push(Clause::Bind(pat, value));
1464 } else {
1465 built.push(Clause::Do(parse_items(ci)?));
1466 }
1467 }
1468 built.push(Clause::Do(parse_items(last)?));
1469 Ok(Expr::Block(built.into_boxed_slice()))
1470}
1471
1472/// Whether a block clause is a **binding** — it carries a top-level `=` punct.
1473/// A nested `=` lives inside a `(…)`/`[…]`/`{…}` item, never as a top-level
1474/// `Item::Punct`, so this is exactly the `<binding>` vs `<chain>` split the
1475/// `let` group makes (and [`parse_binding`] then splits at the first `=`).
1476fn clause_is_binding(items: &[&Item<'_>]) -> bool {
1477 items.iter().any(|it| matches!(it, Item::Punct("=")))
1478}
1479
1480/// Parse a `{…}` block into a map literal: each non-comment chain of the `<seq>`
1481/// is one entry (see `parse_entry`); comment-only chains are skipped (so a map may
1482/// be laid out multi-line with blank lines and comments). Entry order is source
1483/// order — evaluation re-sorts by the value order.
1484fn parse_map<'a>(seq: &Seq<'a>) -> Result<Expr, ParseError> {
1485 let mut entries: Vec<(Expr, Expr)> = Vec::new();
1486 for items in chain_slices(seq) {
1487 entries.push(parse_entry(&items)?);
1488 }
1489 Ok(Expr::Map(entries))
1490}
1491
1492/// Parse one map entry `<key> ":" <value>`. The `:` is a single `<punct>` item at
1493/// position 1; the key is the single head item (`parse_key`) and the value is the
1494/// rest of the chain parsed as an `<expr>`. An *empty* value tail is sugar for the
1495/// unit value `()` (so `{ .foo: }` == `{ .foo: () }`).
1496fn parse_entry<'a>(items: &[&Item<'a>]) -> Result<(Expr, Expr), ParseError> {
1497 if items.len() < 2 || !matches!(items[1], Item::Punct(":")) {
1498 return Err(ParseError::MalformedMapKey);
1499 }
1500 let key = parse_key(items[0])?;
1501 let value_items = &items[2..];
1502 let value = if value_items.is_empty() {
1503 Expr::Unit // empty rest → the unit value ()
1504 } else {
1505 parse_items(value_items)?
1506 };
1507 Ok((key, value))
1508}
1509
1510/// Parse a map-entry key item. A bare *non-digit* atom key is the **symbol** of
1511/// the same spelling — `one` == `.one` — mirroring `.foo` exactly; a key is just
1512/// a token. A **digit-leading** bare key (`0`, `5`) is rejected to avoid the
1513/// number/symbol confusion: write `.0` for the symbol or `(0)` for the integer.
1514/// Wrap a key as a `(…)` **computed** key to evaluate it: `(one)` is the *value*
1515/// of variable `one`, `(0)` the integer `0`; an *empty* `()` is the unit key.
1516/// The remaining forms are a `.`-prefixed symbol (`.one`) and a `#[…]` / `#{…}`
1517/// literal key (`#[]` the empty-list key). A bare `[…]` / `{…}` key routes to the
1518/// same place only to be refused with its `#`-spelling nudge (`BareListLiteral` /
1519/// `BareMapLiteral`); anything else is a `MalformedMapKey`.
1520fn parse_key<'a>(item: &Item<'a>) -> Result<Expr, ParseError> {
1521 match item {
1522 // A bare non-digit `<sym>` key is the symbol of that spelling, read
1523 // exactly as `.<sym>` would be. A digit-leading key is rejected — write
1524 // `.5` (the symbol) or `(5)` (the computed integer key) to disambiguate.
1525 Item::Sym(s) if s.as_bytes()[0].is_ascii_digit() => Err(ParseError::MalformedMapKey),
1526 Item::Sym(s) => Ok(Expr::Symbol(text(s))),
1527 // A bare `{…}` in key position is a block expression at the atom layer,
1528 // which has no reading as a key — refuse it with the `#{…}` nudge rather
1529 // than silently keying on a block value (write `#{…}` for a map key).
1530 Item::Braces(_) => Err(ParseError::BareMapLiteral),
1531 // `.sym` symbol key, `(…)` computed key (empty `()` the unit key), `"…"`
1532 // string key, and the explicit `#[…]` / `#{…}` literal keys — each reads
1533 // exactly as the same item would at atom position. A bare `[…]` routes
1534 // here too, only to be refused with its `#[…]`-spelling nudge.
1535 Item::Prefixed { sigil: '.', .. }
1536 | Item::Prefixed { sigil: '#', .. }
1537 | Item::Brackets(_)
1538 | Item::Parens(_)
1539 | Item::Str(_) => parse_aexpr(item),
1540 // A **path** key (`{ Mod.k: v }`) denotes a concrete value by reference,
1541 // so it is a lookup key alongside the `.sym` / bare-sym forms (§E).
1542 Item::Run(members) => parse_path(members).map_err(|_| ParseError::MalformedMapKey),
1543 _ => Err(ParseError::MalformedMapKey),
1544 }
1545}
1546
1547// ===========================================================================
1548// patterns
1549// ===========================================================================
1550
1551/// `&<pattern> body` — the one binding form every abstraction (and `let`, and a
1552/// `__match` clause) is built from. Greedily **collects** into one multi-parameter
1553/// [`Lambda`]: when `body` is *directly* another abstraction (a run of `&p0 &p1 …`,
1554/// or a curried binder group), `pat` is prepended to its head so the whole run
1555/// shares one code node with a known arity. Collection stops at the first
1556/// non-abstraction body (a `&` nested inside an application does not extend the
1557/// arity) and at 255 parameters — a head that would exceed the `applied: u8`
1558/// counter starts a fresh nested `Lambda` instead of overflowing.
1559fn abs(pat: Pattern, body: Expr) -> Expr {
1560 match body {
1561 Expr::Abs(lam) if lam.head.len() < 255 => {
1562 // Prepend `pat` to the (freshly built, so uniquely owned) inner head.
1563 let lam = Rc::try_unwrap(lam).unwrap_or_else(|rc| (*rc).clone());
1564 let mut head = Vec::with_capacity(lam.head.len() + 1);
1565 head.push(pat);
1566 head.extend(Vec::from(lam.head));
1567 Expr::Abs(Rc::new(Lambda {
1568 head: head.into_boxed_slice(),
1569 body: lam.body,
1570 // Free variables are a resolution artifact: the parser leaves the
1571 // capture plan unresolved and `resolve.rs` fills it in.
1572 captures: Captures::Unresolved,
1573 }))
1574 }
1575 _ => Expr::Abs(Rc::new(Lambda {
1576 head: Box::new([pat]),
1577 body,
1578 captures: Captures::Unresolved,
1579 })),
1580 }
1581}
1582
1583/// Build an application node from a flat call spine `[callee, arg0, …]` (≥ 2
1584/// elements). A single-element `items` is *not* an application — the caller
1585/// returns that element directly.
1586fn app(items: Vec<Expr>) -> Expr {
1587 debug_assert!(
1588 items.len() >= 2,
1589 "an application has a callee and ≥1 argument"
1590 );
1591 Expr::App(items.into_boxed_slice())
1592}
1593
1594/// Apply `f` to one more `arg`, extending `f`'s call spine when it already is one
1595/// so `g x &y …` and `x |> f a` stay single flattened `App` nodes (a partial
1596/// applied to a trailing abstraction / pipe operand). Currying makes this
1597/// equivalent to a fresh binary application, and it re-renders identically.
1598fn append_arg(f: Expr, arg: Expr) -> Expr {
1599 match f {
1600 Expr::App(items) => {
1601 let mut v = Vec::from(items);
1602 v.push(arg);
1603 Expr::App(v.into_boxed_slice())
1604 }
1605 _ => Expr::App(Box::new([f, arg])),
1606 }
1607}
1608
1609/// Whether `item` is a bare **matching literal** — a number, a `.`-symbol, or a
1610/// string — used where a whole `&`-header or `let` LHS is expected. These are
1611/// equality patterns, not binders, so a bare one reads as a stray value or an
1612/// intended binding; Elly requires the wrap (`&(42)`, `&(.foo)`, `&("s")`; see
1613/// `BareLiteralHeader`). Nested in `[…]`/`{…}`/`(…)`/an or-pattern they are fine.
1614fn is_bare_literal_header(item: &Item<'_>) -> bool {
1615 match item {
1616 Item::Sym(s) => parse_int(s).is_some(),
1617 Item::Prefixed { sigil: '.', .. } => true,
1618 Item::Str(_) => true,
1619 _ => false,
1620 }
1621}
1622
1623/// Build the abstraction(s) an `&`-header `&<inner>` introduces over `body`. A
1624/// `(…)` inner is a **binder group** whose commas curry (`&(a, b)` → two params);
1625/// any other inner is a single-parameter pattern.
1626fn build_binder<'a>(inner: &Item<'a>, body: Expr) -> Result<Expr, ParseError> {
1627 match inner {
1628 Item::Parens(seq) => build_group(seq, body),
1629 // A bare matching literal (number `&42`, symbol `&.foo`, string `&"s"`) as
1630 // the whole header must be parenthesized: `&(42)`, `&(.foo)`, `&("s")` (see
1631 // `BareLiteralHeader`). Nested literals reach `parse_atom_item` unaffected.
1632 it if is_bare_literal_header(it) => Err(ParseError::BareLiteralHeader),
1633 other => {
1634 let pat = parse_pattern(&[other])?;
1635 Ok(abs(pat, body))
1636 }
1637 }
1638}
1639
1640/// Build a curried binder group `&(p0, p1, …)`: each non-comment chain is one
1641/// parameter pattern, curried left-to-right (leftmost outermost). An empty group
1642/// `&()` is the nullary marker — one param, the unit pattern that matches `()`.
1643fn build_group<'a>(seq: &Seq<'a>, body: Expr) -> Result<Expr, ParseError> {
1644 let params = chain_slices(seq);
1645 if params.is_empty() {
1646 // `&()` → the unit pattern: assert the argument is unit `()`.
1647 return Ok(abs(Pattern::Unit, body));
1648 }
1649 let mut acc = body;
1650 for chain in params.iter().rev() {
1651 let pat = parse_pattern(chain)?;
1652 acc = abs(pat, acc);
1653 }
1654 Ok(acc)
1655}
1656
1657/// Parse `case <subject>? { arms }`. The subject is exactly one item (atom, group, run)
1658/// or empty. Subjectless `case { ... }` is lowered to `&__arg (case __arg { ... })`
1659/// as an [`Expr::Abs`] using the reserved name `__arg`.
1660fn parse_case<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
1661 // Gather the pieces after the `case` keyword: a glued run head contributes
1662 // its tail members, then the following chain items, forming `[subject?, {…}]`.
1663 let mut rest: Vec<&Item<'a>> = Vec::new();
1664 if let Item::Run(members) = items[0] {
1665 for m in &members[1..] {
1666 rest.push(m);
1667 }
1668 }
1669 rest.extend_from_slice(&items[1..]);
1670 // The arm block is the trailing `{…}`; what precedes it is the subject.
1671 let (last, subject_items) = rest.split_last().ok_or(ParseError::CaseMissingArms)?;
1672 let arms_seq = match last {
1673 Item::Braces(seq) => seq,
1674 _ => return Err(ParseError::CaseMissingArms),
1675 };
1676 let cases = parse_arms(arms_seq)?;
1677 match subject_items {
1678 // Subjectless: lower to `&__arg (case __arg { … })`.
1679 [] => {
1680 let subject = Rc::new(Expr::Name(text("__arg")));
1681 Ok(abs(
1682 Pattern::Bind(text("__arg")),
1683 Expr::Case { subject, cases },
1684 ))
1685 }
1686 // `case <subject> { … }` — the subject is exactly one item, an atom.
1687 [subj] => Ok(Expr::Case {
1688 subject: Rc::new(parse_aexpr(subj)?),
1689 cases,
1690 }),
1691 _ => Err(ParseError::CaseSubjectExtra),
1692 }
1693}
1694
1695/// Parse `case` arms from a block. One arm per non-comment chain.
1696/// Empty blocks raise [`CaseMissingArms`].
1697fn parse_arms<'a>(seq: &Seq<'a>) -> Result<Box<[(Pattern, Expr)]>, ParseError> {
1698 let chains = chain_slices(seq);
1699 if chains.is_empty() {
1700 return Err(ParseError::CaseMissingArms);
1701 }
1702 let mut arms = Vec::with_capacity(chains.len());
1703 for chain in &chains {
1704 arms.push(parse_arm(chain)?);
1705 }
1706 Ok(arms.into_boxed_slice())
1707}
1708
1709/// Parse an arm: `& <pattern> (when (<cond>))* <body>`.
1710/// Header must start with `&`. Guards are folded using [`peel_when`].
1711fn parse_arm<'a>(items: &[&Item<'a>]) -> Result<(Pattern, Expr), ParseError> {
1712 let inner = match items.first() {
1713 Some(Item::Prefixed { sigil: '&', item }) => item.as_ref(),
1714 _ => return Err(ParseError::ArmNotBinder),
1715 };
1716 // A bare matching literal needs parens, as everywhere a header does.
1717 if is_bare_literal_header(inner) {
1718 return Err(ParseError::BareLiteralHeader);
1719 }
1720 let base = parse_pattern(&[inner])?;
1721 let (pat, rest) = peel_when(base, &items[1..])?;
1722 if rest.is_empty() {
1723 return Err(ParseError::ArmMissingBody);
1724 }
1725 let body = parse_items(rest)?;
1726 Ok((pat, body))
1727}
1728
1729/// Fold `when (<cond>)` guards onto `base`. Guards are nested as [`Pattern::When`]
1730/// and tried in order. Conditions must be `(…)` groups.
1731fn peel_when<'a, 'b>(
1732 mut base: Pattern,
1733 mut items: &'b [&'b Item<'a>],
1734) -> Result<(Pattern, &'b [&'b Item<'a>]), ParseError> {
1735 while !items.is_empty() && is_when_kw(items[0]) {
1736 let (group, next): (&Seq<'a>, &[&Item<'a>]) = match items[0] {
1737 // Glued run `when(…)`: the group is the run's lone second member.
1738 Item::Run(members) if members.len() == 2 => match &members[1] {
1739 Item::Parens(seq) => (seq, &items[1..]),
1740 _ => return Err(ParseError::WhenMissingCond),
1741 },
1742 Item::Run(_) => return Err(ParseError::WhenMissingCond),
1743 // Bare `when`: the group is the next chain item.
1744 _ => match items.get(1) {
1745 Some(Item::Parens(seq)) => (seq, &items[2..]),
1746 _ => return Err(ParseError::WhenMissingCond),
1747 },
1748 };
1749 let cond = parse_when_cond(group)?;
1750 base = Pattern::When {
1751 inner: Box::new(base),
1752 when: cond,
1753 };
1754 items = next;
1755 }
1756 Ok((base, items))
1757}
1758
1759/// Is `item` the `when` guard keyword — bare, or a glued run head (§D)?
1760fn is_when_kw(item: &Item) -> bool {
1761 head_keyword(item) == Some("when")
1762}
1763
1764/// Parse a `when` condition. Plain guards `(<chain>)` use `None`; pattern guards
1765/// `(<pattern> = <chain>)` use `Some(pattern)`.
1766fn parse_when_cond<'a>(seq: &Seq<'a>) -> Result<(Option<Box<Pattern>>, Expr), ParseError> {
1767 let chain = single_chain(seq).ok_or(ParseError::WhenMissingCond)?;
1768 if chain.is_empty() {
1769 return Err(ParseError::WhenMissingCond);
1770 }
1771 if chain.iter().any(|it| matches!(it, Item::Punct("="))) {
1772 let (pat, expr) = parse_binding(&chain)?;
1773 Ok((Some(Box::new(pat)), expr))
1774 } else {
1775 Ok((None, parse_items(&chain)?))
1776 }
1777}
1778
1779/// The non-comment chains of a `<seq>`, each as its non-comment items, with the
1780/// [chain-joining pass](join_chains) applied.
1781fn chain_slices<'a, 'b>(seq: &'b Seq<'a>) -> Vec<Vec<&'b Item<'a>>> {
1782 let chains = seq
1783 .0
1784 .iter()
1785 .filter(|c| !is_comment_only(c))
1786 .map(|c| {
1787 c.0.iter()
1788 .filter(|it| !matches!(it, Item::Comm(_)))
1789 .collect()
1790 })
1791 .collect();
1792 join_chains(chains)
1793}
1794
1795/// Whether a `<braces>` seq contains any chain whose second non-comment item is
1796/// `:` — the shape a block shares with the old bare-map spelling. Such a group is
1797/// rejected with the `#{…}` nudge ([`parse_block`]); a brace group with no
1798/// `:`-clause is a block (step 6).
1799fn braces_contain_colon(seq: &Seq<'_>) -> bool {
1800 for chain in seq.0.iter().filter(|c| !is_comment_only(c)) {
1801 let items: Vec<&Item<'_>> = chain
1802 .0
1803 .iter()
1804 .filter(|it| !matches!(it, Item::Comm(_)))
1805 .collect();
1806 if items.len() >= 2 && matches!(items[1], Item::Punct(":")) {
1807 return true;
1808 }
1809 }
1810 false
1811}
1812
1813/// The chain-joining pass (`docs/todo/elly-syntax-v1.md` step 2, review §1): over
1814/// a `<seq>`'s non-comment chains, fold a chain into the one before it whenever
1815/// the pair reads as one continued expression that is *ill-formed as two separate
1816/// chains today* — so no existing program changes meaning. A chain joins onto its
1817/// predecessor when
1818///
1819/// - the predecessor ends with a trailing continuation — a `&`-binder header
1820/// (`AbsWithoutBody`), the punct `=` (`MalformedBinding`), the punct `|>`, or a
1821/// `when (…)` guard with no body — or
1822/// - the chain itself opens with a leading continuation — the punct `|>`, or one
1823/// of the continuation keywords `elif` / `else` / `catch`.
1824///
1825/// Joins cascade: a folded chain is re-examined, so `x =` ⏎ `&y` ⏎ `body` collapses
1826/// to the single chain `x = &y body`. A leading continuation at the very start of a
1827/// seq has no predecessor and is left alone (it fails downstream as it does today).
1828fn join_chains<'a, 'b>(chains: Vec<Vec<&'b Item<'a>>>) -> Vec<Vec<&'b Item<'a>>> {
1829 let mut out: Vec<Vec<&'b Item<'a>>> = Vec::with_capacity(chains.len());
1830 for chain in chains {
1831 let join = matches!(out.last(), Some(prev) if trailing_join(prev) || leading_join(&chain));
1832 if join {
1833 // `out` is non-empty because `join` implies a predecessor.
1834 out.last_mut().unwrap().extend(chain);
1835 } else {
1836 out.push(chain);
1837 }
1838 }
1839 out
1840}
1841
1842/// Whether a chain ends with a trailing continuation (see [`join_chains`]).
1843fn trailing_join(chain: &[&Item]) -> bool {
1844 match chain.last() {
1845 Some(Item::Prefixed { sigil: '&', .. }) => true,
1846 Some(Item::Punct("=")) | Some(Item::Punct("|>")) => true,
1847 // A `when (…)` guard with no body: the arm's body is on the next line.
1848 _ => {
1849 let n = chain.len();
1850 n >= 2
1851 && matches!(chain[n - 2], Item::Sym("when"))
1852 && matches!(chain[n - 1], Item::Parens(_))
1853 }
1854 }
1855}
1856
1857/// Whether a chain opens with a leading continuation (see [`join_chains`]).
1858fn leading_join(chain: &[&Item]) -> bool {
1859 match chain.first() {
1860 Some(Item::Punct("|>")) => true,
1861 Some(first) => matches!(head_keyword(first), Some("elif" | "else" | "catch")),
1862 None => false,
1863 }
1864}
1865
1866/// The single non-comment chain of a `<seq>` (its non-comment items), or `None`
1867/// if the seq holds zero or more than one chain (e.g. a comma-separated group).
1868fn single_chain<'a, 'b>(seq: &'b Seq<'a>) -> Option<Vec<&'b Item<'a>>> {
1869 let mut chains = chain_slices(seq).into_iter();
1870 let first = chains.next()?;
1871 if chains.next().is_some() {
1872 return None;
1873 }
1874 Some(first)
1875}
1876
1877/// Parse a pattern from a chain of items. Splits on the first top-level `|`
1878/// (or-pattern), then `=` (equality / at-pattern), else defers to the `as`/atom
1879/// levels.
1880fn parse_pattern<'a>(items: &[&Item<'a>]) -> Result<Pattern, ParseError> {
1881 if items.is_empty() {
1882 return Err(ParseError::BadPattern);
1883 }
1884 // `|` has the lowest precedence — split first so `=` and `as` bind tighter
1885 // inside each arm. Left-associative on multiple `|`.
1886 if let Some(idx) = items.iter().position(|it| matches!(it, Item::Punct("|"))) {
1887 let left = parse_pattern(&items[..idx])?;
1888 let right = parse_pattern(&items[idx + 1..])?;
1889 // Both arms must bind the same names in the same order (see
1890 // `ParseError::OrBindersMismatch`); that agreement is checked by the
1891 // resolver, which has the binders' de Bruijn order naturally.
1892 return Ok(Pattern::Or(Box::new(left), Box::new(right)));
1893 }
1894 // Prefix ordering comparators `< <ref>` / `> <ref>` (the rest is an atom
1895 // comparand, like `= <ref>`); they only appear at the head of a pattern.
1896 match items[0] {
1897 Item::Punct("<") => return Ok(Pattern::Less(parse_comparand(&items[1..])?)),
1898 Item::Punct(">") => return Ok(Pattern::Greater(parse_comparand(&items[1..])?)),
1899 _ => {}
1900 }
1901 if let Some(idx) = items.iter().position(|it| matches!(it, Item::Punct("="))) {
1902 if idx == 0 {
1903 // `= <ref>` — equality against an atom (name or literal).
1904 return Ok(Pattern::Equal(parse_comparand(&items[1..])?));
1905 }
1906 // `name = pat` — at-pattern (the rest is a *pattern*).
1907 let name = at_name(&items[..idx])?;
1908 let inner = parse_pattern(&items[idx + 1..])?;
1909 return Ok(Pattern::At(name, Box::new(inner)));
1910 }
1911 parse_as(items)
1912}
1913
1914/// Parse the right-hand side of an `=` / `<` / `>` comparand: exactly one
1915/// **atom** — a name (`= x`), an integer (`= 5`), or a symbol (`= .foo`). A
1916/// pattern is a *closed grammar*, so a comparand never recurses into the general
1917/// expression parser (`parse_items`); a compound `= f x` / `< (f x)` is rejected
1918/// (`ComparandNotAtom`). The atom keeps the ordinary expression default at match
1919/// time — a name is a lookup — which is the only non-literal a match evaluates.
1920fn parse_comparand<'a>(items: &[&Item<'a>]) -> Result<Expr, ParseError> {
1921 match items {
1922 [it] => match it {
1923 Item::Sym(s) => parse_sym(s),
1924 Item::Prefixed { sigil: '.', item } => parse_dot_symbol(item),
1925 Item::Str(s) => Ok(Expr::Str(decode_str(s)?)),
1926 // A **path** (`Bool.false`) is one atom denoting a value by reference
1927 // — a lookup, not a computation, so it is a legitimate comparand (§E).
1928 Item::Run(members) => parse_path(members),
1929 _ => Err(ParseError::ComparandNotAtom),
1930 },
1931 _ => Err(ParseError::ComparandNotAtom),
1932 }
1933}
1934
1935/// The bound name of an at-pattern's left-hand side: exactly one plain name (not
1936/// a keyword, a `__` name, a discard, or a number).
1937fn at_name<'a>(items: &[&Item<'a>]) -> Result<Text, ParseError> {
1938 plain_name(items, ParseError::BadPattern)
1939}
1940
1941/// The narrowing level: the `Self <pat>` unwrapping qualifier, the prefix
1942/// `(as <Proto>) <pat>`, the postfix `<pat> as <Proto>`, or a bare atom.
1943fn parse_as<'a>(items: &[&Item<'a>]) -> Result<Pattern, ParseError> {
1944 // `Self <pat>` — narrow to an object of the home module and match its
1945 // payload. It qualifies whatever follows, so the rest is a whole pattern and
1946 // `Self x as Int` reads as `Self (x as Int)`. It is a `<pat>`, not a
1947 // top-level `<pattern>`, so a binder has to parenthesize it: `&(Self inner) …`
1948 // is valid, `&Self inner …` is not (`&` prefixes one item, so the latter
1949 // never reaches here with an inner at all).
1950 if let Item::Sym("Self") = items[0] {
1951 if items.len() < 2 {
1952 // Nothing to unwrap *into*. `Self _` is how to say "an object of
1953 // mine, payload ignored" — spelled out, since a bare `Self` in a
1954 // binder is much more likely a slip.
1955 return Err(ParseError::BadPattern);
1956 }
1957 let inner = parse_pattern(&items[1..])?;
1958 return Ok(Pattern::Unwrap {
1959 depth: 0,
1960 inner: Box::new(inner),
1961 });
1962 }
1963 // Prefix: `(as <Proto>) <pat>` — items[0] is a `(as …)` group.
1964 if let Item::Parens(seq) = items[0] {
1965 if let Some(chain) = single_chain(seq) {
1966 if !chain.is_empty() && matches!(chain[0], Item::Sym("as")) {
1967 let proto = parse_proto_ref(&chain[1..])?;
1968 let inner = parse_pattern(&items[1..])?;
1969 return Ok(Pattern::Type(proto, Box::new(inner)));
1970 }
1971 }
1972 }
1973 // Postfix: `<atom> as <Proto>`.
1974 if let Some(idx) = items.iter().position(|it| matches!(it, Item::Sym("as"))) {
1975 let proto = parse_proto_ref(&items[idx + 1..])?;
1976 let inner = parse_atom(&items[..idx])?;
1977 return Ok(Pattern::Type(proto, Box::new(inner)));
1978 }
1979 parse_atom(items)
1980}
1981
1982/// Parse the prototype of an `as <Proto>` pattern: exactly one **name**. A
1983/// prototype is a value like any other now — `Int` is the module `__Int` under its
1984/// shadowable alias, not a keyword — so this is the same closed-grammar atom an
1985/// `=` comparand is, minus the literals: a module is named, never written out.
1986///
1987/// It stays a [`ProtoRef::Ref`] here even when the name is the `__`-spelling of a
1988/// builtin module. The alias spelling only becomes one at *resolve* (it is the
1989/// resolver's bottom tier), so folding both to the compiled [`ProtoRef::Kind`]
1990/// form belongs there, in one place.
1991fn parse_proto_ref(items: &[&Item<'_>]) -> Result<ProtoRef, ParseError> {
1992 let e = match items {
1993 [Item::Sym(s)] => parse_sym(s)?,
1994 // A **path** names a qualified prototype (`as Mod.Proto`), lowered by the
1995 // shared `parse_path` (§E).
1996 [Item::Run(members)] => parse_path(members)?,
1997 _ => return Err(ParseError::UnknownType),
1998 };
1999 match e {
2000 // A name (`Bool`, `Int`), a `__` builtin (`__Int`), `Self`, or a path
2001 // that lowers to a reference / application of references.
2002 e @ (Expr::Name(_) | Expr::Builtin(_) | Expr::Home { .. } | Expr::App(_)) => {
2003 Ok(ProtoRef::Ref(e))
2004 }
2005 // A literal: `as 5` names no module.
2006 _ => Err(ParseError::UnknownType),
2007 }
2008}
2009
2010/// Parse a **path**: a run whose head is an **identifier** and whose every
2011/// following atom is a `.`-prefixed name-or-number segment (`Bool.false`,
2012/// `a.b.c`, `pair.0`, `__Int.add`). It lowers to a left fold of the head applied
2013/// to symbol literals — the same node member access and projection share — so it
2014/// is the reference the closed positions (a comparand, a map key, an `as <Proto>`)
2015/// accept by name.
2016///
2017/// The head admits a plain name, a `__` builtin, or a home leaf — references only.
2018/// A number head is excluded, which is what keeps `3.14` out (it is the rejected
2019/// float run, §G); a string or group head is a run but not a reference, so not a
2020/// path; a `(…)` call member anywhere is computation, not reference, and
2021/// disqualifies. A path is a run (≥ 2 members); the bare-name degenerate case is
2022/// the ordinary atom arm at the call site.
2023fn parse_path<'a>(members: &[Item<'a>]) -> Result<Expr, ParseError> {
2024 if members.len() < 2 {
2025 return Err(ParseError::ComparandNotAtom);
2026 }
2027 // The head is a reference: a plain name, a `__` builtin, or a home leaf.
2028 let head = match &members[0] {
2029 Item::Sym(s) => parse_sym(s)?,
2030 _ => return Err(ParseError::ComparandNotAtom),
2031 };
2032 // Every following member is a `.`-segment (a symbol or a number projection).
2033 for m in &members[1..] {
2034 if !matches!(m, Item::Prefixed { sigil: '.', .. }) {
2035 return Err(ParseError::ComparandNotAtom);
2036 }
2037 }
2038 // Left fold: the head applied to each `.`-segment's symbol in turn.
2039 let mut acc = head;
2040 for m in &members[1..] {
2041 if let Item::Prefixed { sigil: '.', item } = m {
2042 acc = Expr::App(Box::new([acc, parse_dot_symbol(item)?]));
2043 }
2044 }
2045 Ok(acc)
2046}
2047
2048/// Parse an atomic pattern: a single item (or a `(…)` grouping of one pattern).
2049fn parse_atom<'a>(items: &[&Item<'a>]) -> Result<Pattern, ParseError> {
2050 match items {
2051 [it] => parse_atom_item(it),
2052 _ => Err(ParseError::BadPattern),
2053 }
2054}
2055
2056/// Parse a single atomic-pattern item.
2057fn parse_atom_item<'a>(item: &Item<'a>) -> Result<Pattern, ParseError> {
2058 match item {
2059 Item::Sym("_") => Ok(Pattern::Discard),
2060 Item::Sym(s) if is_keyword(s) => Err(ParseError::ReservedKeyword),
2061 Item::Sym(s) if s.starts_with("__") => Err(ParseError::ReservedName),
2062 // A numeric literal is an equality pattern (`= n` sugar, mirroring the
2063 // `.sym → Equal(Symbol)` sugar below); a digit-leading non-integer is a
2064 // malformed number, and everything else is a binder name.
2065 Item::Sym(s) => match parse_int(s) {
2066 Some(n) => Ok(Pattern::Equal(Expr::Int(n))),
2067 None if s.as_bytes()[0].is_ascii_digit() => Err(ParseError::MalformedNumber),
2068 None => Ok(Pattern::Bind(text(s))),
2069 },
2070 // `.sym` literal → `= .sym`.
2071 Item::Prefixed { sigil: '.', item } => Ok(Pattern::Equal(parse_dot_symbol(item)?)),
2072 // A `<str>` literal is an equality pattern — the textual twin of `.sym`.
2073 Item::Str(s) => Ok(Pattern::Equal(Expr::Str(decode_str(s)?))),
2074 // Bare `[…]` / `{…}` in pattern position are always rejected (reshuffle):
2075 // write `#[…]` / `#{…}`. No `{…}` block exists in a pattern and the empty
2076 // `{}` is no longer the empty-map pattern, so every bare brace is refused —
2077 // exactly as every bare bracket is.
2078 Item::Brackets(_) => Err(ParseError::BareListLiteral),
2079 // `(…)` grouping — a single subpattern; commas (multiple chains) are an
2080 // error (they only curry after `&`).
2081 Item::Parens(seq) => match single_chain(seq) {
2082 Some(chain) if !chain.is_empty() => parse_pattern(&chain),
2083 _ => Err(ParseError::BadPattern),
2084 },
2085 Item::Braces(_) => Err(ParseError::BareMapLiteral),
2086 // `#[…]` / `#{…}` — the explicit spellings of the list / map patterns,
2087 // reading exactly as the bare forms (reshuffle phase 1); any other
2088 // `#`-prefixed shape is a reserved literal notation (§2).
2089 Item::Prefixed { sigil: '#', item } => match item.as_ref() {
2090 Item::Brackets(seq) => parse_list_pattern(seq),
2091 Item::Braces(seq) => parse_map_pattern(seq),
2092 _ => Err(ParseError::ReservedLiteralForm),
2093 },
2094 _ => Err(ParseError::BadPattern),
2095 }
2096}
2097
2098/// Parse a `{ k0: p0, … }` map pattern: each entry is `<key> ":" <value-pattern>`,
2099/// with an optional trailing `...`/`...rest`; `Rest::None` (no rest) is a
2100/// **closed** key set. The **key is itself a pattern**, but must denote a concrete
2101/// value to probe. A **lookup** key names a concrete value: `.sym` (the literal),
2102/// bare `sym` (the literal), or the equality pattern `(= <expr>)` / `= <expr>` for
2103/// a computed key. A grouped **binder** pattern in key position is instead a
2104/// **capture** — `(k)` / `(_)` peels the smallest remaining entry, binding its key
2105/// (see `match_pattern` in `eval.rs`); this is why the computed key is `(= k)`,
2106/// not `(k)`. The entry splits on its first top-level `:`.
2107fn parse_map_pattern<'a>(seq: &Seq<'a>) -> Result<Pattern, ParseError> {
2108 let chains = chain_slices(seq);
2109 let n = chains.len();
2110 let mut entries: Vec<(MapKey, Pattern)> = Vec::new();
2111 let mut rest: Option<Box<Pattern>> = None;
2112 for (i, chain) in chains.iter().enumerate() {
2113 if let Some(r) = as_rest(chain) {
2114 if i != n - 1 {
2115 return Err(ParseError::BadPattern); // rest must be trailing
2116 }
2117 rest = Some(r);
2118 } else {
2119 let colon = chain
2120 .iter()
2121 .position(|it| matches!(it, Item::Punct(":")))
2122 .ok_or(ParseError::MalformedMapKey)?;
2123 let val_items = &chain[colon + 1..];
2124 if colon == 0 || val_items.is_empty() {
2125 return Err(ParseError::MalformedMapKey); // empty key or value
2126 }
2127 // A bare single `<sym>` key is the literal symbol (like a map literal);
2128 // otherwise the key is a pattern — an equality form is a concrete-key
2129 // lookup, any other (binder) pattern is a key capture.
2130 let key_items = &chain[..colon];
2131 let bare_sym = match key_items {
2132 [Item::Sym(s)] => Some(*s),
2133 _ => None,
2134 };
2135 let key = if let Some(s) = bare_sym {
2136 // A bare non-digit sym key is the literal symbol (`five` → `.five`);
2137 // a digit-leading key is rejected — write `.5` (the symbol) or
2138 // `(5)` / `(= 5)` (the integer key) to disambiguate.
2139 if s.as_bytes()[0].is_ascii_digit() {
2140 return Err(ParseError::MalformedMapKey);
2141 }
2142 MapKey::Lookup(Expr::Symbol(text(s)))
2143 } else if let [Item::Run(members)] = key_items {
2144 // A **path** key (`{ Mod.k: p }`) denotes a concrete value by
2145 // reference — a lookup, like the `.sym` / bare-sym forms (§E).
2146 MapKey::Lookup(parse_path(members)?)
2147 } else {
2148 match parse_pattern(key_items)? {
2149 Pattern::Equal(expr) => MapKey::Lookup(expr),
2150 kpat => MapKey::Capture(Box::new(kpat)),
2151 }
2152 };
2153 let valpat = parse_pattern(val_items)?;
2154 entries.push((key, valpat));
2155 }
2156 }
2157 Ok(Pattern::Map { entries, rest })
2158}
2159
2160/// Parse a `[…]` list pattern: fixed-arity element patterns with an optional
2161/// trailing `...`/`...rest`.
2162fn parse_list_pattern<'a>(seq: &Seq<'a>) -> Result<Pattern, ParseError> {
2163 let chains = chain_slices(seq);
2164 let n = chains.len();
2165 let mut elems: Vec<Pattern> = Vec::new();
2166 let mut rest: Option<Box<Pattern>> = None;
2167 for (i, chain) in chains.iter().enumerate() {
2168 if let Some(r) = as_rest(chain) {
2169 if i != n - 1 {
2170 return Err(ParseError::BadPattern); // rest must be trailing
2171 }
2172 rest = Some(r);
2173 } else {
2174 elems.push(parse_pattern(chain)?);
2175 }
2176 }
2177 Ok(Pattern::List { elems, rest })
2178}
2179
2180/// Recognize a trailing-rest element chain: `...rest` (a name) or `...` (anon).
2181/// Both lex as nested `.` sigils (three deep), so no new Muon token is needed.
2182fn as_rest<'a>(chain: &[&Item<'a>]) -> Option<Box<Pattern>> {
2183 if chain.len() != 1 {
2184 return None;
2185 }
2186 // `it = .( .( … ) )`: two `.` sigils, then either `.name` (→ a `Bind` over the
2187 // remainder) or a bare `.` punct (→ a `Discard` over it).
2188 if let Item::Prefixed {
2189 sigil: '.',
2190 item: a,
2191 } = chain[0]
2192 {
2193 if let Item::Prefixed {
2194 sigil: '.',
2195 item: b,
2196 } = a.as_ref()
2197 {
2198 match b.as_ref() {
2199 Item::Prefixed {
2200 sigil: '.',
2201 item: c,
2202 } => {
2203 if let Item::Sym(name) = c.as_ref() {
2204 return Some(Box::new(Pattern::Bind(text(name))));
2205 }
2206 }
2207 Item::Punct(".") => return Some(Box::new(Pattern::Discard)),
2208 _ => {}
2209 }
2210 }
2211 }
2212 None
2213}
2214
2215/// Does this chain hold only comments (so it contributes no expression)?
2216fn is_comment_only(chain: &Chain) -> bool {
2217 chain.0.iter().all(|it| matches!(it, Item::Comm(_)))
2218}
2219
2220/// Is `s` an Elly keyword (reserved syntax, neither a reference nor bindable)?
2221/// `as` leads the type-narrowing pattern qualifier (recognized structurally
2222/// before this check); `Self` is the home module — a *reference* to it is the
2223/// module leaf (see `parse_sym`), and the reservation is what keeps no binder
2224/// from taking the name and what lets `Self <pat>` head the unwrap pattern;
2225/// `with`/`when`/`match`/`case`/`of` are parked for future forms (a parallel
2226/// binding, a guard, and the surface `case`/`match` sugar over the `__match`
2227/// builtin).
2228fn is_keyword(s: &str) -> bool {
2229 matches!(
2230 s,
2231 "Self"
2232 | "let"
2233 | "recur"
2234 | "import"
2235 | "const"
2236 | "local"
2237 | "with"
2238 | "as"
2239 | "when"
2240 | "match"
2241 | "case"
2242 | "of"
2243 | "if"
2244 | "elif"
2245 | "else"
2246 | "catch"
2247 )
2248}
2249
2250/// Can `name` be bound and then referenced from Elly source as a plain name? It
2251/// must be a Muon `<sym>` and pass the same tests `plain_name` applies to a
2252/// binding's left-hand side: not a keyword, not a `__` reserved name, not the
2253/// discard `_`, and not something that `reads_as_number` accepts.
2254///
2255/// This is the rule for names a *host* binds from outside a program — the PyO3
2256/// `elly.Env` prelude — where there is no left-hand side to parse but source must
2257/// still be able to read the binding back. Keep it in step with `plain_name`;
2258/// `crates/elly-core/tests/module.rs` pins the two together.
2259pub fn is_bindable_name(name: &str) -> bool {
2260 muon::is_sym(name)
2261 && !is_keyword(name)
2262 && !name.starts_with("__")
2263 && name != "_"
2264 && !reads_as_number(name)
2265}
2266
2267/// Is this item a chain split point — a `&`-binder, or a `let` / `recur` keyword
2268/// (bare or as a glued **run head**, §D)? A keyword run is a split point because
2269/// it is the keyword form reading its group from inside the run; `head_keyword`
2270/// covers the bare-sym case too, so no separate bare check is needed.
2271fn is_split_point(item: &Item) -> bool {
2272 is_binder(item)
2273 || matches!(
2274 head_keyword(item),
2275 Some("let") | Some("recur") | Some("case")
2276 )
2277}
2278
2279/// Is this item a `&`-prefixed binder?
2280fn is_binder(item: &Item) -> bool {
2281 matches!(item, Item::Prefixed { sigil: '&', .. })
2282}
2283
2284/// The **keyword head** of an item, if it is one: a bare keyword sym, or the head
2285/// sym of a **keyword-headed run** (§D). A keyword written glued to its group —
2286/// `let(x = 1) e`, `import"spec"`, `const(a, b)` — fuses into one run whose head
2287/// is the keyword, and the group comes from the run's second member rather than
2288/// the next chain item. Reading the head is what makes `let(x=1) e` equal
2289/// `let (x=1) e`.
2290fn head_keyword<'a>(item: &Item<'a>) -> Option<&'a str> {
2291 match item {
2292 Item::Sym(s) if is_keyword(s) => Some(s),
2293 Item::Run(members) => match members.first() {
2294 Some(Item::Sym(s)) if is_keyword(s) => Some(s),
2295 _ => None,
2296 },
2297 _ => None,
2298 }
2299}