Replace hand-written evaluator with bootstrapped spec, emit flat Python

- evaluator.py: replace 1200 lines of hand-written eval with thin shim
  that re-exports from bootstrapped sx_ref.py
- bootstrap_py.py: emit all fn-bodied defines as `def` (not `lambda`),
  flatten tail-position if/cond/case/when to if/elif with returns,
  fix &rest handling in _emit_define_as_def
- platform_py.py: EvalError imports from evaluator.py so catches work
- __init__.py: remove SX_USE_REF conditional, always use bootstrapped
- tests/run.py: reset render_active after render tests for isolation
- Removes setrecursionlimit(5000) hack — no longer needed with flat code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 09:18:17 +00:00
parent 3906ab3558
commit d8cddbd971
6 changed files with 1684 additions and 1350 deletions

View File

@@ -31,20 +31,11 @@ from .parser import (
parse_all,
serialize,
)
import os as _os
if _os.environ.get("SX_USE_REF") == "1":
from .ref.sx_ref import (
EvalError,
evaluate,
make_env,
)
else:
from .evaluator import (
EvalError,
evaluate,
make_env,
)
from .evaluator import (
EvalError,
evaluate,
make_env,
)
from .primitives import (
all_primitives,

File diff suppressed because it is too large Load Diff

View File

@@ -635,11 +635,9 @@ class PyEmitter:
pad = " " * indent
name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1])
val_expr = expr[2]
# If value is a lambda/fn, check if body uses set! on let-bound vars
# and emit as def for proper mutation support
# Always emit fn-bodied defines as def statements for flat control flow
if (isinstance(val_expr, list) and val_expr and
isinstance(val_expr[0], Symbol) and val_expr[0].name in ("fn", "lambda")
and self._body_uses_set(val_expr)):
isinstance(val_expr[0], Symbol) and val_expr[0].name in ("fn", "lambda")):
return self._emit_define_as_def(name, val_expr, indent)
val = self.emit(val_expr)
return f"{pad}{self._mangle(name)} = {val}"
@@ -667,11 +665,23 @@ class PyEmitter:
params = fn_expr[1]
body = fn_expr[2:]
param_names = []
for p in params:
i = 0
while i < len(params):
p = params[i]
if isinstance(p, Symbol) and p.name == "&rest":
if i + 1 < len(params):
rest_name = self._mangle(params[i + 1].name if isinstance(params[i + 1], Symbol) else str(params[i + 1]))
param_names.append(f"*{rest_name}")
i += 2
continue
else:
i += 1
continue
if isinstance(p, Symbol):
param_names.append(self._mangle(p.name))
else:
param_names.append(str(p))
i += 1
params_str = ", ".join(param_names)
py_name = self._mangle(name)
# Find set! target variables that are used from nested lambda scopes
@@ -718,7 +728,8 @@ class PyEmitter:
"""Emit body expressions as statements into lines list.
Handles let as local variable declarations, and returns the last
expression.
expression. Control flow in tail position (if, cond, case, when)
is flattened to if/elif statements with returns in each branch.
"""
pad = " " * indent
for i, expr in enumerate(body):
@@ -737,10 +748,126 @@ class PyEmitter:
lines.append(self.emit_statement(sub, indent))
continue
if is_last:
lines.append(f"{pad}return {self.emit(expr)}")
self._emit_return_expr(expr, lines, indent)
else:
lines.append(self.emit_statement(expr, indent))
def _emit_return_expr(self, expr, lines: list, indent: int) -> None:
"""Emit an expression in return position, flattening control flow."""
pad = " " * indent
if isinstance(expr, list) and expr and isinstance(expr[0], Symbol):
name = expr[0].name
if name == "if":
self._emit_if_return(expr, lines, indent)
return
if name == "cond":
self._emit_cond_return(expr, lines, indent)
return
if name == "case":
self._emit_case_return(expr, lines, indent)
return
if name == "when":
self._emit_when_return(expr, lines, indent)
return
if name in ("let", "let*"):
self._emit_let_as_stmts(expr, lines, indent, True)
return
if name in ("do", "begin"):
self._emit_body_stmts(expr[1:], lines, indent)
return
lines.append(f"{pad}return {self.emit(expr)}")
def _emit_if_return(self, expr, lines: list, indent: int) -> None:
"""Emit if as statement with returns in each branch."""
pad = " " * indent
lines.append(f"{pad}if sx_truthy({self.emit(expr[1])}):")
self._emit_return_expr(expr[2], lines, indent + 1)
if len(expr) > 3:
lines.append(f"{pad}else:")
self._emit_return_expr(expr[3], lines, indent + 1)
else:
lines.append(f"{pad}return NIL")
def _emit_when_return(self, expr, lines: list, indent: int) -> None:
"""Emit when as statement with return in body, else return NIL."""
pad = " " * indent
lines.append(f"{pad}if sx_truthy({self.emit(expr[1])}):")
body_parts = expr[2:]
if len(body_parts) == 1:
self._emit_return_expr(body_parts[0], lines, indent + 1)
else:
for b in body_parts[:-1]:
lines.append(self.emit_statement(b, indent + 1))
self._emit_return_expr(body_parts[-1], lines, indent + 1)
lines.append(f"{pad}return NIL")
def _emit_cond_return(self, expr, lines: list, indent: int) -> None:
"""Emit cond as if/elif/else with returns in each branch."""
pad = " " * indent
clauses = expr[1:]
if not clauses:
lines.append(f"{pad}return NIL")
return
is_scheme = (
all(isinstance(c, list) and len(c) == 2 for c in clauses)
and not any(isinstance(c, Keyword) for c in clauses)
)
has_else = False
first_clause = True
if is_scheme:
for clause in clauses:
test, body = clause[0], clause[1]
if ((isinstance(test, Symbol) and test.name in ("else", ":else")) or
(isinstance(test, Keyword) and test.name == "else")):
lines.append(f"{pad}else:")
has_else = True
else:
kw = "if" if first_clause else "elif"
lines.append(f"{pad}{kw} sx_truthy({self.emit(test)}):")
first_clause = False
self._emit_return_expr(body, lines, indent + 1)
else:
i = 0
while i < len(clauses) - 1:
test, body = clauses[i], clauses[i + 1]
if ((isinstance(test, Keyword) and test.name == "else") or
(isinstance(test, Symbol) and test.name in ("else", ":else"))):
lines.append(f"{pad}else:")
has_else = True
else:
kw = "if" if first_clause else "elif"
lines.append(f"{pad}{kw} sx_truthy({self.emit(test)}):")
first_clause = False
self._emit_return_expr(body, lines, indent + 1)
i += 2
if not has_else:
lines.append(f"{pad}return NIL")
def _emit_case_return(self, expr, lines: list, indent: int) -> None:
"""Emit case as if/elif/else with returns in each branch."""
pad = " " * indent
match_val = self.emit(expr[1])
clauses = expr[2:]
lines.append(f"{pad}_match = {match_val}")
has_else = False
first_clause = True
i = 0
while i < len(clauses) - 1:
test = clauses[i]
body = clauses[i + 1]
if ((isinstance(test, Keyword) and test.name == "else") or
(isinstance(test, Symbol) and test.name in ("else", ":else"))):
lines.append(f"{pad}else:")
has_else = True
else:
kw = "if" if first_clause else "elif"
lines.append(f"{pad}{kw} _match == {self.emit(test)}:")
first_clause = False
self._emit_return_expr(body, lines, indent + 1)
i += 2
if not has_else:
lines.append(f"{pad}return NIL")
def _emit_let_as_stmts(self, expr, lines: list, indent: int, is_last: bool) -> None:
"""Emit a let expression as local variable declarations."""
pad = " " * indent

View File

@@ -578,8 +578,11 @@ def sx_expr_source(x):
return x.source if isinstance(x, SxExpr) else str(x)
class EvalError(Exception):
pass
try:
from shared.sx.evaluator import EvalError
except ImportError:
class EvalError(Exception):
pass
def _sx_append(lst, item):

File diff suppressed because it is too large Load Diff

View File

@@ -771,6 +771,15 @@ def main():
print(f"# --- {spec_name} ---")
eval_file(spec["file"], env)
# Reset render state after render tests to avoid leaking
# into subsequent specs (bootstrapped evaluator checks render_active)
if spec_name == "render":
try:
from shared.sx.ref.sx_ref import set_render_active_b
set_render_active_b(False)
except ImportError:
pass
# Summary
print()
print(f"1..{test_num}")