All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 33s
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>
268 lines
9.8 KiB
Python
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)
|