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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user