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,
|
parse_all,
|
||||||
serialize,
|
serialize,
|
||||||
)
|
)
|
||||||
import os as _os
|
from .evaluator import (
|
||||||
|
EvalError,
|
||||||
if _os.environ.get("SX_USE_REF") == "1":
|
evaluate,
|
||||||
from .ref.sx_ref import (
|
make_env,
|
||||||
EvalError,
|
)
|
||||||
evaluate,
|
|
||||||
make_env,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
from .evaluator import (
|
|
||||||
EvalError,
|
|
||||||
evaluate,
|
|
||||||
make_env,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .primitives import (
|
from .primitives import (
|
||||||
all_primitives,
|
all_primitives,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -635,11 +635,9 @@ class PyEmitter:
|
|||||||
pad = " " * indent
|
pad = " " * indent
|
||||||
name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1])
|
name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1])
|
||||||
val_expr = expr[2]
|
val_expr = expr[2]
|
||||||
# If value is a lambda/fn, check if body uses set! on let-bound vars
|
# Always emit fn-bodied defines as def statements for flat control flow
|
||||||
# and emit as def for proper mutation support
|
|
||||||
if (isinstance(val_expr, list) and val_expr and
|
if (isinstance(val_expr, list) and val_expr and
|
||||||
isinstance(val_expr[0], Symbol) and val_expr[0].name in ("fn", "lambda")
|
isinstance(val_expr[0], Symbol) and val_expr[0].name in ("fn", "lambda")):
|
||||||
and self._body_uses_set(val_expr)):
|
|
||||||
return self._emit_define_as_def(name, val_expr, indent)
|
return self._emit_define_as_def(name, val_expr, indent)
|
||||||
val = self.emit(val_expr)
|
val = self.emit(val_expr)
|
||||||
return f"{pad}{self._mangle(name)} = {val}"
|
return f"{pad}{self._mangle(name)} = {val}"
|
||||||
@@ -667,11 +665,23 @@ class PyEmitter:
|
|||||||
params = fn_expr[1]
|
params = fn_expr[1]
|
||||||
body = fn_expr[2:]
|
body = fn_expr[2:]
|
||||||
param_names = []
|
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):
|
if isinstance(p, Symbol):
|
||||||
param_names.append(self._mangle(p.name))
|
param_names.append(self._mangle(p.name))
|
||||||
else:
|
else:
|
||||||
param_names.append(str(p))
|
param_names.append(str(p))
|
||||||
|
i += 1
|
||||||
params_str = ", ".join(param_names)
|
params_str = ", ".join(param_names)
|
||||||
py_name = self._mangle(name)
|
py_name = self._mangle(name)
|
||||||
# Find set! target variables that are used from nested lambda scopes
|
# 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.
|
"""Emit body expressions as statements into lines list.
|
||||||
|
|
||||||
Handles let as local variable declarations, and returns the last
|
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
|
pad = " " * indent
|
||||||
for i, expr in enumerate(body):
|
for i, expr in enumerate(body):
|
||||||
@@ -737,10 +748,126 @@ class PyEmitter:
|
|||||||
lines.append(self.emit_statement(sub, indent))
|
lines.append(self.emit_statement(sub, indent))
|
||||||
continue
|
continue
|
||||||
if is_last:
|
if is_last:
|
||||||
lines.append(f"{pad}return {self.emit(expr)}")
|
self._emit_return_expr(expr, lines, indent)
|
||||||
else:
|
else:
|
||||||
lines.append(self.emit_statement(expr, indent))
|
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:
|
def _emit_let_as_stmts(self, expr, lines: list, indent: int, is_last: bool) -> None:
|
||||||
"""Emit a let expression as local variable declarations."""
|
"""Emit a let expression as local variable declarations."""
|
||||||
pad = " " * indent
|
pad = " " * indent
|
||||||
|
|||||||
@@ -578,8 +578,11 @@ def sx_expr_source(x):
|
|||||||
return x.source if isinstance(x, SxExpr) else str(x)
|
return x.source if isinstance(x, SxExpr) else str(x)
|
||||||
|
|
||||||
|
|
||||||
class EvalError(Exception):
|
try:
|
||||||
pass
|
from shared.sx.evaluator import EvalError
|
||||||
|
except ImportError:
|
||||||
|
class EvalError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _sx_append(lst, item):
|
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} ---")
|
print(f"# --- {spec_name} ---")
|
||||||
eval_file(spec["file"], env)
|
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
|
# Summary
|
||||||
print()
|
print()
|
||||||
print(f"1..{test_num}")
|
print(f"1..{test_num}")
|
||||||
|
|||||||
Reference in New Issue
Block a user