>_ENGINEERING REPORT
elisprs is an Emacs Lisp runtime in Rust that runs .el outside Emacs as a fusevm frontend: it lowers elisp to fusevm::Chunk and executes on the shared bytecode VM. This report describes the architecture, the object heap, the lowering pipeline, and the dependency posture. Numbers below are facts about the current tree, not aspirational metrics.
Summary
Emacs Lisp has historically been inseparable from its host editor. elisprs separates the two: it runs .el as standalone programs and is the fifth consumer of fusevm — alongside zshrs, stryke, awkrs, and vimlrs.
elisprs is a pure fusevm frontend with no bespoke VM or JIT: an elisp-correct reader, a Lisp-2 obarray with lexical and dynamic binding, 22 compiler-recognized special forms with macros, and a 234-subr standard library plus an elisp prelude. The compiler lowers each form to a fusevm::Chunk; fusevm executes it with its three-tier Cranelift JIT, and --aot/--aot-exe emit native objects/binaries. The elisp object heap rides through the VM as Value::Obj handles. The --lsp and --dap servers, regexp engine, and rkyv bytecode cache are all implemented.
The value model & object heap
Emacs Lisp is a Lisp-2 (separate value and function cells per symbol) with both lexical and dynamic scope. elisprs owns those semantics directly on top of fusevm's value type — there is no rust_lisp dependency. The cons/symbol/vector/closure/hash-table cells live in an ElispHost arena and travel through the VM as opaque handles:
Value = fusevm::Value
elisprs re-exports fusevm::Value. Immediates (ints, floats, strings, bool, nil) are native VM values; every compound elisp object is a heap cell referenced as Value::Obj(u32) into the host arena.
The reader
reader.rs is a hand-written elisp-correct reader: 1+/1-, #', ?c, keywords, dotted pairs (a . b), and backquote (`/,/,@) all parse and decompose to ordinary forms.
The obarray
Every symbol (an Obj::Symbol) gets a value cell and a function cell; resolution follows function-cell aliases. Subrs and user closures are both heap function objects in the function cell.
Lexical + dynamic binding
Closures capture a chain of Scope frames (lexical, indefinite extent, mutable via setq); symbols marked special via defvar/defconst bind dynamically through a specstack that unwinds on scope exit.
The lowering pipeline
elisprs compiles, it does not interpret. lib.rs::eval_forms reads, macro-expands, lowers each form with compiler::compile_top, and runs the resulting chunk with host::run_chunk:
.el → reader → Value (Value::Obj heap handles) → macroexpand
↘ compiler.rs → fusevm::Chunk → fusevm VM + Cranelift JIT / AOT
No fork of the value enum
elisp cells are not new fusevm::Value variants. They live in the ElispHost arena and ride through the VM as Value::Obj(u32) handles, resolved by the extension handler — fusevm stays language-agnostic.
Extension ops
The compiler emits an elisp Op::Extended(id, arg) range (CALL, GETVAR/SETVAR, FSET, SPECBIND/LETBIND/UNBIND, MAKE_CLOSURE, TRUTHY) dispatched into the host for funcall, variable cells, and dynamic binding.
Sub-chunks for lambdas
Each top-level form lowers through a ChunkBuilder; lambda/closure bodies become their own fusevm::Chunk, so calling a closure runs that chunk on a nested VM. Hot numeric ops lower to native fusevm arithmetic for the JIT.
JIT + AOT
Lowered elisp inherits fusevm's three-tier Cranelift JIT, the persistent on-disk JIT cache, and aot::compile_object — the same engine vimlrs, zshrs, and awkrs run on. --aot-exe links a standalone native binary.
Component status
| Component | State | Notes |
|---|---|---|
| reader.rs | Working | Elisp-correct: 1+/#'/?c/:kw, dotted pairs, backquote (`/,/,@). |
| obarray + lexical & dynamic binding | Working | Value + function cells; Scope frames + specstack unwind. |
| special forms (22) + macros | Working | let/cond/while/condition-case/defmacro/… lowered in compiler.rs. |
| subr library (234) + elisp prelude | Working | Lists, numbers, strings, predicates, IO, functional; prelude.rs for derived surface. |
elisp CLI + REPL | Working | File / -e / REPL; --version/--help. |
| compiler.rs → fusevm::Chunk | Working | compile_top/compile_program; runs via host::run_chunk. |
--aot / --aot-exe | Working | compiler + fusevm::aot::compile_object; native object or standalone exe (aot.rs). |
--lsp / --dap | Working | Full stdio servers — completion/hover/signature/symbols (lsp.rs); breakpoints/stepping (dap.rs). |
dotted pairs · backquote · lexical · setcar | Working | Implemented in the reader / host / builtins. |
| regexp engine · rkyv bytecode cache | Working | regexp.rs (fancy-regex); cache.rs at ~/.elisprs/scripts.rkyv. |
Dependency posture
elisprs is a focused frontend over fusevm plus a handful of protocol/support crates — nine direct dependencies, all pulling their weight:
| Crate | Role |
|---|---|
| fusevm | Language-agnostic bytecode VM + three-tier Cranelift JIT + AOT (jit-disk-cache, aot features); the execution engine elisp lowers onto |
| lsp-server / lsp-types | LSP transport + protocol types for --lsp (src/lsp.rs) |
| serde / serde_json | JSON-RPC for LSP/DAP; serialization of the AOT heap image |
| rkyv / bincode | The ~/.elisprs/scripts.rkyv bytecode cache — zero-copy outer shard, bincode per-form chunks |
| fancy-regex | Backtracking regexp engine behind string-match / replace-regexp-in-string (src/regexp.rs) |
| libc | DAP debuggee stdout capture (pipe + dup2) so program output streams as output events |
Honesty & longevity
Loud failures
The only unlowered forms — save-excursion/save-current-buffer/save-restriction (the buffer milestone) — error explicitly at compile time rather than silently misbehaving.
Real elisp semantics
A genuine Lisp-2 with both lexical and dynamic binding, so behaviour tracks Emacs Lisp rather than a Scheme approximation.
Shared engine
By lowering to fusevm::Chunk, elisprs reuses the same VM, JIT, AOT, and on-disk cache as the sibling languages instead of maintaining a one-off interpreter.
Tested
cargo test covers the reader, the lowering/eval pipeline end-to-end, and error handling, including recursion and macros.
Links
- Docs — index.html
- Source — github.com/MenkeTechnologies/elisprs
- Issues — github.com/MenkeTechnologies/elisprs/issues
- fusevm — github.com/MenkeTechnologies/fusevm (the shared VM)
- License — MIT OR Apache-2.0 (LICENSE)