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_core::parse("(&x x) .foo").unwrap();
assert_eq!(elly_core::eval(&expr).unwrap().to_display(), ".foo");Structs§
- Ctx
- The thin evaluation context threaded through the tree-walker: the unique-id
source (closure identities) and the builtin module cache. It carries no
module registry and no loader — a module’s imports are resolved before
evaluation begins (see
crate::load_module), and a loaded module is an ordinaryRc-held value, kept alive only by whatever holds it. - HostFn
- A host function: a native callback the embedder hands the runtime as a
value, behind the
RcaValue::HostFncarries. - Import
Decl - One
importdeclaration: the name the importing module reaches the instance by, and the spec its host resolver is handed. - Instances
- The const stage’s map from canonical name to instantiated module: what makes one name yield one instance.
- 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. - Module
Syntax - A module source’s top-level declarations: its imports and its
constdeclarations, in source order, and its items. The imports are the module’s first frame slots — the const stage resolves, compiles and instantiates each one, then instantiates this module against the results (seecrate::load_module). The iotas take no slot at all: an iota’s identity is its instance plus its position, derived on reference (seedocs/done/2026-08-13_elly-modules-iotas.md). - Object
Data - An object: a payload and the prototype that gives it its type. Behind the
RcaValue::Objectcarries, so an object is one payload word and a clone is one refcount bump. - Resolved
- What a host answers a module spec with: the module’s canonical name and its source.
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.
- Home
Leaf - Which of the three home-module leaves a reference is. They share one variant because they share the whole of their machinery — one walk to the terminal, one parse clause each, no capture — and differ only in what they evaluate to once it is found.
- Load
Error - Why a module graph could not be loaded. Every one of these is a compile-time failure: the imports are literals in the source, so they are resolved, compiled and instantiated before anything runs, and none of it can raise mid-evaluation.
- 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. - Proto
Ref - The prototype an
as <Proto>pattern narrows to. One rule covers both forms — the subject matches iff its prototype is the named module — and they differ only in how much of the work is left to run time. - 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: - TyKind
- The value kinds whose prototype is a builtin module, so that
as <Proto>against it compiles to a discriminant test (seeProtoRef::Kind). - Value
- A runtime value.
Traits§
- Module
Resolver - A host-provided module loader, consulted at compile time by the const
stage. Maps a spec — whatever a source writes in
import "…"— to the module’s canonical name and source, orNoneif it cannot be found.
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 comes from the caller, so the identities minted by the call continue that context’s id source instead of restarting at zero. A host that hands callables out and takes them back needs it (e.g.elly.Fun.__call__on a closure materialized out of a module), or an item and an unrelated closure could be handed the same id. - compile_
module - Compile Elly module source into a frozen
ModuleData: read the Muon syntax, parse it intoimports andname = bodyitems (parse_module), then resolve each body against the module’s item set (resolve_module_bodies, turning sibling references intoExpr::ModItem). Compiling is purely syntactic — no body is evaluated, and no import is resolved. 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 — 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.
- compile_
module_ named compile_module_framed, recordingnameas the canonical name the code came from — what__Mod.namereports and whatload_modulededuplicates instances by. Only a host that resolved the source knows the name, which is why the plain entry points leave it unset.- eval
- Evaluate an expression in the empty environment.
- eval_
env - Evaluate an expression in a given
envwith aCtx(carrying the unique-id source). The public entry behind a host’s “eval with environment” surface (e.g.elly.Env.eval);evalis a thin wrapper around it. - eval_
main - Evaluate a module instance’s body — the
__mainits source declared — in the module’s own environment, and hand back the result.Ok(None)for a module that declares none, which is what a library is. - instantiate
- Instantiate a compiled module: build its
Moduleterminal — the environment its item bodies run in — and hand it back as theValue::Modulethat 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 testsplain_nameapplies to a binding’s left-hand side: not a keyword, not a__reserved name, not the discard_, and not something thatreads_as_numberaccepts. - load_
module - Load the module
specnames, with its whole import graph: resolve, compile and instantiate every module it reaches, leaves first, one instance per canonical name, and hand back the instance forspecitself. - load_
module_ source load_modulefor source the caller already has, compiled under the canonicalnameit should be known by. Its own imports still go throughresolver.- 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 declarations: one
name = bodyitem or oneimportper non-comment chain, in source order. Unlikeparse_programa module is a multi-chain sequence, and a body is not resolved here —crate::compile_modulerunscrate::resolve::resolve_module_bodiesafterwards so a body may refer to any sibling regardless of order. An item’s 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. - Host
Call - The contract a host callback signs: a whole call’s arguments — exactly
arityof them, since the evaluator gathers before it fires — plus theCtx, answering a value or aRaised. - Import
Spec - What a module’s source writes to name an import — a path, a package-qualified name. Its grammar belongs to the host resolver, not to Elly.
- Module
Name - What a resolver answers a spec with: one name per module, however many specs
reach it. Module instances are deduplicated by this name, so a resolver that
returns the spec unchanged would hand back two instances of one module for
"./foo"and"foo"(seecrate::load_module). - 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.