Files
rose-ash/shared/sx/ref/run_py_sx.py
giles b4944aa2b6 Implement py.sx: self-hosting SX-to-Python bootstrapper
py.sx is an SX-to-Python translator written in SX. Running it on the
Python evaluator against the spec files produces byte-for-byte identical
output to the hand-written bootstrap_py.py (128/128 defines match,
1490 lines, 88955 bytes).

The bootstrapper bootstraps itself: G0 (Python) == G1 (SX).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:12:50 +00:00

124 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""
Bootstrap runner: execute py.sx against spec files to produce sx_ref.py.
This is the G1 bootstrapper — py.sx (SX-to-Python translator written in SX)
is loaded into the Python evaluator, which then uses it to translate the
spec .sx files into Python.
The output should be identical to: python bootstrap_py.py
Usage:
python run_py_sx.py > sx_ref_g1.py
"""
from __future__ import annotations
import os
import sys
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
sys.path.insert(0, _PROJECT)
from shared.sx.parser import parse_all
from shared.sx.types import Symbol
from shared.sx.ref.bootstrap_py import (
PREAMBLE, PLATFORM_PY, PRIMITIVES_PY_PRE, PRIMITIVES_PY_POST,
PLATFORM_DEPS_PY, FIXUPS_PY, CONTINUATIONS_PY,
ADAPTER_FILES, SPEC_MODULES,
_assemble_primitives_py, public_api_py,
)
def load_py_sx(evaluator_env: dict) -> dict:
"""Load py.sx into an evaluator environment and return it."""
py_sx_path = os.path.join(_HERE, "py.sx")
with open(py_sx_path) as f:
source = f.read()
exprs = parse_all(source)
# Import the evaluator
from shared.sx.evaluator import evaluate, make_env
env = make_env()
for expr in exprs:
evaluate(expr, env)
return env
def extract_defines(source: str) -> list[tuple[str, list]]:
"""Parse .sx source, return list of (name, define-expr) for top-level defines."""
exprs = parse_all(source)
defines = []
for expr in exprs:
if isinstance(expr, list) and expr and isinstance(expr[0], Symbol):
if expr[0].name == "define":
name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1])
defines.append((name, expr))
return defines
def main():
from shared.sx.evaluator import evaluate
# Load py.sx into evaluator
env = load_py_sx({})
# Get the py-translate-file function
py_translate_file = env.get("py-translate-file")
if py_translate_file is None:
print("ERROR: py-translate-file not found in py.sx environment", file=sys.stderr)
sys.exit(1)
# Same file list and order as bootstrap_py.py compile_ref_to_py()
sx_files = [
("eval.sx", "eval"),
("forms.sx", "forms (server definition forms)"),
("render.sx", "render (core)"),
("adapter-html.sx", "adapter-html"),
("adapter-sx.sx", "adapter-sx"),
("deps.sx", "deps (component dependency analysis)"),
("signals.sx", "signals (reactive signal runtime)"),
]
# Build output — static sections are identical
parts = []
parts.append(PREAMBLE)
parts.append(PLATFORM_PY)
parts.append(PRIMITIVES_PY_PRE)
parts.append(_assemble_primitives_py(None))
parts.append(PRIMITIVES_PY_POST)
parts.append(PLATFORM_DEPS_PY)
# Translate each spec file using py.sx
for filename, label in sx_files:
filepath = os.path.join(_HERE, filename)
if not os.path.exists(filepath):
continue
with open(filepath) as f:
src = f.read()
defines = extract_defines(src)
# Convert defines to SX-compatible format: list of [name, expr] pairs
sx_defines = [[name, expr] for name, expr in defines]
parts.append(f"\n# === Transpiled from {label} ===\n")
# Bind defines as data in env to avoid evaluator trying to execute AST
env["_defines"] = sx_defines
result = evaluate(
[Symbol("py-translate-file"), Symbol("_defines")],
env,
)
parts.append(result)
parts.append(FIXUPS_PY)
parts.append(public_api_py(True, True, True))
print("\n".join(parts))
if __name__ == "__main__":
main()