Implement delimited continuations (shift/reset) across all evaluators
Bootstrap shift/reset to both Python and JS targets. The implementation uses exception-based capture with re-evaluation: reset wraps in try/catch for ShiftSignal, shift raises to the nearest reset, and continuation invocation pushes a resume value and re-evaluates the body. - Add Continuation type and _ShiftSignal to shared/sx/types.py - Add sf_reset/sf_shift to hand-written evaluator.py - Add async versions to async_eval.py - Add shift/reset dispatch to eval.sx spec - Bootstrap to Python: FIXUPS_PY with sf_reset/sf_shift, regenerate sx_ref.py - Bootstrap to JS: Continuation/ShiftSignal types, sfReset/sfShift in fixups - Add continuation? primitive to both bootstrappers and primitives.sx - Allow callables (including Continuation) in hand-written HO map - 44 unit tests (22 per evaluator) covering: passthrough, abort, invoke, double invoke, predicate, stored continuation, nested reset, practical patterns - Update continuations essay to reflect implemented status with examples Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .types import Component, HandlerDef, Keyword, Lambda, Macro, NIL, PageDef, RelationDef, Symbol
|
||||
from .types import Component, Continuation, HandlerDef, Keyword, Lambda, Macro, NIL, PageDef, RelationDef, Symbol, _ShiftSignal
|
||||
from .primitives import _PRIMITIVES
|
||||
|
||||
|
||||
@@ -874,6 +874,42 @@ def _sf_defpage(expr: list, env: dict) -> PageDef:
|
||||
return page
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Delimited continuations — shift / reset
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_RESET_RESUME = [] # stack of resume values; empty = not resuming
|
||||
|
||||
_RESET_SENTINEL = object()
|
||||
|
||||
|
||||
def _sf_reset(expr, env):
|
||||
"""(reset body) — establish a continuation delimiter."""
|
||||
body = expr[1]
|
||||
try:
|
||||
return _trampoline(_eval(body, env))
|
||||
except _ShiftSignal as sig:
|
||||
def cont_fn(value=NIL):
|
||||
_RESET_RESUME.append(value)
|
||||
try:
|
||||
return _trampoline(_eval(body, env))
|
||||
finally:
|
||||
_RESET_RESUME.pop()
|
||||
k = Continuation(cont_fn)
|
||||
sig_env = dict(sig.env)
|
||||
sig_env[sig.k_name] = k
|
||||
return _trampoline(_eval(sig.body, sig_env))
|
||||
|
||||
|
||||
def _sf_shift(expr, env):
|
||||
"""(shift k body) — capture continuation to nearest reset."""
|
||||
if _RESET_RESUME:
|
||||
return _RESET_RESUME[-1]
|
||||
k_name = expr[1].name # symbol
|
||||
body = expr[2]
|
||||
raise _ShiftSignal(k_name, body, env)
|
||||
|
||||
|
||||
_SPECIAL_FORMS: dict[str, Any] = {
|
||||
"if": _sf_if,
|
||||
"when": _sf_when,
|
||||
@@ -901,6 +937,8 @@ _SPECIAL_FORMS: dict[str, Any] = {
|
||||
"defpage": _sf_defpage,
|
||||
"defquery": _sf_defquery,
|
||||
"defaction": _sf_defaction,
|
||||
"reset": _sf_reset,
|
||||
"shift": _sf_shift,
|
||||
}
|
||||
|
||||
|
||||
@@ -913,9 +951,11 @@ def _ho_map(expr: list, env: dict) -> list:
|
||||
raise EvalError("map requires fn and collection")
|
||||
fn = _trampoline(_eval(expr[1], env))
|
||||
coll = _trampoline(_eval(expr[2], env))
|
||||
if not isinstance(fn, Lambda):
|
||||
raise EvalError(f"map requires lambda, got {type(fn).__name__}")
|
||||
return [_trampoline(_call_lambda(fn, [item], env)) for item in coll]
|
||||
if isinstance(fn, Lambda):
|
||||
return [_trampoline(_call_lambda(fn, [item], env)) for item in coll]
|
||||
if callable(fn):
|
||||
return [fn(item) for item in coll]
|
||||
raise EvalError(f"map requires lambda, got {type(fn).__name__}")
|
||||
|
||||
|
||||
def _ho_map_indexed(expr: list, env: dict) -> list:
|
||||
|
||||
Reference in New Issue
Block a user