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>
257 lines
8.5 KiB
Python
257 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Run test-cek.sx using the bootstrapped evaluator with CEK module loaded."""
|
|
from __future__ import annotations
|
|
import os, sys
|
|
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
|
|
sys.path.insert(0, _PROJECT)
|
|
|
|
from shared.sx.parser import parse_all
|
|
from shared.sx.ref import sx_ref
|
|
from shared.sx.ref.sx_ref import (
|
|
eval_expr, trampoline, make_env, env_get, env_has, env_set,
|
|
env_extend, env_merge,
|
|
)
|
|
from shared.sx.types import (
|
|
NIL, Symbol, Keyword, Lambda, Component, Island, Continuation, Macro,
|
|
_ShiftSignal,
|
|
)
|
|
|
|
# Build env with primitives
|
|
env = make_env()
|
|
|
|
# Platform test functions
|
|
_suite_stack: list[str] = []
|
|
_pass_count = 0
|
|
_fail_count = 0
|
|
|
|
def _try_call(thunk):
|
|
try:
|
|
trampoline(eval_expr([thunk], env))
|
|
return {"ok": True}
|
|
except Exception as e:
|
|
return {"ok": False, "error": str(e)}
|
|
|
|
def _report_pass(name):
|
|
global _pass_count
|
|
_pass_count += 1
|
|
ctx = " > ".join(_suite_stack)
|
|
print(f" PASS: {ctx} > {name}")
|
|
return NIL
|
|
|
|
def _report_fail(name, error):
|
|
global _fail_count
|
|
_fail_count += 1
|
|
ctx = " > ".join(_suite_stack)
|
|
print(f" FAIL: {ctx} > {name}: {error}")
|
|
return NIL
|
|
|
|
def _push_suite(name):
|
|
_suite_stack.append(name)
|
|
print(f"{' ' * (len(_suite_stack)-1)}Suite: {name}")
|
|
return NIL
|
|
|
|
def _pop_suite():
|
|
if _suite_stack:
|
|
_suite_stack.pop()
|
|
return NIL
|
|
|
|
def _test_env():
|
|
return env
|
|
|
|
def _sx_parse(source):
|
|
return parse_all(source)
|
|
|
|
def _sx_parse_one(source):
|
|
"""Parse a single expression."""
|
|
exprs = parse_all(source)
|
|
return exprs[0] if exprs else NIL
|
|
|
|
def _make_continuation(fn):
|
|
return Continuation(fn)
|
|
|
|
env["try-call"] = _try_call
|
|
env["report-pass"] = _report_pass
|
|
env["report-fail"] = _report_fail
|
|
env["push-suite"] = _push_suite
|
|
env["pop-suite"] = _pop_suite
|
|
env["test-env"] = _test_env
|
|
env["sx-parse"] = _sx_parse
|
|
env["sx-parse-one"] = _sx_parse_one
|
|
env["env-get"] = env_get
|
|
env["env-has?"] = env_has
|
|
env["env-set!"] = env_set
|
|
env["env-extend"] = env_extend
|
|
env["make-continuation"] = _make_continuation
|
|
env["continuation?"] = lambda x: isinstance(x, Continuation)
|
|
env["continuation-fn"] = lambda c: c.fn
|
|
|
|
def _make_cek_continuation(captured, rest_kont):
|
|
"""Create a Continuation that stores captured CEK frames as data."""
|
|
data = {"captured": captured, "rest-kont": rest_kont}
|
|
# The fn is a dummy — invocation happens via CEK's continue-with-call
|
|
return Continuation(lambda v=NIL: v)
|
|
|
|
# Monkey-patch to store data
|
|
_orig_make_cek_cont = _make_cek_continuation
|
|
def _make_cek_continuation_with_data(captured, rest_kont):
|
|
c = _orig_make_cek_cont(captured, rest_kont)
|
|
c._cek_data = {"captured": captured, "rest-kont": rest_kont}
|
|
return c
|
|
|
|
env["make-cek-continuation"] = _make_cek_continuation_with_data
|
|
env["continuation-data"] = lambda c: getattr(c, '_cek_data', {})
|
|
|
|
# Register platform functions from sx_ref that cek.sx and eval.sx need
|
|
# These are normally available as transpiled Python but need to be in the
|
|
# SX env when interpreting .sx files directly.
|
|
|
|
# Type predicates and constructors
|
|
env["callable?"] = lambda x: callable(x) or isinstance(x, (Lambda, Component, Island, Continuation))
|
|
env["lambda?"] = lambda x: isinstance(x, Lambda)
|
|
env["component?"] = lambda x: isinstance(x, Component)
|
|
env["island?"] = lambda x: isinstance(x, Island)
|
|
env["macro?"] = lambda x: isinstance(x, Macro)
|
|
env["thunk?"] = sx_ref.is_thunk
|
|
env["thunk-expr"] = sx_ref.thunk_expr
|
|
env["thunk-env"] = sx_ref.thunk_env
|
|
env["make-thunk"] = sx_ref.make_thunk
|
|
env["make-lambda"] = sx_ref.make_lambda
|
|
env["make-component"] = sx_ref.make_component
|
|
env["make-island"] = sx_ref.make_island
|
|
env["make-macro"] = sx_ref.make_macro
|
|
env["make-symbol"] = lambda n: Symbol(n)
|
|
env["lambda-params"] = lambda f: f.params
|
|
env["lambda-body"] = lambda f: f.body
|
|
env["lambda-closure"] = lambda f: f.closure
|
|
env["lambda-name"] = lambda f: f.name
|
|
env["set-lambda-name!"] = lambda f, n: setattr(f, 'name', n) or NIL
|
|
env["component-params"] = lambda c: c.params
|
|
env["component-body"] = lambda c: c.body
|
|
env["component-closure"] = lambda c: c.closure
|
|
env["component-has-children?"] = lambda c: c.has_children
|
|
env["component-affinity"] = lambda c: getattr(c, 'affinity', 'auto')
|
|
env["component-set-param-types!"] = lambda c, t: setattr(c, 'param_types', t) or NIL
|
|
env["macro-params"] = lambda m: m.params
|
|
env["macro-rest-param"] = lambda m: m.rest_param
|
|
env["macro-body"] = lambda m: m.body
|
|
env["macro-closure"] = lambda m: m.closure
|
|
env["env-merge"] = env_merge
|
|
env["symbol-name"] = lambda s: s.name if isinstance(s, Symbol) else str(s)
|
|
env["keyword-name"] = lambda k: k.name if isinstance(k, Keyword) else str(k)
|
|
env["type-of"] = sx_ref.type_of
|
|
env["primitive?"] = lambda n: n in sx_ref.PRIMITIVES
|
|
env["get-primitive"] = lambda n: sx_ref.PRIMITIVES.get(n)
|
|
env["strip-prefix"] = lambda s, p: s[len(p):] if s.startswith(p) else s
|
|
env["inspect"] = repr
|
|
env["debug-log"] = lambda *args: None
|
|
env["error"] = sx_ref.error
|
|
env["apply"] = lambda f, args: f(*args)
|
|
|
|
# Functions from eval.sx that cek.sx references
|
|
env["trampoline"] = trampoline
|
|
env["eval-expr"] = eval_expr
|
|
env["eval-list"] = sx_ref.eval_list
|
|
env["eval-call"] = sx_ref.eval_call
|
|
env["call-lambda"] = sx_ref.call_lambda
|
|
env["call-component"] = sx_ref.call_component
|
|
env["parse-keyword-args"] = sx_ref.parse_keyword_args
|
|
env["sf-lambda"] = sx_ref.sf_lambda
|
|
env["sf-defcomp"] = sx_ref.sf_defcomp
|
|
env["sf-defisland"] = sx_ref.sf_defisland
|
|
env["sf-defmacro"] = sx_ref.sf_defmacro
|
|
env["sf-defstyle"] = sx_ref.sf_defstyle
|
|
env["sf-deftype"] = sx_ref.sf_deftype
|
|
env["sf-defeffect"] = sx_ref.sf_defeffect
|
|
env["sf-letrec"] = sx_ref.sf_letrec
|
|
env["sf-named-let"] = sx_ref.sf_named_let
|
|
env["sf-dynamic-wind"] = sx_ref.sf_dynamic_wind
|
|
env["sf-scope"] = sx_ref.sf_scope
|
|
env["sf-provide"] = sx_ref.sf_provide
|
|
env["qq-expand"] = sx_ref.qq_expand
|
|
env["expand-macro"] = sx_ref.expand_macro
|
|
env["cond-scheme?"] = sx_ref.cond_scheme_p
|
|
|
|
# Higher-order form handlers
|
|
env["ho-map"] = sx_ref.ho_map
|
|
env["ho-map-indexed"] = sx_ref.ho_map_indexed
|
|
env["ho-filter"] = sx_ref.ho_filter
|
|
env["ho-reduce"] = sx_ref.ho_reduce
|
|
env["ho-some"] = sx_ref.ho_some
|
|
env["ho-every"] = sx_ref.ho_every
|
|
env["ho-for-each"] = sx_ref.ho_for_each
|
|
env["call-fn"] = sx_ref.call_fn
|
|
|
|
# Render-related (stub for testing — no active rendering)
|
|
env["render-active?"] = lambda: False
|
|
env["is-render-expr?"] = lambda expr: False
|
|
env["render-expr"] = lambda expr, env: NIL
|
|
|
|
# Scope primitives
|
|
env["scope-push!"] = sx_ref.PRIMITIVES.get("scope-push!", lambda *a: NIL)
|
|
env["scope-pop!"] = sx_ref.PRIMITIVES.get("scope-pop!", lambda *a: NIL)
|
|
env["context"] = sx_ref.PRIMITIVES.get("context", lambda *a: NIL)
|
|
env["emit!"] = sx_ref.PRIMITIVES.get("emit!", lambda *a: NIL)
|
|
env["emitted"] = sx_ref.PRIMITIVES.get("emitted", lambda *a: [])
|
|
|
|
# Dynamic wind
|
|
env["push-wind!"] = lambda before, after: NIL
|
|
env["pop-wind!"] = lambda: NIL
|
|
env["call-thunk"] = lambda f, e: f() if callable(f) else trampoline(eval_expr([f], e))
|
|
|
|
# Mutation helpers used by parse-keyword-args etc
|
|
env["dict-get"] = lambda d, k: d.get(k, NIL) if isinstance(d, dict) else NIL
|
|
|
|
# defhandler, defpage, defquery, defaction — these are registrations
|
|
# Use the bootstrapped versions if they exist, otherwise stub
|
|
for name in ["sf-defhandler", "sf-defpage", "sf-defquery", "sf-defaction"]:
|
|
pyname = name.replace("-", "_")
|
|
fn = getattr(sx_ref, pyname, None)
|
|
if fn:
|
|
env[name] = fn
|
|
else:
|
|
env[name] = lambda args, e, _n=name: NIL
|
|
|
|
# Load test framework
|
|
with open(os.path.join(_HERE, "test-framework.sx")) as f:
|
|
for expr in parse_all(f.read()):
|
|
trampoline(eval_expr(expr, env))
|
|
|
|
# Load frames module
|
|
print("Loading frames.sx ...")
|
|
with open(os.path.join(_HERE, "frames.sx")) as f:
|
|
for expr in parse_all(f.read()):
|
|
trampoline(eval_expr(expr, env))
|
|
|
|
# Load CEK module
|
|
print("Loading cek.sx ...")
|
|
with open(os.path.join(_HERE, "cek.sx")) as f:
|
|
for expr in parse_all(f.read()):
|
|
trampoline(eval_expr(expr, env))
|
|
|
|
# Define cek-eval helper in SX
|
|
for expr in parse_all("""
|
|
(define cek-eval
|
|
(fn (source)
|
|
(let ((exprs (sx-parse source)))
|
|
(let ((result nil))
|
|
(for-each (fn (e) (set! result (eval-expr-cek e (test-env)))) exprs)
|
|
result))))
|
|
"""):
|
|
trampoline(eval_expr(expr, env))
|
|
|
|
# Run tests
|
|
print("=" * 60)
|
|
print("Running test-cek.sx")
|
|
print("=" * 60)
|
|
|
|
with open(os.path.join(_HERE, "test-cek.sx")) as f:
|
|
for expr in parse_all(f.read()):
|
|
trampoline(eval_expr(expr, env))
|
|
|
|
print("=" * 60)
|
|
print(f"Results: {_pass_count} passed, {_fail_count} failed")
|
|
print("=" * 60)
|
|
sys.exit(1 if _fail_count > 0 else 0)
|