Decouple core evaluator from web platform, extract libraries

The core evaluator (spec/evaluator.sx) is now the irreducible computational
core with zero web, rendering, or type-system knowledge. 2531 → 2313 lines.

- Add extensible special form registry (*custom-special-forms* + register-special-form!)
- Add render dispatch hooks (*render-check* / *render-fn*) replacing hardcoded render-active?/is-render-expr?/render-expr
- Extract freeze scopes → spec/freeze.sx (library, not core)
- Extract content addressing → spec/content.sx (library, not core)
- Move sf-deftype/sf-defeffect → spec/types.sx (self-registering)
- Move sf-defstyle → web/forms.sx (self-registering with all web forms)
- Move web tests (defpage, streaming) → web/tests/test-forms.sx
- Add is-else-clause? helper (replaces 5 inline patterns)
- Make escape-html/escape-attr library functions in render.sx (pure SX, not platform-provided)
- Add foundations plan: Step 3.5 (data representations), Step 3.7 (verified components), OCaml for Step 4d
- Update all three bootstrappers (JS 957/957, Python 744/744, OCaml 952/952)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 08:37:50 +00:00
parent 5ab3ecb7e0
commit 06666ac8c4
21 changed files with 886 additions and 603 deletions

View File

@@ -123,9 +123,11 @@
"provide-push!" "provide_push"
"provide-pop!" "provide_pop"
"sx-serialize" "sx_serialize"
"render-active?" "render_active_p"
"is-render-expr?" "is_render_expr"
"render-expr" "render_expr"
"*custom-special-forms*" "custom_special_forms"
"register-special-form!" "register_special_form"
"*render-check*" "render_check"
"*render-fn*" "render_fn"
"is-else-clause?" "is_else_clause"
"HTML_TAGS" "html_tags"
"VOID_ELEMENTS" "void_elements"
"BOOLEAN_ATTRS" "boolean_attrs"
@@ -192,15 +194,12 @@
"cek-call" "cek-run" "sx-call" "sx-apply"
"collect!" "collected" "clear-collected!" "context" "emit!" "emitted"
"scope-push!" "scope-pop!" "provide-push!" "provide-pop!"
"render-active?" "render-expr" "is-render-expr?"
"with-island-scope" "register-in-scope"
"signal-value" "signal-set-value" "signal-subscribers"
"signal-add-sub!" "signal-remove-sub!" "signal-deps" "signal-set-deps"
"notify-subscribers" "flush-subscribers" "dispose-computed"
"continuation?" "continuation-data" "make-cek-continuation"
"dynamic-wind-call" "strip-prefix"
"sf-defhandler" "sf-defpage" "sf-defquery" "sf-defaction"
"make-handler-def" "make-query-def" "make-action-def" "make-page-def"
"component-set-param-types!" "parse-comp-params" "parse-macro-params"
"parse-keyword-args"))
@@ -215,6 +214,15 @@
;; Check _known_defines (set by bootstrap.py)
(some (fn (d) (= d name)) _known_defines)))))
;; Dynamic globals — top-level defines that hold SX values (not functions).
;; When these appear as callees, use cek_call for dynamic dispatch.
(define ml-dynamic-globals
(list "*render-check*" "*render-fn*"))
(define ml-is-dyn-global?
(fn ((name :as string))
(some (fn (g) (= g name)) ml-dynamic-globals)))
;; Check if a variable is "dynamic" — locally bound to a non-function expression.
;; These variables hold SX values (from eval-expr, get, etc.) and need cek_call
;; when used as callees. We encode this in the set-vars list as "dyn:name".
@@ -421,8 +429,12 @@
(let ((head (first expr))
(args (rest expr)))
(if (not (= (type-of head) "symbol"))
;; Data list
(str "[" (join "; " (map (fn (x) (ml-expr-inner x set-vars)) expr)) "]")
;; Non-symbol head: if head is a list (call expr), dispatch via cek_call;
;; otherwise treat as data list
(if (list? head)
(str "(cek_call (" (ml-expr-inner head set-vars)
") (List [" (join "; " (map (fn (x) (ml-expr-inner x set-vars)) args)) "]))")
(str "[" (join "; " (map (fn (x) (ml-expr-inner x set-vars)) expr)) "]"))
(let ((op (symbol-name head)))
(cond
;; fn/lambda
@@ -607,8 +619,8 @@
;; Regular function call
:else
(let ((callee (ml-mangle op)))
(if (ml-is-dyn-var? op set-vars)
;; Dynamic callee (local var bound to non-fn expr) — dispatch via cek_call
(if (or (ml-is-dyn-var? op set-vars) (ml-is-dyn-global? op))
;; Dynamic callee (local var or dynamic global) — dispatch via cek_call
(str "(cek_call (" callee ") (List [" (join "; " (map (fn (x) (ml-expr-inner x set-vars)) args)) "]))")
;; Static callee — direct OCaml call
(if (empty? args)