Implement explicit CEK machine, continuations, effect signatures, fix dynamic-wind and inspect shadowing
Three-phase foundations implementation:
Phase A — Activate dormant shift/reset continuations with 24 SX-native tests
covering basic semantics, predicates, stored continuations, nested reset,
scope interaction, and TCO.
Phase B — Bridge compile-time effect system to runtime: boundary_parser extracts
46 effect annotations, platform provides populate_effect_annotations() and
check_component_effects() for static analysis. 6 new type tests.
Phase C — Explicit CEK machine (frames.sx + cek.sx): evaluation state as data
({control, env, kont, phase, value}), 21 frame types, two-phase step function
(step-eval/step-continue), native shift/reset via frame capture. Bootstrapper
integration: --spec-modules cek transpiles to Python with iterative cek_run.
43 interpreted + 49 transpiled tests passing.
Bug fixes:
- inspect() shadowed by `import inspect` in PLATFORM_ASYNC_PY — renamed to
`import inspect as _inspect`
- dynamic-wind missing platform functions (call_thunk, push_wind!, pop_wind!) —
added with try/finally error safety via dynamic_wind_call
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1095,6 +1095,37 @@ def for_each_indexed(fn, coll):
|
||||
def map_dict(fn, d):
|
||||
return {k: fn(k, v) for k, v in d.items()}
|
||||
|
||||
# Dynamic wind support (used by sf-dynamic-wind in eval.sx)
|
||||
_wind_stack = []
|
||||
|
||||
def push_wind_b(before, after):
|
||||
_wind_stack.append((before, after))
|
||||
return NIL
|
||||
|
||||
def pop_wind_b():
|
||||
if _wind_stack:
|
||||
_wind_stack.pop()
|
||||
return NIL
|
||||
|
||||
def call_thunk(f, env):
|
||||
"""Call a zero-arg function/lambda."""
|
||||
if is_callable(f) and not is_lambda(f):
|
||||
return f()
|
||||
if is_lambda(f):
|
||||
return trampoline(call_lambda(f, [], env))
|
||||
return trampoline(eval_expr([f], env))
|
||||
|
||||
def dynamic_wind_call(before, body, after, env):
|
||||
"""Execute dynamic-wind with try/finally for error safety."""
|
||||
call_thunk(before, env)
|
||||
push_wind_b(before, after)
|
||||
try:
|
||||
result = call_thunk(body, env)
|
||||
finally:
|
||||
pop_wind_b()
|
||||
call_thunk(after, env)
|
||||
return result
|
||||
|
||||
# Aliases used directly by transpiled code
|
||||
first = PRIMITIVES["first"]
|
||||
last = PRIMITIVES["last"]
|
||||
@@ -1184,6 +1215,43 @@ PLATFORM_DEPS_PY = (
|
||||
' c.io_refs = set(refs) if not isinstance(refs, set) else refs\n'
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Platform: CEK module — explicit CEK machine support
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
PLATFORM_CEK_PY = '''
|
||||
# =========================================================================
|
||||
# Platform: CEK module — explicit CEK machine
|
||||
# =========================================================================
|
||||
|
||||
# Standalone aliases for primitives used by cek.sx / frames.sx
|
||||
inc = PRIMITIVES["inc"]
|
||||
dec = PRIMITIVES["dec"]
|
||||
zip_pairs = PRIMITIVES["zip-pairs"]
|
||||
|
||||
continuation_p = PRIMITIVES["continuation?"]
|
||||
|
||||
def make_cek_continuation(captured, rest_kont):
|
||||
"""Create a Continuation storing captured CEK frames as data."""
|
||||
c = Continuation(lambda v=NIL: v)
|
||||
c._cek_data = {"captured": captured, "rest-kont": rest_kont}
|
||||
return c
|
||||
|
||||
def continuation_data(c):
|
||||
"""Return the _cek_data dict from a CEK continuation."""
|
||||
return getattr(c, '_cek_data', {}) or {}
|
||||
'''
|
||||
|
||||
# Iterative override for cek_run — replaces transpiled recursive version
|
||||
CEK_FIXUPS_PY = '''
|
||||
# Override recursive cek_run with iterative loop (avoids Python stack overflow)
|
||||
def cek_run(state):
|
||||
"""Drive CEK machine to completion (iterative)."""
|
||||
while not cek_terminal_p(state):
|
||||
state = cek_step(state)
|
||||
return cek_value(state)
|
||||
'''
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Platform: async adapter — async evaluation, I/O dispatch
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1194,7 +1262,7 @@ PLATFORM_ASYNC_PY = '''
|
||||
# =========================================================================
|
||||
|
||||
import contextvars
|
||||
import inspect
|
||||
import inspect as _inspect
|
||||
|
||||
from shared.sx.primitives_io import (
|
||||
IO_PRIMITIVES, RequestContext, execute_io,
|
||||
@@ -1287,7 +1355,7 @@ def sx_parse(src):
|
||||
|
||||
|
||||
def is_async_coroutine(x):
|
||||
return inspect.iscoroutine(x)
|
||||
return _inspect.iscoroutine(x)
|
||||
|
||||
|
||||
async def async_await(x):
|
||||
@@ -1542,6 +1610,68 @@ def public_api_py(has_html: bool, has_sx: bool, has_deps: bool = False,
|
||||
'def make_env(**kwargs):',
|
||||
' """Create an environment with initial bindings."""',
|
||||
' return _Env(dict(kwargs))',
|
||||
'',
|
||||
'',
|
||||
'def populate_effect_annotations(env, effect_map=None):',
|
||||
' """Populate *effect-annotations* in env from boundary declarations.',
|
||||
'',
|
||||
' If effect_map is provided, use it directly (dict of name -> effects list).',
|
||||
' Otherwise, parse boundary.sx via boundary_parser.',
|
||||
' """',
|
||||
' if effect_map is None:',
|
||||
' from shared.sx.ref.boundary_parser import parse_boundary_effects',
|
||||
' effect_map = parse_boundary_effects()',
|
||||
' anns = env.get("*effect-annotations*", {})',
|
||||
' if not isinstance(anns, dict):',
|
||||
' anns = {}',
|
||||
' anns.update(effect_map)',
|
||||
' env["*effect-annotations*"] = anns',
|
||||
' return anns',
|
||||
'',
|
||||
'',
|
||||
'def check_component_effects(env, comp_name=None):',
|
||||
' """Check effect violations for components in env.',
|
||||
'',
|
||||
' If comp_name is given, check only that component.',
|
||||
' Returns list of diagnostic dicts (warnings, not errors).',
|
||||
' """',
|
||||
' anns = env.get("*effect-annotations*")',
|
||||
' if not anns:',
|
||||
' return []',
|
||||
' diagnostics = []',
|
||||
' names = [comp_name] if comp_name else [k for k in env if isinstance(k, str) and k.startswith("~")]',
|
||||
' for name in names:',
|
||||
' val = env.get(name)',
|
||||
' if val is not None and type_of(val) == "component":',
|
||||
' comp_effects = anns.get(name)',
|
||||
' if comp_effects is None:',
|
||||
' continue # unannotated — skip',
|
||||
' body = val.body if hasattr(val, "body") else None',
|
||||
' if body is None:',
|
||||
' continue',
|
||||
' _walk_effects(body, name, comp_effects, anns, diagnostics)',
|
||||
' return diagnostics',
|
||||
'',
|
||||
'',
|
||||
'def _walk_effects(node, comp_name, caller_effects, anns, diagnostics):',
|
||||
' """Walk AST node and check effect calls."""',
|
||||
' if not isinstance(node, list) or not node:',
|
||||
' return',
|
||||
' head = node[0]',
|
||||
' if isinstance(head, Symbol):',
|
||||
' callee = head.name',
|
||||
' callee_effects = anns.get(callee)',
|
||||
' if callee_effects is not None and caller_effects is not None:',
|
||||
' for e in callee_effects:',
|
||||
' if e not in caller_effects:',
|
||||
' diagnostics.append({',
|
||||
' "level": "warning",',
|
||||
' "message": f"`{callee}` has effects {callee_effects} but `{comp_name}` only allows {caller_effects or \'[pure]\'}",',
|
||||
' "component": comp_name,',
|
||||
' })',
|
||||
' break',
|
||||
' for child in node[1:]:',
|
||||
' _walk_effects(child, comp_name, caller_effects, anns, diagnostics)',
|
||||
])
|
||||
return '\n'.join(lines)
|
||||
|
||||
@@ -1563,8 +1693,16 @@ SPEC_MODULES = {
|
||||
"signals": ("signals.sx", "signals (reactive signal runtime)"),
|
||||
"page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"),
|
||||
"types": ("types.sx", "types (gradual type system)"),
|
||||
"frames": ("frames.sx", "frames (CEK continuation frames)"),
|
||||
"cek": ("cek.sx", "cek (explicit CEK machine evaluator)"),
|
||||
}
|
||||
|
||||
# Explicit ordering for spec modules with dependencies.
|
||||
# Modules listed here are emitted in this order; any not listed use alphabetical.
|
||||
SPEC_MODULE_ORDER = [
|
||||
"deps", "engine", "frames", "page-helpers", "router", "signals", "types", "cek",
|
||||
]
|
||||
|
||||
EXTENSION_NAMES = {"continuations"}
|
||||
|
||||
EXTENSION_FORMS = {
|
||||
|
||||
Reference in New Issue
Block a user