Files
rose-ash/shared/sx/ref/run_js_sx.py
giles dcf620f223
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 33s
Add live CEK stepper island — interactive stepping debugger
A defisland that lets users type an SX expression, step through CEK
evaluation one transition at a time, and see C/E/K registers update
live. Demonstrates that cek-step is pure data->data.

- cek.sx geography: add ~geography/cek/demo-stepper island with
  source input, step/run/reset buttons, state display, step history
- platform_js.py: register CEK stepping primitives (make-cek-state,
  cek-step, cek-terminal?, cek-value, make-env, sx-serialize) so
  island code can access them

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 15:59:59 +00:00

268 lines
9.8 KiB
Python

#!/usr/bin/env python3
"""
Bootstrap compiler: js.sx (self-hosting SX-to-JS translator) → sx-browser.js.
This is the canonical JS bootstrapper. js.sx is loaded into the Python evaluator,
which uses it to translate the .sx spec files into JavaScript. Platform code
(types, primitives, DOM interface) comes from platform_js.py.
Usage:
python run_js_sx.py # stdout
python run_js_sx.py -o shared/static/scripts/sx-browser.js # file
"""
from __future__ import annotations
import os
import sys
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
if _PROJECT not in sys.path:
sys.path.insert(0, _PROJECT)
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, 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,
)
_js_sx_env = None # cached
def load_js_sx() -> dict:
"""Load js.sx into an evaluator environment and return it."""
global _js_sx_env
if _js_sx_env is not None:
return _js_sx_env
js_sx_path = os.path.join(_HERE, "js.sx")
with open(js_sx_path) as f:
source = f.read()
exprs = parse_all(source)
from shared.sx.ref.sx_ref import evaluate, make_env
env = make_env()
for expr in exprs:
evaluate(expr, env)
_js_sx_env = env
return env
def compile_ref_to_js(
adapters: list[str] | None = None,
modules: list[str] | None = None,
extensions: list[str] | None = None,
spec_modules: list[str] | None = None,
) -> str:
"""Compile SX spec files to JavaScript using js.sx.
Args:
adapters: List of adapter names to include. None = all.
modules: List of primitive module names. None = all.
extensions: List of extensions (continuations). None = none.
spec_modules: List of spec modules (deps, router, signals). None = auto.
"""
from datetime import datetime, timezone
from shared.sx.ref.sx_ref import evaluate
ref_dir = _HERE
env = load_js_sx()
# Resolve adapter set
if adapters is None:
adapter_set = set(ADAPTER_FILES.keys())
else:
adapter_set = set()
for a in adapters:
if a not in ADAPTER_FILES:
raise ValueError(f"Unknown adapter: {a!r}. Valid: {', '.join(ADAPTER_FILES)}")
adapter_set.add(a)
for dep in ADAPTER_DEPS.get(a, []):
adapter_set.add(dep)
# Resolve spec modules
spec_mod_set = set()
if spec_modules:
for sm in spec_modules:
if sm not in SPEC_MODULES:
raise ValueError(f"Unknown spec module: {sm!r}. Valid: {', '.join(SPEC_MODULES)}")
spec_mod_set.add(sm)
if "dom" in adapter_set and "signals" in SPEC_MODULES:
spec_mod_set.add("signals")
if "boot" in adapter_set:
spec_mod_set.add("router")
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()
if extensions:
for e in extensions:
if e not in EXTENSION_NAMES:
raise ValueError(f"Unknown extension: {e!r}. Valid: {', '.join(EXTENSION_NAMES)}")
ext_set.add(e)
has_continuations = "continuations" in ext_set
# Build file list: core + adapters + spec modules
sx_files = [
("eval.sx", "eval"),
("render.sx", "render (core)"),
]
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):
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
has_dom = "dom" in adapter_set
has_engine = "engine" in adapter_set
has_orch = "orchestration" in adapter_set
has_boot = "boot" in adapter_set
has_parser = "parser" in adapter_set
has_signals = "signals" in spec_mod_set
adapter_label = "+".join(sorted(adapter_set)) if adapter_set else "core-only"
# Platform JS blocks keyed by adapter name
adapter_platform = {
"parser": PLATFORM_PARSER_JS,
"dom": PLATFORM_DOM_JS,
"engine": PLATFORM_ENGINE_PURE_JS,
"orchestration": PLATFORM_ORCHESTRATION_JS,
"boot": PLATFORM_BOOT_JS,
}
# Determine primitive modules
prim_modules = None
if modules is not None:
prim_modules = [m for m in _ALL_JS_MODULES if m.startswith("core.")]
for m in modules:
if m not in prim_modules:
if m not in PRIMITIVES_JS_MODULES:
raise ValueError(f"Unknown module: {m!r}. Valid: {', '.join(PRIMITIVES_JS_MODULES)}")
prim_modules.append(m)
# Build output
parts = []
parts.append(PREAMBLE)
parts.append(PLATFORM_JS_PRE)
parts.append('\n // =========================================================================')
parts.append(' // Primitives')
parts.append(' // =========================================================================\n')
parts.append(' var PRIMITIVES = {};')
parts.append(_assemble_primitives_js(prim_modules))
parts.append(PLATFORM_JS_POST)
if has_deps:
parts.append(PLATFORM_DEPS_JS)
if has_parser:
parts.append(adapter_platform["parser"])
# CEK platform aliases must come before transpiled cek.sx (which uses them)
if has_cek:
parts.append(PLATFORM_CEK_JS)
# Translate each spec file using js.sx
for filename, label in sx_files:
filepath = os.path.join(ref_dir, filename)
if not os.path.exists(filepath):
continue
with open(filepath) as f:
src = f.read()
defines = extract_defines(src)
sx_defines = [[name, expr] for name, expr in defines]
parts.append(f"\n // === Transpiled from {label} ===\n")
env["_defines"] = sx_defines
result = evaluate(
[Symbol("js-translate-file"), Symbol("_defines")],
env,
)
parts.append(result)
# Platform JS for selected adapters
if not has_dom:
parts.append("\n var _hasDom = false;\n")
# CEK fixups + general fixups BEFORE boot (boot hydrates islands that need these)
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)
for name in ("dom", "engine", "orchestration", "boot"):
if name in adapter_set and name in adapter_platform:
parts.append(adapter_platform[name])
if has_continuations:
parts.append(CONTINUATIONS_JS)
if has_dom:
parts.append(ASYNC_IO_JS)
parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps, has_router, has_signals, has_page_helpers, has_cek))
parts.append(EPILOGUE)
build_ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
return "\n".join(parts).replace("BUILD_TIMESTAMP", build_ts)
if __name__ == "__main__":
import argparse
p = argparse.ArgumentParser(description="Bootstrap-compile SX reference spec to JavaScript via js.sx")
p.add_argument("--adapters", "-a",
help="Comma-separated adapter list (html,sx,dom,engine). Default: all")
p.add_argument("--modules", "-m",
help="Comma-separated primitive modules (core.* always included). Default: all")
p.add_argument("--extensions",
help="Comma-separated extensions (continuations). Default: none.")
p.add_argument("--spec-modules",
help="Comma-separated spec modules (deps). Default: none.")
default_output = os.path.join(_HERE, "..", "..", "static", "scripts", "sx-browser.js")
p.add_argument("--output", "-o", default=default_output,
help="Output file (default: shared/static/scripts/sx-browser.js)")
args = p.parse_args()
adapters = args.adapters.split(",") if args.adapters else None
modules = args.modules.split(",") if args.modules else None
extensions = args.extensions.split(",") if args.extensions else None
spec_modules = args.spec_modules.split(",") if args.spec_modules else None
js = compile_ref_to_js(adapters, modules, extensions, spec_modules)
with open(args.output, "w") as f:
f.write(js)
included = ", ".join(adapters) if adapters else "all"
mods = ", ".join(modules) if modules else "all"
ext_label = ", ".join(extensions) if extensions else "none"
print(f"Wrote {args.output} ({len(js)} bytes, adapters: {included}, modules: {mods}, extensions: {ext_label})",
file=sys.stderr)