Delete sx_ref.py — OCaml is the sole SX evaluator

Removes the 5993-line bootstrapped Python evaluator (sx_ref.py) and all
code that depended on it exclusively. Both bootstrappers (JS + OCaml)
now use a new synchronous OCaml bridge (ocaml_sync.py) to run the
transpiler. JS build produces identical output; OCaml bootstrap produces
byte-identical sx_ref.ml.

Key changes:
- New shared/sx/ocaml_sync.py: sync subprocess bridge to sx_server.exe
- hosts/javascript/bootstrap.py: serialize defines → temp file → OCaml eval
- hosts/ocaml/bootstrap.py: same pattern for OCaml transpiler
- shared/sx/{html,async_eval,resolver,jinja_bridge,handlers,pages,deps,helpers}:
  stub or remove sx_ref imports; runtime uses OCaml bridge (SX_USE_OCAML=1)
- sx/sxc/pages: parse defpage/defhandler from AST instead of Python eval
- hosts/ocaml/lib/sx_primitives.ml: append handles non-list 2nd arg per spec
- Deleted: sx_ref.py, async_eval_ref.py, 6 Python test runners, misc ref/ files

Test results: JS 1078/1078, OCaml 1114/1114.
sx_docs SSR has pre-existing rendering issues to investigate separately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 14:32:55 +00:00
parent 482bc0ca5e
commit d735e28b39
30 changed files with 400 additions and 9239 deletions

View File

@@ -1,251 +0,0 @@
#!/usr/bin/env python3
"""Run test-cek-reactive.sx — tests for deref-as-shift reactive rendering."""
from __future__ import annotations
import os, sys
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
_SPEC_TESTS = os.path.join(_PROJECT, "spec", "tests")
_WEB_TESTS = os.path.join(_PROJECT, "web", "tests")
sys.path.insert(0, _PROJECT)
sys.setrecursionlimit(20000)
from shared.sx.parser import parse_all
from shared.sx.ref import sx_ref
from shared.sx.ref.sx_ref import (
make_env, env_get, env_has, env_set,
env_extend, env_merge,
)
# Use tree-walk evaluator for interpreting .sx test files.
# The CEK override (eval_expr = cek_run) would cause the interpreted cek.sx
# to delegate to the transpiled CEK, not the interpreted one being tested.
# Override both the local names AND the module-level names so that transpiled
# functions (ho_map, call_lambda, etc.) also use tree-walk internally.
eval_expr = sx_ref._tree_walk_eval_expr
trampoline = sx_ref._tree_walk_trampoline
sx_ref.eval_expr = eval_expr
sx_ref.trampoline = trampoline
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_with_data(captured, rest_kont):
c = Continuation(lambda v=NIL: v)
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', {})
# 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?"] = sx_ref.is_primitive
env["get-primitive"] = sx_ref.get_primitive
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 (needed for reactive-shift-deref island cleanup)
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
env["dict-get"] = lambda d, k: d.get(k, NIL) if isinstance(d, dict) else NIL
env["identical?"] = lambda a, b: a is b
# defhandler, defpage, defquery, defaction stubs
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(_SPEC_TESTS, "test-framework.sx")) as f:
for expr in parse_all(f.read()):
trampoline(eval_expr(expr, env))
# Load signals module
print("Loading signals.sx ...")
with open(os.path.join(_PROJECT, "web", "signals.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(_PROJECT, "spec", "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(_PROJECT, "spec", "cek.sx")) as f:
for expr in parse_all(f.read()):
trampoline(eval_expr(expr, env))
# Run tests
print("=" * 60)
print("Running test-cek-reactive.sx")
print("=" * 60)
with open(os.path.join(_WEB_TESTS, "test-cek-reactive.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)

View File

@@ -1,276 +0,0 @@
#!/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, "..", "..", ".."))
_SPEC_TESTS = os.path.join(_PROJECT, "spec", "tests")
_WEB_TESTS = os.path.join(_PROJECT, "web", "tests")
sys.path.insert(0, _PROJECT)
from shared.sx.ref.sx_ref import sx_parse as parse_all
from shared.sx.ref import sx_ref
from shared.sx.ref.sx_ref import (
make_env, env_get, env_has, env_set,
env_extend, env_merge,
)
# Use tree-walk evaluator for interpreting .sx test files.
# The CEK override (eval_expr = cek_run) would cause the interpreted cek.sx
# to delegate to the transpiled CEK, not the interpreted one being tested.
# Override both the local names AND the module-level names so that transpiled
# functions (ho_map, call_lambda, etc.) also use tree-walk internally.
eval_expr = sx_ref._tree_walk_eval_expr
trampoline = sx_ref._tree_walk_trampoline
sx_ref.eval_expr = eval_expr
sx_ref.trampoline = trampoline
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-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 dispatch globals — evaluator checks *render-check* and *render-fn*
env["*render-check*"] = NIL
env["*render-fn*"] = NIL
# Custom special forms registry — modules register forms at load time
env["*custom-special-forms*"] = {}
def _register_special_form(name, handler):
env["*custom-special-forms*"][name] = handler
return NIL
env["register-special-form!"] = _register_special_form
# is-else-clause? — check if a cond/case test is an else marker
def _is_else_clause(test):
if isinstance(test, Keyword) and test.name == "else":
return True
if isinstance(test, Symbol) and test.name in ("else", ":else"):
return True
return False
env["is-else-clause?"] = _is_else_clause
# 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
# defstyle, defhandler, defpage, defquery, defaction — now registered via
# register-special-form! by forms.sx at load time. Stub them here in case
# forms.sx is not loaded (CEK tests don't load it).
for form_name in ["defstyle", "defhandler", "defpage", "defquery", "defaction"]:
if form_name not in env["*custom-special-forms*"]:
env["*custom-special-forms*"][form_name] = lambda args, e, _n=form_name: NIL
# Load test framework
with open(os.path.join(_SPEC_TESTS, "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(_PROJECT, "spec", "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(_PROJECT, "spec", "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(_SPEC_TESTS, "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)

View File

@@ -1,108 +0,0 @@
#!/usr/bin/env python3
"""Run test-continuations.sx using the bootstrapped evaluator with continuations enabled."""
from __future__ import annotations
import os, sys, subprocess, tempfile
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
_SPEC_TESTS = os.path.join(_PROJECT, "spec", "tests")
_WEB_TESTS = os.path.join(_PROJECT, "web", "tests")
sys.path.insert(0, _PROJECT)
# Bootstrap a fresh sx_ref with continuations enabled
print("Bootstrapping with --extensions continuations ...")
result = subprocess.run(
[sys.executable, os.path.join(_HERE, "..", "bootstrap.py"),
"--extensions", "continuations"],
capture_output=True, text=True, cwd=_PROJECT,
)
if result.returncode != 0:
print("Bootstrap FAILED:")
print(result.stderr)
sys.exit(1)
# Write to temp file and import
tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False, dir=_HERE)
tmp.write(result.stdout)
tmp.close()
try:
import importlib.util
spec = importlib.util.spec_from_file_location("sx_ref_cont", tmp.name)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
finally:
os.unlink(tmp.name)
from shared.sx.types import NIL
parse_all = mod.sx_parse
# Use tree-walk evaluator for interpreting .sx test files.
# CEK is now the default, but test runners need tree-walk so that
# transpiled HO forms (ho_map, etc.) don't re-enter CEK mid-evaluation.
eval_expr = mod._tree_walk_eval_expr
trampoline = mod._tree_walk_trampoline
mod.eval_expr = eval_expr
mod.trampoline = trampoline
env = mod.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
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
# Load test framework
with open(os.path.join(_SPEC_TESTS, "test-framework.sx")) as f:
for expr in parse_all(f.read()):
trampoline(eval_expr(expr, env))
# Run tests
print("=" * 60)
print("Running test-continuations.sx")
print("=" * 60)
with open(os.path.join(_SPEC_TESTS, "test-continuations.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)

View File

@@ -1,164 +0,0 @@
#!/usr/bin/env python3
"""Run test-signals.sx using the bootstrapped evaluator with signal primitives.
Uses bootstrapped signal functions from sx_ref.py directly, patching apply
to handle SX lambdas from the interpreter (test expressions create lambdas
that need evaluator dispatch).
"""
from __future__ import annotations
import os, sys
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
_SPEC_TESTS = os.path.join(_PROJECT, "spec", "tests")
_WEB_TESTS = os.path.join(_PROJECT, "web", "tests")
sys.path.insert(0, _PROJECT)
from shared.sx.ref.sx_ref import sx_parse as parse_all
from shared.sx.ref import sx_ref
from shared.sx.ref.sx_ref import make_env, scope_push, scope_pop, sx_context
from shared.sx.types import NIL, Island, Lambda
# Use tree-walk evaluator for interpreting .sx test files.
eval_expr = sx_ref._tree_walk_eval_expr
trampoline = sx_ref._tree_walk_trampoline
sx_ref.eval_expr = eval_expr
sx_ref.trampoline = trampoline
# Build env with primitives
env = make_env()
# --- Patch apply BEFORE anything else ---
# Test expressions create SX Lambdas that bootstrapped code calls via apply.
# Patch the module-level function so all bootstrapped functions see it.
# apply is used by swap! and other forms to call functions with arg lists
def _apply(f, args):
if isinstance(f, Lambda):
return trampoline(eval_expr([f] + list(args), env))
return f(*args)
sx_ref.__dict__["apply"] = _apply
# cons needs to handle tuples from Python *args (swap! passes &rest as tuple)
_orig_cons = sx_ref.PRIMITIVES.get("cons")
def _cons(x, c):
if isinstance(c, tuple):
c = list(c)
return [x] + (c or [])
sx_ref.__dict__["cons"] = _cons
sx_ref.PRIMITIVES["cons"] = _cons
# 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
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
# Signal functions are now pure SX (transpiled into sx_ref.py from signals.sx)
# Wire both low-level dict-based signal functions and high-level API
env["identical?"] = sx_ref.is_identical
env["island?"] = lambda x: isinstance(x, Island)
# Scope primitives (used by signals.sx for reactive tracking)
env["scope-push!"] = scope_push
env["scope-pop!"] = scope_pop
env["context"] = sx_context
# Low-level signal functions (now pure SX, transpiled from signals.sx)
env["make-signal"] = sx_ref.make_signal
env["signal?"] = sx_ref.is_signal
env["signal-value"] = sx_ref.signal_value
env["signal-set-value!"] = sx_ref.signal_set_value
env["signal-subscribers"] = sx_ref.signal_subscribers
env["signal-add-sub!"] = sx_ref.signal_add_sub
env["signal-remove-sub!"] = sx_ref.signal_remove_sub
env["signal-deps"] = sx_ref.signal_deps
env["signal-set-deps!"] = sx_ref.signal_set_deps
# Bootstrapped signal functions from sx_ref.py
env["signal"] = sx_ref.signal
env["deref"] = sx_ref.deref
env["reset!"] = sx_ref.reset_b
env["swap!"] = sx_ref.swap_b
env["computed"] = sx_ref.computed
env["effect"] = sx_ref.effect
# batch has a bootstrapper issue with _batch_depth global variable access.
# Wrap it to work correctly in the test context.
def _batch(thunk):
sx_ref._batch_depth = getattr(sx_ref, '_batch_depth', 0) + 1
sx_ref.cek_call(thunk, None)
sx_ref._batch_depth -= 1
if sx_ref._batch_depth == 0:
queue = list(sx_ref._batch_queue)
sx_ref._batch_queue = []
seen = []
pending = []
for s in queue:
for sub in sx_ref.signal_subscribers(s):
if sub not in seen:
seen.append(sub)
pending.append(sub)
for sub in pending:
sub()
return NIL
env["batch"] = _batch
env["notify-subscribers"] = sx_ref.notify_subscribers
env["flush-subscribers"] = sx_ref.flush_subscribers
env["dispose-computed"] = sx_ref.dispose_computed
env["with-island-scope"] = sx_ref.with_island_scope
env["register-in-scope"] = sx_ref.register_in_scope
env["callable?"] = sx_ref.is_callable
# Load test framework
with open(os.path.join(_SPEC_TESTS, "test-framework.sx")) as f:
for expr in parse_all(f.read()):
trampoline(eval_expr(expr, env))
# Run tests
print("=" * 60)
print("Running test-signals.sx")
print("=" * 60)
with open(os.path.join(_WEB_TESTS, "test-signals.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)

View File

@@ -1,333 +0,0 @@
#!/usr/bin/env python3
"""
Run SX spec tests using the bootstrapped Python evaluator.
Usage:
python3 hosts/python/tests/run_tests.py # all spec tests
python3 hosts/python/tests/run_tests.py test-primitives # specific test
python3 hosts/python/tests/run_tests.py --full # include optional modules
"""
from __future__ import annotations
import os, sys
# Increase recursion limit for TCO tests (Python's default 1000 is too low)
sys.setrecursionlimit(5000)
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
_SPEC_TESTS = os.path.join(_PROJECT, "spec", "tests")
sys.path.insert(0, _PROJECT)
from shared.sx.ref.sx_ref import sx_parse as parse_all
from shared.sx.ref import sx_ref
from shared.sx.ref.sx_ref import (
make_env, env_get, env_has, env_set, env_extend, env_merge,
)
from shared.sx.types import (
NIL, Symbol, Keyword, Lambda, Component, Island, Macro,
)
# Use tree-walk evaluator
eval_expr = sx_ref._tree_walk_eval_expr
trampoline = sx_ref._tree_walk_trampoline
sx_ref.eval_expr = eval_expr
sx_ref.trampoline = trampoline
# Check for --full flag
full_build = "--full" in sys.argv
# Build env with primitives
env = make_env()
# ---------------------------------------------------------------------------
# Test infrastructure
# ---------------------------------------------------------------------------
_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
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
# ---------------------------------------------------------------------------
# Test helpers
# ---------------------------------------------------------------------------
def _deep_equal(a, b):
if a is b:
return True
if a is NIL and b is NIL:
return True
if a is NIL or b is NIL:
return a is None and b is NIL or b is None and a is NIL
if type(a) != type(b):
# number comparison: int vs float
if isinstance(a, (int, float)) and isinstance(b, (int, float)):
return a == b
return False
if isinstance(a, list):
if len(a) != len(b):
return False
return all(_deep_equal(x, y) for x, y in zip(a, b))
if isinstance(a, dict):
ka = {k for k in a if k != "_nil"}
kb = {k for k in b if k != "_nil"}
if ka != kb:
return False
return all(_deep_equal(a[k], b[k]) for k in ka)
return a == b
env["equal?"] = _deep_equal
env["identical?"] = lambda a, b: a is b
def _test_env():
return make_env()
def _sx_parse(source):
return parse_all(source)
def _sx_parse_one(source):
exprs = parse_all(source)
return exprs[0] if exprs else NIL
env["test-env"] = _test_env
env["sx-parse"] = _sx_parse
env["sx-parse-one"] = _sx_parse_one
env["cek-eval"] = lambda s: trampoline(eval_expr(parse_all(s)[0], make_env())) if parse_all(s) else NIL
env["eval-expr-cek"] = lambda expr, e=None: trampoline(eval_expr(expr, e or env))
# Env operations
env["env-get"] = env_get
env["env-has?"] = env_has
env["env-set!"] = env_set
env["env-bind!"] = lambda e, k, v: e.__setitem__(k, v) or v
env["env-extend"] = env_extend
env["env-merge"] = env_merge
# Missing primitives
env["upcase"] = lambda s: str(s).upper()
env["downcase"] = lambda s: str(s).lower()
env["make-keyword"] = lambda name: Keyword(name)
env["make-symbol"] = lambda name: Symbol(name)
env["string-length"] = lambda s: len(str(s))
env["dict-get"] = lambda d, k: d.get(k, NIL) if isinstance(d, dict) else NIL
env["apply"] = lambda f, *args: f(*args[-1]) if args and isinstance(args[-1], list) else f()
# Render helpers
def _render_html(src, e=None):
if isinstance(src, str):
parsed = parse_all(src)
if not parsed:
return ""
expr = parsed[0] if len(parsed) == 1 else [Symbol("do")] + parsed
result = sx_ref.render_to_html(expr, e or make_env())
# Reset render mode
sx_ref._render_mode = False
return result
result = sx_ref.render_to_html(src, e or env)
sx_ref._render_mode = False
return result
env["render-html"] = _render_html
env["render-to-html"] = _render_html
env["string-contains?"] = lambda s, sub: str(sub) in str(s)
# Type system helpers
env["test-prim-types"] = lambda: {
"+": "number", "-": "number", "*": "number", "/": "number",
"mod": "number", "inc": "number", "dec": "number",
"abs": "number", "min": "number", "max": "number",
"str": "string", "upper": "string", "lower": "string",
"trim": "string", "join": "string", "replace": "string",
"=": "boolean", "<": "boolean", ">": "boolean",
"<=": "boolean", ">=": "boolean",
"not": "boolean", "nil?": "boolean", "empty?": "boolean",
"number?": "boolean", "string?": "boolean", "boolean?": "boolean",
"list?": "boolean", "dict?": "boolean",
"contains?": "boolean", "has-key?": "boolean",
"starts-with?": "boolean", "ends-with?": "boolean",
"len": "number", "first": "any", "rest": "list",
"last": "any", "nth": "any", "cons": "list",
"append": "list", "concat": "list", "reverse": "list",
"sort": "list", "slice": "list", "range": "list",
"flatten": "list", "keys": "list", "vals": "list",
"assoc": "dict", "dissoc": "dict", "merge": "dict", "dict": "dict",
"get": "any", "type-of": "string",
}
env["test-prim-param-types"] = lambda: {
"+": {"positional": [["a", "number"]], "rest-type": "number"},
"-": {"positional": [["a", "number"]], "rest-type": "number"},
"*": {"positional": [["a", "number"]], "rest-type": "number"},
"/": {"positional": [["a", "number"]], "rest-type": "number"},
"inc": {"positional": [["n", "number"]], "rest-type": NIL},
"dec": {"positional": [["n", "number"]], "rest-type": NIL},
"upper": {"positional": [["s", "string"]], "rest-type": NIL},
"lower": {"positional": [["s", "string"]], "rest-type": NIL},
"keys": {"positional": [["d", "dict"]], "rest-type": NIL},
"vals": {"positional": [["d", "dict"]], "rest-type": NIL},
}
env["component-param-types"] = lambda c: getattr(c, "_param_types", NIL)
env["component-set-param-types!"] = lambda c, t: setattr(c, "_param_types", t) or NIL
env["component-params"] = lambda c: c.params
env["component-body"] = lambda c: c.body
env["component-has-children"] = lambda c: c.has_children
env["component-affinity"] = lambda c: getattr(c, "affinity", "auto")
# Type accessors
env["callable?"] = lambda x: callable(x) or isinstance(x, (Lambda, Component, Island))
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-macro"] = sx_ref.make_macro
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-closure"] = lambda c: c.closure
env["component-name"] = lambda c: c.name
env["component-has-children?"] = lambda c: c.has_children
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["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["sx-serialize"] = sx_ref.sx_serialize if hasattr(sx_ref, "sx_serialize") else lambda x: str(x)
# Render dispatch globals — evaluator checks *render-check* and *render-fn*
env["*render-check*"] = NIL
env["*render-fn*"] = NIL
# Custom special forms registry — modules register forms at load time
env["*custom-special-forms*"] = {}
def _register_special_form(name, handler):
env["*custom-special-forms*"][name] = handler
return NIL
env["register-special-form!"] = _register_special_form
# is-else-clause? — check if a cond/case test is an else marker
def _is_else_clause(test):
if isinstance(test, Keyword) and test.name == "else":
return True
if isinstance(test, Symbol) and test.name in ("else", ":else"):
return True
return False
env["is-else-clause?"] = _is_else_clause
# Strict mode stubs (not yet bootstrapped to Python — no-ops for now)
env["set-strict!"] = lambda val: NIL
env["set-prim-param-types!"] = lambda types: NIL
env["value-matches-type?"] = lambda val, t: True
env["*strict*"] = False
env["primitive?"] = lambda name: name in env
env["get-primitive"] = lambda name: env.get(name, NIL)
# ---------------------------------------------------------------------------
# Load test framework
# ---------------------------------------------------------------------------
framework_src = open(os.path.join(_SPEC_TESTS, "test-framework.sx")).read()
for expr in parse_all(framework_src):
trampoline(eval_expr(expr, env))
# ---------------------------------------------------------------------------
# Determine which tests to run
# ---------------------------------------------------------------------------
args = [a for a in sys.argv[1:] if not a.startswith("--")]
# Tests requiring optional modules (only with --full)
REQUIRES_FULL = {"test-continuations.sx", "test-continuations-advanced.sx", "test-types.sx", "test-freeze.sx", "test-strict.sx", "test-cek.sx", "test-cek-advanced.sx", "test-signals-advanced.sx"}
test_files = []
if args:
for arg in args:
name = arg if arg.endswith(".sx") else f"{arg}.sx"
p = os.path.join(_SPEC_TESTS, name)
if os.path.exists(p):
test_files.append(p)
else:
print(f"Test file not found: {name}")
else:
for f in sorted(os.listdir(_SPEC_TESTS)):
if f.startswith("test-") and f.endswith(".sx") and f != "test-framework.sx":
if not full_build and f in REQUIRES_FULL:
print(f"Skipping {f} (requires --full)")
continue
test_files.append(os.path.join(_SPEC_TESTS, f))
# ---------------------------------------------------------------------------
# Run tests
# ---------------------------------------------------------------------------
for test_file in test_files:
name = os.path.basename(test_file)
print("=" * 60)
print(f"Running {name}")
print("=" * 60)
try:
src = open(test_file).read()
exprs = parse_all(src)
for expr in exprs:
trampoline(eval_expr(expr, env))
except Exception as e:
print(f"ERROR in {name}: {e}")
_fail_count += 1
# Summary
print("=" * 60)
print(f"Results: {_pass_count} passed, {_fail_count} failed")
print("=" * 60)
sys.exit(1 if _fail_count > 0 else 0)

View File

@@ -1,194 +0,0 @@
#!/usr/bin/env python3
"""Run test-types.sx using the bootstrapped evaluator with types 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, "..", "..", ".."))
_SPEC_DIR = os.path.join(_PROJECT, "spec")
_SPEC_TESTS = os.path.join(_PROJECT, "spec", "tests")
_WEB_TESTS = os.path.join(_PROJECT, "web", "tests")
sys.path.insert(0, _PROJECT)
from shared.sx.ref.sx_ref import sx_parse as parse_all
from shared.sx.ref import sx_ref
from shared.sx.ref.sx_ref import make_env, env_get, env_has, env_set
from shared.sx.types import NIL, Component
# Use tree-walk evaluator for interpreting .sx test files.
# CEK is now the default, but the test runners need tree-walk so that
# transpiled HO forms (ho_map, etc.) don't re-enter CEK mid-evaluation.
eval_expr = sx_ref._tree_walk_eval_expr
trampoline = sx_ref._tree_walk_trampoline
sx_ref.eval_expr = eval_expr
sx_ref.trampoline = trampoline
# 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)) # call the thunk
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
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
# Test fixtures — provide the functions that tests expect
# test-prim-types: dict of primitive return types for type inference
def _test_prim_types():
return {
"+": "number", "-": "number", "*": "number", "/": "number",
"mod": "number", "inc": "number", "dec": "number",
"abs": "number", "min": "number", "max": "number",
"floor": "number", "ceil": "number", "round": "number",
"str": "string", "upper": "string", "lower": "string",
"trim": "string", "join": "string", "replace": "string",
"format": "string", "substr": "string",
"=": "boolean", "<": "boolean", ">": "boolean",
"<=": "boolean", ">=": "boolean", "!=": "boolean",
"not": "boolean", "nil?": "boolean", "empty?": "boolean",
"number?": "boolean", "string?": "boolean", "boolean?": "boolean",
"list?": "boolean", "dict?": "boolean", "symbol?": "boolean",
"keyword?": "boolean", "contains?": "boolean", "has-key?": "boolean",
"starts-with?": "boolean", "ends-with?": "boolean",
"len": "number", "first": "any", "rest": "list",
"last": "any", "nth": "any", "cons": "list",
"append": "list", "concat": "list", "reverse": "list",
"sort": "list", "slice": "list", "range": "list",
"flatten": "list", "keys": "list", "vals": "list",
"map-dict": "dict", "assoc": "dict", "dissoc": "dict",
"merge": "dict", "dict": "dict",
"get": "any", "type-of": "string",
}
# test-prim-param-types: dict of primitive param type specs
# Format: {name → {"positional" [["name" "type"] ...] "rest-type" type-or-nil}}
def _test_prim_param_types():
return {
"+": {"positional": [["a", "number"]], "rest-type": "number"},
"-": {"positional": [["a", "number"]], "rest-type": "number"},
"*": {"positional": [["a", "number"]], "rest-type": "number"},
"/": {"positional": [["a", "number"]], "rest-type": "number"},
"inc": {"positional": [["n", "number"]], "rest-type": NIL},
"dec": {"positional": [["n", "number"]], "rest-type": NIL},
"upper": {"positional": [["s", "string"]], "rest-type": NIL},
"lower": {"positional": [["s", "string"]], "rest-type": NIL},
"keys": {"positional": [["d", "dict"]], "rest-type": NIL},
"vals": {"positional": [["d", "dict"]], "rest-type": NIL},
}
# test-env: returns a fresh env for use in tests (same as the test env)
def _test_env():
return env
# sx-parse: parse an SX string and return list of AST nodes
def _sx_parse(source):
return parse_all(source)
# dict-get: used in some legacy tests
def _dict_get(d, k):
v = d.get(k) if isinstance(d, dict) else NIL
return v if v is not None else NIL
# component-set-param-types! and component-param-types: type annotation accessors
def _component_set_param_types(comp, types_dict):
comp.param_types = types_dict
return NIL
def _component_param_types(comp):
return getattr(comp, 'param_types', NIL)
# Platform functions used by types.sx but not SX primitives
def _component_params(c):
return c.params
def _component_body(c):
return c.body
def _component_has_children(c):
return c.has_children
def _map_dict(fn, d):
from shared.sx.types import Lambda as _Lambda
result = {}
for k, v in d.items():
if isinstance(fn, _Lambda):
# Call SX lambda through the evaluator
result[k] = trampoline(eval_expr([fn, k, v], env))
else:
result[k] = fn(k, v)
return result
env["test-prim-types"] = _test_prim_types
env["test-prim-param-types"] = _test_prim_param_types
env["test-env"] = _test_env
env["sx-parse"] = _sx_parse
env["dict-get"] = _dict_get
env["component-set-param-types!"] = _component_set_param_types
env["component-param-types"] = _component_param_types
env["component-params"] = _component_params
env["component-body"] = _component_body
env["component-has-children"] = _component_has_children
env["map-dict"] = _map_dict
env["env-get"] = env_get
env["env-has?"] = env_has
env["env-set!"] = env_set
# Load test framework (macros + assertion helpers)
with open(os.path.join(_SPEC_TESTS, "test-framework.sx")) as f:
for expr in parse_all(f.read()):
trampoline(eval_expr(expr, env))
# Load types module
with open(os.path.join(_SPEC_DIR, "types.sx")) as f:
for expr in parse_all(f.read()):
trampoline(eval_expr(expr, env))
# Run tests
print("=" * 60)
print("Running test-types.sx")
print("=" * 60)
with open(os.path.join(_SPEC_TESTS, "test-types.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)