Add Scheme forms: named let, letrec, dynamic-wind, three-tier equality
Spec (eval.sx, primitives.sx): - Named let: (let loop ((i 0)) body) — self-recursive lambda with TCO - letrec: mutually recursive local bindings with closure patching - dynamic-wind: entry/exit guards with wind stack for future continuations - eq?/eqv?/equal?: identity, atom-value, and deep structural equality Implementation (evaluator.py, async_eval.py, primitives.py): - Both sync and async evaluators implement all four forms - 33 new tests covering all forms including TCO at 10k depth Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -306,6 +306,11 @@ def _sf_or(expr: list, env: dict) -> Any:
|
||||
def _sf_let(expr: list, env: dict) -> Any:
|
||||
if len(expr) < 3:
|
||||
raise EvalError("let requires bindings and body")
|
||||
|
||||
# Named let: (let name ((x 0) ...) body)
|
||||
if isinstance(expr[1], Symbol):
|
||||
return _sf_named_let(expr, env)
|
||||
|
||||
bindings = expr[1]
|
||||
local = dict(env)
|
||||
|
||||
@@ -336,6 +341,127 @@ def _sf_let(expr: list, env: dict) -> Any:
|
||||
return _Thunk(body[-1], local)
|
||||
|
||||
|
||||
def _sf_named_let(expr: list, env: dict) -> Any:
|
||||
"""``(let name ((x 0) (y 1)) body...)`` — self-recursive loop.
|
||||
|
||||
Desugars to a lambda bound to *name* whose closure includes itself,
|
||||
called with the initial values. Tail calls to *name* produce TCO thunks.
|
||||
"""
|
||||
loop_name = expr[1].name
|
||||
bindings = expr[2]
|
||||
body = expr[3:]
|
||||
|
||||
params: list[str] = []
|
||||
inits: list[Any] = []
|
||||
|
||||
if isinstance(bindings, list):
|
||||
if bindings and isinstance(bindings[0], list):
|
||||
for binding in bindings:
|
||||
var = binding[0]
|
||||
params.append(var.name if isinstance(var, Symbol) else var)
|
||||
inits.append(binding[1])
|
||||
elif len(bindings) % 2 == 0:
|
||||
for i in range(0, len(bindings), 2):
|
||||
var = bindings[i]
|
||||
params.append(var.name if isinstance(var, Symbol) else var)
|
||||
inits.append(bindings[i + 1])
|
||||
|
||||
# Build loop body (wrap in begin if multiple expressions)
|
||||
loop_body = body[0] if len(body) == 1 else [Symbol("begin")] + list(body)
|
||||
|
||||
# Create self-recursive lambda
|
||||
loop_fn = Lambda(params, loop_body, dict(env), name=loop_name)
|
||||
loop_fn.closure[loop_name] = loop_fn
|
||||
|
||||
# Evaluate initial values in enclosing env, then call
|
||||
init_vals = [_trampoline(_eval(init, env)) for init in inits]
|
||||
return _call_lambda(loop_fn, init_vals, env)
|
||||
|
||||
|
||||
def _sf_letrec(expr: list, env: dict) -> Any:
|
||||
"""``(letrec ((name1 val1) ...) body)`` — mutually recursive bindings.
|
||||
|
||||
All names are bound to NIL first, then values are evaluated (so they
|
||||
can reference each other), then lambda closures are patched.
|
||||
"""
|
||||
if len(expr) < 3:
|
||||
raise EvalError("letrec requires bindings and body")
|
||||
bindings = expr[1]
|
||||
local = dict(env)
|
||||
|
||||
names: list[str] = []
|
||||
val_exprs: list[Any] = []
|
||||
|
||||
if isinstance(bindings, list):
|
||||
if bindings and isinstance(bindings[0], list):
|
||||
for binding in bindings:
|
||||
var = binding[0]
|
||||
vname = var.name if isinstance(var, Symbol) else var
|
||||
names.append(vname)
|
||||
val_exprs.append(binding[1])
|
||||
local[vname] = NIL
|
||||
elif len(bindings) % 2 == 0:
|
||||
for i in range(0, len(bindings), 2):
|
||||
var = bindings[i]
|
||||
vname = var.name if isinstance(var, Symbol) else var
|
||||
names.append(vname)
|
||||
val_exprs.append(bindings[i + 1])
|
||||
local[vname] = NIL
|
||||
|
||||
# Evaluate all values — they can see each other's names (initially NIL)
|
||||
values = [_trampoline(_eval(ve, local)) for ve in val_exprs]
|
||||
|
||||
# Bind final values
|
||||
for name, val in zip(names, values):
|
||||
local[name] = val
|
||||
|
||||
# Patch lambda closures so they see the final bindings
|
||||
for val in values:
|
||||
if isinstance(val, Lambda):
|
||||
for name in names:
|
||||
val.closure[name] = local[name]
|
||||
|
||||
body = expr[2:]
|
||||
for body_expr in body[:-1]:
|
||||
_trampoline(_eval(body_expr, local))
|
||||
return _Thunk(body[-1], local)
|
||||
|
||||
|
||||
def _sf_dynamic_wind(expr: list, env: dict) -> Any:
|
||||
"""``(dynamic-wind before body after)`` — entry/exit guards.
|
||||
|
||||
All three arguments are thunks (zero-arg functions).
|
||||
*before* is called on entry, *after* is always called on exit (even on
|
||||
error). The wind stack is maintained for future continuation support.
|
||||
"""
|
||||
if len(expr) != 4:
|
||||
raise EvalError("dynamic-wind requires 3 arguments (before, body, after)")
|
||||
before = _trampoline(_eval(expr[1], env))
|
||||
body_fn = _trampoline(_eval(expr[2], env))
|
||||
after = _trampoline(_eval(expr[3], env))
|
||||
|
||||
def _call_thunk(fn: Any) -> Any:
|
||||
if isinstance(fn, Lambda):
|
||||
return _trampoline(_call_lambda(fn, [], env))
|
||||
if callable(fn):
|
||||
return fn()
|
||||
raise EvalError(f"dynamic-wind: expected thunk, got {type(fn).__name__}")
|
||||
|
||||
# Entry
|
||||
_call_thunk(before)
|
||||
_WIND_STACK.append((before, after))
|
||||
try:
|
||||
result = _call_thunk(body_fn)
|
||||
finally:
|
||||
_WIND_STACK.pop()
|
||||
_call_thunk(after)
|
||||
return result
|
||||
|
||||
|
||||
# Wind stack for dynamic-wind (thread-safe enough for sync evaluator)
|
||||
_WIND_STACK: list[tuple] = []
|
||||
|
||||
|
||||
def _sf_lambda(expr: list, env: dict) -> Lambda:
|
||||
if len(expr) < 3:
|
||||
raise EvalError("lambda requires params and body")
|
||||
@@ -883,6 +1009,7 @@ _SPECIAL_FORMS: dict[str, Any] = {
|
||||
"or": _sf_or,
|
||||
"let": _sf_let,
|
||||
"let*": _sf_let,
|
||||
"letrec": _sf_letrec,
|
||||
"lambda": _sf_lambda,
|
||||
"fn": _sf_lambda,
|
||||
"define": _sf_define,
|
||||
@@ -895,6 +1022,7 @@ _SPECIAL_FORMS: dict[str, Any] = {
|
||||
"quote": _sf_quote,
|
||||
"->": _sf_thread_first,
|
||||
"set!": _sf_set_bang,
|
||||
"dynamic-wind": _sf_dynamic_wind,
|
||||
"defmacro": _sf_defmacro,
|
||||
"quasiquote": _sf_quasiquote,
|
||||
"defhandler": _sf_defhandler,
|
||||
|
||||
Reference in New Issue
Block a user