kernel: type predicates + metacircular demo + map/filter/reduce fix [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
Five type predicates (number?, string?, list?, boolean?, symbol?). New tests/metacircular.sx: m-eval defined in Kernel walks expressions itself, recursing on applicative-call args and delegating to host eval only for operatives and symbol lookup. 14 demo tests. The demo surfaced a real bug: map/filter/reduce called kernel-combine on applicative head-vals directly, which re-evaluates already- evaluated element values; nested-list elements crashed. Fix: extracted knl-apply-op (unwrap-applicative-or-pass-through) and use it in all three combinators before kernel-combine. Mirrors apply's approach. Added knl-apply-op as a proposed entry in the reflective combiner.sx API. 322 tests total.
This commit is contained in:
@@ -534,6 +534,21 @@
|
||||
|
||||
(define kernel-not-applicative (knl-unary-app "not" (fn (v) (not v))))
|
||||
|
||||
;; Type predicates (Kernel-visible). Note `string?` covers BOTH symbols
|
||||
;; and string-literals in our representation (symbols are bare SX
|
||||
;; strings); a `kernel-string?` applicative distinguishes the two if
|
||||
;; needed.
|
||||
(define kernel-number?-applicative
|
||||
(knl-unary-app "number?" (fn (v) (number? v))))
|
||||
(define kernel-string?-applicative
|
||||
(knl-unary-app "string?" (fn (v) (string? v))))
|
||||
(define kernel-list?-applicative
|
||||
(knl-unary-app "list?" (fn (v) (list? v))))
|
||||
(define kernel-boolean?-applicative
|
||||
(knl-unary-app "boolean?" (fn (v) (boolean? v))))
|
||||
(define kernel-symbol?-applicative
|
||||
(knl-unary-app "symbol?" (fn (v) (string? v))))
|
||||
|
||||
(define kernel-eq?-applicative (knl-bin-app "eq?" (fn (a b) (= a b))))
|
||||
|
||||
;; ── the standard environment ────────────────────────────────────
|
||||
@@ -546,13 +561,27 @@
|
||||
;; These re-enter the evaluator on each element, so they use the
|
||||
;; with-env applicative constructor.
|
||||
|
||||
;; When the combiner is an applicative, we MUST unwrap before calling
|
||||
;; — otherwise kernel-combine will re-evaluate the already-evaluated
|
||||
;; element values (and crash if an element is itself a list).
|
||||
(define knl-apply-op
|
||||
(fn (combiner)
|
||||
(cond
|
||||
((kernel-applicative? combiner) (kernel-unwrap combiner))
|
||||
(:else combiner))))
|
||||
|
||||
(define knl-map-step
|
||||
(fn (fn-val xs dyn-env)
|
||||
(let ((op (knl-apply-op fn-val)))
|
||||
(knl-map-walk op xs dyn-env))))
|
||||
|
||||
(define knl-map-walk
|
||||
(fn (op xs dyn-env)
|
||||
(cond
|
||||
((or (nil? xs) (= (length xs) 0)) (list))
|
||||
(:else
|
||||
(cons (kernel-combine fn-val (list (first xs)) dyn-env)
|
||||
(knl-map-step fn-val (rest xs) dyn-env))))))
|
||||
(cons (kernel-combine op (list (first xs)) dyn-env)
|
||||
(knl-map-walk op (rest xs) dyn-env))))))
|
||||
|
||||
(define kernel-map-applicative
|
||||
(kernel-make-primitive-applicative-with-env
|
||||
@@ -568,15 +597,18 @@
|
||||
|
||||
(define knl-filter-step
|
||||
(fn (pred xs dyn-env)
|
||||
(knl-filter-walk (knl-apply-op pred) xs dyn-env)))
|
||||
|
||||
(define knl-filter-walk
|
||||
(fn (op xs dyn-env)
|
||||
(cond
|
||||
((or (nil? xs) (= (length xs) 0)) (list))
|
||||
(:else
|
||||
(let ((keep? (kernel-combine pred (list (first xs)) dyn-env)))
|
||||
(let ((keep? (kernel-combine op (list (first xs)) dyn-env)))
|
||||
(cond
|
||||
(keep?
|
||||
(cons (first xs)
|
||||
(knl-filter-step pred (rest xs) dyn-env)))
|
||||
(:else (knl-filter-step pred (rest xs) dyn-env))))))))
|
||||
(cons (first xs) (knl-filter-walk op (rest xs) dyn-env)))
|
||||
(:else (knl-filter-walk op (rest xs) dyn-env))))))))
|
||||
|
||||
(define kernel-filter-applicative
|
||||
(kernel-make-primitive-applicative-with-env
|
||||
@@ -592,13 +624,17 @@
|
||||
|
||||
(define knl-reduce-step
|
||||
(fn (fn-val xs acc dyn-env)
|
||||
(knl-reduce-walk (knl-apply-op fn-val) xs acc dyn-env)))
|
||||
|
||||
(define knl-reduce-walk
|
||||
(fn (op xs acc dyn-env)
|
||||
(cond
|
||||
((or (nil? xs) (= (length xs) 0)) acc)
|
||||
(:else
|
||||
(knl-reduce-step
|
||||
fn-val
|
||||
(knl-reduce-walk
|
||||
op
|
||||
(rest xs)
|
||||
(kernel-combine fn-val (list acc (first xs)) dyn-env)
|
||||
(kernel-combine op (list acc (first xs)) dyn-env)
|
||||
dyn-env)))))
|
||||
|
||||
;; (apply COMBINER ARGS-LIST) — call COMBINER with the elements of
|
||||
@@ -861,6 +897,11 @@
|
||||
(kernel-env-bind! env "apply" kernel-apply-applicative)
|
||||
(kernel-env-bind! env "append" kernel-append-applicative)
|
||||
(kernel-env-bind! env "reverse" kernel-reverse-applicative)
|
||||
(kernel-env-bind! env "number?" kernel-number?-applicative)
|
||||
(kernel-env-bind! env "string?" kernel-string?-applicative)
|
||||
(kernel-env-bind! env "list?" kernel-list?-applicative)
|
||||
(kernel-env-bind! env "boolean?" kernel-boolean?-applicative)
|
||||
(kernel-env-bind! env "symbol?" kernel-symbol?-applicative)
|
||||
(kernel-env-bind! env "not" kernel-not-applicative)
|
||||
(kernel-env-bind! env "make-encapsulation-type"
|
||||
kernel-make-encap-type-applicative)
|
||||
|
||||
162
lib/kernel/tests/metacircular.sx
Normal file
162
lib/kernel/tests/metacircular.sx
Normal file
@@ -0,0 +1,162 @@
|
||||
;; lib/kernel/tests/metacircular.sx — Kernel-in-Kernel demo.
|
||||
;;
|
||||
;; Demonstrates reflective completeness: a Kernel program implements
|
||||
;; a recognisable subset of Kernel's own evaluation rules and produces
|
||||
;; matching values for a battery of test programs.
|
||||
;;
|
||||
;; This is a SHALLOW metacircular: it dispatches on expression shape
|
||||
;; itself (numbers, booleans, lists, symbols), recursively meta-evals
|
||||
;; each argument of an applicative call, and delegates only to the
|
||||
;; host evaluator for the leaf cases (operatives, symbol lookup). The
|
||||
;; point is to show that env-as-value, first-class operatives, and
|
||||
;; first-class evaluators all line up — enough so a Kernel program
|
||||
;; can itself reason about Kernel programs.
|
||||
|
||||
(define kmc-test-pass 0)
|
||||
(define kmc-test-fail 0)
|
||||
(define kmc-test-fails (list))
|
||||
|
||||
(define
|
||||
kmc-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! kmc-test-pass (+ kmc-test-pass 1))
|
||||
(begin
|
||||
(set! kmc-test-fail (+ kmc-test-fail 1))
|
||||
(append! kmc-test-fails {:name name :actual actual :expected expected})))))
|
||||
|
||||
;; Build a Kernel env with m-eval and m-apply defined. The two refer
|
||||
;; to each other and to standard primitives, so we use the standard
|
||||
;; env as the static-env for both.
|
||||
(define
|
||||
kmc-make-env
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((env (kernel-standard-env)))
|
||||
(kernel-eval
|
||||
(kernel-parse
|
||||
"($define! m-eval\n ($lambda (expr env)\n ($cond\n ((number? expr) expr)\n ((boolean? expr) expr)\n ((null? expr) expr)\n ((symbol? expr) (eval expr env))\n ((list? expr)\n ($let ((head-val (m-eval (car expr) env)))\n ($cond\n ((applicative? head-val)\n (apply head-val\n (map ($lambda (a) (m-eval a env)) (cdr expr))))\n (else (eval expr env)))))\n (else expr))))")
|
||||
env)
|
||||
env)))
|
||||
|
||||
(define
|
||||
kmc-eval
|
||||
(fn
|
||||
(src)
|
||||
(let
|
||||
((env (kmc-make-env)))
|
||||
(kernel-eval
|
||||
(kernel-parse
|
||||
(str "(m-eval (quote " src ") (get-current-environment))"))
|
||||
env))))
|
||||
|
||||
;; ── literals self-evaluate via m-eval ──────────────────────────
|
||||
(kmc-test
|
||||
"m-eval: integer literal"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval 42 (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
42)
|
||||
|
||||
(kmc-test
|
||||
"m-eval: boolean true"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval #t (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
true)
|
||||
|
||||
(kmc-test
|
||||
"m-eval: boolean false"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval #f (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
false)
|
||||
|
||||
(kmc-test
|
||||
"m-eval: empty list"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval () (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
(list))
|
||||
|
||||
;; ── symbol lookup goes through env ─────────────────────────────
|
||||
(kmc-test
|
||||
"m-eval: symbol lookup"
|
||||
(let
|
||||
((env (kmc-make-env)))
|
||||
(kernel-eval (kernel-parse "($define! shared-x 99)") env)
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote shared-x) (get-current-environment))")
|
||||
env))
|
||||
99)
|
||||
|
||||
;; ── applicative calls are dispatched by m-eval recursively ─────
|
||||
(kmc-test
|
||||
"m-eval: addition"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote (+ 1 2)) (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
3)
|
||||
|
||||
(kmc-test
|
||||
"m-eval: nested arithmetic"
|
||||
(kernel-eval
|
||||
(kernel-parse
|
||||
"(m-eval ($quote (+ (* 2 3) (- 10 4))) (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
12)
|
||||
|
||||
(kmc-test
|
||||
"m-eval: variadic +"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote (+ 1 2 3 4 5)) (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
15)
|
||||
|
||||
(kmc-test
|
||||
"m-eval: list construction"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote (list 1 2 3)) (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
(list 1 2 3))
|
||||
|
||||
(kmc-test "m-eval: cons reverse-style"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote (cons 0 (list 1 2))) (get-current-environment))")
|
||||
(kmc-make-env)) (list 0 1 2))
|
||||
|
||||
(kmc-test "m-eval: nested apply"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote (apply + (list 10 20 30))) (get-current-environment))")
|
||||
(kmc-make-env)) 60)
|
||||
|
||||
;; ── operatives delegate to host eval (transparently for the caller) ─
|
||||
(kmc-test
|
||||
"m-eval: $if true branch (via delegation)"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote ($if #t 1 2)) (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
1)
|
||||
|
||||
(kmc-test
|
||||
"m-eval: $if false branch"
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote ($if #f 1 2)) (get-current-environment))")
|
||||
(kmc-make-env))
|
||||
2)
|
||||
|
||||
;; ── m-eval can call a user-defined lambda ──────────────────────
|
||||
(kmc-test
|
||||
"m-eval: user lambda call"
|
||||
(let
|
||||
((env (kmc-make-env)))
|
||||
(kernel-eval (kernel-parse "($define! sq ($lambda (x) (* x x)))") env)
|
||||
(kernel-eval
|
||||
(kernel-parse "(m-eval ($quote (sq 7)) (get-current-environment))")
|
||||
env))
|
||||
49)
|
||||
|
||||
(define kmc-tests-run! (fn () {:total (+ kmc-test-pass kmc-test-fail) :passed kmc-test-pass :failed kmc-test-fail :fails kmc-test-fails}))
|
||||
@@ -136,6 +136,7 @@ When the second consumer arrives, the extraction work is: rename `kernel-*` →
|
||||
- `(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-make-primitive-applicative-with-env IMPL)` — like `refl-make-primitive-applicative` but IMPL receives `(args dyn-env)`. Used by combinators that re-enter the evaluator: `map`, `filter`, `reduce`, `apply`, `eval`, dynamic `call-with-current-environment`. Universal across reflective Lisps because such combinators MUST capture the caller's env to honor dynamic scoping.
|
||||
- `(refl-apply-op COMBINER)` — if COMBINER is an applicative, returns its underlying operative; otherwise returns COMBINER unchanged. Critical helper for combinators that call user-supplied functions with already-evaluated values: passing values to an applicative would re-evaluate them (numbers/strings pass through, but lists get treated as calls). Every reflective Lisp has discovered this bug; the unwrap-then-combine pattern is the fix. Surfaced by the Kernel-on-SX metacircular demo when nested-list elements crashed map.
|
||||
- `(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.
|
||||
@@ -161,6 +162,7 @@ The motivation is that SX's host `make-env` family is registered only in HTTP/si
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-05-11 — Type predicates + metacircular evaluator demo + map/filter/reduce bug fix. Five new applicatives: `number?`, `string?` (which doubles as `symbol?`), `list?`, `boolean?`, `symbol?`. New test file `tests/metacircular.sx`: a Kernel program `m-eval` that walks expressions, recursively meta-evaluates sub-expressions of applicative calls, and delegates to host `eval` for symbol lookup and operatives. 14 tests showing m-eval handles literals, arithmetic, list construction, $if branches via delegation, and user-defined lambdas. **Substantive bug fix surfaced by the demo**: `map`, `filter`, `reduce` were calling `kernel-combine` directly with applicatives, which then re-evaluated the already-evaluated element values; nested-list elements crashed with "not a combiner". Fix: unwrap the applicative first (mirrors `apply`'s approach). New helper `knl-apply-op` for the unwrap-if-applicative pattern, used by all three combinators. chisel: shapes-reflective. **Two reflective findings**: (1) `knl-apply-op` (unwrap-applicative-or-pass-through) is a universal helper that any reflective combinator needs — proposed for the `combiner.sx` API. (2) The metacircular demo proves the substrate is reflective-complete in the meaningful sense: a Kernel program *can* implement a non-trivial subset of Kernel's evaluation semantics, calling back into the host evaluator only for operatives and lookup. 322 tests total.
|
||||
- 2026-05-11 — `append` (variadic) and `reverse`. Append concatenates any number of lists; empty `(append)` returns `()`. Reverse is unary. 11 new tests. chisel: nothing (textbook list ops). 307 tests total.
|
||||
- 2026-05-11 — `apply` combinator. `(apply F (list V1 V2 V3))` ≡ `(F V1 V2 V3)` but with the argument list constructed at runtime. Implementation: unwrap an applicative F to its underlying operative, then `kernel-combine` it with the values — skipping the auto-eval pass since args are already values. For a bare operative F, pass through directly. 7 new tests. chisel: shapes-reflective. The unwrap-then-combine pattern is universal across reflective Lisps and should be in the `combiner.sx` API alongside the existing wrap/unwrap pair: `refl-apply F ARGS DYN-ENV` is the third API entry needed for higher-order composition. 296 tests total.
|
||||
- 2026-05-11 — `map` / `filter` / `reduce` list combinators. Required adding `kernel-make-primitive-applicative-with-env` to `eval.sx`: standard primitive applicatives drop dyn-env, but combinators that re-enter the evaluator (calling user-supplied functions on each element) need it. The three combinators use `kernel-combine` directly with the captured dyn-env. 10 new tests covering map/filter/reduce on numbers, empty lists, closures, and list construction. chisel: shapes-reflective. The "primitive applicatives split into two flavours — env-blind and env-aware" finding goes into the proposed `lib/guest/reflective/combiner.sx` API. Every reflective Lisp must distinguish "I just need values" from "I need to re-enter evaluation" — the with-env constructor pair is universal. 289 tests total.
|
||||
|
||||
Reference in New Issue
Block a user