Expand description
Elly — a small language layered on Muon (see docs/elly-spec.md).
This crate implements the first, deliberately small subset: references,
application, & abstraction, symbols, and positional lists with
projection. It reads the syntax with the muon crate, parses that notation
tree into an Expr, and evaluates it with a naive tree-walking
evaluator.
let expr = elly::parse("(&x x) .foo").unwrap();
assert_eq!(elly::eval(&expr).unwrap().to_display(), ".foo");Structs§
- Ctx
- The thin evaluation context threaded through the tree-walker: the unique-id
source (closure identities) plus the optional module resolver
__Mod.loadconsults. It carries no module registry — a loaded module is an ordinaryRc-held value, kept alive only by the closures that materialize from it, so the design stays module-local with no global state (seedocs/done/2026-08-07_elly-modules-v0.md). - Module
Data - A loaded module: an immutable table of unevaluated item bodies
(“dehydrated code”), sorted by name. Built once by
crate::compile_moduleand then frozen behind anRc. Sibling references inside the bodies are compiled toExpr::ModItem— a sink resolved through the home module carried in the environment (EnvNode::Module), not through any registry — so aModuleDataowns nothing butExprs and never points back at a value, and the value heap stays acyclic. Seedocs/done/2026-08-07_elly-modules-v0.md. - Recur
- The contents of a
recur: the binding group frozen into aModuleDataitem table, plus the body it scopes over.
Enums§
- Builtin
- A native builtin operation in the reserved
__namespace. All are curried; see the integers section ofdocs/elly-spec.md. - EnvNode
- One link of the environment list: a single binding carrying its
valinline, or the frame that terminates the walk. The empty environment isNone, not a node —Envis anOption<Rc<EnvNode>>, which the null-pointer niche keeps one word wide, so ending a chain costs no allocation and no refcount traffic. A lambda activation conses oneConsper name its head binds — zero for a binder-less head (&_,&(< 2)) — so a partial application’s environment is just the consed prefix, shared with later partials byRcrefcount (no copy-on-write). Names are not stored: references were resolved to a de Bruijn index against this list’s shape. - Error
- A failure at the syntax or parse stage.
- Expr
- An Elly expression in the first subset. Strings and records are deferred (see the spec), so there is no variant for them yet.
- Parse
Error - A parse failure: the input is a valid Muon tree but not a well-formed Elly program in this subset.
- Pattern
- A pattern: the left-hand side of any binding (
&<pat> …,let (<pat> = …), a__matchclause). Matched against a value by the evaluator’s native matcher (match_patternineval.rs), which either extends the environment or refutes.Bind/Discardare irrefutable; every other shape may refute. - Raised
- A value unwinding through the single error channel (see the errors section
of
docs/elly-spec.md). Evaluation returnsResult<Value, Raised>; a raise unwinds the?chain up to the nearest boundary. Two kinds unwind together, differing only in what catches them: - Value
- A runtime value.
Traits§
- Module
Resolver - A host-provided source loader consulted by
__Mod.load. Maps a module spec (the string argument) to its Elly source, orNoneif it cannot be found. Theellycore isno_std, so file I/O lives in the host behind this callback (seedocs/done/2026-08-07_elly-modules-v0.md).
Functions§
- apply
- Apply a value to a single argument — the public entry point behind a host’s
“call this value” surface (e.g. the Python bindings’
elly.Fun.__call__). - apply_
with - Apply a value to a single argument under a caller-supplied
Ctx. Identical toapplyexcept that the context — and so the module resolver a__Mod.loadin the applied body consults — comes from the caller. A host that hands out callables obtained from a resolver-carrying context needs this, so calling one back later can still load modules (e.g.elly.Fun.__call__on a closure materialized out of a module). - compile_
module - Compile Elly module source into a frozen
ModuleData: read the Muon syntax, parse it intoname = bodybindings (parse_module), then resolve each body against the module’s item set (resolve_module_bodies, turning sibling references intoExpr::ModItem). Loading is purely syntactic — no body is evaluated. This is what__Mod.loadruns on the source its resolver returns; seedocs/done/2026-08-07_elly-modules-v0.md. - compile_
module_ framed - Compile Elly module source against a frame: an ordered list of names the module is instantiated against — its imports, a host prelude, module-minted identities. A body’s reference to one resolves to a de Bruijn local whose index walks past the module terminal into the frame, so it costs one indexed lookup and nothing is evaluated on access.
- eval
- Evaluate an expression in the empty environment, with no module loader —
a
__Mod.loadraises.no_module_loader. Seeeval_withto supply one. - eval_
env - Evaluate an expression in a given
envwith aCtx(carrying the unique-id source and optional module resolver). The public entry behind a host’s “eval with environment” surface (e.g.elly.Env.eval). The existingevalandeval_withare thin wrappers around this. - eval_
with - Evaluate an expression in the empty environment with a module
resolveravailable to__Mod.load(seeModuleResolver). Otherwise identical toeval. - instantiate
- Instantiate a compiled module: build its
Moduleterminal — the environment its item bodies run in — and hand it back as theValue::Objectthat is that terminal. - is_
bindable_ name - Can
namebe bound and then referenced from Elly source as a plain name? It must be a Muon<sym>and pass the same tests [plain_name] applies to a binding’s left-hand side: not a keyword, not a__reserved name, not the discard_, and not something that [reads_as_number]. - parse
- Parse Elly source into an
Expr: read the Muon syntax, parse that notation tree into the AST, then resolve names — a free variable is rejected here (a parse-timeParseError::UnboundName) rather than raising at eval. - parse_
module - Parse a module source’s top-level sequence into its bindings: one
name = bodyper non-comment chain, in source order, to a list of (item-name, unresolved body). Unlikeparse_programa module is a multi-chain sequence, and the body is not resolved here —crate::compile_modulerunscrate::resolve::resolve_module_bodiesafterwards so a body may refer to any sibling regardless of order. The left-hand side is a single bare name in v0 (no destructuring at module top level); duplicate names and non-name LHSs are rejected. Seedocs/done/2026-08-07_elly-modules-v0.md. - parse_
open - Parse Elly source into an
Exprwithout name resolution, so free variables are left in place (an open term). Used by tooling that wants the raw AST and by the parse golden suite, which resolves open terms withresolve_open(auto-binding their free names). Preferparsefor evaluating a whole program. - parse_
preluded - Parse Elly source into an
Exprresolved against a namedprelude: names in the prelude resolve to de Bruijn locals, unknown names are rejected withParseError::UnboundName. The prelude is an ordered slice of names (insertion order) that forms a fixed outer frame below all lambda scopes. Preferparsefor a whole program without a prelude. - parse_
program - Parse a whole parsed program (one top-level chain) to an expression.
- resolve
- Resolve a whole program against an empty scope: every free name is rejected
with
ParseError::UnboundName. This is whatelly::parseruns, so an unbound reference is a parse-time error. Rewritesexprin place (names → de Bruijn locals). - resolve_
module_ bodies - Resolve each body of a module’s bindings in place (name → de Bruijn
Local, sibling item →Expr::ModItem, elseParseError::UnboundName). The item names form an extra reference tier consulted after lexical scope and before the unbound check, so a body may refer to any sibling regardless of source order — the basis of order-independent, mutually recursive top-level definitions (seedocs/done/2026-08-07_elly-modules-v0.md). Called bycrate::compile_moduleaftercrate::parse::parse_module. - resolve_
open - Resolve an open term: a free name is not rejected but auto-bound below the
whole scope, so the pass runs to completion on a fragment. Used by tooling and
by the parse golden suite, which resolves open terms (their s-expressions read
like the spec) without a rejection — the real index assignment and or-arm
agreement still run, and names render by name, so the goldens do not change.
Prefer
resolvefor a whole program. - resolve_
prelude - Resolve
expragainst a named outerprelude(insertion order): a reference toprelude[i]becomes the indexiof the root frame — reached directly at the top level, or through a lambda’s capture list from inside one — and any name neither in scope nor inpreludeisUnboundName. Likeresolve_openbut the root frame is fixed and complete (no auto-bind). Used byelly::parse_preluded, whose caller supplies the prelude values in that same order.
Type Aliases§
- Env
- A shared, persistent environment: a nameless cons list of the bindings the
running activation has made (innermost/most-recent first), on top of the
closure’s captured
Frame. A resolved reference (Expr::Local) reaches its value by walking its de Bruijnindexnextlinks and cloning the cell’s value — a pointer chase, no name compare and no per-link clone (contrast the old name-keyed cons list) — and an index that runs past the consed cells lands in the frame, an array index. The resolver (resolve.rs) assigns each reference the exact number of cells between it and its binder, in the same order the matcher conses them. - Text
- An owned, cheap-clone text leaf — hipstr’s single-threaded
Local(Rc) backend: inline for short strings (≤ 23 bytes on 64-bit), else a sharedRc<str>slice, an O(1) clone either way. Replaces the former&'a strsource borrow soExpr/Valueare'staticand no longer tied to the Muon source (seedocs/todo/elly-impl-expr-repr-v1.md). A'statichost tag is stored for free viaText::from_static.