# Kernel-on-SX: first-class everything The natural successor to SX's recently-completed env-as-value work (sx-improvements Phase 4). Kernel — John Shutt's reformulation of Lisp from his 2010 PhD — pushes *first-class* all the way: environments, evaluators, special forms (operatives), lambda variants are all runtime values, manipulable by programs. SX already has env-as-value; Kernel is what env-as-value looks like *all the way*. **The chisel:** *reflection*. Every language in the current set treats some part of itself as fixed and ineffable — Common Lisp's special forms, Erlang's process model, OCaml's modules. Kernel reifies more of itself than any other language does. Implementing it stresses the substrate's *self-knowledge*: which parts of evaluation does SX expose to user programs, and which stay opaque? **What this exposes about the substrate:** - Whether `eval-expr` can be called as a primitive on user-supplied environments without breaking invariants. - Whether CEK frames can be reified as values (they currently aren't). - Whether special-form dispatch can be table-driven and user-extensible at runtime. - Whether the macro hygiene story extends to Shutt's "hygienic operatives" (operatives that don't capture). **End-state goal:** Kernel's R-1RK core — `$vau`/`$lambda`/`wrap`/`unwrap`, first-class environments, the applicative–operative distinction, the standard environment, encapsulations. ## Ground rules - Scope: `lib/kernel/**` and `plans/kernel-on-sx.md` only. Substrate work belongs to `sx-improvements.md` — if a feature is missing, file it there, don't fix from this plan. - Consumes from `lib/guest/`: `core/lex.sx`, `core/pratt.sx` (s-expression-shaped, minimal demand), `core/ast.sx`, `core/match.sx`. - **May propose** a new sub-layer `lib/guest/reflective/` — environment reification helpers, applicative-vs-operative dispatch, evaluator continuation protocols. A second consumer would be needed; candidates are a hypothetical "MetaScheme" or a Common-Lisp port that exposes its evaluator. - Branch: `loops/kernel`. Standard worktree pattern. ## Architecture sketch ``` Kernel source text (S-expression syntax) │ ▼ lib/kernel/parser.sx — bog-standard s-expr reader │ ▼ lib/kernel/eval.sx — kernel-eval: walks the AST, threads first-class env │ dispatches to operatives via env-bound bindings, not │ a hardcoded switch ▼ lib/kernel/runtime.sx — applicative/operative tagged values, wrap/unwrap, │ standard environment construction, encapsulations ▼ SX CEK evaluator ``` ## Semantic mappings | Kernel construct | SX mapping | |------------------|-----------| | `($lambda (x) body)` | applicative: `(make-applicative (fn (x) body))` — args evaluated | | `($vau (x) e body)` | operative: `(make-operative (fn (x e) body))` — args UN-evaluated, dynamic env passed as `e` | | `(wrap op)` | applicative wrapping an operative: evaluate args, then call op | | `(unwrap app)` | get the underlying operative of an applicative | | `($define! x v)` | operative: bind `x` to `v` in dynamic env | | `(eval expr env)` | call `kernel-eval` on `expr` in `env` — first-class | | `(make-environment)` | fresh empty env | | `(get-current-environment)` | reify the calling env (via SX env-as-value) | | `($if c t e)` | operative: evaluate `c`, then `t` or `e` in dynamic env | The whole interesting thing: there are no special forms hardcoded in the evaluator. `$if`, `$define!`, `$lambda` are all *operatives* bound in the standard environment. User code can rebind them. The evaluator is just `lookup-and-call`. ## Roadmap ### Phase 1 — Parser - [x] S-expression reader with the standard atoms (number, string, symbol, boolean, nil) and lists. - [ ] Reader macros optional; defer to Phase 6. - [x] Tests in `lib/kernel/tests/parse.sx`. ### Phase 2 — Core evaluator with first-class environments - [x] `kernel-eval expr env` — primary entry, walks AST, threads env as a value. - [x] Symbol lookup → environment value (using SX env-as-value primitives). - [x] List → look up head, dispatch on tag (applicative vs operative). - [x] No hardcoded special forms — even `if`/`define`/`lambda` are env-bound. - [x] Tests in `lib/kernel/tests/eval.sx`. ### Phase 3 — `$vau` / `$lambda` / `wrap` / `unwrap` - [x] Operative tagged value: `{:type :operative :params :env-param :body :static-env}`. - [x] Applicative tagged value wraps an operative + the "evaluate args first" contract. - [x] `$vau` builds operatives; `$lambda` is `wrap` ∘ `$vau`. - [x] `wrap` / `unwrap` round-trip cleanly. - [x] Tests: define a custom operative, define a custom applicative on top of it. ### Phase 4 — Standard environment - [ ] Standard env construction: bind `$if`, `$define!`, `$lambda`, `$vau`, `wrap`, `unwrap`, `eval`, `make-environment`, `get-current-environment`, plus arithmetic and list primitives. - [ ] Tests: classic Kernel programs (factorial, list operations, environment manipulation). ### Phase 5 — Encapsulations - [ ] `make-encapsulation-type` returns three operatives: encapsulator, predicate, decapsulator. Standard Kernel idiom for opaque types. - [ ] Tests: implement promises, streams, or simple modules via encapsulations. ### Phase 6 — Hygienic operatives (Shutt's later work) - [ ] Operatives that don't capture caller bindings — uses scope sets / frame stamps to track provenance. - [ ] Bridge to SX's hygienic macro story; possibly extends `lib/guest/reflective/` with hygiene primitives. - [ ] Tests: write an operative that introduces a binding and verify it doesn't shadow caller's same-named bindings. ### Phase 7 — Propose `lib/guest/reflective/` - [ ] Once Phase 3 lands and stabilises, identify which env-reification + dispatch primitives are reusable. Candidate API: `make-operative`, `make-applicative`, `with-current-env`, `eval-in-env`. - [ ] Find a second consumer (Common-Lisp's macro-expansion evaluator? a metacircular Scheme variant? a future plan). - [ ] Only extract once two consumers exist (per stratification rule). ## lib/guest feedback loop **Consumes:** `core/lex`, `core/pratt`, `core/ast`, `core/match`. **Stresses substrate:** env-as-value (Phase 4 of sx-improvements) under heavy use; `eval` as a primitive on user environments; potentially CEK frame reification. **May propose:** `lib/guest/reflective/` sub-layer — environment manipulation, evaluator-as-value, applicative/operative dispatch protocols. **Proposed `lib/guest/reflective/combiner.sx` API** (from Phase 3 chiselling — pending second consumer): - `(refl-make-primitive-operative IMPL)` — IMPL receives `(args dyn-env)`, args unevaluated. - `(refl-make-user-operative PARAMS EPARAM BODY STATIC-ENV)` — for $vau-like constructors. The EPARAM sentinel for "ignore dyn-env" is a fixed keyword (`:refl-ignore` in the proposal). - `(refl-wrap OP)` / `(refl-unwrap APP)` — round-trip pair. - `(refl-operative? V)` / `(refl-applicative? V)` / `(refl-combiner? V)`. - `(refl-call-combiner COMBINER ARGS DYN-ENV)` — the dispatch fork. Pairs with `refl-eval` from the evaluator kit. - Representation: `{:refl-tag :operative :impl FN}` or `{:refl-tag :operative :params P :env-param EP :body B :static-env SE}`; applicatives are `{:refl-tag :applicative :underlying OP}`. The dispatch decision lives in one fork: presence of `:impl` is primitive, presence of `:body` is user-defined. - Driving insight: every reflective Lisp must distinguish "eval my args first" from "hand me the syntax". The tag protocol is identical across Kernel, CL fexprs, vau-style Schemes, possibly Forth's IMMEDIATE words. **Proposed `lib/guest/reflective/env.sx` API** (from Phase 2 chiselling — pending second consumer per the two-consumer rule): - `(refl-make-env)` / `(refl-extend-env PARENT)` — fresh / chained envs, plain SX dicts so they're easy to introspect. - `(refl-env? V)` — predicate. - `(refl-env-bind! ENV NAME VAL)` — local bind; parent is untouched. - `(refl-env-has? ENV NAME)` — recursive presence check. - `(refl-env-lookup ENV NAME)` — recursive lookup, raises on miss. - Representation: `{:refl-tag :env :bindings DICT :parent ENV-OR-NIL}`. Pure-SX dicts so any guest can serialize, diff, snapshot, or rewind environments without help from the host. The motivation is that SX's host `make-env` family is registered only in HTTP/site-mode platform setup, so a guest that needs first-class envs in CLI / test contexts has to roll its own anyway. A shared kit means the next reflective consumer (CL macro evaluator? metacircular Scheme?) doesn't need to redo the work. **What it teaches:** whether SX's recent env-as-value direction generalises to "evaluator-as-value." If Kernel implements cleanly in <2000 lines, env-as-value is real. If it requires substrate fixes at every turn, env-as-value was incomplete and the substrate is telling us what's missing. ## References - Shutt, "Fexprs as the basis of Lisp function application" (PhD thesis, 2010). - Kernel Report (R-1RK): https://web.cs.wpi.edu/~jshutt/kernel.html - Klisp implementation (Andres Navarro) — pragmatic reference. ## Progress log - 2026-05-11 — Phase 3 operatives landed. `lib/kernel/runtime.sx` adds `$vau` (primitive operative that returns a user operative), `$lambda` (sugar for `wrap ∘ $vau`), `wrap` and `unwrap` (Kernel-level applicatives), plus `operative?` and `applicative?` predicates. `kernel-base-env` wires them all into a fresh env. `kernel-eval.sx` now dispatches in `kernel-call-operative` between primitive ops (carry `:impl`) and user ops (carry `:params :env-param :body :static-env`). Parameter binding is a flat list — destructuring/`&rest` deferred. Env-param sentinel: spell `_` or `#ignore` → `:knl-ignore`, which skips the dyn-env bind. 34 tests in `tests/vau.sx`, including the headline custom-operative + custom-applicative composition. chisel: shapes-reflective. Two further reflective-API candidates surfaced: (a) the operative/applicative tag protocol — `make-primitive-operative`, `make-user-operative`, `wrap`, `unwrap` are general for any Lisp-of-fexprs; (b) the call-dispatch fork (primitive vs user) is a *single decision* that every reflective evaluator hits. Both shape go into the proposed `lib/guest/reflective/combiner.sx` candidate. - 2026-05-10 — Phase 2 evaluator landed. `lib/kernel/eval.sx` is `lookup-and-combine`: zero hardcoded special forms. `kernel-eval EXPR ENV` dispatches on shape — literals self-evaluate, Kernel strings unwrap, symbols lookup, lists evaluate head and combine. `kernel-combine` distinguishes operatives (impl receives un-evaluated args + dynamic env) from applicatives (eval args, recurse into underlying op). `kernel-wrap`/`kernel-unwrap` round-trip cleanly. 36 tests verify literal evaluation, symbol lookup with parent-chain shadowing, tagged-value predicates, and the operative-vs-applicative contract (notably `$if` only evaluates the chosen branch, `$quote` returns its arg unevaluated). chisel: shapes-reflective. Substrate gap surfaced: SX's `make-env` / `env-bind!` family is only registered in HTTP/site mode (`http_setup_platform_constructors`), not in CLI epoch mode used for tests. So Kernel envs are modelled in pure SX as `{:knl-tag :env :bindings DICT :parent P}` — a binding-dict + parent-pointer + recursive lookup walk. This is exactly the `lib/guest/reflective/env.sx` candidate API: any reflective language needs first-class env values that can be extended, queried, and walked. Recording the shape (constructor, extend, bind!, has?, lookup) here for the eventual Phase 7 extraction. - 2026-05-10 — Phase 1 parser landed. `lib/kernel/parser.sx` reads R-1RK lexical syntax: numbers (int/float/exp), strings (with escapes), symbols (permissive — anything non-delimiting), booleans `#t`/`#f`, the empty list `()`, nested lists, and `;` line comments. Reader macros (`'` `,` `,@`) deferred per plan. AST: numbers/booleans/lists pass through; strings are wrapped as `{:knl-string …}` to distinguish from symbols which are bare SX strings. 54 tests in `lib/kernel/tests/parse.sx` pass via `sx_server.exe` epoch protocol. chisel: consumes-lex (uses `lex-digit?` and `lex-whitespace?` from `lib/guest/lex.sx` — pratt deliberately not consumed because Kernel is plain s-expressions, no precedence climbing). ## Blockers _(none yet — main risk is substrate gap discovery during Phase 2)_