kernel: Phase 3 $vau/$lambda/wrap/unwrap + 34 tests [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s

User-defined operatives via $vau; applicatives via $lambda (wrap ∘ $vau).
wrap/unwrap as Kernel-level applicatives. kernel-call-operative forks
on :impl (primitive) vs :body (user) tag. kernel-base-env wires the
four combiners + operative?/applicative? predicates. Env-param sentinel
`_` / `#ignore` → :knl-ignore (skip dyn-env bind). Flat parameter list
only; destructuring later. Headline test: custom applicative + custom
operative composed from user code.
This commit is contained in:
2026-05-11 07:43:45 +00:00
parent 7e57e0b215
commit 0da39de68a
4 changed files with 533 additions and 20 deletions

View File

@@ -1,8 +1,8 @@
;; lib/kernel/eval.sx — Kernel evaluator (Phase 2 skeleton).
;; lib/kernel/eval.sx — Kernel evaluator.
;;
;; The evaluator is `lookup-and-combine`: there are no hardcoded special
;; forms. Even $if / $define! / $lambda will be ordinary operatives bound
;; in the standard environment (Phase 4). This file builds the dispatch
;; forms. Even $if / $define! / $lambda are ordinary operatives bound in
;; the standard environment (Phase 4). This file builds the dispatch
;; machinery and the operative/applicative tagged-value protocol.
;;
;; Tagged values
@@ -12,24 +12,31 @@
;; keyed by symbol name; parent walks up the lookup chain.
;;
;; {:knl-tag :operative :impl FN}
;; A primitive operative. FN receives (args dyn-env) — args are the
;; Primitive operative. FN receives (args dyn-env) — args are the
;; UN-evaluated argument expressions, dyn-env is the calling env.
;;
;; {:knl-tag :applicative :underlying OP}
;; An applicative wraps an operative. Calls evaluate args first, then
;; forward to the underlying operative.
;; {:knl-tag :operative :params P :env-param EP :body B :static-env SE}
;; User-defined operative (built by $vau). Same tag; dispatch in
;; kernel-call-operative forks on which keys are present.
;;
;; User-defined ($vau) operatives are added in Phase 3 — same tag, with
;; extra fields :params :env-param :body :static-env.
;; {:knl-tag :applicative :underlying OP}
;; An applicative wraps an operative. Calls evaluate args first,
;; then forward to the underlying operative.
;;
;; The env-param of a user operative may be the sentinel :knl-ignore,
;; in which case the dynamic env is not bound.
;;
;; Public API
;; (kernel-eval EXPR ENV) — primary entry
;; (kernel-combine COMBINER ARGS DYN-ENV) — apply a combiner
;; (kernel-combine COMBINER ARGS DYN-ENV)
;; (kernel-call-operative OP ARGS DYN-ENV)
;; (kernel-bind-params! ENV PARAMS ARGS)
;; (kernel-make-env) / (kernel-extend-env P)
;; (kernel-env-bind! E N V) / (kernel-env-lookup E N)
;; (kernel-env-has? E N) / (kernel-env? V)
;; (kernel-make-primitive-operative IMPL)
;; (kernel-make-primitive-applicative IMPL) — IMPL receives evaled args
;; (kernel-make-primitive-applicative IMPL)
;; (kernel-make-user-operative PARAMS EPARAM BODY STATIC-ENV)
;; (kernel-wrap OP) / (kernel-unwrap APP)
;; (kernel-operative? V) / (kernel-applicative? V) / (kernel-combiner? V)
;;
@@ -72,6 +79,10 @@
(define kernel-make-primitive-operative (fn (impl) {:impl impl :knl-tag :operative}))
(define
kernel-make-user-operative
(fn (params eparam body static-env) {:knl-tag :operative :static-env static-env :params params :body body :env-param eparam}))
(define
kernel-operative?
(fn (v) (and (dict? v) (= (get v :knl-tag) :operative))))
@@ -100,8 +111,6 @@
((kernel-applicative? app) (get app :underlying))
(:else (error "kernel-unwrap: argument must be an applicative")))))
;; A primitive applicative: sugar for (wrap (primitive-operative …)) where
;; the impl receives already-evaluated args.
(define
kernel-make-primitive-applicative
(fn
@@ -136,7 +145,8 @@
(fn
(combiner args dyn-env)
(cond
((kernel-operative? combiner) ((get combiner :impl) args dyn-env))
((kernel-operative? combiner)
(kernel-call-operative combiner args dyn-env))
((kernel-applicative? combiner)
(kernel-combine
(get combiner :underlying)
@@ -144,6 +154,44 @@
dyn-env))
(:else (error (str "kernel-eval: not a combiner: " combiner))))))
;; Operatives may be primitive (:impl is a host fn) or user-defined
;; (carry :params / :env-param / :body / :static-env). The dispatch
;; fork is here so kernel-combine stays small.
(define
kernel-call-operative
(fn
(op args dyn-env)
(cond
((dict-has? op :impl) ((get op :impl) args dyn-env))
((dict-has? op :body)
(let
((local (kernel-extend-env (get op :static-env))))
(kernel-bind-params! local (get op :params) args)
(let
((eparam (get op :env-param)))
(when
(not (= eparam :knl-ignore))
(kernel-env-bind! local eparam dyn-env)))
(kernel-eval (get op :body) local)))
(:else (error "kernel-call-operative: malformed operative")))))
;; Phase 3 supports a flat parameter list only — destructuring later.
(define
kernel-bind-params!
(fn
(env params args)
(cond
((or (nil? params) (= (length params) 0))
(cond
((or (nil? args) (= (length args) 0)) nil)
(:else (error "kernel-call: too many arguments"))))
((or (nil? args) (= (length args) 0))
(error "kernel-call: too few arguments"))
(:else
(begin
(kernel-env-bind! env (first params) (first args))
(kernel-bind-params! env (rest params) (rest args)))))))
(define
kernel-eval-args
(fn
@@ -155,7 +203,6 @@
(kernel-eval (first args) env)
(kernel-eval-args (rest args) env))))))
;; Evaluate a sequence of forms in env, returning the value of the last.
(define
kernel-eval-program
(fn

167
lib/kernel/runtime.sx Normal file
View File

@@ -0,0 +1,167 @@
;; lib/kernel/runtime.sx — the operativeapplicative substrate.
;;
;; Builds the first user-visible operatives so Kernel programs can
;; construct their own combiners:
;;
;; $vau — primitive operative that returns a user operative
;; $lambda — primitive operative; sugar for (wrap ($vau …))
;; wrap — primitive applicative; wraps an operative
;; unwrap — primitive applicative; extracts the underlying op
;;
;; In Kernel, $lambda is *defined* in terms of $vau and wrap:
;; ($define! $lambda
;; ($vau (formals . body) #ignore
;; (wrap (eval (list $vau formals #ignore (cons $sequence body)) env))))
;; Phase 3 supplies it natively (single-expression body) so tests can
;; build applicatives without a working $define!/$sequence yet. The
;; native-then-portable migration is a Phase 4 concern.
;;
;; The env-param sentinel
;; ----------------------
;; A user operative records an `:env-param` slot. If the source said
;; `#ignore`, the slot holds the keyword :knl-ignore and kernel-call-
;; operative skips binding the dynamic env. The parser doesn't recognise
;; `#ignore` yet (Phase 1 covered #t/#f only); guests must spell it
;; `_` for now — the spelling-to-sentinel conversion lives here in
;; knl-eparam-sentinel.
;;
;; Public API
;; (kernel-base-env) — fresh env with $vau, $lambda, wrap, unwrap
;;
;; Consumes: lib/kernel/eval.sx (everything tagged kernel-*).
(define
knl-eparam-sentinel
(fn
(sym)
(cond
((= sym "_") :knl-ignore)
((= sym "#ignore") :knl-ignore)
(:else sym))))
;; Validate that a formals list is a plain list of symbol names.
(define
knl-formals-ok?
(fn
(formals)
(cond
((not (list? formals)) false)
((= (length formals) 0) true)
((string? (first formals)) (knl-formals-ok? (rest formals)))
(:else false))))
;; ── $vau ─────────────────────────────────────────────────────────
;; ($vau FORMALS ENV-PARAM BODY) → user operative.
;;
;; FORMALS — unevaluated list of parameter symbols.
;; ENV-PARAM — symbol (or `_` / `#ignore`).
;; BODY — single expression (Phase 3 limitation; $sequence later).
;;
;; The returned operative closes over the env where $vau was invoked.
(define
kernel-vau-impl
(fn
(args dyn-env)
(cond
((not (= (length args) 3))
(error "$vau: expects (formals env-param body)"))
(:else
(let
((formals (first args))
(eparam-raw (nth args 1))
(body (nth args 2)))
(cond
((not (knl-formals-ok? formals))
(error "$vau: formals must be a list of symbols"))
((not (string? eparam-raw))
(error "$vau: env-param must be a symbol"))
(:else
(kernel-make-user-operative
formals
(knl-eparam-sentinel eparam-raw)
body
dyn-env))))))))
(define
kernel-vau-operative
(kernel-make-primitive-operative kernel-vau-impl))
;; ── $lambda ──────────────────────────────────────────────────────
;; ($lambda FORMALS BODY) → user applicative.
;;
;; Equivalent to (wrap ($vau FORMALS #ignore BODY)) — args are evaluated
;; before the operative body runs, and the operative ignores the dynamic
;; environment.
(define
kernel-lambda-impl
(fn
(args dyn-env)
(cond
((not (= (length args) 2))
(error "$lambda: expects (formals body)"))
(:else
(let
((formals (first args)) (body (nth args 1)))
(cond
((not (knl-formals-ok? formals))
(error "$lambda: formals must be a list of symbols"))
(:else
(kernel-wrap
(kernel-make-user-operative formals :knl-ignore body dyn-env)))))))))
(define
kernel-lambda-operative
(kernel-make-primitive-operative kernel-lambda-impl))
;; ── wrap / unwrap as Kernel applicatives ─────────────────────────
(define
kernel-wrap-applicative
(kernel-make-primitive-applicative
(fn
(args)
(cond
((not (= (length args) 1))
(error "wrap: expects exactly 1 argument"))
(:else (kernel-wrap (first args)))))))
(define
kernel-unwrap-applicative
(kernel-make-primitive-applicative
(fn
(args)
(cond
((not (= (length args) 1))
(error "unwrap: expects exactly 1 argument"))
(:else (kernel-unwrap (first args)))))))
;; Convenience predicates as applicatives too — tests want them.
(define
kernel-operative?-applicative
(kernel-make-primitive-applicative
(fn (args) (kernel-operative? (first args)))))
(define
kernel-applicative?-applicative
(kernel-make-primitive-applicative
(fn (args) (kernel-applicative? (first args)))))
;; ── Base environment ─────────────────────────────────────────────
;; A fresh env with the Phase 3 combiners bound. Standard env (Phase 4)
;; will extend this with $if, $define!, arithmetic, list ops, etc.
(define
kernel-base-env
(fn
()
(let
((env (kernel-make-env)))
(kernel-env-bind! env "$vau" kernel-vau-operative)
(kernel-env-bind! env "$lambda" kernel-lambda-operative)
(kernel-env-bind! env "wrap" kernel-wrap-applicative)
(kernel-env-bind! env "unwrap" kernel-unwrap-applicative)
(kernel-env-bind! env "operative?" kernel-operative?-applicative)
(kernel-env-bind! env "applicative?" kernel-applicative?-applicative)
env)))

289
lib/kernel/tests/vau.sx Normal file
View File

@@ -0,0 +1,289 @@
;; lib/kernel/tests/vau.sx — exercises lib/kernel/runtime.sx.
;;
;; Verifies the Phase 3 promise: user-defined operatives and applicatives
;; constructible from inside the language. Tests build a Kernel
;; base-env, bind a few helper applicatives (+, *, list, =, $if), and
;; run programs that construct and use custom combiners.
(define kv-test-pass 0)
(define kv-test-fail 0)
(define kv-test-fails (list))
(define
kv-test
(fn
(name actual expected)
(if
(= actual expected)
(set! kv-test-pass (+ kv-test-pass 1))
(begin
(set! kv-test-fail (+ kv-test-fail 1))
(append! kv-test-fails {:name name :actual actual :expected expected})))))
(define kv-eval-src (fn (src env) (kernel-eval (kernel-parse src) env)))
(define
kv-make-env
(fn
()
(let
((env (kernel-base-env)))
(kernel-env-bind!
env
"+"
(kernel-make-primitive-applicative
(fn (args) (+ (first args) (nth args 1)))))
(kernel-env-bind!
env
"*"
(kernel-make-primitive-applicative
(fn (args) (* (first args) (nth args 1)))))
(kernel-env-bind!
env
"-"
(kernel-make-primitive-applicative
(fn (args) (- (first args) (nth args 1)))))
(kernel-env-bind!
env
"="
(kernel-make-primitive-applicative
(fn (args) (= (first args) (nth args 1)))))
(kernel-env-bind!
env
"list"
(kernel-make-primitive-applicative (fn (args) args)))
(kernel-env-bind!
env
"cons"
(kernel-make-primitive-applicative
(fn (args) (cons (first args) (nth args 1)))))
(kernel-env-bind!
env
"$quote"
(kernel-make-primitive-operative (fn (args dyn-env) (first args))))
(kernel-env-bind!
env
"$if"
(kernel-make-primitive-operative
(fn
(args dyn-env)
(if
(kernel-eval (first args) dyn-env)
(kernel-eval (nth args 1) dyn-env)
(kernel-eval (nth args 2) dyn-env)))))
env)))
;; ── $vau: builds an operative ───────────────────────────────────
(kv-test
"vau: identity returns first arg unevaluated"
(kv-eval-src "(($vau (a) _ a) hello)" (kv-make-env))
"hello")
(kv-test
"vau: returns args as raw expressions"
(kv-eval-src "(($vau (a b) _ (list a b)) (+ 1 2) (+ 3 4))" (kv-make-env))
(list (list "+" 1 2) (list "+" 3 4)))
(kv-test
"vau: env-param is a kernel env"
(kernel-env? (kv-eval-src "(($vau () e e))" (kv-make-env)))
true)
(kv-test
"vau: returns operative"
(kernel-operative? (kv-eval-src "($vau (x) _ x)" (kv-make-env)))
true)
(kv-test
"vau: returns operative not applicative"
(kernel-applicative? (kv-eval-src "($vau (x) _ x)" (kv-make-env)))
false)
(kv-test
"vau: zero-arg body"
(kv-eval-src "(($vau () _ 42))" (kv-make-env))
42)
(kv-test
"vau: static-env closure captured"
(let
((outer (kv-make-env)))
(kernel-env-bind! outer "captured" 17)
(let
((op (kv-eval-src "($vau () _ captured)" outer))
(caller (kv-make-env)))
(kernel-env-bind! caller "captured" 99)
(kernel-combine op (list) caller)))
17)
(kv-test
"vau: env-param exposes caller's dynamic env"
(let
((outer (kv-make-env)))
(kernel-env-bind! outer "x" 1)
(let
((op (kv-eval-src "($vau () e e)" outer)) (caller (kv-make-env)))
(kernel-env-bind! caller "x" 2)
(let
((e-val (kernel-combine op (list) caller)))
(kernel-env-lookup e-val "x"))))
2)
;; ── $lambda: applicatives evaluate their args ───────────────────
(kv-test
"lambda: identity"
(kv-eval-src "(($lambda (x) x) 42)" (kv-make-env))
42)
(kv-test
"lambda: addition"
(kv-eval-src "(($lambda (x y) (+ x y)) 3 4)" (kv-make-env))
7)
(kv-test
"lambda: args are evaluated before bind"
(kv-eval-src "(($lambda (x) x) (+ 2 3))" (kv-make-env))
5)
(kv-test
"lambda: zero args"
(kv-eval-src "(($lambda () 99))" (kv-make-env))
99)
(kv-test
"lambda: returns applicative"
(kernel-applicative? (kv-eval-src "($lambda (x) x)" (kv-make-env)))
true)
(kv-test
"lambda: returns applicative not operative"
(kernel-operative? (kv-eval-src "($lambda (x) x)" (kv-make-env)))
false)
(kv-test
"lambda: higher-order"
(kv-eval-src "(($lambda (f) (f 10)) ($lambda (x) (+ x 1)))" (kv-make-env))
11)
;; ── wrap / unwrap as user-callable applicatives ─────────────────
(kv-test
"wrap: makes applicative from operative"
(kernel-applicative? (kv-eval-src "(wrap ($vau (x) _ x))" (kv-make-env)))
true)
(kv-test
"wrap: result evaluates its arg"
(kv-eval-src "((wrap ($vau (x) _ x)) (+ 1 2))" (kv-make-env))
3)
(kv-test
"unwrap: extracts operative from applicative"
(kernel-operative? (kv-eval-src "(unwrap ($lambda (x) x))" (kv-make-env)))
true)
(kv-test
"wrap/unwrap roundtrip preserves identity"
(kv-eval-src
"(($lambda (op) (= op (unwrap (wrap op)))) ($vau (x) _ x))"
(kv-make-env))
true)
;; ── operative? / applicative? as user-visible predicates ────────
(kv-test
"operative? on vau result"
(kv-eval-src "(operative? ($vau (x) _ x))" (kv-make-env))
true)
(kv-test
"operative? on lambda result"
(kv-eval-src "(operative? ($lambda (x) x))" (kv-make-env))
false)
(kv-test
"applicative? on lambda result"
(kv-eval-src "(applicative? ($lambda (x) x))" (kv-make-env))
true)
(kv-test
"applicative? on vau result"
(kv-eval-src "(applicative? ($vau (x) _ x))" (kv-make-env))
false)
(kv-test
"operative? on number"
(kv-eval-src "(operative? 42)" (kv-make-env))
false)
;; ── Build BOTH layers from user code ────────────────────────────
;; The headline Phase 3 test: defining an operative on top of an
;; applicative defined on top of a vau.
(kv-test
"custom: applicative + operative compose"
(let
((env (kv-make-env)))
(kernel-env-bind! env "square" (kv-eval-src "($lambda (x) (* x x))" env))
(kv-eval-src "(square 4)" env))
16)
(kv-test "custom: operative captures argument syntax"
;; ($capture x) returns the raw expression `x`, regardless of value.
(let ((env (kv-make-env)))
(kernel-env-bind! env "$capture"
(kv-eval-src "($vau (form) _ form)" env))
(kv-eval-src "($capture (+ 1 2))" env))
(list "+" 1 2))
(kv-test "custom: applicative re-wraps an operative"
;; Build a captured operative, then wrap it into an applicative that
;; evaluates args before re-entry. This exercises wrap+$vau composed.
(let ((env (kv-make-env)))
(kernel-env-bind! env "id-app"
(kv-eval-src "(wrap ($vau (x) _ x))" env))
(kv-eval-src "(id-app (+ 10 20))" env))
30)
;; ── Error cases ──────────────────────────────────────────────────
(kv-test
"vau: rejects non-list formals"
(guard (e (true :raised)) (kv-eval-src "($vau x _ x)" (kv-make-env)))
:raised)
(kv-test
"vau: rejects non-symbol formal"
(guard (e (true :raised)) (kv-eval-src "($vau (1) _ x)" (kv-make-env)))
:raised)
(kv-test
"vau: rejects non-symbol env-param"
(guard (e (true :raised)) (kv-eval-src "($vau (x) 7 x)" (kv-make-env)))
:raised)
(kv-test
"vau: too few args at call site"
(guard
(e (true :raised))
(kv-eval-src "(($vau (x y) _ x) 1)" (kv-make-env)))
:raised)
(kv-test
"vau: too many args at call site"
(guard
(e (true :raised))
(kv-eval-src "(($vau (x) _ x) 1 2)" (kv-make-env)))
:raised)
(kv-test
"wrap: rejects non-operative"
(guard (e (true :raised)) (kv-eval-src "(wrap 42)" (kv-make-env)))
:raised)
(kv-test
"unwrap: rejects non-applicative"
(guard (e (true :raised)) (kv-eval-src "(unwrap 42)" (kv-make-env)))
:raised)
(define kv-tests-run! (fn () {:total (+ kv-test-pass kv-test-fail) :passed kv-test-pass :failed kv-test-fail :fails kv-test-fails}))

View File

@@ -68,11 +68,11 @@ The whole interesting thing: there are no special forms hardcoded in the evaluat
- [x] Tests in `lib/kernel/tests/eval.sx`.
### Phase 3 — `$vau` / `$lambda` / `wrap` / `unwrap`
- [ ] Operative tagged value: `{:type :operative :params :env-param :body :static-env}`.
- [ ] Applicative tagged value wraps an operative + the "evaluate args first" contract.
- [ ] `$vau` builds operatives; `$lambda` is `wrap``$vau`.
- [ ] `wrap` / `unwrap` round-trip cleanly.
- [ ] Tests: define a custom operative, define a custom applicative on top of it.
- [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.
@@ -100,6 +100,15 @@ The whole interesting thing: there are no special forms hardcoded in the evaluat
**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.
@@ -119,6 +128,7 @@ The motivation is that SX's host `make-env` family is registered only in HTTP/si
## 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).