Skip to main content

Crate elly_core

Crate elly_core 

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_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 ordinary Rc-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 Rc a Value::HostFn carries.
ImportDecl
One import declaration: 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.
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.
ModuleSyntax
A module source’s top-level declarations: its imports and its const declarations, 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 (see crate::load_module). The iotas take no slot at all: an iota’s identity is its instance plus its position, derived on reference (see docs/done/2026-08-13_elly-modules-iotas.md).
ObjectData
An object: a payload and the prototype that gives it its type. Behind the Rc a Value::Object carries, 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 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.
HomeLeaf
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.
LoadError
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.
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.
ProtoRef
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 returns Result<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 (see ProtoRef::Kind).
Value
A runtime value.

Traits§

ModuleResolver
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, or None if 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 to apply except 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 into imports and name = body items (parse_module), then resolve each body against the module’s item set (resolve_module_bodies, turning sibling references into Expr::ModItem). Compiling is purely syntactic — no body is evaluated, and no import is resolved. 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 — 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, recording name as the canonical name the code came from — what __Mod.name reports and what load_module deduplicates 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 env with a Ctx (carrying the unique-id source). The public entry behind a host’s “eval with environment” surface (e.g. elly.Env.eval); eval is a thin wrapper around it.
eval_main
Evaluate a module instance’s body — the __main its 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 Module terminal — the environment its item bodies run in — and hand it back as the Value::Module 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 accepts.
load_module
Load the module spec names, 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 for spec itself.
load_module_source
load_module for source the caller already has, compiled under the canonical name it should be known by. Its own imports still go through resolver.
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 declarations: one name = body item or one import per non-comment chain, in source order. Unlike parse_program a module is a multi-chain sequence, and a 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. 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. 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.
HostCall
The contract a host callback signs: a whole call’s arguments — exactly arity of them, since the evaluator gathers before it fires — plus the Ctx, answering a value or a Raised.
ImportSpec
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.
ModuleName
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" (see crate::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 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.