Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 56s
Phase 1 of the lib-guest-reflective extraction plan.
lib/guest/reflective/env.sx — canonical wire shape
{:refl-tag :env :bindings DICT :parent ENV-OR-NIL} with mutable
defaults (dict-set!), plus *-with adapter-cfg variants for consumers
with their own shape (modelled after lib/guest/match.sx). 13 forms,
~5 KB.
lib/kernel/eval.sx — env block collapses from ~30 lines to 6 thin
wrappers (kernel-env? = refl-env?, etc.). No semantic change; envs
now carry :refl-tag :env instead of :knl-tag :env. All 322 Kernel
tests pass unchanged across 7 suites (parse 62, eval 36, vau 38,
standard 127, encap 19, hygiene 26, metacircular 14).
Next: Phase 2 — Tcl adapter cfg in lib/tcl/runtime.sx using
refl-env-lookup-with against the existing :level/:locals/:parent
frame shape.
215 lines
7.2 KiB
Plaintext
215 lines
7.2 KiB
Plaintext
;; lib/kernel/eval.sx — Kernel evaluator.
|
|
;;
|
|
;; The evaluator is `lookup-and-combine`: there are no hardcoded special
|
|
;; 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
|
|
;; -------------
|
|
;; {:refl-tag :env :bindings DICT :parent PARENT-OR-NIL}
|
|
;; A first-class Kernel environment. Bindings is a mutable SX dict
|
|
;; keyed by symbol name; parent walks up the lookup chain. Shape
|
|
;; and operations are inherited from lib/guest/reflective/env.sx
|
|
;; (canonical wire shape) — Kernel-side names are thin wrappers.
|
|
;;
|
|
;; {:knl-tag :operative :impl FN}
|
|
;; Primitive operative. FN receives (args dyn-env) — args are the
|
|
;; UN-evaluated argument expressions, dyn-env is the calling env.
|
|
;;
|
|
;; {: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.
|
|
;;
|
|
;; {: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)
|
|
;; (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)
|
|
;; (kernel-make-user-operative PARAMS EPARAM BODY STATIC-ENV)
|
|
;; (kernel-wrap OP) / (kernel-unwrap APP)
|
|
;; (kernel-operative? V) / (kernel-applicative? V) / (kernel-combiner? V)
|
|
;;
|
|
;; Consumes: lib/kernel/parser.sx (kernel-string?, kernel-string-value)
|
|
|
|
;; ── Environments — delegated to lib/guest/reflective/env.sx ──────
|
|
;; The env values themselves now carry `:refl-tag :env` (shared with the
|
|
;; reflective kit). Kernel's API names stay; bodies are thin wrappers.
|
|
|
|
(define kernel-env? refl-env?)
|
|
(define kernel-make-env refl-make-env)
|
|
(define kernel-extend-env refl-extend-env)
|
|
(define kernel-env-bind! refl-env-bind!)
|
|
(define kernel-env-has? refl-env-has?)
|
|
(define kernel-env-lookup refl-env-lookup)
|
|
|
|
;; ── Tagged-value constructors and predicates ─────────────────────
|
|
|
|
(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))))
|
|
|
|
(define
|
|
kernel-applicative?
|
|
(fn (v) (and (dict? v) (= (get v :knl-tag) :applicative))))
|
|
|
|
(define
|
|
kernel-combiner?
|
|
(fn (v) (or (kernel-operative? v) (kernel-applicative? v))))
|
|
|
|
(define
|
|
kernel-wrap
|
|
(fn
|
|
(op)
|
|
(cond
|
|
((kernel-operative? op) {:knl-tag :applicative :underlying op})
|
|
(:else (error "kernel-wrap: argument must be an operative")))))
|
|
|
|
(define
|
|
kernel-unwrap
|
|
(fn
|
|
(app)
|
|
(cond
|
|
((kernel-applicative? app) (get app :underlying))
|
|
(:else (error "kernel-unwrap: argument must be an applicative")))))
|
|
|
|
(define
|
|
kernel-make-primitive-applicative
|
|
(fn
|
|
(impl)
|
|
(kernel-wrap
|
|
(kernel-make-primitive-operative (fn (args dyn-env) (impl args))))))
|
|
|
|
;; As above, but IMPL receives (args dyn-env). Used by combinators that
|
|
;; re-enter the evaluator (map, filter, reduce, apply, eval, ...).
|
|
(define kernel-make-primitive-applicative-with-env
|
|
(fn (impl)
|
|
(kernel-wrap
|
|
(kernel-make-primitive-operative
|
|
(fn (args dyn-env) (impl args dyn-env))))))
|
|
|
|
;; ── The evaluator ────────────────────────────────────────────────
|
|
|
|
(define
|
|
kernel-eval
|
|
(fn
|
|
(expr env)
|
|
(cond
|
|
((number? expr) expr)
|
|
((boolean? expr) expr)
|
|
((nil? expr) expr)
|
|
((kernel-string? expr) (kernel-string-value expr))
|
|
((string? expr) (kernel-env-lookup env expr))
|
|
((list? expr)
|
|
(cond
|
|
((= (length expr) 0) expr)
|
|
(:else
|
|
(let
|
|
((combiner (kernel-eval (first expr) env))
|
|
(args (rest expr)))
|
|
(kernel-combine combiner args env)))))
|
|
(:else (error (str "kernel-eval: unknown form: " expr))))))
|
|
|
|
(define
|
|
kernel-combine
|
|
(fn
|
|
(combiner args dyn-env)
|
|
(cond
|
|
((kernel-operative? combiner)
|
|
(kernel-call-operative combiner args dyn-env))
|
|
((kernel-applicative? combiner)
|
|
(kernel-combine
|
|
(get combiner :underlying)
|
|
(kernel-eval-args args dyn-env)
|
|
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)))
|
|
;; :body is a list of forms — evaluate in sequence, return last.
|
|
(knl-eval-body (get op :body) local)))
|
|
(:else (error "kernel-call-operative: malformed operative")))))
|
|
|
|
(define knl-eval-body
|
|
(fn (forms env)
|
|
(cond
|
|
((= (length forms) 1) (kernel-eval (first forms) env))
|
|
(:else
|
|
(begin
|
|
(kernel-eval (first forms) env)
|
|
(knl-eval-body (rest forms) env))))))
|
|
|
|
;; 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
|
|
(args env)
|
|
(cond
|
|
((or (nil? args) (= (length args) 0)) (list))
|
|
(:else
|
|
(cons
|
|
(kernel-eval (first args) env)
|
|
(kernel-eval-args (rest args) env))))))
|
|
|
|
(define
|
|
kernel-eval-program
|
|
(fn
|
|
(forms env)
|
|
(cond
|
|
((or (nil? forms) (= (length forms) 0)) nil)
|
|
((= (length forms) 1) (kernel-eval (first forms) env))
|
|
(:else
|
|
(begin
|
|
(kernel-eval (first forms) env)
|
|
(kernel-eval-program (rest forms) env))))))
|