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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user