Hosting KidLisp on Clojure / ClojureScript#
Date: 2026-04-17 Author: Claude (for @jeffrey) Scope: Feasibility assessment of moving the KidLisp runtime from hand-rolled JS to a Clojure/ClojureScript host, plus the state of Clojure → WASM as of early 2026.
TL;DR#
Not recommended as a wholesale migration. ClojureScript is a natural Lisp host in principle, but KidLisp's specific surface area — bespoke reader syntax, tight coupling to the Aesthetic Computer Disk API, and an expectation of ~instant boot inside an already-large web runtime — makes a rewrite costly while delivering modest linguistic upside. The most defensible path, if Clojure appeals for other reasons, is a compile-time target (KidLisp AST → ClojureScript forms) rather than a runtime port. Clojure on WASM in 2026 is still pre-production across the board; nothing on that track is ready to replace a shipping JS runtime.
1. What KidLisp actually is today#
system/public/aesthetic.computer/lib/kidlisp.mjs— ~15,400 lines of hand-written ES modules: reader, evaluator, effect dispatch, and timing DSL all in one.- 118 built-in functions across 12 categories (drawing, audio, math, control, animation, data, etc.).
- Extra ports already exist outside the browser runtime:
kidlisp-gameboy/— KidLisp → GBDK/C compiler for Game Boy.kidlisp-n64/— bare-metal / libdragon experiments.
- Integrated with the JS-native Disk API (
lib/disk.mjs) via destructured function bags ({ wipe, ink, line, ... }) — effects are immediate-mode calls into canvas/WebGL/audio graph. - Non-standard Lisp features that matter for any host decision:
- Timing literals:
1s,2s...,0.5s!. - Cached-code references:
$abc123,(embed $code ...). - Handle/timestamp references:
@user/123456. - Unquoted URLs in arg position:
(paste https://example.com/a.png x y). - Dynamic color atoms:
rainbow,zebra,c0..c150. - Reader-visible dashes are subtraction, not identifier chars.
- Timing literals:
These extensions mean KidLisp is only approximately a Lisp at the reader level. Any host — Clojure included — would need a custom reader, so "it's already a Lisp" is a weaker argument than it first appears.
2. Why ClojureScript looks tempting#
- Homoiconicity. KidLisp ASTs map cleanly onto
clojure.coresequences and symbols. - Macros. Timing (
1s,2s...),(once ...),(later ...)all read like macro fodder — ClojureScript macros would give them a first-class home instead of special-cased evaluator branches. - Persistent data structures for free, plus proper recur/trampolines for the repeat/bunch loops.
- REPL-driven iteration. shadow-cljs + reagent-style hot reload is culturally aligned with "tweak a number, keep the canvas" — which is exactly what
REPORT-kidlisp-realtime-state.mdsays KidLisp still doesn't do well. - Compiler infrastructure. Google Closure's advanced optimizations, dead-code elimination, and source maps are mature.
- Shared language across hosts. In principle one ClojureScript codebase could target browser (cljs), native (jank/GraalVM), and JVM simultaneously — appealing for the Game Boy / N64 / OS targets already in the repo.
3. Why it's the wrong move for KidLisp specifically#
3.1 Bundle and boot cost#
- A minimal self-hosted ClojureScript runtime (for runtime eval of user code — which KidLisp must do) brings in
cljs.js+ the analyzer + the reader: ~1–2 MB gzipped in practice, even after advanced optimizations, because you cannot DCE a runtime evaluator. - Scittle / SCI is smaller (~300–500 KB gzipped) but is an interpreter with different perf characteristics than the present kidlisp evaluator.
- The current kidlisp.mjs ships as part of Disk; its footprint is already accounted for and tree-shakes against the rest of the runtime. A CLJS host would add weight on top of disk.mjs (572 KB) rather than replacing anything JS-shaped.
- Aesthetic Computer is mobile-first; a cold-start regression of even 500 ms on low-end devices would be felt immediately.
3.2 Reader is not reusable#
- Clojure's reader cannot parse
2s...,0.5s!,$abc123,@user/123456, or bare URLs. - You'd still write a custom reader. At that point, "ClojureScript is a Lisp" buys you data structures and macros but not parsing.
- Worse: you must teach editor/LSP/formatter tooling that these literals exist, or give them up. The existing
kidlisp-reference.mjsis already a docs-first contract; splitting it across Clojure reader + custom reader risks drift.
3.3 Effect boundary friction#
- KidLisp calls are side effects on a JS graphics/audio API designed around destructuring (
{ wipe, ink, paste }). ClojureScript interop with that shape is verbose ((.wipe api)/(js/api.wipe)) unless you wrap everything, and then you're maintaining two APIs. - The Disk API changes often (see
disk.mjschurn). Every change becomes a double-edit: JS definition + CLJS wrapper.
3.4 Ecosystem/ops mismatch#
- The rest of AC is
.mjs: boot, bios, disk, session server, netlify functions, lith deploy. Introducing a ClojureScript build chain (shadow-cljs, deps.edn, JVM on the build box) fights the current fish-based single-toolchain ethos. - lith (DO VPS) deploys pull from the tangled knot and run Node. Adding a JVM-class dep to the deploy pipeline is a real cost.
3.5 Existing external ports regress#
kidlisp-gameboycompiles KidLisp → C for GBDK. A ClojureScript-hosted evaluator doesn't help this path (still need a bespoke compiler).kidlisp-n64is assembly-adjacent. Same story.- If anything, the Game Boy and N64 work suggests KidLisp's semantic model is the stable asset and the evaluator language is incidental — which argues for keeping the evaluator where its neighbors live (JS in the browser, C on GB, asm on N64), not centralizing on Clojure.
4. State of Clojure → WASM (early 2026)#
Nothing in this space is production-ready for replacing a shipping web runtime. Summary of the tracks:
4.1 jank (LLVM-native Clojure)#
- Native Clojure dialect by Jeaye Wilkerson, targets LLVM IR → native binaries.
- LLVM's
wasm32backend is mature, so in principle jank can emit WASM. In practice, the Clojure runtime (persistent collections, keywords, vars, multimethods) ships as a C++ runtime library that has to be built for the target; the WASM build path has been "experimental / pre-alpha" through 2025 and into early 2026. - Not a credible host for a browser interpreter today. Would also need AOT — jank doesn't give you in-browser
evalout of the box.
4.2 GraalVM native-image → WASM#
- Oracle's GraalVM has an experimental WebAssembly backend (
native-image --tool:wasm/ Truffle-on-WASM variants). Works for tiny programs; Clojure pulls in a large JVM surface and hitsUnsupportedFeatureErroron real-world code regularly. - Babashka (GraalVM + SCI) demonstrates Clojure can AOT to a native binary, but Babashka's own maintainers have not committed to WASM as a shipping target.
- Use case fit: poor for a browser runtime. Size would dwarf the current evaluator.
4.3 SCI / Scittle (pure JS, not WASM)#
- Small Clojure Interpreter by @borkdude. Runs in the browser today as plain JS (~300–500 KB gz).
- Not a WASM port — it's JS. Often miscategorized in WASM discussions.
- Could theoretically be embedded inside a QuickJS-WASM sandbox, but that's stacking interpreters and helps nobody.
4.4 ClojureScript via JS → WASM glue (e.g. Javy, Spin, Kotlin/JS-WASM paths)#
- Tools like Javy (Shopify) and Spin/Wasmtime embed JS engines inside WASM. You can run ClojureScript-compiled JS inside a WASM-embedded JS engine. That doubles the interpreter layers.
- Useful for serverless edge; irrelevant for AC's browser runtime.
4.5 Ferret (Clojure → C++)#
- Ferret AOT-compiles a Clojure subset to C++. C++ → WASM via Emscripten is routine, so Ferret → WASM is feasible for small, statically-knowable programs.
- Doesn't support
eval, which is the KidLisp core requirement.
4.6 Summary table#
| Track | Supports runtime eval |
Production-ready for browser | Rough browser size |
|---|---|---|---|
| SCI / Scittle (JS) | Yes | Yes (not WASM) | 300–500 KB gz |
| Self-hosted ClojureScript (JS) | Yes | Yes (not WASM) | 1–2 MB gz |
| jank → WASM | No (AOT) | No, pre-alpha | Unknown, likely multi-MB |
| GraalVM native-image → WASM | Partial | No, experimental | Large |
| Ferret → C++ → WASM | No (AOT) | Niche only | Small but feature-limited |
| Javy/QuickJS-WASM hosting CLJS | Yes (via nested JS) | Shipping for edge, not browser | 2–5 MB |
Bottom line: if the reason to move to Clojure is to eventually run KidLisp as WASM, wait. In 2026 the realistic browser deployment of Clojure is still JavaScript (via CLJS or SCI), not WebAssembly.
5. Concrete paths forward (ordered by cost)#
-
Do nothing to the host, improve the current evaluator. The single biggest pain point identified in
REPORT-kidlisp-realtime-state.mdis the full-reset-on-edit behavior. That is a state-management issue, not a language-host issue. Fix it inkidlisp.mjs. -
Adopt Clojure data without adopting the Clojure runtime. Keep the JS evaluator; borrow ideas (persistent vectors via a tiny Immer-like lib,
recur-style trampolines). Cheap, keeps the bundle lean. -
Move the KidLisp compiler (not the interpreter) to Clojure. The Game Boy and N64 ports are essentially compilers. If you want a unified compilation story, a JVM-hosted Clojure compiler that emits C / asm / JS makes sense there, because it runs at dev time, not in the user's browser. This is the highest-value place Clojure would land in AC.
-
Rewrite the browser evaluator in ClojureScript. Only if KidLisp stops being primarily a browser artifact and you commit to shadow-cljs-in-the-deploy-pipeline. Rough order: 6–12 eng-weeks to reach parity, assuming you keep a custom reader and a CLJS-shaped Disk API wrapper. Meaningful perf work on top.
-
Bet on Clojure WASM. Premature in early 2026. Revisit when jank's WASM target ships stable builds and has
eval(or when an ahead-of-time model becomes acceptable for KidLisp).
6. Recommendation#
Keep the runtime in JavaScript. Invest Clojure/ClojureScript effort, if any, in the compiler tier (the place where KidLisp becomes Game Boy C, N64 asm, or native binaries) — not the browser interpreter. Do not block on Clojure WASM; the 2026 state of that ecosystem is not where a shipping creative-computing runtime should live.
Appendix: what would change my mind#
- A production-grade
sciorscittlerelease that is clearly under 150 KB gzipped with a usable macro system. - A decision to rebuild Aesthetic Computer's client around a JVM/GraalVM deploy story for other reasons (e.g. collapsing session-server + site onto a single JVM runtime), at which point CLJS becomes a natural front-end complement.
- jank reaching a 1.0 with a shipping WASM target and in-browser
eval. - KidLisp growing a module system / macro layer that genuinely outstrips what the hand-rolled evaluator can express — at that point a host language with real macros starts paying for itself.