kernel: $quasiquote runtime + reflective/quoting.sx sketch [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
kernel-quasiquote-operative walks the template via mutually-recursive knl-quasi-walk ↔ knl-quasi-walk-list. $unquote forms eval in dyn-env; $unquote-splicing splices list-valued results. No depth tracking (nested quasiquotes flatten). 8 new tests, 230 total. Sketched the universal reflective quoting kit API for the eventual Phase 7 extraction.
This commit is contained in:
@@ -194,6 +194,57 @@
|
|||||||
((not (= (length args) 1)) (error "$quote: expects 1 argument"))
|
((not (= (length args) 1)) (error "$quote: expects 1 argument"))
|
||||||
(:else (first args))))))
|
(:else (first args))))))
|
||||||
|
|
||||||
|
;; Quasiquote: walks the template, evaluating `$unquote` forms in the
|
||||||
|
;; dynamic env and splicing `$unquote-splicing` list results.
|
||||||
|
(define knl-quasi-walk
|
||||||
|
(fn (form dyn-env)
|
||||||
|
(cond
|
||||||
|
((not (list? form)) form)
|
||||||
|
((= (length form) 0) form)
|
||||||
|
((and (string? (first form)) (= (first form) "$unquote"))
|
||||||
|
(cond
|
||||||
|
((not (= (length form) 2))
|
||||||
|
(error "$unquote: expects exactly 1 argument"))
|
||||||
|
(:else (kernel-eval (nth form 1) dyn-env))))
|
||||||
|
(:else (knl-quasi-walk-list form dyn-env)))))
|
||||||
|
|
||||||
|
(define knl-quasi-walk-list
|
||||||
|
(fn (forms dyn-env)
|
||||||
|
(cond
|
||||||
|
((or (nil? forms) (= (length forms) 0)) (list))
|
||||||
|
(:else
|
||||||
|
(let ((head (first forms)))
|
||||||
|
(cond
|
||||||
|
((and (list? head)
|
||||||
|
(= (length head) 2)
|
||||||
|
(string? (first head))
|
||||||
|
(= (first head) "$unquote-splicing"))
|
||||||
|
(let ((spliced (kernel-eval (nth head 1) dyn-env)))
|
||||||
|
(cond
|
||||||
|
((not (list? spliced))
|
||||||
|
(error "$unquote-splicing: value must be a list"))
|
||||||
|
(:else
|
||||||
|
(knl-list-concat
|
||||||
|
spliced
|
||||||
|
(knl-quasi-walk-list (rest forms) dyn-env))))))
|
||||||
|
(:else
|
||||||
|
(cons (knl-quasi-walk head dyn-env)
|
||||||
|
(knl-quasi-walk-list (rest forms) dyn-env)))))))))
|
||||||
|
|
||||||
|
(define knl-list-concat
|
||||||
|
(fn (xs ys)
|
||||||
|
(cond
|
||||||
|
((or (nil? xs) (= (length xs) 0)) ys)
|
||||||
|
(:else (cons (first xs) (knl-list-concat (rest xs) ys))))))
|
||||||
|
|
||||||
|
(define kernel-quasiquote-operative
|
||||||
|
(kernel-make-primitive-operative
|
||||||
|
(fn (args dyn-env)
|
||||||
|
(cond
|
||||||
|
((not (= (length args) 1))
|
||||||
|
(error "$quasiquote: expects exactly 1 argument"))
|
||||||
|
(:else (knl-quasi-walk (first args) dyn-env))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
kernel-eval-applicative
|
kernel-eval-applicative
|
||||||
(kernel-make-primitive-applicative
|
(kernel-make-primitive-applicative
|
||||||
@@ -462,6 +513,7 @@
|
|||||||
(kernel-env-bind! env "$define!" kernel-define!-operative)
|
(kernel-env-bind! env "$define!" kernel-define!-operative)
|
||||||
(kernel-env-bind! env "$sequence" kernel-sequence-operative)
|
(kernel-env-bind! env "$sequence" kernel-sequence-operative)
|
||||||
(kernel-env-bind! env "$quote" kernel-quote-operative)
|
(kernel-env-bind! env "$quote" kernel-quote-operative)
|
||||||
|
(kernel-env-bind! env "$quasiquote" kernel-quasiquote-operative)
|
||||||
(kernel-env-bind! env "eval" kernel-eval-applicative)
|
(kernel-env-bind! env "eval" kernel-eval-applicative)
|
||||||
(kernel-env-bind!
|
(kernel-env-bind!
|
||||||
env
|
env
|
||||||
|
|||||||
@@ -254,4 +254,35 @@
|
|||||||
(ks-eval-in "z" env))
|
(ks-eval-in "z" env))
|
||||||
77)
|
77)
|
||||||
|
|
||||||
|
;; ── quasiquote ──────────────────────────────────────────────────
|
||||||
|
(ks-test "qq: plain atom" (ks-eval "`hello") "hello")
|
||||||
|
(ks-test "qq: plain list" (ks-eval "`(a b c)") (list "a" "b" "c"))
|
||||||
|
(ks-test "qq: unquote splices value"
|
||||||
|
(let ((env (kernel-standard-env)))
|
||||||
|
(ks-eval-in "($define! x 42)" env)
|
||||||
|
(ks-eval-in "`(a ,x b)" env)) (list "a" 42 "b"))
|
||||||
|
(ks-test "qq: unquote-splicing splices list"
|
||||||
|
(let ((env (kernel-standard-env)))
|
||||||
|
(ks-eval-in "($define! xs (list 1 2 3))" env)
|
||||||
|
(ks-eval-in "`(a ,@xs b)" env)) (list "a" 1 2 3 "b"))
|
||||||
|
(ks-test "qq: unquote-splicing at end"
|
||||||
|
(let ((env (kernel-standard-env)))
|
||||||
|
(ks-eval-in "($define! xs (list 9 8))" env)
|
||||||
|
(ks-eval-in "`(a b ,@xs)" env)) (list "a" "b" 9 8))
|
||||||
|
(ks-test "qq: unquote-splicing at start"
|
||||||
|
(let ((env (kernel-standard-env)))
|
||||||
|
(ks-eval-in "($define! xs (list 1 2))" env)
|
||||||
|
(ks-eval-in "`(,@xs c)" env)) (list 1 2 "c"))
|
||||||
|
(ks-test "qq: nested list with unquote inside"
|
||||||
|
(let ((env (kernel-standard-env)))
|
||||||
|
(ks-eval-in "($define! x 5)" env)
|
||||||
|
(ks-eval-in "`(a (b ,x) c)" env))
|
||||||
|
(list "a" (list "b" 5) "c"))
|
||||||
|
(ks-test "qq: error on bare unquote-splicing into non-list"
|
||||||
|
(let ((env (kernel-standard-env)))
|
||||||
|
(ks-eval-in "($define! x 42)" env)
|
||||||
|
(guard (e (true :raised))
|
||||||
|
(ks-eval-in "`(a ,@x b)" env)))
|
||||||
|
:raised)
|
||||||
|
|
||||||
(define ks-tests-run! (fn () {:total (+ ks-test-pass ks-test-fail) :passed ks-test-pass :failed ks-test-fail :fails ks-test-fails}))
|
(define ks-tests-run! (fn () {:total (+ ks-test-pass ks-test-fail) :passed ks-test-pass :failed ks-test-fail :fails ks-test-fails}))
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ When the second consumer arrives, the extraction work is: rename `kernel-*` →
|
|||||||
|
|
||||||
**May propose:** `lib/guest/reflective/` sub-layer — environment manipulation, evaluator-as-value, applicative/operative dispatch protocols.
|
**May propose:** `lib/guest/reflective/` sub-layer — environment manipulation, evaluator-as-value, applicative/operative dispatch protocols.
|
||||||
|
|
||||||
|
**Proposed `lib/guest/reflective/quoting.sx` API** (from quasiquote chiselling — pending second consumer):
|
||||||
|
- `(refl-quasi-walk FORM ENV)` — top-level entry. Recursively walks FORM; an `$unquote` sub-expression is evaluated in ENV and replaces itself in the result.
|
||||||
|
- `(refl-quasi-walk-list FORMS ENV)` — walks a list of forms, splicing `$unquote-splicing` results inline.
|
||||||
|
- `(refl-list-concat XS YS)` — pure-SX list concatenation (no host dependency on `append`).
|
||||||
|
- Driving insight: every reflective Lisp eventually adds quasiquote, and the recursion-with-splicing structure is identical across them. Nesting depth tracking (for `` ``e `` inside `` `e ``) is the only Kernel-specific complication; for the kit, a depth-tracking variant `refl-quasi-walk-depth FORM ENV DEPTH` would be the second-tier API.
|
||||||
|
|
||||||
**Proposed `lib/guest/reflective/hygiene.sx` API** (from Phase 6 chiselling — pending second consumer):
|
**Proposed `lib/guest/reflective/hygiene.sx` API** (from Phase 6 chiselling — pending second consumer):
|
||||||
- The substrate decision: a user-defined combiner's body runs in `(extend STATIC-ENV)`, NOT in the dyn-env. Any `$define!` inside the body binds in this fresh child, so callers' envs stay untouched. This is the cheap, lexical-scope hygiene story that R-1RK has had since the start.
|
- The substrate decision: a user-defined combiner's body runs in `(extend STATIC-ENV)`, NOT in the dyn-env. Any `$define!` inside the body binds in this fresh child, so callers' envs stay untouched. This is the cheap, lexical-scope hygiene story that R-1RK has had since the start.
|
||||||
- `(refl-let BINDINGS BODY)` — bind names in a fresh child of dyn-env, evaluate body there. Values evaluated in OUTER env (parallel semantics).
|
- `(refl-let BINDINGS BODY)` — bind names in a fresh child of dyn-env, evaluate body there. Values evaluated in OUTER env (parallel semantics).
|
||||||
@@ -148,6 +154,7 @@ The motivation is that SX's host `make-env` family is registered only in HTTP/si
|
|||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- 2026-05-11 — `$quasiquote` runtime. The parser's reader macros (Phase 1.5) produced unevaluated `$quasiquote`/`$unquote`/`$unquote-splicing` forms; the runtime side now interprets them. `kernel-quasiquote-operative` walks the template via mutual recursion `knl-quasi-walk` ↔ `knl-quasi-walk-list`: atoms and empty lists pass through; an `($unquote X)` head form returns `(kernel-eval X dyn-env)`; an `($unquote-splicing X)` *inside* a list evaluates X and splices its list result via `knl-list-concat`. Nesting depth (`` `\`...\` ``) is not tracked — for Phase-1.5 simplicity, nested quasiquotes flatten. 8 new tests in `tests/standard.sx`. chisel: shapes-reflective. The quoting walker shape is universal across reflective Lisps; sketched the `lib/guest/reflective/quoting.sx` candidate API (`refl-quasi-walk`, `refl-quasi-walk-list`, `refl-list-concat`). 230 tests total.
|
||||||
- 2026-05-11 — Multi-expression body for `$vau`/`$lambda`. Both forms now accept `(formals env-param body1 body2 ...)` / `(formals body1 body2 ...)`. Implementation: `:body` slot now holds a LIST of forms (was a single expression); `kernel-call-operative` calls a new `knl-eval-body` that evaluates each in sequence, returning the last. No dependency on `$sequence` being in static-env — the iteration lives at the host level. 5 new tests in `tests/vau.sx` (multi-body lambda, multi-body vau, sequenced `$define!`, zero-arg multi-body). chisel: nothing (Kernel-internal improvement; doesn't change the reflective API surface). 223 tests total.
|
- 2026-05-11 — Multi-expression body for `$vau`/`$lambda`. Both forms now accept `(formals env-param body1 body2 ...)` / `(formals body1 body2 ...)`. Implementation: `:body` slot now holds a LIST of forms (was a single expression); `kernel-call-operative` calls a new `knl-eval-body` that evaluates each in sequence, returning the last. No dependency on `$sequence` being in static-env — the iteration lives at the host level. 5 new tests in `tests/vau.sx` (multi-body lambda, multi-body vau, sequenced `$define!`, zero-arg multi-body). chisel: nothing (Kernel-internal improvement; doesn't change the reflective API surface). 223 tests total.
|
||||||
- 2026-05-11 — Phase 1 reader macros landed (the deferred checkbox from Phase 1). Parser now recognises four shorthand forms: `'expr` → `($quote expr)`, `` `expr `` → `($quasiquote expr)`, `,expr` → `($unquote expr)`, `,@expr` → `($unquote-splicing expr)`. Delimiter set extended to include `'`, `` ` ``, `,` so they don't slip into adjacent atom tokens. The runtime already has `$quote`; `$quasiquote` / `$unquote` / `$unquote-splicing` are not bound yet (would need a recursive walker for quasi-quote expansion — left for whenever a consumer needs it). 8 new reader-macro tests in `tests/parse.sx` bring parse to 62, total to 218. chisel: consumes-lex (parser still leans on `lib/guest/lex.sx` whitespace + digit predicates only).
|
- 2026-05-11 — Phase 1 reader macros landed (the deferred checkbox from Phase 1). Parser now recognises four shorthand forms: `'expr` → `($quote expr)`, `` `expr `` → `($quasiquote expr)`, `,expr` → `($unquote expr)`, `,@expr` → `($unquote-splicing expr)`. Delimiter set extended to include `'`, `` ` ``, `,` so they don't slip into adjacent atom tokens. The runtime already has `$quote`; `$quasiquote` / `$unquote` / `$unquote-splicing` are not bound yet (would need a recursive walker for quasi-quote expansion — left for whenever a consumer needs it). 8 new reader-macro tests in `tests/parse.sx` bring parse to 62, total to 218. chisel: consumes-lex (parser still leans on `lib/guest/lex.sx` whitespace + digit predicates only).
|
||||||
- 2026-05-11 — Phase 7 proposal complete (partial extraction per two-consumer rule). Consolidated the four candidate reflective files into the plan's API surface section: `env.sx` (Phase 2), `combiner.sx` (Phase 3), `evaluator.sx` (Phase 4), `hygiene.sx` (Phase 6). Total proposed surface ~25 functions, all sketched with signatures and representation notes. Kernel alone is the first consumer; the *second* consumer must materialise before any actual extraction. Listed candidate second consumers in priority order: metacircular Scheme (highest fit — same scope semantics), CL macro evaluator (medium fit — would drive the deferred hygiene work), Maru/Schemely (eventual). Extraction is estimated at <500 lines moved when the time comes — clean separation of concerns across this loop's six prior commits means the rename-and-move work is mechanical, not a redesign. chisel: proposes-reflective-extraction (the candidate API surface is the entire artefact of this phase). 210 tests across six test files, zero regressions across the loop. The kernel-on-sx loop sustained one feature per commit for seven commits.
|
- 2026-05-11 — Phase 7 proposal complete (partial extraction per two-consumer rule). Consolidated the four candidate reflective files into the plan's API surface section: `env.sx` (Phase 2), `combiner.sx` (Phase 3), `evaluator.sx` (Phase 4), `hygiene.sx` (Phase 6). Total proposed surface ~25 functions, all sketched with signatures and representation notes. Kernel alone is the first consumer; the *second* consumer must materialise before any actual extraction. Listed candidate second consumers in priority order: metacircular Scheme (highest fit — same scope semantics), CL macro evaluator (medium fit — would drive the deferred hygiene work), Maru/Schemely (eventual). Extraction is estimated at <500 lines moved when the time comes — clean separation of concerns across this loop's six prior commits means the rename-and-move work is mechanical, not a redesign. chisel: proposes-reflective-extraction (the candidate API surface is the entire artefact of this phase). 210 tests across six test files, zero regressions across the loop. The kernel-on-sx loop sustained one feature per commit for seven commits.
|
||||||
|
|||||||
Reference in New Issue
Block a user