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>
124 lines
3.7 KiB
Python
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()
|