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