Skip to main content

Crate elly

Crate elly 

Source
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.load consults. It carries no module registry — a loaded module is an ordinary Rc-held value, kept alive only by the closures that materialize from it, so the design stays module-local with no global state (see docs/done/2026-08-07_elly-modules-v0.md).
ModuleData
A loaded module: an immutable table of unevaluated item bodies (“dehydrated code”), sorted by name. Built once by crate::compile_module and then frozen behind an Rc. Sibling references inside the bodies are compiled to Expr::ModItem — a sink resolved through the home module carried in the environment (EnvNode::Module), not through any registry — so a ModuleData owns nothing but Exprs and never points back at a value, and the value heap stays acyclic. See docs/done/2026-08-07_elly-modules-v0.md.
Recur
The contents of a recur: the binding group frozen into a ModuleData item table, plus the body it scopes over.

Enums§

Builtin
A native builtin operation in the reserved __ namespace. All are curried; see the integers section of docs/elly-spec.md.
EnvNode
One link of the environment list: a single binding carrying its val inline, or the frame that terminates the walk. The empty environment is None, not a node — Env is an Option<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 one Cons per 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 by Rc refcount (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.
ParseError
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 __match clause). Matched against a value by the evaluator’s native matcher (match_pattern in eval.rs), which either extends the environment or refutes. Bind/Discard are 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 returns Result<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§

ModuleResolver
A host-provided source loader consulted by __Mod.load. Maps a module spec (the string argument) to its Elly source, or None if it cannot be found. The elly core is no_std, so file I/O lives in the host behind this callback (see docs/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 to apply except that the context — and so the module resolver a __Mod.load in 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 into name = body bindings (parse_module), then resolve each body against the module’s item set (resolve_module_bodies, turning sibling references into Expr::ModItem). Loading is purely syntactic — no body is evaluated. This is what __Mod.load runs on the source its resolver returns; see docs/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.load raises .no_module_loader. See eval_with to supply one.
eval_env
Evaluate an expression in a given env with a Ctx (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 existing eval and eval_with are thin wrappers around this.
eval_with
Evaluate an expression in the empty environment with a module resolver available to __Mod.load (see ModuleResolver). Otherwise identical to eval.
instantiate
Instantiate a compiled module: build its Module terminal — the environment its item bodies run in — and hand it back as the Value::Object that is that terminal.
is_bindable_name
Can name be 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-time ParseError::UnboundName) rather than raising at eval.
parse_module
Parse a module source’s top-level sequence into its bindings: one name = body per non-comment chain, in source order, to a list of (item-name, unresolved body). Unlike parse_program a module is a multi-chain sequence, and the body is not resolved here — crate::compile_module runs crate::resolve::resolve_module_bodies afterwards 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. See docs/done/2026-08-07_elly-modules-v0.md.
parse_open
Parse Elly source into an Expr without 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 with resolve_open (auto-binding their free names). Prefer parse for evaluating a whole program.
parse_preluded
Parse Elly source into an Expr resolved against a named prelude: names in the prelude resolve to de Bruijn locals, unknown names are rejected with ParseError::UnboundName. The prelude is an ordered slice of names (insertion order) that forms a fixed outer frame below all lambda scopes. Prefer parse for 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 what elly::parse runs, so an unbound reference is a parse-time error. Rewrites expr in 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, else ParseError::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 (see docs/done/2026-08-07_elly-modules-v0.md). Called by crate::compile_module after crate::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 resolve for a whole program.
resolve_prelude
Resolve expr against a named outer prelude (insertion order): a reference to prelude[i] becomes the index i of 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 in prelude is UnboundName. Like resolve_open but the root frame is fixed and complete (no auto-bind). Used by elly::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 Bruijn index next links 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 shared Rc<str> slice, an O(1) clone either way. Replaces the former &'a str source borrow so Expr/Value are 'static and no longer tied to the Muon source (see docs/todo/elly-impl-expr-repr-v1.md). A 'static host tag is stored for free via Text::from_static.