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:
2026-03-06 01:11:31 +00:00
parent 9cde15c3ce
commit f34e55aa9b
7 changed files with 785 additions and 26 deletions

View File

@@ -279,6 +279,10 @@ async def _asf_or(expr, env, ctx):
async def _asf_let(expr, env, ctx):
# Named let: (let name ((x 0) ...) body)
if isinstance(expr[1], Symbol):
return await _asf_named_let(expr, env, ctx)
bindings = expr[1]
local = dict(env)
if isinstance(bindings, list):
@@ -299,6 +303,98 @@ async def _asf_let(expr, env, ctx):
return NIL
async def _asf_named_let(expr, env, ctx):
"""Async named let: (let name ((x 0) ...) body)"""
loop_name = expr[1].name
bindings = expr[2]
body = expr[3:]
params: list[str] = []
inits: list = []
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])
loop_body = body[0] if len(body) == 1 else [Symbol("begin")] + list(body)
loop_fn = Lambda(params, loop_body, dict(env), name=loop_name)
loop_fn.closure[loop_name] = loop_fn
init_vals = [await _async_trampoline(await _async_eval(init, env, ctx)) for init in inits]
return await _async_call_lambda(loop_fn, init_vals, env, ctx)
async def _asf_letrec(expr, env, ctx):
"""Async letrec: (letrec ((name1 val1) ...) body)"""
bindings = expr[1]
local = dict(env)
names: list[str] = []
val_exprs: list = []
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
values = [await _async_trampoline(await _async_eval(ve, local, ctx)) for ve in val_exprs]
for name, val in zip(names, values):
local[name] = val
for val in values:
if isinstance(val, Lambda):
for name in names:
val.closure[name] = local[name]
for body_expr in expr[2:-1]:
await _async_trampoline(await _async_eval(body_expr, local, ctx))
if len(expr) > 2:
return _AsyncThunk(expr[-1], local, ctx)
return NIL
async def _asf_dynamic_wind(expr, env, ctx):
"""Async dynamic-wind: (dynamic-wind before body after)"""
before = await _async_trampoline(await _async_eval(expr[1], env, ctx))
body_fn = await _async_trampoline(await _async_eval(expr[2], env, ctx))
after = await _async_trampoline(await _async_eval(expr[3], env, ctx))
async def _call_thunk(fn):
if isinstance(fn, Lambda):
return await _async_trampoline(await _async_call_lambda(fn, [], env, ctx))
if callable(fn):
r = fn()
if inspect.iscoroutine(r):
return await r
return r
raise EvalError(f"dynamic-wind: expected thunk, got {type(fn).__name__}")
await _call_thunk(before)
try:
result = await _call_thunk(body_fn)
finally:
await _call_thunk(after)
return result
async def _asf_lambda(expr, env, ctx):
params_expr = expr[1]
param_names = []
@@ -467,6 +563,7 @@ _ASYNC_SPECIAL_FORMS: dict[str, Any] = {
"or": _asf_or,
"let": _asf_let,
"let*": _asf_let,
"letrec": _asf_letrec,
"lambda": _asf_lambda,
"fn": _asf_lambda,
"define": _asf_define,
@@ -481,6 +578,7 @@ _ASYNC_SPECIAL_FORMS: dict[str, Any] = {
"quasiquote": _asf_quasiquote,
"->": _asf_thread_first,
"set!": _asf_set_bang,
"dynamic-wind": _asf_dynamic_wind,
}