Bootstrap CEK as default evaluator on both JS and Python sides

SPEC_MODULES + SPEC_MODULE_ORDER for frames/cek in platform_js.py,
PLATFORM_CEK_JS + CEK_FIXUPS_JS constants, auto-inclusion in
run_js_sx.py, 70+ RENAMES in js.sx. Python: CEK always-include in
bootstrap_py.py, eval_expr/trampoline overridden to cek_run in
platform_py.py with _tree_walk_* preserved for test runners.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 01:13:11 +00:00
parent f3a9f3ccc0
commit 90febbd91e
7 changed files with 2005 additions and 110 deletions

View File

@@ -1122,6 +1122,7 @@ try:
from .platform_py import (
PREAMBLE, PLATFORM_PY, PRIMITIVES_PY_PRE, PRIMITIVES_PY_POST,
PRIMITIVES_PY_MODULES, _ALL_PY_MODULES,
PLATFORM_PARSER_PY,
PLATFORM_DEPS_PY, PLATFORM_CEK_PY, CEK_FIXUPS_PY, PLATFORM_ASYNC_PY,
FIXUPS_PY, CONTINUATIONS_PY,
_assemble_primitives_py, public_api_py,
@@ -1132,6 +1133,7 @@ except ImportError:
from shared.sx.ref.platform_py import (
PREAMBLE, PLATFORM_PY, PRIMITIVES_PY_PRE, PRIMITIVES_PY_POST,
PRIMITIVES_PY_MODULES, _ALL_PY_MODULES,
PLATFORM_PARSER_PY,
PLATFORM_DEPS_PY, PLATFORM_CEK_PY, CEK_FIXUPS_PY, PLATFORM_ASYNC_PY,
FIXUPS_PY, CONTINUATIONS_PY,
_assemble_primitives_py, public_api_py,
@@ -1224,7 +1226,7 @@ def compile_ref_to_py(
Args:
adapters: List of adapter names to include.
Valid names: html, sx.
Valid names: parser, html, sx.
None = include all server-side adapters.
modules: List of primitive module names to include.
core.* are always included. stdlib.* are opt-in.
@@ -1277,9 +1279,9 @@ def compile_ref_to_py(
spec_mod_set.add("page-helpers")
if "router" in SPEC_MODULES:
spec_mod_set.add("router")
# cek module requires frames
if "cek" in spec_mod_set:
spec_mod_set.add("frames")
# CEK is the canonical evaluator — always include
spec_mod_set.add("cek")
spec_mod_set.add("frames")
has_deps = "deps" in spec_mod_set
has_cek = "cek" in spec_mod_set
@@ -1289,6 +1291,9 @@ def compile_ref_to_py(
("forms.sx", "forms (server definition forms)"),
("render.sx", "render (core)"),
]
# Parser before html/sx — provides serialize used by adapters
if "parser" in adapter_set:
sx_files.append(ADAPTER_FILES["parser"])
for name in ("html", "sx"):
if name in adapter_set:
sx_files.append(ADAPTER_FILES[name])
@@ -1348,6 +1353,7 @@ def compile_ref_to_py(
# Build output
has_html = "html" in adapter_set
has_sx = "sx" in adapter_set
has_parser = "parser" in adapter_set
parts = []
parts.append(PREAMBLE)
@@ -1356,6 +1362,9 @@ def compile_ref_to_py(
parts.append(_assemble_primitives_py(prim_modules))
parts.append(PRIMITIVES_PY_POST)
if has_parser:
parts.append(PLATFORM_PARSER_PY)
if has_deps:
parts.append(PLATFORM_DEPS_PY)

View File

@@ -214,6 +214,10 @@
"render-dom-island" "renderDomIsland"
"reactive-text" "reactiveText"
"reactive-attr" "reactiveAttr"
"cek-reactive-text" "cekReactiveText"
"cek-reactive-attr" "cekReactiveAttr"
"*use-cek-reactive*" "_useCekReactive"
"enable-cek-reactive!" "enableCekReactive"
"reactive-fragment" "reactiveFragment"
"reactive-list" "reactiveList"
"dom-create-element" "domCreateElement"
@@ -520,6 +524,80 @@
"collect!" "sxCollect"
"collected" "sxCollected"
"clear-collected!" "sxClearCollected"
"make-cek-continuation" "makeCekContinuation"
"continuation-data" "continuationData"
"make-cek-state" "makeCekState"
"make-cek-value" "makeCekValue"
"cek-terminal?" "cekTerminal_p"
"cek-run" "cekRun"
"cek-step" "cekStep"
"cek-control" "cekControl"
"cek-env" "cekEnv"
"cek-kont" "cekKont"
"cek-phase" "cekPhase"
"cek-value" "cekValue"
"kont-push" "kontPush"
"kont-top" "kontTop"
"kont-pop" "kontPop"
"kont-empty?" "kontEmpty_p"
"kont-capture-to-reset" "kontCaptureToReset"
"kont-capture-to-reactive-reset" "kontCaptureToReactiveReset"
"has-reactive-reset-frame?" "hasReactiveResetFrame_p"
"frame-type" "frameType"
"make-if-frame" "makeIfFrame"
"make-when-frame" "makeWhenFrame"
"make-begin-frame" "makeBeginFrame"
"make-let-frame" "makeLetFrame"
"make-define-frame" "makeDefineFrame"
"make-set-frame" "makeSetFrame"
"make-arg-frame" "makeArgFrame"
"make-call-frame" "makeCallFrame"
"make-cond-frame" "makeCondFrame"
"make-case-frame" "makeCaseFrame"
"make-thread-frame" "makeThreadFrame"
"make-map-frame" "makeMapFrame"
"make-filter-frame" "makeFilterFrame"
"make-reduce-frame" "makeReduceFrame"
"make-for-each-frame" "makeForEachFrame"
"make-scope-frame" "makeScopeFrame"
"make-reset-frame" "makeResetFrame"
"make-dict-frame" "makeDictFrame"
"make-and-frame" "makeAndFrame"
"make-or-frame" "makeOrFrame"
"make-dynamic-wind-frame" "makeDynamicWindFrame"
"make-reactive-reset-frame" "makeReactiveResetFrame"
"make-deref-frame" "makeDerefFrame"
"step-eval" "stepEval"
"step-continue" "stepContinue"
"step-eval-list" "stepEvalList"
"step-eval-call" "stepEvalCall"
"step-sf-if" "stepSfIf"
"step-sf-when" "stepSfWhen"
"step-sf-begin" "stepSfBegin"
"step-sf-let" "stepSfLet"
"step-sf-define" "stepSfDefine"
"step-sf-set!" "stepSfSet"
"step-sf-and" "stepSfAnd"
"step-sf-or" "stepSfOr"
"step-sf-cond" "stepSfCond"
"step-sf-case" "stepSfCase"
"step-sf-thread-first" "stepSfThreadFirst"
"step-sf-lambda" "stepSfLambda"
"step-sf-scope" "stepSfScope"
"step-sf-provide" "stepSfProvide"
"step-sf-reset" "stepSfReset"
"step-sf-shift" "stepSfShift"
"step-sf-deref" "stepSfDeref"
"step-ho-map" "stepHoMap"
"step-ho-filter" "stepHoFilter"
"step-ho-reduce" "stepHoReduce"
"step-ho-for-each" "stepHoForEach"
"continue-with-call" "continueWithCall"
"sf-case-step-loop" "sfCaseStepLoop"
"eval-expr-cek" "evalExprCek"
"trampoline-cek" "trampolineCek"
"reactive-shift-deref" "reactiveShiftDeref"
"cond-scheme?" "condScheme_p"
"scope-push!" "scopePush"
"scope-pop!" "scopePop"
"provide-push!" "providePush"

View File

@@ -46,8 +46,14 @@ SPEC_MODULES = {
"router": ("router.sx", "router (client-side route matching)"),
"signals": ("signals.sx", "signals (reactive signal runtime)"),
"page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"),
"frames": ("frames.sx", "frames (CEK continuation frames)"),
"cek": ("cek.sx", "cek (explicit CEK machine evaluator)"),
}
# Explicit ordering for spec modules with dependencies.
# Modules listed here are emitted in this order; any not listed use alphabetical.
SPEC_MODULE_ORDER = ["deps", "frames", "page-helpers", "router", "signals", "cek"]
EXTENSION_NAMES = {"continuations"}
CONTINUATIONS_JS = '''
@@ -1476,6 +1482,38 @@ PLATFORM_JS_POST = '''
};'''
PLATFORM_CEK_JS = '''
// =========================================================================
// Platform: CEK module — explicit CEK machine
// =========================================================================
// Standalone aliases for primitives used by cek.sx / frames.sx
var inc = PRIMITIVES["inc"];
var dec = PRIMITIVES["dec"];
var zip_pairs = PRIMITIVES["zip-pairs"];
var continuation_p = PRIMITIVES["continuation?"];
function makeCekContinuation(captured, restKont) {
var c = new Continuation(function(v) { return v !== undefined ? v : NIL; });
c._cek_data = {"captured": captured, "rest-kont": restKont};
return c;
}
function continuationData(c) {
return (c && c._cek_data) ? c._cek_data : {};
}
'''
# Iterative override for cek_run — replaces transpiled recursive version
CEK_FIXUPS_JS = '''
// Override recursive cekRun with iterative loop (avoids stack overflow)
cekRun = function(state) {
while (!cekTerminal_p(state)) { state = cekStep(state); }
return cekValue(state);
};
'''
PLATFORM_DEPS_JS = '''
// =========================================================================
// Platform: deps module — component dependency analysis

View File

@@ -659,51 +659,6 @@ def escape_string(s):
.replace("</script", "<\\\\/script"))
def serialize(val):
"""Serialize an SX value to SX source text.
Note: parser.sx defines sx-serialize with a serialize alias, but parser.sx
is only included in JS builds (for client-side parsing). Python builds
provide this as a platform function.
"""
t = type_of(val)
if t == "sx-expr":
return val.source
if t == "nil":
return "nil"
if t == "boolean":
return "true" if val else "false"
if t == "number":
return str(val)
if t == "string":
return '"' + escape_string(val) + '"'
if t == "symbol":
return symbol_name(val)
if t == "keyword":
return ":" + keyword_name(val)
if t == "raw-html":
escaped = escape_string(raw_html_content(val))
return '(raw! "' + escaped + '")'
if t == "list":
if not val:
return "()"
items = [serialize(x) for x in val]
return "(" + " ".join(items) + ")"
if t == "dict":
items = []
for k, v in val.items():
items.append(":" + str(k))
items.append(serialize(v))
return "{" + " ".join(items) + "}"
if callable(val):
return "nil"
return str(val)
# Aliases for transpiled code — parser.sx defines sx-serialize/sx-serialize-dict
# but parser.sx is JS-only. Provide aliases so transpiled render.sx works.
sx_serialize = serialize
sx_serialize_dict = lambda d: serialize(d)
_SPECIAL_FORM_NAMES = frozenset() # Placeholder — overridden by transpiled adapter-sx.sx
_HO_FORM_NAMES = frozenset()
@@ -1066,6 +1021,55 @@ has_key_p = PRIMITIVES["has-key?"]
dict_p = PRIMITIVES["dict?"]
dissoc = PRIMITIVES["dissoc"]
index_of = PRIMITIVES["index-of"]
lower = PRIMITIVES["lower"]
char_from_code = PRIMITIVES["char-from-code"]
'''
# ---------------------------------------------------------------------------
# Platform: parser module — character classification, number parsing,
# reader macro registry
# ---------------------------------------------------------------------------
PLATFORM_PARSER_PY = '''
# =========================================================================
# Platform interface — Parser
# =========================================================================
import re as _re_parser
_IDENT_START_RE = _re_parser.compile(r"[a-zA-Z_~*+\\-><=/!?&]")
_IDENT_CHAR_RE = _re_parser.compile(r"[a-zA-Z0-9_~*+\\-><=/!?.:&/\\[\\]#,]")
def ident_start_p(ch):
return bool(_IDENT_START_RE.match(ch))
def ident_char_p(ch):
return bool(_IDENT_CHAR_RE.match(ch))
def parse_number(s):
"""Parse a numeric string to int or float."""
try:
if "." in s or "e" in s or "E" in s:
return float(s)
return int(s)
except (ValueError, TypeError):
return float(s)
# Reader macro registry
_reader_macros = {}
def reader_macro_get(name):
return _reader_macros.get(name, NIL)
def reader_macro_set_b(name, handler):
_reader_macros[name] = handler
return NIL
'''
# ---------------------------------------------------------------------------
@@ -1159,6 +1163,23 @@ def cek_run(state):
while not cek_terminal_p(state):
state = cek_step(state)
return cek_value(state)
# CEK is the canonical evaluator — override eval_expr to use it.
# The tree-walk evaluator (eval_expr from eval.sx) is superseded.
_tree_walk_eval_expr = eval_expr
def eval_expr(expr, env):
"""Evaluate expr using the CEK machine."""
return cek_run(make_cek_state(expr, env, []))
# CEK never produces thunks — trampoline becomes identity
_tree_walk_trampoline = trampoline
def trampoline(val):
"""In CEK mode, values are immediate — resolve any legacy thunks."""
if is_thunk(val):
return eval_expr(thunk_expr(val), thunk_env(val))
return val
'''
# ---------------------------------------------------------------------------
@@ -1258,11 +1279,6 @@ def number_p(x):
return isinstance(x, (int, float)) and not isinstance(x, bool)
def sx_parse(src):
from shared.sx.parser import parse_all
return parse_all(src)
def is_async_coroutine(x):
return _inspect.iscoroutine(x)
@@ -1590,9 +1606,10 @@ def public_api_py(has_html: bool, has_sx: bool, has_deps: bool = False,
# ---------------------------------------------------------------------------
ADAPTER_FILES = {
"html": ("adapter-html.sx", "adapter-html"),
"sx": ("adapter-sx.sx", "adapter-sx"),
"async": ("adapter-async.sx", "adapter-async"),
"parser": ("parser.sx", "parser"),
"html": ("adapter-html.sx", "adapter-html"),
"sx": ("adapter-sx.sx", "adapter-sx"),
"async": ("adapter-async.sx", "adapter-async"),
}
SPEC_MODULES = {

View File

@@ -24,11 +24,12 @@ from shared.sx.parser import parse_all
from shared.sx.types import Symbol
from shared.sx.ref.platform_js import (
extract_defines,
ADAPTER_FILES, ADAPTER_DEPS, SPEC_MODULES, EXTENSION_NAMES,
ADAPTER_FILES, ADAPTER_DEPS, SPEC_MODULES, SPEC_MODULE_ORDER, EXTENSION_NAMES,
PREAMBLE, PLATFORM_JS_PRE, PLATFORM_JS_POST,
PRIMITIVES_JS_MODULES, _ALL_JS_MODULES, _assemble_primitives_js,
PLATFORM_DEPS_JS, PLATFORM_PARSER_JS, PLATFORM_DOM_JS,
PLATFORM_ENGINE_PURE_JS, PLATFORM_ORCHESTRATION_JS, PLATFORM_BOOT_JS,
PLATFORM_CEK_JS, CEK_FIXUPS_JS,
CONTINUATIONS_JS, ASYNC_IO_JS,
fixups_js, public_api_js, EPILOGUE,
)
@@ -105,9 +106,17 @@ def compile_ref_to_js(
spec_mod_set.add("deps")
if "page-helpers" in SPEC_MODULES:
spec_mod_set.add("page-helpers")
# CEK needed for reactive rendering (deref-as-shift)
if "dom" in adapter_set:
spec_mod_set.add("cek")
spec_mod_set.add("frames")
# cek module requires frames
if "cek" in spec_mod_set:
spec_mod_set.add("frames")
has_deps = "deps" in spec_mod_set
has_router = "router" in spec_mod_set
has_page_helpers = "page-helpers" in spec_mod_set
has_cek = "cek" in spec_mod_set
# Resolve extensions
ext_set = set()
@@ -126,8 +135,14 @@ def compile_ref_to_js(
for name in ("parser", "html", "sx", "dom", "engine", "orchestration", "boot"):
if name in adapter_set:
sx_files.append(ADAPTER_FILES[name])
# Use explicit ordering for spec modules (respects dependencies)
for name in SPEC_MODULE_ORDER:
if name in spec_mod_set:
sx_files.append(SPEC_MODULES[name])
# Any spec modules not in the order list (future-proofing)
for name in sorted(spec_mod_set):
sx_files.append(SPEC_MODULES[name])
if name not in SPEC_MODULE_ORDER:
sx_files.append(SPEC_MODULES[name])
has_html = "html" in adapter_set
has_sx = "sx" in adapter_set
@@ -201,7 +216,12 @@ def compile_ref_to_js(
if name in adapter_set and name in adapter_platform:
parts.append(adapter_platform[name])
if has_cek:
parts.append(PLATFORM_CEK_JS)
parts.append(fixups_js(has_html, has_sx, has_dom, has_signals, has_deps, has_page_helpers))
if has_cek:
parts.append(CEK_FIXUPS_JS)
if has_continuations:
parts.append(CONTINUATIONS_JS)
if has_dom:

File diff suppressed because it is too large Load Diff