Nelisp
NeLisp = Emacs Lisp VM in pure Elisp + Rust syscall stub
Ask AI about Nelisp
Powered by Claude Β· Grounded in docs
I know everything about Nelisp. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
#+TITLE: NeLisp β Self-Hosted Emacs Lisp VM (v1.0) #+AUTHOR: zawatton #+DATE: 2026-04-27
#+begin_quote Note on language: I'm a non-native English speaker. The phrasing in this README is shaped with help from an LLM, but the design decisions and technical claims are mine. If something reads oddly, please ask and I'll clarify. #+end_quote
- TL;DR
NeLisp is a from-scratch Emacs Lisp runtime where the language layer (JIT, allocator, GC, coding-system, evaluator, bytecode interp) is written in Elisp itself, plus a small Rust binary (~15k LoC) with syscall wrappers and a minimal evaluator so it boots without Emacs. v1.0 ships those four C-runtime equivalents and a 624 KB standalone tarball. Use cases: running Elisp on machines without Emacs, and as a substrate for Lisp Machine / new-dialect experiments.
- What NeLisp is
A research project to implement Emacs Lisp on a substrate other than the existing Emacs C core β modeled after SBCL (Common Lisp implemented in itself, on a small non-Lisp runtime) and deliberately not a Scheme rewrite (Guile-Emacs) or a Rust rewrite of the entire Emacs C core (Remacs).
Two layers. The language runtime is implemented in Elisp itself. Rust is a thin shell that provides OS ABI primitives + a NLSEED v1 seed image loader so the runtime can boot without Emacs. A build-time-only Elisp bootstrap evaluator lives in =build-tool/= as an escape hatch until the native-compiled Elisp seed seam (Doc 49) ships end-to-end.
LoC split: ~54k Elisp vs ~19k Rust. Lisp natively compiles its own runtime; Rust handles the headless entry point and the OS substrate optional surfaces (sqlite / fileio / filenotify / process) live in sibling extension crates so a =--no-default-features= "Rust-min core" build keeps shrinking.
** Elisp β the language runtime (45 files, 54,511 LoC)
| Subsystem | Files | LoC | Phase | |-----------+-------+-----+-------| | JIT compiler (SSA + linear-scan, x86_64 + arm64; written in Elisp itself) | 13 | 12,196 | 7.1 | | Memory management (allocator + GC) | 5 | 6,290 | 7.2--7.3 | | Lisp evaluator (reader, eval, special forms, closures, macros) | 7 | 4,035 | -- | | Module loading and bootstrap | 3 | 2,147 | 7.5 | | Coding-system (logic) | 1 | 1,928 | 7.4 | | Bytecode interpreter | 1 | 1,845 | -- | | Emacs compatibility shims | 3 | 1,680 | -- | | Multi-threading / parallel worker pool (process-level concurrency, file-notify) | 5 | 1,405 | -- | | Buffer model | 2 | 1,076 | -- | | Other infrastructure (dev-audit, heap-types, EC bridge, entry point) | 4 | 1,094 | -- | | Character tables (JIS, data) | 1 | 20,815 | -- |
** Rust β the no-Emacs path (47 files, 18,909 LoC across 9 crates)
| Subsystem | Crate | Files | LoC | Phase | |-----------+-------+-------+-----+-------| | Bootstrap Elisp evaluator (25 special forms + 75 builtins; build-time escape hatch per Doc 49 Β§6) | =build-tool= | 18 | 8,037 | 8.0.1--8.0.2 | | Anvil tool surface (MCP server + =anvil__registry= + binaries) | =anvil-runtime= | 12 | 5,063 | 8.0.4--8.0.5 | | Rust-min runtime core (syscall thin wrappers + mmap/mprotect + icache + NLSEED v1 seed loader) | =nelisp-runtime= | 11 | 3,188 | 7.0 / 49 | | Syscall extension crates (=types= / =fileio= / =filenotify= / =process=; default-ON, opt-out via =--no-default-features=) | =nelisp-syscall-= | 4 | 1,852 | 49.3 | | SQLite FFI extension (default-ON compatibility shim) | =nelisp-sqlite-rs= | 1 | 623 | 49.3 | | =exec-bytes= dev bridge (raw native-code JIT smoke; not part of runtime core) | =nelisp-runtime-cli= | 1 | 146 | 49.2 |
v1.0 ships the four cores Emacs's C core normally gives you β native compile, allocator, GC, coding-system β all written in Elisp itself.
** Two things Emacs proper doesn't ship in the same form
- A JIT compiler written in Elisp itself. Emacs 28+ has native-comp, but it depends on libgccjit (an external C library) and is used as AOT in practice. NeLisp's JIT is ~12k LoC of Elisp source you can read end-to-end and modify, with runtime codegen via =mmap= + =PROT_EXEC=.
- Real parallel execution. Emacs's =make-thread= is cooperative β one Lisp thread runs at a time. NeLisp's worker pool gives process-level concurrency under the same Lisp runtime, which is what made the orchestrator and the long-running MCP handler design tractable.
- Status β v1.0 (2026-04-27)
NeLisp v1.0 ships two standalone deployment paths as of today:
-
Stage D v2.0 β bundled-Emacs tarball (~25 MB compressed). =bin/anvil= resolves a stripped Emacs from =$ANVIL_HOME/emacs/bin/emacs= first, so a host without =apt install emacs= can still run NeLisp. Existing dev checkouts fall through to system PATH unchanged. See =README-stage-d-v2.0.org= and =RELEASE_NOTES.md=.
-
Phase 8.0 β Rust-side Elisp interpreter + MCP stdio server. =bin/anvil mcp serve --no-emacs= starts an MCP JSON-RPC server with zero Emacs process spawn: a single =anvil-runtime= Rust binary (~421 KB) implements the reader, evaluator, MCP protocol, and anvil tool registry. An MCP client (Claude Code, Claude Desktop, any MCP-speaking agent) calls =anvil-host-*= tools through this path without requiring Emacs to be installed at all. See =docs/design/44-phase8.0-rust-elisp-interpreter.org= (LOCKED).
** Install
Two paths.
*** Prerequisites by platform
The build itself (=cargo build --release=) is identical across platforms; only the toolchain prereqs vary. After the build, =target/release/nelisp= launches identically.
Tier guarantees (Soak gate, Doc 32 v2 Β§11):
- =linux-x86_64= β 1st-class (CI blocker, every release must pass).
- =macos-arm64= / =linux-arm64= β best-effort, v1.0 time-boxed.
- =windows-native= β deferred to post-v2.0; use WSL2 or MSYS2 mingw64.
Linux (Debian / Ubuntu):
#+begin_src shell sudo apt install build-essential curl pkg-config git curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh . "$HOME/.cargo/env" #+end_src
Linux (Fedora / RHEL):
#+begin_src shell sudo dnf install gcc curl git curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh . "$HOME/.cargo/env" #+end_src
macOS (arm64 or x86_64):
#+begin_src shell xcode-select --install # one-time brew install rustup-init && rustup-init . "$HOME/.cargo/env" #+end_src
Windows β WSL2 (recommended): Install a Debian/Ubuntu distro under WSL2 and follow the Linux (Debian/Ubuntu) row inside WSL. =target/release/nelisp= can be invoked from PowerShell via =wsl nelisp ...=.
Windows β MSYS2 mingw64:
#+begin_src shell
from the MSYS2 mingw64 shell
pacman -S --needed mingw-w64-x86_64-toolchain mingw-w64-x86_64-rust git curl #+end_src
Plain cmd.exe / PowerShell without MSYS2 or WSL is post-v2.0 scope.
*** From source β recommended for hacking and using the Elisp API
#+begin_src shell git clone https://github.com/zawatton/nelisp ~/nelisp cd ~/nelisp/nelisp-runtime cargo build --release # ~30 s on a warm cache cargo test --lib # 291 unit tests, all PASS #+end_src
Build outputs land under =target/release/=:
- =libnelisp_runtime.so= β cdylib for embedding (Rust API in =src/lib.rs=)
- =nelisp-runtime= β minimal CLI (=--version= / =--syscall-smoke=)
The Elisp source for the host-Emacs entry points stays under =~/nelisp/src/=.
*** Pre-built standalone tarball β single-binary deployment
For users who want NeLisp + the bundled Rust runtime without cloning / building. No Emacs install required at runtime:
#+begin_src shell curl -fsSL https://github.com/zawatton/nelisp/releases/latest/download/install-v3.sh | bash #+end_src
Default install root: =$HOME/.local/share/anvil-stage-d-v3.0/=. (The =anvil-= prefix is historical packaging β the tarball is the NeLisp runtime; the bundled =bin/anvil= launcher is one optional front end.) Verification, manual extraction, and platform notes: [[file:README-stage-d-v3.0.org][README-stage-d-v3.0.org]].
Older release lines, for reference only:
- =README-stage-d-v2.0.org= β bundled-Emacs tarball, ~25 MB
- =README-stage-d.org= β legacy host-Emacs-required path
** Launch
The pre-built tarball ships a tiny =nelisp= CLI that wraps the Rust evaluator. No host Emacs needed:
#+begin_src shell nelisp --version # nelisp 0.5.0 nelisp eval "(+ 1 2 3)" # => 6 nelisp eval "(mapcar (function (lambda (n) (* n n))) '(1 2 3 4))" # => (1 4 9 16)
load + run a file (last form's value is printed)
nelisp -l ~/nelisp/examples/hello/hello.el
pipe Elisp through stdin
echo '(defun sq (x) (* x x)) (sq 7)' | nelisp - # => 49 #+end_src
If you built from source, the CLI lives at =nelisp-runtime/target/release/nelisp=; the install tarball puts it on =$PATH=.
** Quick start
For larger workflows the evaluator is reachable three ways; pick the one that matches your context.
*** From Emacs Lisp β REPL straight into NeLisp's own evaluator
#+begin_src elisp (add-to-list 'load-path "~/nelisp/src") ; or the install root's src/ (require 'nelisp-bootstrap) (nelisp-bootstrap-init) ; one-time self-host load
(nelisp-eval-string "(+ 1 2 3)") ; => 6 (nelisp-eval-string "(mapcar (lambda (n) (* n n)) '(1 2 3 4))") ;; => (1 4 9 16)
(nelisp-load-file "~/nelisp/examples/hello/hello.el") (nelisp-eval-string "(hello-world)") ;; => "Hello, World!" #+end_src
The forms go through NeLisp's own reader + evaluator (the Rust crate in cdylib mode, bridged into Elisp). Host Emacs is a launcher only β result and host-evaluator state stay separate.
*** From Rust β embed the runtime as a library
#+begin_src rust use nelisp_runtime::{eval_str, Sexp}; let v: Sexp = eval_str("(+ 1 2 3)").unwrap(); assert_eq!(v, Sexp::Int(6)); #+end_src
Public Rust API: =src/lib.rs=. Entry points are =eval=, =eval_str=, =eval_str_all=, =apply_function= (see =eval/mod.rs=).
*** Acceptance demos via cargo test
The Phase 8.0.2 test suite carries acceptance demos that exercise representative Elisp on the runtime end-to-end:
#+begin_src shell cd ~/nelisp/nelisp-runtime cargo test --lib eval::tests::demo #+end_src
Covers arithmetic / let / lambda / mapcar / if / condition-case (6 demos, ~10 ms total).
#+begin_quote One downstream MCP-server front end that exposes NeLisp's evaluator as a Claude Code tool ships under =bin/anvil= in the standalone tarball. That layer is optional; the runtime above is self-contained. #+end_quote
** Architecture
#+begin_src text ββββββββββββββββββββββββββββββββββββββββββββββββββββ β MCP client (Claude Code / Claude Desktop / β¦) β βββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ β MCP stdio JSON-RPC βββββββββββββββββββΌβββββββββββββββββββββββββββββββββ β anvil-runtime crate β Phase 8.0.4-8.0.5 β mcp/ + anvil_*_registry.rs + bin/anvil-runtime β ββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β Native-compiled Elisp seed β Doc 49 Β§2 (target) β reader / evaluator / macro / compiler / loader β ββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β nelisp-runtime crate (Rust-min core) β Doc 49 Β§1 D2 β syscall + mmap/mprotect + icache + seed loader β ββββββββββββββ¬ββββββββββββββββββββββ¬ββββββββββββββββ€ β extension β extension β dev bridge β β crates β crates β crate β β ββββββββββ β βββββββββββββββββββ β βββββββββββββ β β βsqlite- β β βsyscall-fileio β β βruntime-cliβ β β β rs β β βsyscall-filenotyβ β β exec-bytesβ β β β β β βsyscall-process β β β (JIT smokeβ β β ββββββββββ β βsyscall-types β β β bridge) β β β β βββββββββββββββββββ β βββββββββββββ β ββββββββββββββ΄ββββββββββββββββββββββ΄ββββββββββββββββ€ β OS substrate (libc / Linux / macOS) β Phase 7.0 / 9d ββββββββββββββββββββββββββββββββββββββββββββββββββββ #+end_src
Two boundaries are pinned:
- Doc 46 (=architecture Ξ± boundary final=) β =nelisp-runtime= owns no =anvil_*= or =mcp= modules. The optional MCP front end lives in the sibling =anvil-runtime= crate and calls into =nelisp-runtime= through a path dependency.
- Doc 49 (=Rust-min core / self-host=) β =nelisp-runtime= owns no Elisp interpreter. Rust handles only OS ABI (raw syscalls, mmap/mprotect/munmap, icache flush) plus a NLSEED v1 seed image loader. Reader / evaluator / macro / compiler / stdlib loader live in native-compiled Elisp seed. Optional surfaces (=sqlite= / =fileio= / =filenotify= / =process=) are extension crates pulled through default-ON compatibility features; turning them off (=cargo build --no-default-features -p nelisp-runtime= / =make runtime-core-min=) gives the canonical Rust-min build. The =nelisp-runtime-cli= bin (=nelisp-exec-bytes=) is a dev bridge for raw native-code smoke and is not part of runtime core.
The Rust bootstrap reader / evaluator that previously lived inside =nelisp-runtime= is being migrated out under Doc 49. Until the seed seam ships end-to-end the bootstrap reader/evaluator stays in =build-tool/= as a build-time-only escape hatch.
** Soak gate (Doc 32 v2 Β§2.7)
| Tier | Duration | RSS growth ceiling | Status (2026-04-27) | |------------------+----------+--------------------+---------------------| | blocker (CI) | 1h | < 5 MB | PASS (linux-x86_64) | | post-ship audit | 24h | < 10 MB / 24h | scheduled, weekly |
Tier matrix (Doc 32 v2 Β§11):
| Platform | v1.0 status | |-----------------+-----------------------------------| | linux-x86_64 | blocker (CI gate, must pass) | | macos-arm64 | best-effort 95%+ (v1.0 time-boxed) | | linux-arm64 | best-effort 95%+ (v1.0 time-boxed) | | windows-native | post-v2.0 scope |
=v1.0 time-boxed= = explicit time-boxed exception ratified in Doc 32 v2 Β§11. v1.1+ promotes arm64 to blocker; the audit grep guard in =test/nelisp-release-test.el= retires at that point.
** What is not in v1.0
Said plainly so expectations are calibrated:
- No buffer / marker / window primitives. NeLisp is the language runtime, not the editor. =emacs-nox= is the closest existing comparison if scripting is all you need; what's different here is the 624 KB standalone tarball and a runtime small enough to read end-to-end.
- Phase 8.0 evaluator is a minimal subset (25 special forms + 75 builtins): GC bridge, bignum, full backquote semantics, full =save-excursion= are deferred to Phase 8.x follow-up patches.
- Default =--no-emacs= still falls back to =emacs --batch= for cold-init failures (3-year safety window).
- macOS arm64 / Linux arm64 = best-effort only (= time-boxed for v1.0).
- Windows native =--no-emacs= is post-v2.0 scope.
** Test status (HEAD, 2026-04-29)
- Rust runtime side: =cargo test --lib= = 291 pass / 0 failed (Linux x86_64 measured 2026-04-29).
- Elisp side (ERT): =make test= = 2452 tests / 2409 passed / 0 unexpected / 43 skipped. The 43 skipped are platform-gated SQLite cases on the Linux x86_64 run.
- 3-platform 1-hour soak (Linux x86_64 / macOS M1 / Windows MSYS2) passed end-to-end, 3600 iter @ 1 Hz.
** Key design documents
| Doc | Topic | Status | |-----+----------------------------------------+-----------------------| | 18 | Stage D launcher charter | LOCKED | | 27 | Phase 7+ self-implemented C runtime | LOCKED-2026-04-25-v2 | | 28 | Phase 7.1 NeLisp native compiler | LOCKED-2026-04-25-v2 | | 32 | Phase 7.5 integration + standalone E2E | LOCKED-2026-04-25-v2 | | 44 | Phase 8.0 Rust Elisp interpreter | LOCKED-2026-04-27 |
- Earlier status (Phase 0 β 2026-04-22) β preserved for reference
Phase 0 (design documents) was completed 2026-04-22. Key decisions locked at that time and still in force:
- Bootstrap strategy: Host-Emacs bootstrap (Candidate A), transferring SBCL's genesis methodology (Rhodes 2008) to Emacs as the host
- Scope: Stage A only (= Elisp substrate). Stage B (AI-native Emacs) and Stage C (AI/Emacs boundary fusion) belong to =anvil.el= successors
- Concurrency: Actor model as primary (Phase 4), STM experimental (Phase 4b), fiber (Phase 3), threads deferred (Phase 5)
- Division of labor: NeLisp core team focuses on AI-critical paths (buffer/event/concurrency/eval/FFI); community handles long tail (display render, primitive utilities) post-v1.0 β Linux kernel / SBCL governance model
The roadmap is in =docs/05-roadmap.org=.
- Philosophy (short form)
Emacs Lisp is not philosophically cleaner than Scheme or Common Lisp β but decades of accumulated, lived-in Elisp is a cultural asset worth self-hosting /on its own terms/, rather than porting it to someone else's substrate (Scheme, Rust, JavaScript).
/Esperanto is a constructed language of philosophical elegance, but English is the international language./ Elisp is English here.
The long form lives in =docs/00-motivation.org=.
- Non-goals
- Scheme rewrite (Guile-Emacs direction) β rejected
- Rust rewrite (Remacs direction) β rejected
- A replacement editor for Emacs β out of scope for NeLisp itself (Stage B/C handled by =anvil.el= or successors)
- Replacing /all/ 3000 C functions β non-goal (Remacs failure pattern). NeLisp core team rewrites AI-critical paths, community fills the long tail
- The three-stage vision
#+begin_src text βββββββββββββββββββββββββββββββββββββββββββββββββββ β Stage C: AI and Emacs as one β β anvil.el (and beyond) β βββββββββββββββββββββββββββββββββββββββββββββββ β β β Stage B: Emacs made AI-native β β β anvil.el (and beyond) β β βββββββββββββββββββββββββββββββββββββββββββ β β β β β Stage A: Emacs internals in pure Elisp β β β β NeLisp (this project) β β β (no C to traverse β everything editable) β β β β β βββββββββββββββββββββββββββββββββββββββββββ β β β βββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββ #+end_src
NeLisp builds the substrate (Stage A). Stage B and C are built on top of NeLisp, as extension packages.
- Repository layout
#+begin_src text nelisp/ βββ BRIEFING.org # First-session design instructions βββ README.org # This file βββ .gitignore βββ packages/ # Extracted optional libraries β βββ README.org # Package split convention β βββ nelisp-json/ # Pilot extracted library package β βββ README.org β βββ src/ β βββ test/ βββ docs/ βββ 00-motivation.org # Why this, why now, why not Scheme (USER voice) βββ 01-related-work.org # 7 projects audited + paper priorities βββ 02-scoping.org # Stage A/B/C + AI-critical vs long tail βββ 03-architecture.org # Bootstrap A, primitive boundary, Phase 1 subset βββ 04-concurrency.org # Actor primary Phase 4, novelty claim βββ 05-roadmap.org # Phase 1-5 timeline, release schedule βββ 90-ideas-backlog.org # Deferred interesting ideas βββ papers/ # Academic paper reading stubs βββ README.org βββ rhodes-2008-sbcl.org # Priority A: bootstrap methodology βββ corallo-2020-native-comp.org # Priority A: AOT hybrid βββ bolz-2009-meta-tracing.org # Priority A: JIT for Phase 3-4 βββ maclachlan-1992-python.org # Priority A: CL-in-CL precedent #+end_src
- Optional packages
NeLisp v1.0 now supports an SBCL/Quicklisp-style split between the core =src/= tree and individually extractable libraries under =packages/*/=. The =nelisp-json= package is the pilot extraction and defines the canonical layout for future package moves:
- =packages//src/.el=
- =packages//test/-test.el=
- =packages//README.org=
The build keeps these packages on the Emacs load-path, so existing =require= forms continue to work unchanged. See =packages/README.org= and =packages/nelisp-json/README.org=.
- Related work (audited)
| Project | Approach | Status / diff with NeLisp | |-------------------+---------------------------+------------------------------------------| | Remacs | Emacs C β Rust | Archived 2020, scope explosion | | emacs-ng | Rust + Deno (JS) | Low activity 2024, JS disabled | | Guile-Emacs | Elisp on Guile (Scheme) | 12-year stall, relaunched EmacsConf 2024 | | native-comp | Elisp β libgccjit β .eln | Shipped in Emacs 28+, does not rewrite VM | | SBCL | CL in CL (CMU CL fork) | /Methodology source/ (Rhodes 2008) | | PyPy | Python in RPython + JIT | Successful self-host, STM branch failed | | PicoLisp | Tiny Lisp, pil21 LLVM | pil21 self-hosts LLVM phase | | NeLisp | Elisp in Elisp, editor-native | First editor-integrated self-host |
See =docs/01-related-work.org= Β§10 for the synthesis.
- License
TBD. Leading candidate: GPLv3+ (to match Emacs) with per-module reconsideration if needed. Decided before Phase 1 push.
- Contact
zawatton β kurozawawo@gmail.com
