pub enum Expr {
Show 13 variants
Int(BigInt),
List(Vec<Expr>),
Name(Text),
Local {
name: Box<Text>,
index: u32,
},
Builtin(Builtin),
ModItem {
name: Box<Text>,
index: u32,
depth: u32,
},
Recur(Rc<ModuleData>),
Symbol(Text),
Str(Text),
App(Box<[Expr]>),
Abs(Rc<Lambda>),
Map(Vec<(Expr, Expr)>),
Home {
leaf: HomeLeaf,
depth: u32,
},
}Expand description
An Elly expression in the first subset. Strings and records are deferred (see the spec), so there is no variant for them yet.
Variants§
Int(BigInt)
An arbitrary-precision integer literal, parsed from the token (0, -123,
0xCAFE); see the integers section of the spec.
List(Vec<Expr>)
A runtime list literal from a Muon <list> ([…]), of any arity: []
(the empty list, which is unit), [e] (a genuine 1-list, distinct from
e), or [e0, e1, …]. Parens (…) never produce this — they are
grouping / argument spread at parse time (see parse.rs).
Name(Text)
A bare name: a reference to an enclosing &-binding, else a free name.
Produced by the parser as the unresolved form; the resolution pass
(resolve.rs) rewrites every in-scope Name into a Expr::Local and
rejects the rest. Tooling that wants the raw, pre-resolution tree
(parse_open) keeps seeing Name; the evaluator only ever sees Local.
Local
A resolved reference: a flat de Bruijn index produced by the resolver
from a Expr::Name. The environment is a nameless cons list of bindings
(one cell per bound name, innermost first), and index counts cells to walk
from the top (0 = the most recent binding) — so the evaluator’s lookup is a
pointer chase with no string compare and no per-link clone. The original
name is kept only for rendering, boxed so it does not widen Expr past
four words (it is read solely by to_sexp / to_json): a Local then
prints exactly like the Name it replaced ((name foo) / {"name":"foo"}),
so the goldens are unchanged.
Builtin(Builtin)
A __-builtin resolved at parse (see Builtin::from_name). __ names
are un-bindable, so a reference like __Int.add never depends on the
environment; resolving it here turns each call into a direct dispatch rather
than a failing env walk plus a name match at eval. Rendered exactly like the
Name it replaces ((name __Int.add) / {"name":"__Int.add"}) so the
goldens are unchanged.
ModItem
A module sibling reference: a name that resolved not to a lexical
binding but to another item of the enclosing module (see
docs/done/2026-08-07_elly-modules-v0.md). Produced only by the module resolver
(resolve::resolve_module_bodies), never by parse; it is a sink — it
carries no module reference and is resolved at eval through the home module
carried in the environment (EnvNode::Module).
index is the item’s position in the home module’s (name-sorted) item
table, assigned at resolve so eval indexes straight into it with no name
compare — the module-tier analogue of a Local’s de Bruijn index. name is
kept for rendering only (boxed to keep Expr at four words). Renders
(moditem factorial) / {"moditem":"factorial"}.
depth is how many Module terminals the walk
skips before the one that owns this item. A program with one module has
no nesting and every ModItem carries 0; a recur inside
a module body puts a second terminal on the chain, and a reference to the
module’s own item from inside the recur has to step over the recur’s.
It is the barrier-stack distance the resolver measured, not rendered (like
a Local’s index, it is derived addressing).
Recur(Rc<ModuleData>)
A recursive binding group as an expression: recur (a = va, b = vb) e.
The bindings are simultaneous — each sees all of them, itself included —
which is what separates it from let, whose group is sequential and
desugars to nested App/Abs with no node of its
own.
The group is a module — the whole of one, body included: the bindings are
its (name-sorted) item table, and they reach each other as
ModItems through the terminal rather than by
capturing references to each other, the edge that would cycle. The
expression the group scopes over is the ModuleData’s own
body, the slot a module source fills with
__main. Evaluating this builds the terminal over the current environment
(the cons-shaped frame) and runs that body under it; see
docs/done/2026-08-13_elly-recur.md and docs/done/2026-08-14_elly-run.md.
Symbol(Text)
A .-prefixed literal (.foo, .0), stored without the dot — the dot
is the Muon sigil and is re-added when displayed / dumped.
Str(Text)
A string literal, decoded at parse from a Muon <str> (JSON/Muon
escapes resolved, \uXXXX surrogate pairs combined) into owned UTF-8. It
is self-evaluating: eval clones the handle into Value::Str. Rendered as
the bare quoted, re-escaped literal ("foo") — the quotes already mark it
apart from .foo / foo, so no (str …) wrapper.
App(Box<[Expr]>)
Application: a flattened call spine whose element 0 is the callee and
[1..] the arguments. A source spine f a b c and a spread call f(a, b)
both parse into one node with a known call-site arity, so the evaluator
binds a whole call’s arguments in one pass (see apply_n in eval.rs).
Always holds at least a callee and one argument (two elements); currying is
recovered by apply_n’s over-application loop, so a flat spine is
semantically identical to the left-nested binary applications it replaced —
which is exactly how it is re-rendered in to_sexp / to_json.
Abs(Rc<Lambda>)
Abstraction &<pattern> body: the one universal binding form, its code
(Lambda: the parameter patterns and the body) shared behind Rc. A run
of &p0 &p1 … is collected at parse into one multi-parameter Lambda
(arity head.len()), so applying it matches a whole call’s arguments in
one pass. The evaluator builds a closure by cloning the Rc<Lambda> and
capturing the environment, rather than deep-cloning the subtree. Rendered as
nested (abs p0 (abs p1 …)) — the curried reading the collected head stands
for — to keep the goldens stable.
Map(Vec<(Expr, Expr)>)
A map literal from a Muon <block> ({ … }): its entries as
(key-expr, value-expr) pairs, in source order. Keys stay expressions
until eval, so computed keys ((expr)) just evaluate like any other
expression; see the maps section of the spec.
Home
A home-module leaf: __module, __new or __value, each reading the
module the body it sits in was written in (see HomeLeaf). Like a
ModItem it is a sink — it carries no module reference
and finds its module through the environment’s terminal at eval — and like
one it costs a leaf and no capture slot.
depth is how many Module terminals to step
over before the module: a leaf names the nearest terminal written as a
module, so a recur between the leaf and its module is
stepped over and a method that loops internally still constructs objects of
its own type. The resolver measures it (the same barrier-stack distance a
ModItem carries) and rejects a leaf with no module on the stack; it is
derived addressing, so it is not rendered. See
docs/done/2026-08-15_elly-objects-v0.md.