diff --git a/hosts/javascript/bootstrap.py b/hosts/javascript/bootstrap.py
index c7463bd..6afe395 100644
--- a/hosts/javascript/bootstrap.py
+++ b/hosts/javascript/bootstrap.py
@@ -20,8 +20,8 @@ _PROJECT = os.path.abspath(os.path.join(_HERE, "..", ".."))
if _PROJECT not in sys.path:
sys.path.insert(0, _PROJECT)
-from shared.sx.parser import parse_all
-from shared.sx.types import Symbol
+import tempfile
+from shared.sx.parser import serialize
from hosts.javascript.platform import (
extract_defines,
ADAPTER_FILES, ADAPTER_DEPS, SPEC_MODULES, SPEC_MODULE_ORDER, EXTENSION_NAMES,
@@ -35,29 +35,23 @@ from hosts.javascript.platform import (
)
-_js_sx_env = None # cached
+_bridge = None # cached OcamlSync instance
-def load_js_sx() -> dict:
- """Load js.sx into an evaluator environment and return it."""
- global _js_sx_env
- if _js_sx_env is not None:
- return _js_sx_env
+def _get_bridge():
+ """Get or create the OCaml sync bridge with transpiler loaded."""
+ global _bridge
+ if _bridge is not None:
+ return _bridge
+ from shared.sx.ocaml_sync import OcamlSync
+ _bridge = OcamlSync()
+ _bridge.load(os.path.join(_HERE, "transpiler.sx"))
+ return _bridge
- js_sx_path = os.path.join(_HERE, "transpiler.sx")
- with open(js_sx_path) as f:
- source = f.read()
- exprs = parse_all(source)
-
- from shared.sx.ref.sx_ref import evaluate, make_env
-
- env = make_env()
- for expr in exprs:
- evaluate(expr, env)
-
- _js_sx_env = env
- return env
+def load_js_sx():
+ """Load js.sx transpiler into the OCaml kernel. Returns the bridge."""
+ return _get_bridge()
def compile_ref_to_js(
@@ -75,16 +69,13 @@ def compile_ref_to_js(
spec_modules: List of spec modules (deps, router, signals). None = auto.
"""
from datetime import datetime, timezone
- from shared.sx.ref.sx_ref import evaluate
- ref_dir = os.path.join(_PROJECT, "shared", "sx", "ref")
- # Source directories: core spec, web framework, and legacy ref (for bootstrapper tools)
+ # Source directories: core spec and web framework
_source_dirs = [
os.path.join(_PROJECT, "spec"), # Core spec
os.path.join(_PROJECT, "web"), # Web framework
- ref_dir, # Legacy location (fallback)
]
- env = load_js_sx()
+ bridge = _get_bridge()
# Resolve adapter set
if adapters is None:
@@ -219,11 +210,16 @@ def compile_ref_to_js(
sx_defines = [[name, expr] for name, expr in defines]
parts.append(f"\n // === Transpiled from {label} ===\n")
- env["_defines"] = sx_defines
- result = evaluate(
- [Symbol("js-translate-file"), Symbol("_defines")],
- env,
- )
+ # Serialize defines to SX, write to temp file, load into OCaml kernel
+ defines_sx = serialize(sx_defines)
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".sx", delete=False) as tmp:
+ tmp.write(f"(define _defines \'{defines_sx})\n")
+ tmp_path = tmp.name
+ try:
+ bridge.load(tmp_path)
+ finally:
+ os.unlink(tmp_path)
+ result = bridge.eval("(js-translate-file _defines)")
parts.append(result)
# Platform JS for selected adapters
diff --git a/hosts/ocaml/bootstrap.py b/hosts/ocaml/bootstrap.py
index 236530b..45feb81 100644
--- a/hosts/ocaml/bootstrap.py
+++ b/hosts/ocaml/bootstrap.py
@@ -90,18 +90,17 @@ let cek_run_iterative state =
def compile_spec_to_ml(spec_dir: str | None = None) -> str:
"""Compile the SX spec to OCaml source."""
- from shared.sx.ref.sx_ref import eval_expr, trampoline, make_env, sx_parse
+ import tempfile
+ from shared.sx.ocaml_sync import OcamlSync
+ from shared.sx.parser import serialize
if spec_dir is None:
spec_dir = os.path.join(_PROJECT, "spec")
- # Load the transpiler
- env = make_env()
+ # Load the transpiler into OCaml kernel
+ bridge = OcamlSync()
transpiler_path = os.path.join(_HERE, "transpiler.sx")
- with open(transpiler_path) as f:
- transpiler_src = f.read()
- for expr in sx_parse(transpiler_src):
- trampoline(eval_expr(expr, env))
+ bridge.load(transpiler_path)
# Spec files to transpile (in dependency order)
# stdlib.sx functions are already registered as OCaml primitives —
@@ -138,21 +137,29 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str:
seen[n] = i
defines = [(n, e) for i, (n, e) in enumerate(defines) if seen[n] == i]
- # Build the defines list for the transpiler
+ # Build the defines list and known names for the transpiler
defines_list = [[name, expr] for name, expr in defines]
- env["_defines"] = defines_list
+ known_names = [name for name, _ in defines]
- # Pass known define names so the transpiler can distinguish
- # static (OCaml fn) calls from dynamic (SX value) calls
- env["_known_defines"] = [name for name, _ in defines]
+ # Serialize defines + known names to temp file, load into kernel
+ defines_sx = serialize(defines_list)
+ known_sx = serialize(known_names)
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".sx", delete=False) as tmp:
+ tmp.write(f"(define _defines \'{defines_sx})\n")
+ tmp.write(f"(define _known_defines \'{known_sx})\n")
+ tmp_path = tmp.name
+ try:
+ bridge.load(tmp_path)
+ finally:
+ os.unlink(tmp_path)
# Call ml-translate-file — emits as single let rec block
- translate_expr = sx_parse("(ml-translate-file _defines)")[0]
- result = trampoline(eval_expr(translate_expr, env))
+ result = bridge.eval("(ml-translate-file _defines)")
parts.append(f"\n(* === Transpiled from {label} === *)\n")
parts.append(result)
+ bridge.stop()
parts.append(FIXUPS)
output = "\n".join(parts)
diff --git a/hosts/ocaml/lib/sx_primitives.ml b/hosts/ocaml/lib/sx_primitives.ml
index 51a480a..2a27dcb 100644
--- a/hosts/ocaml/lib/sx_primitives.ml
+++ b/hosts/ocaml/lib/sx_primitives.ml
@@ -353,8 +353,16 @@ let () =
| [x; Nil] -> List [x]
| _ -> raise (Eval_error "cons: value and list"));
register "append" (fun args ->
- let all = List.concat_map (fun a -> as_list a) args in
- List all);
+ match args with
+ | [List la | ListRef { contents = la }; List lb | ListRef { contents = lb }] ->
+ List (la @ lb)
+ | [List la | ListRef { contents = la }; Nil] -> List la
+ | [Nil; List lb | ListRef { contents = lb }] -> List lb
+ | [List la | ListRef { contents = la }; v] -> List (la @ [v])
+ | [v; List lb | ListRef { contents = lb }] -> List ([v] @ lb)
+ | _ ->
+ let all = List.concat_map as_list args in
+ List all);
register "reverse" (fun args ->
match args with
| [List l] | [ListRef { contents = l }] -> List (List.rev l)
diff --git a/hosts/python/tests/run_cek_reactive_tests.py b/hosts/python/tests/run_cek_reactive_tests.py
deleted file mode 100644
index 0855371..0000000
--- a/hosts/python/tests/run_cek_reactive_tests.py
+++ /dev/null
@@ -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)
diff --git a/hosts/python/tests/run_cek_tests.py b/hosts/python/tests/run_cek_tests.py
deleted file mode 100644
index 20ffbc8..0000000
--- a/hosts/python/tests/run_cek_tests.py
+++ /dev/null
@@ -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)
diff --git a/hosts/python/tests/run_continuation_tests.py b/hosts/python/tests/run_continuation_tests.py
deleted file mode 100644
index 5822878..0000000
--- a/hosts/python/tests/run_continuation_tests.py
+++ /dev/null
@@ -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)
diff --git a/hosts/python/tests/run_signal_tests.py b/hosts/python/tests/run_signal_tests.py
deleted file mode 100644
index 668bc88..0000000
--- a/hosts/python/tests/run_signal_tests.py
+++ /dev/null
@@ -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)
diff --git a/hosts/python/tests/run_tests.py b/hosts/python/tests/run_tests.py
deleted file mode 100644
index 0324589..0000000
--- a/hosts/python/tests/run_tests.py
+++ /dev/null
@@ -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)
diff --git a/hosts/python/tests/run_type_tests.py b/hosts/python/tests/run_type_tests.py
deleted file mode 100644
index d7b0806..0000000
--- a/hosts/python/tests/run_type_tests.py
+++ /dev/null
@@ -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)
diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js
index e922a28..fe97fa3 100644
--- a/shared/static/scripts/sx-browser.js
+++ b/shared/static/scripts/sx-browser.js
@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
- var SX_VERSION = "2026-03-24T13:30:18Z";
+ var SX_VERSION = "2026-03-24T14:17:24Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -2509,13 +2509,11 @@ PRIMITIVES["WEB_FORM_NAMES"] = WEB_FORM_NAMES;
var sxParse = function(source) { return (function() {
var pos = 0;
var lenSrc = len(source);
- var skipComment = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && !isSxTruthy((nth(source, pos) == "\
-"))))) { pos = (pos + 1);
+ var skipComment = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && !isSxTruthy((nth(source, pos) == "\n"))))) { pos = (pos + 1);
continue; } else { return NIL; } } };
PRIMITIVES["skip-comment"] = skipComment;
var skipWs = function() { while(true) { if (isSxTruthy((pos < lenSrc))) { { var ch = nth(source, pos);
-if (isSxTruthy(sxOr((ch == " "), (ch == "\ "), (ch == "\
-"), (ch == "\
")))) { pos = (pos + 1);
+if (isSxTruthy(sxOr((ch == " "), (ch == "\t"), (ch == "\n"), (ch == "\r")))) { pos = (pos + 1);
continue; } else if (isSxTruthy((ch == ";"))) { pos = (pos + 1);
skipComment();
continue; } else { return NIL; } } } else { return NIL; } } };
@@ -2539,8 +2537,7 @@ var _ = (pos = (pos + 1));
var d3 = hexDigitValue(nth(source, pos));
var _ = (pos = (pos + 1));
buf = (String(buf) + String(charFromCode(((((d0 * 4096) + (d1 * 256)) + (d2 * 16)) + d3))));
-continue; } } else { buf = (String(buf) + String((isSxTruthy((esc == "n")) ? "\
-" : (isSxTruthy((esc == "t")) ? "\ " : (isSxTruthy((esc == "r")) ? "\
" : esc)))));
+continue; } } else { buf = (String(buf) + String((isSxTruthy((esc == "n")) ? "\n" : (isSxTruthy((esc == "t")) ? "\t" : (isSxTruthy((esc == "r")) ? "\r" : esc)))));
pos = (pos + 1);
continue; } } } else { buf = (String(buf) + String(ch));
pos = (pos + 1);
@@ -5347,8 +5344,7 @@ PRIMITIVES["build-event-detail"] = buildEventDetail;
var paramsSx = (String("(") + String(join(" ", paramStrs)) + String(")"));
var formName = (isSxTruthy((compType == "island")) ? "defisland" : "defcomp");
var affinityStr = (isSxTruthy((isSxTruthy((compType == "component")) && isSxTruthy(!isSxTruthy(isNil(affinity))) && !isSxTruthy((affinity == "auto")))) ? (String(" :affinity ") + String(affinity)) : "");
- return (String("(") + String(formName) + String(" ") + String(name) + String(" ") + String(paramsSx) + String(affinityStr) + String("\
- ") + String(bodySx) + String(")"));
+ return (String("(") + String(formName) + String(" ") + String(name) + String(" ") + String(paramsSx) + String(affinityStr) + String("\n ") + String(bodySx) + String(")"));
})());
})(); };
PRIMITIVES["build-component-source"] = buildComponentSource;
diff --git a/shared/sx/__init__.py b/shared/sx/__init__.py
index 9322cfb..2dbb62f 100644
--- a/shared/sx/__init__.py
+++ b/shared/sx/__init__.py
@@ -32,7 +32,6 @@ from .parser import (
serialize,
)
from .types import EvalError
-from .ref.sx_ref import evaluate, make_env
from .primitives import (
all_primitives,
diff --git a/shared/sx/async_eval.py b/shared/sx/async_eval.py
index 78c8e52..305a03b 100644
--- a/shared/sx/async_eval.py
+++ b/shared/sx/async_eval.py
@@ -53,7 +53,9 @@ from .types import Component, Island, Keyword, Lambda, Macro, NIL, Symbol
_expand_components: contextvars.ContextVar[bool] = contextvars.ContextVar(
"_expand_components", default=False
)
-from .ref.sx_ref import expand_macro as _expand_macro
+# sx_ref.py removed — stub so module loads. OCaml bridge handles macro expansion.
+def _expand_macro(*a, **kw):
+ raise RuntimeError("sx_ref.py has been removed — use SX_USE_OCAML=1")
from .types import EvalError
from .primitives import _PRIMITIVES
from .primitives_io import IO_PRIMITIVES, RequestContext, execute_io
diff --git a/shared/sx/deps.py b/shared/sx/deps.py
index f8d681a..0de5557 100644
--- a/shared/sx/deps.py
+++ b/shared/sx/deps.py
@@ -14,7 +14,7 @@ from .types import Component, Island, Macro, Symbol
def _use_ref() -> bool:
- return os.environ.get("SX_USE_REF") == "1"
+ return False # sx_ref.py removed — always use fallback
# ---------------------------------------------------------------------------
@@ -209,9 +209,17 @@ def page_render_plan(page_sx: str, env: dict[str, Any], io_names: set[str] | Non
"""
if io_names is None:
io_names = get_all_io_names()
- from .ref.sx_ref import page_render_plan as _ref_prp
- plan = _ref_prp(page_sx, env, list(io_names))
- return plan
+ # Use fallback implementation (sx_ref.py removed)
+ needed = _components_needed_fallback(page_sx, env)
+ server, client, io_deps = [], [], []
+ for name in needed:
+ comp = env.get(name)
+ if comp and hasattr(comp, 'io_refs') and comp.io_refs:
+ client.append(name)
+ else:
+ server.append(name)
+ return {"components": {n: ("server" if n in server else "client") for n in needed},
+ "server": server, "client": client, "io-deps": io_deps}
def get_all_io_names() -> set[str]:
diff --git a/shared/sx/handlers.py b/shared/sx/handlers.py
index 7504e9c..fb77c77 100644
--- a/shared/sx/handlers.py
+++ b/shared/sx/handlers.py
@@ -80,30 +80,76 @@ def clear_handlers(service: str | None = None) -> None:
# Loading — parse .sx files and collect HandlerDef instances
# ---------------------------------------------------------------------------
+def _parse_defhandler(expr: list) -> HandlerDef | None:
+ """Extract HandlerDef from a (defhandler name :path ... (&key ...) body) form."""
+ from .types import Keyword
+ if len(expr) < 3:
+ return None
+ name = expr[1].name if hasattr(expr[1], 'name') else str(expr[1])
+
+ # Parse keyword options and find params/body
+ path = None
+ method = "get"
+ csrf = True
+ returns = "element"
+ params_list = None
+ body = None
+
+ i = 2
+ while i < len(expr):
+ item = expr[i]
+ if isinstance(item, Keyword) and i + 1 < len(expr):
+ kn = item.name
+ val = expr[i + 1]
+ if kn == "path":
+ path = val if isinstance(val, str) else str(val)
+ elif kn == "method":
+ method = val.name if hasattr(val, 'name') else str(val)
+ elif kn == "csrf":
+ csrf = val not in (False, "false")
+ elif kn == "returns":
+ returns = val if isinstance(val, str) else str(val)
+ i += 2
+ elif isinstance(item, list) and not params_list:
+ # This is the params list (&key ...)
+ params_list = item
+ i += 1
+ else:
+ body = item
+ i += 1
+
+ param_names = []
+ if params_list:
+ for p in params_list:
+ if hasattr(p, 'name') and p.name not in ("&key", "&rest"):
+ param_names.append(p.name)
+
+ return HandlerDef(
+ name=name, params=param_names, body=body or [],
+ path=path, method=method, csrf=csrf, returns=returns,
+ )
+
+
def load_handler_file(filepath: str, service_name: str) -> list[HandlerDef]:
"""Parse an .sx file, evaluate it, and register any HandlerDef values."""
from .parser import parse_all
import os
- from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline
- _eval = lambda expr, env: _trampoline(_raw_eval(expr, env))
from .jinja_bridge import get_component_env
with open(filepath, encoding="utf-8") as f:
source = f.read()
- # Seed env with component definitions so handlers can reference components
- env = dict(get_component_env())
+ # Parse defhandler forms from the AST to extract handler registration info
exprs = parse_all(source)
handlers: list[HandlerDef] = []
for expr in exprs:
- _eval(expr, env)
-
- # Collect all HandlerDef values from the env
- for key, val in env.items():
- if isinstance(val, HandlerDef):
- register_handler(service_name, val)
- handlers.append(val)
+ if (isinstance(expr, list) and expr
+ and hasattr(expr[0], 'name') and expr[0].name == "defhandler"):
+ hd = _parse_defhandler(expr)
+ if hd:
+ register_handler(service_name, hd)
+ handlers.append(hd)
return handlers
diff --git a/shared/sx/helpers.py b/shared/sx/helpers.py
index 71d51b1..4c4546b 100644
--- a/shared/sx/helpers.py
+++ b/shared/sx/helpers.py
@@ -448,10 +448,7 @@ async def render_to_html(__name: str, **kwargs: Any) -> str:
"""
from .jinja_bridge import get_component_env, _get_request_context
import os
- if os.environ.get("SX_USE_REF") == "1":
- from .ref.async_eval_ref import async_render
- else:
- from .async_eval import async_render
+ from .async_eval import async_render
ast = _build_component_ast(__name, **kwargs)
env = dict(get_component_env())
diff --git a/shared/sx/html.py b/shared/sx/html.py
index 694a03f..14361c1 100644
--- a/shared/sx/html.py
+++ b/shared/sx/html.py
@@ -28,7 +28,12 @@ import contextvars
from typing import Any
from .types import Component, Island, Keyword, Lambda, Macro, NIL, Symbol
-from .ref.sx_ref import eval_expr as _raw_eval, call_component as _raw_call_component, expand_macro as _expand_macro, trampoline as _trampoline
+# sx_ref.py removed — these stubs exist so the module loads.
+# With SX_USE_OCAML=1, rendering goes through the OCaml bridge; these
+# are only called if a service falls back to Python-side rendering.
+def _not_available(*a, **kw):
+ raise RuntimeError("sx_ref.py has been removed — use SX_USE_OCAML=1")
+_raw_eval = _raw_call_component = _expand_macro = _trampoline = _not_available
def _eval(expr, env):
"""Evaluate and unwrap thunks — all html.py _eval calls are non-tail."""
diff --git a/shared/sx/jinja_bridge.py b/shared/sx/jinja_bridge.py
index 1456224..cb51e1d 100644
--- a/shared/sx/jinja_bridge.py
+++ b/shared/sx/jinja_bridge.py
@@ -30,17 +30,7 @@ from typing import Any
from .types import NIL, Component, Island, Keyword, Lambda, Macro, Symbol
from .parser import parse
-import os as _os
-if _os.environ.get("SX_USE_OCAML") == "1":
- # OCaml kernel bridge — render via persistent subprocess.
- # html_render and _render_component are set up lazily since the bridge
- # requires an async event loop. The sync sx() function falls back to
- # the ref renderer; async callers use ocaml_bridge directly.
- from .ref.sx_ref import render as html_render, render_html_component as _render_component
-elif _os.environ.get("SX_USE_REF") == "1":
- from .ref.sx_ref import render as html_render, render_html_component as _render_component
-else:
- from .html import render as html_render, _render_component
+from .html import render as html_render, _render_component
_logger = logging.getLogger("sx.bridge")
@@ -413,17 +403,28 @@ def register_components(sx_source: str, *, _defer_postprocess: bool = False) ->
When *_defer_postprocess* is True, skip deps/io_refs/hash computation.
Call ``finalize_components()`` once after all files are loaded.
"""
- from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline
- _eval = lambda expr, env: _trampoline(_raw_eval(expr, env))
from .parser import parse_all
from .css_registry import scan_classes_from_sx
# Snapshot existing component names before eval
existing = set(_COMPONENT_ENV.keys())
+ # Evaluate definitions — OCaml kernel handles everything.
+ # Python-side component registry is populated minimally for CSS/deps.
exprs = parse_all(sx_source)
for expr in exprs:
- _eval(expr, _COMPONENT_ENV)
+ if (isinstance(expr, list) and expr and isinstance(expr[0], Symbol)
+ and expr[0].name in ("defcomp", "defisland", "defmacro",
+ "define", "defstyle", "deftype",
+ "defeffect", "defrelation", "defhandler")):
+ name_sym = expr[1] if len(expr) > 1 else None
+ name = name_sym.name if hasattr(name_sym, 'name') else str(name_sym) if name_sym else None
+ if name and expr[0].name in ("defcomp", "defisland"):
+ _COMPONENT_ENV[name] = Component(
+ name=name.lstrip("~"),
+ params=[], has_children=False,
+ body=expr[-1], closure={},
+ )
# Pre-scan CSS classes for newly registered components.
all_classes: set[str] | None = None
diff --git a/shared/sx/ocaml_sync.py b/shared/sx/ocaml_sync.py
new file mode 100644
index 0000000..a8002d5
--- /dev/null
+++ b/shared/sx/ocaml_sync.py
@@ -0,0 +1,147 @@
+"""
+Synchronous OCaml bridge — persistent subprocess for build-time evaluation.
+
+Used by bootstrappers (JS cli.py, OCaml bootstrap.py) that need a sync
+evaluator to run transpiler.sx. For async runtime use, see ocaml_bridge.py.
+"""
+from __future__ import annotations
+
+import os
+import subprocess
+import sys
+
+_DEFAULT_BIN = os.path.join(
+ os.path.dirname(__file__),
+ "../../hosts/ocaml/_build/default/bin/sx_server.exe",
+)
+
+
+class OcamlSyncError(Exception):
+ """Error from the OCaml SX kernel."""
+
+
+def _sx_unescape(s: str) -> str:
+ """Unescape an SX string literal (left-to-right, one pass)."""
+ out = []
+ i = 0
+ while i < len(s):
+ if s[i] == '\\' and i + 1 < len(s):
+ c = s[i + 1]
+ if c == 'n':
+ out.append('\n')
+ elif c == 'r':
+ out.append('\r')
+ elif c == 't':
+ out.append('\t')
+ elif c == '"':
+ out.append('"')
+ elif c == '\\':
+ out.append('\\')
+ else:
+ out.append(c)
+ i += 2
+ else:
+ out.append(s[i])
+ i += 1
+ return ''.join(out)
+
+
+class OcamlSync:
+ """Synchronous bridge to the OCaml sx_server subprocess."""
+
+ def __init__(self, binary: str | None = None):
+ self._binary = binary or os.environ.get("SX_OCAML_BIN") or _DEFAULT_BIN
+ self._proc: subprocess.Popen | None = None
+
+ def _ensure(self):
+ if self._proc is not None and self._proc.poll() is None:
+ return
+ self._proc = subprocess.Popen(
+ [self._binary],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ # Wait for (ready)
+ line = self._readline()
+ if line != "(ready)":
+ raise OcamlSyncError(f"Expected (ready), got: {line}")
+
+ def _send(self, command: str):
+ assert self._proc and self._proc.stdin
+ self._proc.stdin.write((command + "\n").encode())
+ self._proc.stdin.flush()
+
+ def _readline(self) -> str:
+ assert self._proc and self._proc.stdout
+ data = self._proc.stdout.readline()
+ if not data:
+ raise OcamlSyncError("OCaml subprocess died unexpectedly")
+ return data.decode().rstrip("\n")
+
+ def _read_response(self) -> str:
+ """Read a single response. Returns the value string or raises on error."""
+ line = self._readline()
+ # Length-prefixed blob: (ok-len N)
+ if line.startswith("(ok-len "):
+ n = int(line[8:-1])
+ assert self._proc and self._proc.stdout
+ data = self._proc.stdout.read(n)
+ self._proc.stdout.readline() # trailing newline
+ value = data.decode()
+ # Blob is SX-serialized — strip string quotes and unescape
+ if value.startswith('"') and value.endswith('"'):
+ value = _sx_unescape(value[1:-1])
+ return value
+ if line == "(ok)":
+ return ""
+ if line.startswith("(ok-raw "):
+ return line[8:-1]
+ if line.startswith("(ok "):
+ value = line[4:-1]
+ if value.startswith('"') and value.endswith('"'):
+ value = _sx_unescape(value[1:-1])
+ return value
+ if line.startswith("(error "):
+ msg = line[7:-1]
+ if msg.startswith('"') and msg.endswith('"'):
+ msg = _sx_unescape(msg[1:-1])
+ raise OcamlSyncError(msg)
+ raise OcamlSyncError(f"Unexpected response: {line}")
+
+ def eval(self, source: str) -> str:
+ """Evaluate SX source, return result as string."""
+ self._ensure()
+ escaped = source.replace("\\", "\\\\").replace('"', '\\"')
+ self._send(f'(eval "{escaped}")')
+ return self._read_response()
+
+ def load(self, path: str) -> str:
+ """Load an .sx file into the kernel."""
+ self._ensure()
+ self._send(f'(load "{path}")')
+ return self._read_response()
+
+ def load_source(self, source: str) -> str:
+ """Load SX source directly into the kernel."""
+ self._ensure()
+ escaped = source.replace("\\", "\\\\").replace('"', '\\"')
+ self._send(f'(load-source "{escaped}")')
+ return self._read_response()
+
+ def stop(self):
+ if self._proc and self._proc.poll() is None:
+ self._proc.terminate()
+ self._proc.wait(timeout=5)
+ self._proc = None
+
+
+# Singleton
+_global: OcamlSync | None = None
+
+
+def get_sync_bridge() -> OcamlSync:
+ global _global
+ if _global is None:
+ _global = OcamlSync()
+ return _global
diff --git a/shared/sx/pages.py b/shared/sx/pages.py
index 509abe4..41f1794 100644
--- a/shared/sx/pages.py
+++ b/shared/sx/pages.py
@@ -32,7 +32,7 @@ logger = logging.getLogger("sx.pages")
def _eval_error_sx(e: EvalError, context: str) -> str:
"""Render an EvalError as SX content that's visible to the developer."""
- from .ref.sx_ref import escape_html as _esc
+ from html import escape as _esc
msg = _esc(str(e))
ctx = _esc(context)
return (
@@ -141,29 +141,60 @@ def get_page_helpers(service: str) -> dict[str, Any]:
# Loading — parse .sx files and collect PageDef instances
# ---------------------------------------------------------------------------
+def _parse_defpage(expr: list) -> PageDef | None:
+ """Extract PageDef from a (defpage name :path ... :content ...) form."""
+ from .types import Keyword
+ if len(expr) < 3:
+ return None
+ name = expr[1].name if hasattr(expr[1], 'name') else str(expr[1])
+
+ kwargs: dict[str, Any] = {}
+ i = 2
+ while i < len(expr):
+ item = expr[i]
+ if isinstance(item, Keyword) and i + 1 < len(expr):
+ kwargs[item.name] = expr[i + 1]
+ i += 2
+ else:
+ i += 1
+
+ path = kwargs.get("path")
+ if not path or not isinstance(path, str):
+ return None
+
+ auth = kwargs.get("auth", "public")
+ if hasattr(auth, 'name'):
+ auth = auth.name
+
+ return PageDef(
+ name=name, path=path, auth=auth,
+ layout=kwargs.get("layout"),
+ cache=None,
+ data_expr=kwargs.get("data"),
+ content_expr=kwargs.get("content"),
+ filter_expr=kwargs.get("filter"),
+ aside_expr=kwargs.get("aside"),
+ menu_expr=kwargs.get("menu"),
+ )
+
+
def load_page_file(filepath: str, service_name: str) -> list[PageDef]:
- """Parse an .sx file, evaluate it, and register any PageDef values."""
+ """Parse an .sx file and register any defpage definitions."""
from .parser import parse_all
- from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline
- _eval = lambda expr, env: _trampoline(_raw_eval(expr, env))
- from .jinja_bridge import get_component_env
with open(filepath, encoding="utf-8") as f:
source = f.read()
- # Seed env with component definitions so pages can reference components
- env = dict(get_component_env())
exprs = parse_all(source)
pages: list[PageDef] = []
for expr in exprs:
- _eval(expr, env)
-
- # Collect all PageDef values from the env
- for key, val in env.items():
- if isinstance(val, PageDef):
- register_page(service_name, val)
- pages.append(val)
+ if (isinstance(expr, list) and expr
+ and hasattr(expr[0], 'name') and expr[0].name == "defpage"):
+ pd = _parse_defpage(expr)
+ if pd:
+ register_page(service_name, pd)
+ pages.append(pd)
return pages
@@ -177,6 +208,50 @@ def load_page_dir(directory: str, service_name: str) -> list[PageDef]:
return pages
+# ---------------------------------------------------------------------------
+# URL → SX expression conversion (was in sx_ref.py, pure logic)
+# ---------------------------------------------------------------------------
+
+def prepare_url_expr(url_path: str, env: dict) -> list:
+ """Convert a URL path to an SX expression, quoting unknown symbols."""
+ from .parser import parse_all
+ from .types import Symbol
+
+ if not url_path or url_path == "/":
+ return []
+ trimmed = url_path.lstrip("/")
+ sx_source = trimmed.replace(".", " ")
+ exprs = parse_all(sx_source)
+ if not exprs:
+ return []
+ expr = exprs[0]
+ if not isinstance(expr, list):
+ return expr
+ # Auto-quote unknown symbols (not in env, not keywords/components)
+ return _auto_quote(expr, env)
+
+
+def _auto_quote(expr, env: dict):
+ from .types import Symbol
+ if not isinstance(expr, list) or not expr:
+ return expr
+ head = expr[0]
+ children = []
+ for child in expr[1:]:
+ if isinstance(child, list):
+ children.append(_auto_quote(child, env))
+ elif isinstance(child, Symbol):
+ name = child.name
+ if (name in env or name.startswith(":") or
+ name.startswith("~") or name.startswith("!")):
+ children.append(child)
+ else:
+ children.append(name) # quote as string
+ else:
+ children.append(child)
+ return [head] + children
+
+
# ---------------------------------------------------------------------------
# Page execution
# ---------------------------------------------------------------------------
diff --git a/shared/sx/ref/async_eval_ref.py b/shared/sx/ref/async_eval_ref.py
deleted file mode 100644
index 96a79d5..0000000
--- a/shared/sx/ref/async_eval_ref.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""Async evaluation — thin re-export from bootstrapped sx_ref.py.
-
-The async adapter (adapter-async.sx) is now bootstrapped directly into
-sx_ref.py alongside the sync evaluator. This file re-exports the public
-API so existing imports keep working.
-
-All async rendering, serialization, and evaluation logic lives in the spec:
- - shared/sx/ref/adapter-async.sx (canonical SX source)
- - shared/sx/ref/sx_ref.py (bootstrapped Python)
-
-Platform async primitives (I/O dispatch, context vars, RequestContext)
-are in shared/sx/ref/platform_py.py → PLATFORM_ASYNC_PY.
-"""
-
-from . import sx_ref
-
-# Re-export the public API used by handlers.py, helpers.py, pages.py, etc.
-EvalError = sx_ref.EvalError
-async_eval = sx_ref.async_eval
-async_render = sx_ref.async_render
-async_eval_to_sx = sx_ref.async_eval_to_sx
-async_eval_slot_to_sx = sx_ref.async_eval_slot_to_sx
diff --git a/shared/sx/ref/bootstrap_test.py b/shared/sx/ref/bootstrap_test.py
deleted file mode 100644
index 0f1ef43..0000000
--- a/shared/sx/ref/bootstrap_test.py
+++ /dev/null
@@ -1,245 +0,0 @@
-#!/usr/bin/env python3
-"""
-Bootstrap compiler: test.sx -> pytest test module.
-
-Reads test.sx and emits a Python test file that runs each deftest
-as a pytest test case, grouped into classes by defsuite.
-
-The emitted tests use the SX evaluator to run SX test bodies,
-verifying that the Python implementation matches the spec.
-
-Usage:
- python bootstrap_test.py --output shared/sx/tests/test_sx_spec.py
- pytest shared/sx/tests/test_sx_spec.py -v
-"""
-from __future__ import annotations
-
-import os
-import re
-import sys
-import argparse
-
-_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.types import Symbol, Keyword, NIL as SX_NIL
-
-
-def _slugify(name: str) -> str:
- """Convert a test/suite name to a valid Python identifier."""
- s = name.lower().strip()
- s = re.sub(r'[^a-z0-9]+', '_', s)
- s = s.strip('_')
- return s
-
-
-def _sx_to_source(expr) -> str:
- """Convert an SX AST node back to SX source string."""
- if isinstance(expr, bool):
- return "true" if expr else "false"
- if isinstance(expr, (int, float)):
- return str(expr)
- if isinstance(expr, str):
- escaped = expr.replace('\\', '\\\\').replace('"', '\\"')
- return f'"{escaped}"'
- if expr is None or expr is SX_NIL:
- return "nil"
- if isinstance(expr, Symbol):
- return expr.name
- if isinstance(expr, Keyword):
- return f":{expr.name}"
- if isinstance(expr, dict):
- pairs = []
- for k, v in expr.items():
- pairs.append(f":{k} {_sx_to_source(v)}")
- return "{" + " ".join(pairs) + "}"
- if isinstance(expr, list):
- if not expr:
- return "()"
- return "(" + " ".join(_sx_to_source(e) for e in expr) + ")"
- return str(expr)
-
-
-def _parse_test_sx(path: str) -> tuple[list[dict], list]:
- """Parse test.sx and return (suites, preamble_exprs).
-
- Preamble exprs are define forms (assertion helpers) that must be
- evaluated before tests run. Suites contain the actual test cases.
- """
- with open(path) as f:
- content = f.read()
-
- exprs = parse_all(content)
- suites = []
- preamble = []
-
- for expr in exprs:
- if not isinstance(expr, list) or not expr:
- continue
- head = expr[0]
- if isinstance(head, Symbol) and head.name == "defsuite":
- suite = _parse_suite(expr)
- if suite:
- suites.append(suite)
- elif isinstance(head, Symbol) and head.name == "define":
- preamble.append(expr)
-
- return suites, preamble
-
-
-def _parse_suite(expr: list) -> dict | None:
- """Parse a (defsuite "name" ...) form."""
- if len(expr) < 2:
- return None
-
- name = expr[1]
- if not isinstance(name, str):
- return None
-
- tests = []
- for child in expr[2:]:
- if not isinstance(child, list) or not child:
- continue
- head = child[0]
- if isinstance(head, Symbol):
- if head.name == "deftest":
- test = _parse_test(child)
- if test:
- tests.append(test)
- elif head.name == "defsuite":
- sub = _parse_suite(child)
- if sub:
- tests.append(sub)
-
- return {"type": "suite", "name": name, "tests": tests}
-
-
-def _parse_test(expr: list) -> dict | None:
- """Parse a (deftest "name" body ...) form."""
- if len(expr) < 3:
- return None
- name = expr[1]
- if not isinstance(name, str):
- return None
- body = expr[2:]
- return {"type": "test", "name": name, "body": body}
-
-
-def _emit_py(suites: list[dict], preamble: list) -> str:
- """Emit a pytest module from parsed suites."""
- # Serialize preamble (assertion helpers) as SX source
- preamble_sx = "\n".join(_sx_to_source(expr) for expr in preamble)
- preamble_escaped = preamble_sx.replace('\\', '\\\\').replace("'", "\\'")
-
- lines = []
- lines.append('"""Auto-generated from test.sx — SX spec self-tests.')
- lines.append('')
- lines.append('DO NOT EDIT. Regenerate with:')
- lines.append(' python shared/sx/ref/bootstrap_test.py --output shared/sx/tests/test_sx_spec.py')
- lines.append('"""')
- lines.append('from __future__ import annotations')
- lines.append('')
- lines.append('import pytest')
- lines.append('from shared.sx.parser import parse_all')
- lines.append('from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline')
- lines.append('')
- lines.append('')
- lines.append(f"_PREAMBLE = '''{preamble_escaped}'''")
- lines.append('')
- lines.append('')
- lines.append('def _make_env() -> dict:')
- lines.append(' """Create a fresh env with assertion helpers loaded."""')
- lines.append(' env = {}')
- lines.append(' for expr in parse_all(_PREAMBLE):')
- lines.append(' _trampoline(_eval(expr, env))')
- lines.append(' return env')
- lines.append('')
- lines.append('')
- lines.append('def _run(sx_source: str, env: dict | None = None) -> object:')
- lines.append(' """Evaluate SX source and return the result."""')
- lines.append(' if env is None:')
- lines.append(' env = _make_env()')
- lines.append(' exprs = parse_all(sx_source)')
- lines.append(' result = None')
- lines.append(' for expr in exprs:')
- lines.append(' result = _trampoline(_eval(expr, env))')
- lines.append(' return result')
- lines.append('')
-
- for suite in suites:
- _emit_suite(suite, lines, indent=0)
-
- return "\n".join(lines)
-
-
-def _emit_suite(suite: dict, lines: list[str], indent: int):
- """Emit a pytest class for a suite."""
- class_name = f"TestSpec{_slugify(suite['name']).title().replace('_', '')}"
- pad = " " * indent
- lines.append(f'{pad}class {class_name}:')
- lines.append(f'{pad} """test.sx suite: {suite["name"]}"""')
- lines.append('')
-
- for item in suite["tests"]:
- if item["type"] == "test":
- _emit_test(item, lines, indent + 1)
- elif item["type"] == "suite":
- _emit_suite(item, lines, indent + 1)
-
- lines.append('')
-
-
-def _emit_test(test: dict, lines: list[str], indent: int):
- """Emit a pytest test method."""
- method_name = f"test_{_slugify(test['name'])}"
- pad = " " * indent
-
- # Convert body expressions to SX source
- body_parts = []
- for expr in test["body"]:
- body_parts.append(_sx_to_source(expr))
-
- # Wrap in (do ...) if multiple expressions, or use single
- if len(body_parts) == 1:
- sx_source = body_parts[0]
- else:
- sx_source = "(do " + " ".join(body_parts) + ")"
-
- # Escape for Python string
- sx_escaped = sx_source.replace('\\', '\\\\').replace("'", "\\'")
-
- lines.append(f"{pad}def {method_name}(self):")
- lines.append(f"{pad} _run('{sx_escaped}')")
- lines.append('')
-
-
-def main():
- parser = argparse.ArgumentParser(description="Bootstrap test.sx to pytest")
- parser.add_argument("--output", "-o", help="Output file path")
- parser.add_argument("--dry-run", action="store_true", help="Print to stdout")
- args = parser.parse_args()
-
- test_sx = os.path.join(_HERE, "test.sx")
- suites, preamble = _parse_test_sx(test_sx)
-
- print(f"Parsed {len(suites)} suites, {len(preamble)} preamble defines from test.sx", file=sys.stderr)
- total_tests = sum(
- sum(1 for t in s["tests"] if t["type"] == "test")
- for s in suites
- )
- print(f"Total test cases: {total_tests}", file=sys.stderr)
-
- output = _emit_py(suites, preamble)
-
- if args.output and not args.dry_run:
- with open(args.output, "w") as f:
- f.write(output)
- print(f"Wrote {args.output}", file=sys.stderr)
- else:
- print(output)
-
-
-if __name__ == "__main__":
- main()
diff --git a/shared/sx/ref/demo-signals.html b/shared/sx/ref/demo-signals.html
deleted file mode 100644
index 3695762..0000000
--- a/shared/sx/ref/demo-signals.html
+++ /dev/null
@@ -1,182 +0,0 @@
-
-
-
-
- SX Reactive Islands Demo
-
-
-
- SX Reactive Islands
- Signals transpiled from signals.sx spec via bootstrap_js.py
-
-
-
-
1. Signal: Counter
-
-
- 0
-
-
-
-
signal + computed + effect
-
-
-
-
-
2. Batch: Two signals, one notification
-
- first: 0
- second: 0
-
-
-
-
-
-
-
batch coalesces writes: 2 updates, 1 re-render
-
-
-
-
-
3. Effect: Auto-tracking + Cleanup
-
-
-
-
-
-
effect returns cleanup fn; dispose stops tracking
-
-
-
-
-
4. Computed chain: base → doubled → quadrupled
-
-
- base: 1
-
-
-
- doubled:
- quadrupled:
-
-
Three-level computed dependency graph, auto-propagation
-
-
-
-
-
-
diff --git a/shared/sx/ref/prove.sx b/shared/sx/ref/prove.sx
deleted file mode 100644
index 658dda7..0000000
--- a/shared/sx/ref/prove.sx
+++ /dev/null
@@ -1,782 +0,0 @@
-;; ==========================================================================
-;; prove.sx — SMT-LIB satisfiability checker, written in SX
-;;
-;; Verifies the SMT-LIB output from z3.sx. For the class of assertions
-;; z3.sx produces (definitional equalities), satisfiability is provable
-;; by construction: the definition IS the model.
-;;
-;; This closes the loop:
-;; primitives.sx → z3.sx → SMT-LIB → prove.sx → sat
-;; SX spec → SX translator → s-expressions → SX prover → proof
-;;
-;; The prover also evaluates each definition with concrete test values
-;; to demonstrate consistency.
-;;
-;; Usage:
-;; (prove-check smtlib-string) — verify a single check-sat block
-;; (prove-translate expr) — translate + verify a define-* form
-;; (prove-file exprs) — verify all define-* forms
-;; ==========================================================================
-
-
-;; --------------------------------------------------------------------------
-;; SMT-LIB expression evaluator
-;; --------------------------------------------------------------------------
-
-;; Evaluate an SMT-LIB expression in a variable environment
-(define smt-eval
- (fn (expr (env :as dict))
- (cond
- ;; Numbers
- (number? expr) expr
-
- ;; String literals
- (string? expr)
- (cond
- (= expr "true") true
- (= expr "false") false
- :else expr)
-
- ;; Booleans
- (= expr true) true
- (= expr false) false
-
- ;; Symbols — look up in env
- (= (type-of expr) "symbol")
- (let ((name (symbol-name expr)))
- (cond
- (= name "true") true
- (= name "false") false
- :else (get env name expr)))
-
- ;; Lists — function application
- (list? expr)
- (if (empty? expr) nil
- (let ((head (first expr))
- (args (rest expr)))
- (if (not (= (type-of head) "symbol"))
- expr
- (let ((op (symbol-name head)))
- (cond
- ;; Arithmetic
- (= op "+")
- (reduce (fn (a b) (+ a b)) 0
- (map (fn (a) (smt-eval a env)) args))
- (= op "-")
- (if (= (len args) 1)
- (- 0 (smt-eval (first args) env))
- (- (smt-eval (nth args 0) env)
- (smt-eval (nth args 1) env)))
- (= op "*")
- (reduce (fn (a b) (* a b)) 1
- (map (fn (a) (smt-eval a env)) args))
- (= op "/")
- (let ((a (smt-eval (nth args 0) env))
- (b (smt-eval (nth args 1) env)))
- (if (= b 0) 0 (/ a b)))
- (= op "div")
- (let ((a (smt-eval (nth args 0) env))
- (b (smt-eval (nth args 1) env)))
- (if (= b 0) 0 (/ a b)))
- (= op "mod")
- (let ((a (smt-eval (nth args 0) env))
- (b (smt-eval (nth args 1) env)))
- (if (= b 0) 0 (mod a b)))
-
- ;; Comparison
- (= op "=")
- (= (smt-eval (nth args 0) env)
- (smt-eval (nth args 1) env))
- (= op "<")
- (< (smt-eval (nth args 0) env)
- (smt-eval (nth args 1) env))
- (= op ">")
- (> (smt-eval (nth args 0) env)
- (smt-eval (nth args 1) env))
- (= op "<=")
- (<= (smt-eval (nth args 0) env)
- (smt-eval (nth args 1) env))
- (= op ">=")
- (>= (smt-eval (nth args 0) env)
- (smt-eval (nth args 1) env))
-
- ;; Logic
- (= op "and")
- (every? (fn (a) (smt-eval a env)) args)
- (= op "or")
- (some (fn (a) (smt-eval a env)) args)
- (= op "not")
- (not (smt-eval (first args) env))
-
- ;; ite (if-then-else)
- (= op "ite")
- (if (smt-eval (nth args 0) env)
- (smt-eval (nth args 1) env)
- (smt-eval (nth args 2) env))
-
- ;; Function call — look up in env
- :else
- (let ((fn-def (get env op nil)))
- (if (nil? fn-def)
- (list op (map (fn (a) (smt-eval a env)) args))
- ;; fn-def is {:params [...] :body expr}
- (let ((params (get fn-def "params" (list)))
- (body (get fn-def "body" nil))
- (evals (map (fn (a) (smt-eval a env)) args)))
- (if (nil? body)
- ;; Uninterpreted — return symbolic
- (list op evals)
- ;; Evaluate body with params bound
- (smt-eval body
- (merge env
- (smt-bind-params params evals))))))))))))
-
- :else expr)))
-
-
-;; Bind parameter names to values
-(define smt-bind-params
- (fn ((params :as list) (vals :as list))
- (smt-bind-loop params vals {})))
-
-(define smt-bind-loop
- (fn ((params :as list) (vals :as list) (acc :as dict))
- (if (or (empty? params) (empty? vals))
- acc
- (smt-bind-loop (rest params) (rest vals)
- (assoc acc (first params) (first vals))))))
-
-
-;; --------------------------------------------------------------------------
-;; SMT-LIB statement parser
-;; --------------------------------------------------------------------------
-
-;; Extract declarations and assertions from parsed SMT-LIB
-(define smt-extract-statements
- (fn ((exprs :as list))
- (smt-extract-loop exprs {} (list))))
-
-(define smt-extract-loop
- (fn ((exprs :as list) (decls :as dict) (assertions :as list))
- (if (empty? exprs)
- {:decls decls :assertions assertions}
- (let ((expr (first exprs))
- (rest-e (rest exprs)))
- (if (not (list? expr))
- (smt-extract-loop rest-e decls assertions)
- (if (empty? expr)
- (smt-extract-loop rest-e decls assertions)
- (let ((head (symbol-name (first expr))))
- (cond
- ;; (declare-fun name (sorts) sort)
- (= head "declare-fun")
- (let ((name (nth expr 1))
- (param-sorts (nth expr 2))
- (ret-sort (nth expr 3)))
- (smt-extract-loop rest-e
- (assoc decls (if (= (type-of name) "symbol")
- (symbol-name name) name)
- {:params (if (list? param-sorts)
- (map (fn (s) (if (= (type-of s) "symbol")
- (symbol-name s) (str s)))
- param-sorts)
- (list))
- :ret (if (= (type-of ret-sort) "symbol")
- (symbol-name ret-sort) (str ret-sort))})
- assertions))
-
- ;; (assert ...)
- (= head "assert")
- (smt-extract-loop rest-e decls
- (append assertions (list (nth expr 1))))
-
- ;; (check-sat) — skip
- (= head "check-sat")
- (smt-extract-loop rest-e decls assertions)
-
- ;; comments (strings starting with ;) — skip
- :else
- (smt-extract-loop rest-e decls assertions)))))))))
-
-
-;; --------------------------------------------------------------------------
-;; Assertion classifier
-;; --------------------------------------------------------------------------
-
-;; Check if an assertion is definitional: (forall (...) (= (f ...) body))
-;; or (= (f) body) for nullary
-(define smt-definitional?
- (fn (assertion)
- (if (not (list? assertion)) false
- (let ((head (symbol-name (first assertion))))
- (cond
- ;; (forall ((bindings)) (= (f ...) body))
- (= head "forall")
- (let ((body (nth assertion 2)))
- (and (list? body)
- (= (symbol-name (first body)) "=")))
- ;; (= (f ...) body)
- (= head "=")
- true
- :else false)))))
-
-
-;; Extract the function name, parameters, and body from a definitional assertion
-(define smt-extract-definition
- (fn (assertion)
- (let ((head (symbol-name (first assertion))))
- (cond
- ;; (forall (((x Int) (y Int))) (= (f x y) body))
- (= head "forall")
- (let ((bindings (first (nth assertion 1)))
- (eq-expr (nth assertion 2))
- (call (nth eq-expr 1))
- (body (nth eq-expr 2)))
- {:name (if (= (type-of (first call)) "symbol")
- (symbol-name (first call)) (str (first call)))
- :params (map (fn (b)
- (if (list? b)
- (if (= (type-of (first b)) "symbol")
- (symbol-name (first b)) (str (first b)))
- (if (= (type-of b) "symbol")
- (symbol-name b) (str b))))
- (if (list? bindings) bindings (list bindings)))
- :body body})
-
- ;; (= (f) body)
- (= head "=")
- (let ((call (nth assertion 1))
- (body (nth assertion 2)))
- {:name (if (list? call)
- (if (= (type-of (first call)) "symbol")
- (symbol-name (first call)) (str (first call)))
- (str call))
- :params (list)
- :body body})
-
- :else nil))))
-
-
-;; --------------------------------------------------------------------------
-;; Test value generation
-;; --------------------------------------------------------------------------
-
-(define smt-test-values
- (list
- (list 0)
- (list 1)
- (list -1)
- (list 5)
- (list 42)
- (list 1 2)
- (list -3 7)
- (list 5 5)
- (list 100 -50)
- (list 3 1)
- (list 1 1 10)
- (list 5 1 3)
- (list -5 1 10)
- (list 3 3 3)
- (list 7 2 9)))
-
-
-;; --------------------------------------------------------------------------
-;; Proof engine
-;; --------------------------------------------------------------------------
-
-;; Verify a single definitional assertion by construction + evaluation
-(define smt-verify-definition
- (fn ((def-info :as dict) (decls :as dict))
- (let ((name (get def-info "name"))
- (params (get def-info "params"))
- (body (get def-info "body"))
- (n-params (len params)))
-
- ;; Build the model: define f = λparams.body
- (let ((model (assoc decls name {:params params :body body}))
- ;; Select test values matching arity
- (tests (filter (fn ((tv :as list)) (= (len tv) n-params)) smt-test-values))
- ;; Run tests
- (results (map
- (fn ((test-vals :as list))
- (let ((env (merge model (smt-bind-params params test-vals)))
- ;; Evaluate body directly
- (body-result (smt-eval body env))
- ;; Evaluate via function call
- (call-expr (cons (first (sx-parse name)) test-vals))
- (call-result (smt-eval call-expr env)))
- {:vals test-vals
- :body-result body-result
- :call-result call-result
- :equal (= body-result call-result)}))
- tests)))
- {:name name
- :status (if (every? (fn ((r :as dict)) (get r "equal")) results) "sat" "FAIL")
- :proof "by construction (definition is the model)"
- :tests-passed (len (filter (fn ((r :as dict)) (get r "equal")) results))
- :tests-total (len results)
- :sample (if (empty? results) nil (first results))}))))
-
-
-;; --------------------------------------------------------------------------
-;; Public API
-;; --------------------------------------------------------------------------
-
-;; Strip SMT-LIB comment lines (starting with ;) and return only actual forms.
-;; Handles comments that contain ( characters.
-(define smt-strip-comments
- (fn ((s :as string))
- (let ((lines (split s "\n"))
- (non-comment (filter
- (fn ((line :as string)) (not (starts-with? (trim line) ";")))
- lines)))
- (join "\n" non-comment))))
-
-;; Verify SMT-LIB output (string) — parse, classify, prove
-(define prove-check
- (fn ((smtlib-str :as string))
- (let ((parsed (sx-parse (smt-strip-comments smtlib-str)))
- (stmts (smt-extract-statements parsed))
- (decls (get stmts "decls"))
- (assertions (get stmts "assertions")))
- (if (empty? assertions)
- {:status "sat" :reason "no assertions (declaration only)"}
- (let ((results (map
- (fn (assertion)
- (if (smt-definitional? assertion)
- (let ((def-info (smt-extract-definition assertion)))
- (if (nil? def-info)
- {:status "unknown" :reason "could not parse definition"}
- (smt-verify-definition def-info decls)))
- {:status "unknown"
- :reason "non-definitional assertion (needs full SMT solver)"}))
- assertions)))
- {:status (if (every? (fn ((r :as dict)) (= (get r "status") "sat")) results)
- "sat" "unknown")
- :assertions (len assertions)
- :results results})))))
-
-
-;; Translate a define-* form AND verify it — the full pipeline
-(define prove-translate
- (fn (expr)
- (let ((smtlib (z3-translate expr))
- (proof (prove-check smtlib))
- (status (get proof "status"))
- (results (get proof "results" (list))))
- (str smtlib "\n"
- ";; ─── prove.sx ───\n"
- ";; status: " status "\n"
- (if (empty? results) ""
- (let ((r (first results)))
- (str ";; proof: " (get r "proof" "") "\n"
- ";; tested: " (str (get r "tests-passed" 0))
- "/" (str (get r "tests-total" 0))
- " ground instances\n")))))))
-
-
-;; Batch verify: translate and prove all define-* forms
-(define prove-file
- (fn ((exprs :as list))
- (let ((translatable
- (filter
- (fn (expr)
- (and (list? expr)
- (>= (len expr) 2)
- (= (type-of (first expr)) "symbol")
- (let ((name (symbol-name (first expr))))
- (or (= name "define-primitive")
- (= name "define-io-primitive")
- (= name "define-special-form")))))
- exprs))
- (results (map
- (fn (expr)
- (let ((smtlib (z3-translate expr))
- (proof (prove-check smtlib))
- (name (nth expr 1)))
- (assoc proof "name" name)))
- translatable))
- (sat-count (len (filter (fn ((r :as dict)) (= (get r "status") "sat")) results)))
- (total (len results)))
- {:total total
- :sat sat-count
- :all-sat (= sat-count total)
- :results results})))
-
-
-;; ==========================================================================
-;; Phase 2: Property-based constraint solving
-;; ==========================================================================
-;;
-;; Properties are dicts:
-;; {:name "+-commutative"
-;; :vars ("a" "b")
-;; :test (fn (a b) (= (+ a b) (+ b a))) — for bounded checking
-;; :holds (= (+ a b) (+ b a)) — quoted AST for SMT-LIB
-;; :given (fn (lo hi) (<= lo hi)) — optional precondition
-;; :given-expr (<= lo hi) — quoted AST of precondition
-;; :domain (-20 21)} — optional custom range
-
-
-;; --------------------------------------------------------------------------
-;; Domain generation
-;; --------------------------------------------------------------------------
-
-;; Default domain bounds by arity — balance coverage vs. combinatorics
-(define prove-domain-for
- (fn ((arity :as number))
- (cond
- (<= arity 1) (range -50 51) ;; 101 values
- (= arity 2) (range -20 21) ;; 41^2 = 1,681 pairs
- (= arity 3) (range -8 9) ;; 17^3 = 4,913 triples
- :else (range -5 6)))) ;; 11^n for n >= 4
-
-;; Cartesian product: all n-tuples from a domain
-(define prove-tuples
- (fn ((domain :as list) (arity :as number))
- (if (<= arity 0) (list (list))
- (if (= arity 1)
- (map (fn (x) (list x)) domain)
- (let ((sub (prove-tuples domain (- arity 1))))
- (prove-tuples-expand domain sub (list)))))))
-
-(define prove-tuples-expand
- (fn ((domain :as list) (sub :as list) (acc :as list))
- (if (empty? domain) acc
- (prove-tuples-expand
- (rest domain) sub
- (append acc
- (map (fn ((t :as list)) (cons (first domain) t)) sub))))))
-
-
-;; --------------------------------------------------------------------------
-;; Function application by arity (no apply primitive available)
-;; --------------------------------------------------------------------------
-
-(define prove-call
- (fn ((f :as lambda) (vals :as list))
- (let ((n (len vals)))
- (cond
- (= n 0) (f)
- (= n 1) (f (nth vals 0))
- (= n 2) (f (nth vals 0) (nth vals 1))
- (= n 3) (f (nth vals 0) (nth vals 1) (nth vals 2))
- (= n 4) (f (nth vals 0) (nth vals 1) (nth vals 2) (nth vals 3))
- :else nil))))
-
-
-;; --------------------------------------------------------------------------
-;; Bounded model checker
-;; --------------------------------------------------------------------------
-
-;; Search for a counterexample. Returns nil if property holds for all tested
-;; values, or the first counterexample found.
-(define prove-search
- (fn ((test-fn :as lambda) given-fn (domain :as list) (vars :as list))
- (let ((arity (len vars))
- (tuples (prove-tuples domain arity)))
- (prove-search-loop test-fn given-fn tuples 0 0))))
-
-(define prove-search-loop
- (fn ((test-fn :as lambda) given-fn (tuples :as list) (tested :as number) (skipped :as number))
- (if (empty? tuples)
- {:status "verified" :tested tested :skipped skipped}
- (let ((vals (first tuples))
- (rest-t (rest tuples)))
- ;; Check precondition (if any)
- (if (and (not (nil? given-fn))
- (not (prove-call given-fn vals)))
- ;; Precondition not met — skip this combination
- (prove-search-loop test-fn given-fn rest-t tested (+ skipped 1))
- ;; Evaluate the property
- (if (prove-call test-fn vals)
- ;; Passed — continue
- (prove-search-loop test-fn given-fn rest-t (+ tested 1) skipped)
- ;; Failed — counterexample found
- {:status "falsified"
- :tested tested
- :skipped skipped
- :counterexample vals}))))))
-
-
-;; --------------------------------------------------------------------------
-;; Property verification (public API)
-;; --------------------------------------------------------------------------
-
-;; Verify a single property via bounded model checking
-(define prove-property
- (fn ((prop :as dict))
- (let ((name (get prop "name"))
- (vars (get prop "vars"))
- (test-fn (get prop "test"))
- (given-fn (get prop "given" nil))
- (custom (get prop "domain" nil))
- (domain (if (nil? custom)
- (prove-domain-for (len vars))
- (range (nth custom 0) (nth custom 1)))))
- (let ((result (prove-search test-fn given-fn domain vars)))
- (assoc result "name" name)))))
-
-;; Batch verify a list of properties
-(define prove-properties
- (fn ((props :as list))
- (let ((results (map prove-property props))
- (verified (filter (fn ((r :as dict)) (= (get r "status") "verified")) results))
- (falsified (filter (fn ((r :as dict)) (= (get r "status") "falsified")) results)))
- {:total (len results)
- :verified (len verified)
- :falsified (len falsified)
- :all-verified (= (len falsified) 0)
- :results results})))
-
-
-;; --------------------------------------------------------------------------
-;; SMT-LIB generation for properties
-;; --------------------------------------------------------------------------
-
-;; Generate SMT-LIB for a property — asserts (not (forall ...)) so that
-;; Z3 returning "unsat" proves the property holds universally.
-(define prove-property-smtlib
- (fn ((prop :as dict))
- (let ((name (get prop "name"))
- (vars (get prop "vars"))
- (holds (get prop "holds"))
- (given-e (get prop "given-expr" nil))
- (bindings (join " "
- (map (fn ((v :as string)) (str "(" v " Int)")) vars)))
- (holds-smt (z3-expr holds))
- (body (if (nil? given-e)
- holds-smt
- (str "(=> " (z3-expr given-e) " " holds-smt ")"))))
- (str "; Property: " name "\n"
- "; Strategy: assert negation, check for unsat\n"
- "(assert (not (forall ((" bindings "))\n"
- " " body ")))\n"
- "(check-sat) ; expect unsat\n"))))
-
-;; Generate SMT-LIB for all properties, including necessary definitions
-(define prove-properties-smtlib
- (fn ((props :as list) (primitives-exprs :as list))
- (let ((defs (z3-translate-file primitives-exprs))
- (prop-smts (map prove-property-smtlib props)))
- (str ";; ================================================================\n"
- ";; Auto-generated by prove.sx — property verification conditions\n"
- ";; Feed to Z3 for unbounded proofs\n"
- ";; ================================================================\n\n"
- ";; --- Primitive definitions ---\n"
- defs "\n\n"
- ";; --- Properties ---\n"
- (join "\n" prop-smts)))))
-
-
-;; ==========================================================================
-;; Property library: algebraic laws of SX primitives
-;; ==========================================================================
-
-(define sx-properties
- (list
-
- ;; ----- Arithmetic identities -----
-
- {:name "+-commutative"
- :vars (list "a" "b")
- :test (fn (a b) (= (+ a b) (+ b a)))
- :holds '(= (+ a b) (+ b a))}
-
- {:name "+-associative"
- :vars (list "a" "b" "c")
- :test (fn (a b c) (= (+ (+ a b) c) (+ a (+ b c))))
- :holds '(= (+ (+ a b) c) (+ a (+ b c)))}
-
- {:name "+-identity"
- :vars (list "a")
- :test (fn (a) (= (+ a 0) a))
- :holds '(= (+ a 0) a)}
-
- {:name "*-commutative"
- :vars (list "a" "b")
- :test (fn (a b) (= (* a b) (* b a)))
- :holds '(= (* a b) (* b a))}
-
- {:name "*-associative"
- :vars (list "a" "b" "c")
- :test (fn (a b c) (= (* (* a b) c) (* a (* b c))))
- :holds '(= (* (* a b) c) (* a (* b c)))}
-
- {:name "*-identity"
- :vars (list "a")
- :test (fn (a) (= (* a 1) a))
- :holds '(= (* a 1) a)}
-
- {:name "*-zero"
- :vars (list "a")
- :test (fn (a) (= (* a 0) 0))
- :holds '(= (* a 0) 0)}
-
- {:name "distributive"
- :vars (list "a" "b" "c")
- :test (fn (a b c) (= (* a (+ b c)) (+ (* a b) (* a c))))
- :holds '(= (* a (+ b c)) (+ (* a b) (* a c)))}
-
- {:name "--inverse"
- :vars (list "a")
- :test (fn (a) (= (- a a) 0))
- :holds '(= (- a a) 0)}
-
- ;; ----- inc / dec -----
-
- {:name "inc-is-plus-1"
- :vars (list "n")
- :test (fn (n) (= (inc n) (+ n 1)))
- :holds '(= (inc n) (+ n 1))}
-
- {:name "dec-is-minus-1"
- :vars (list "n")
- :test (fn (n) (= (dec n) (- n 1)))
- :holds '(= (dec n) (- n 1))}
-
- {:name "inc-dec-inverse"
- :vars (list "n")
- :test (fn (n) (= (dec (inc n)) n))
- :holds '(= (dec (inc n)) n)}
-
- {:name "dec-inc-inverse"
- :vars (list "n")
- :test (fn (n) (= (inc (dec n)) n))
- :holds '(= (inc (dec n)) n)}
-
- ;; ----- abs -----
-
- {:name "abs-non-negative"
- :vars (list "n")
- :test (fn (n) (>= (abs n) 0))
- :holds '(>= (abs n) 0)}
-
- {:name "abs-idempotent"
- :vars (list "n")
- :test (fn (n) (= (abs (abs n)) (abs n)))
- :holds '(= (abs (abs n)) (abs n))}
-
- {:name "abs-symmetric"
- :vars (list "n")
- :test (fn (n) (= (abs n) (abs (- 0 n))))
- :holds '(= (abs n) (abs (- 0 n)))}
-
- ;; ----- Predicates -----
-
- {:name "odd-not-even"
- :vars (list "n")
- :test (fn (n) (= (odd? n) (not (even? n))))
- :holds '(= (odd? n) (not (even? n)))}
-
- {:name "even-mod-2"
- :vars (list "n")
- :test (fn (n) (= (even? n) (= (mod n 2) 0)))
- :holds '(= (even? n) (= (mod n 2) 0))}
-
- {:name "zero-is-zero"
- :vars (list "n")
- :test (fn (n) (= (zero? n) (= n 0)))
- :holds '(= (zero? n) (= n 0))}
-
- {:name "not-involution"
- :vars (list "n")
- :test (fn (n) (= (not (not (zero? n))) (zero? n)))
- :holds '(= (not (not (zero? n))) (zero? n))}
-
- ;; ----- min / max -----
-
- {:name "min-commutative"
- :vars (list "a" "b")
- :test (fn (a b) (= (min a b) (min b a)))
- :holds '(= (min a b) (min b a))}
-
- {:name "max-commutative"
- :vars (list "a" "b")
- :test (fn (a b) (= (max a b) (max b a)))
- :holds '(= (max a b) (max b a))}
-
- {:name "min-le-both"
- :vars (list "a" "b")
- :test (fn (a b) (and (<= (min a b) a) (<= (min a b) b)))
- :holds '(and (<= (min a b) a) (<= (min a b) b))}
-
- {:name "max-ge-both"
- :vars (list "a" "b")
- :test (fn (a b) (and (>= (max a b) a) (>= (max a b) b)))
- :holds '(and (>= (max a b) a) (>= (max a b) b))}
-
- {:name "min-max-identity"
- :vars (list "a" "b")
- :test (fn (a b) (= (+ (min a b) (max a b)) (+ a b)))
- :holds '(= (+ (min a b) (max a b)) (+ a b))}
-
- ;; ----- clamp -----
-
- {:name "clamp-in-range"
- :vars (list "x" "lo" "hi")
- :test (fn (x lo hi) (and (<= lo (clamp x lo hi))
- (<= (clamp x lo hi) hi)))
- :given (fn (x lo hi) (<= lo hi))
- :holds '(and (<= lo (clamp x lo hi)) (<= (clamp x lo hi) hi))
- :given-expr '(<= lo hi)}
-
- {:name "clamp-identity-in-range"
- :vars (list "x" "lo" "hi")
- :test (fn (x lo hi) (= (clamp x lo hi) x))
- :given (fn (x lo hi) (and (<= lo hi) (<= lo x) (<= x hi)))
- :holds '(= (clamp x lo hi) x)
- :given-expr '(and (<= lo hi) (<= lo x) (<= x hi))}
-
- {:name "clamp-idempotent"
- :vars (list "x" "lo" "hi")
- :test (fn (x lo hi) (= (clamp (clamp x lo hi) lo hi)
- (clamp x lo hi)))
- :given (fn (x lo hi) (<= lo hi))
- :holds '(= (clamp (clamp x lo hi) lo hi) (clamp x lo hi))
- :given-expr '(<= lo hi)}
-
- ;; ----- Comparison -----
-
- {:name "lt-gt-flip"
- :vars (list "a" "b")
- :test (fn (a b) (= (< a b) (> b a)))
- :holds '(= (< a b) (> b a))}
-
- {:name "le-not-gt"
- :vars (list "a" "b")
- :test (fn (a b) (= (<= a b) (not (> a b))))
- :holds '(= (<= a b) (not (> a b)))}
-
- {:name "ge-not-lt"
- :vars (list "a" "b")
- :test (fn (a b) (= (>= a b) (not (< a b))))
- :holds '(= (>= a b) (not (< a b)))}
-
- {:name "trichotomy"
- :vars (list "a" "b")
- :test (fn (a b) (or (< a b) (= a b) (> a b)))
- :holds '(or (< a b) (= a b) (> a b))}
-
- {:name "lt-transitive"
- :vars (list "a" "b" "c")
- :test (fn (a b c) (if (and (< a b) (< b c)) (< a c) true))
- :given (fn (a b c) (and (< a b) (< b c)))
- :holds '(< a c)
- :given-expr '(and (< a b) (< b c))}
-
- ;; ----- Inequality -----
-
- {:name "neq-is-not-eq"
- :vars (list "a" "b")
- :test (fn (a b) (= (!= a b) (not (= a b))))
- :holds '(= (!= a b) (not (= a b)))}))
-
-
-;; --------------------------------------------------------------------------
-;; Run all built-in properties
-;; --------------------------------------------------------------------------
-
-(define prove-all-properties
- (fn ()
- (prove-properties sx-properties)))
diff --git a/shared/sx/ref/reader_z3.py b/shared/sx/ref/reader_z3.py
deleted file mode 100644
index 8ab2297..0000000
--- a/shared/sx/ref/reader_z3.py
+++ /dev/null
@@ -1,89 +0,0 @@
-"""
-#z3 reader macro — translates SX spec declarations to SMT-LIB format.
-
-Self-hosted: loads z3.sx (the translator written in SX) and executes it
-via the SX evaluator. The Python code here is pure host infrastructure —
-all translation logic lives in z3.sx.
-
-Usage:
- from shared.sx.ref.reader_z3 import z3_translate, register_z3_macro
-
- # Register as reader macro (enables #z3 in parser)
- register_z3_macro()
-
- # Or call directly
- smtlib = z3_translate(parse('(define-primitive "inc" :params (n) ...)'))
-"""
-from __future__ import annotations
-
-import os
-from typing import Any
-
-
-# ---------------------------------------------------------------------------
-# Load z3.sx into an evaluator environment (cached)
-# ---------------------------------------------------------------------------
-
-_z3_env: dict[str, Any] | None = None
-
-
-def _get_z3_env() -> dict[str, Any]:
- """Load and evaluate z3.sx, returning the environment with all z3-* functions.
-
- Platform primitives (type-of, symbol-name, keyword-name) are registered
- in primitives.py. z3.sx uses canonical primitive names (get, assoc) so
- no additional bindings are needed.
- """
- global _z3_env
- if _z3_env is not None:
- return _z3_env
-
- from shared.sx.parser import parse_all
- from shared.sx.ref.sx_ref import make_env, eval_expr as _eval, trampoline as _trampoline
-
- env = make_env()
- z3_path = os.path.join(os.path.dirname(__file__), "z3.sx")
- with open(z3_path, encoding="utf-8") as f:
- for expr in parse_all(f.read()):
- _trampoline(_eval(expr, env))
-
- _z3_env = env
- return env
-
-
-# ---------------------------------------------------------------------------
-# Public API
-# ---------------------------------------------------------------------------
-
-def z3_translate(expr: Any) -> str:
- """Translate an SX define-* form to SMT-LIB.
-
- Delegates to z3-translate defined in z3.sx.
- """
- from shared.sx.ref.sx_ref import trampoline as _trampoline, call_lambda as _call_lambda
-
- env = _get_z3_env()
- return _trampoline(_call_lambda(env["z3-translate"], [expr], env))
-
-
-def z3_translate_file(source: str) -> str:
- """Parse an SX spec file and translate all define-* forms to SMT-LIB.
-
- Delegates to z3-translate-file defined in z3.sx.
- """
- from shared.sx.parser import parse_all
- from shared.sx.ref.sx_ref import trampoline as _trampoline, call_lambda as _call_lambda
-
- env = _get_z3_env()
- exprs = parse_all(source)
- return _trampoline(_call_lambda(env["z3-translate-file"], [exprs], env))
-
-
-# ---------------------------------------------------------------------------
-# Reader macro registration
-# ---------------------------------------------------------------------------
-
-def register_z3_macro():
- """Register #z3 as a reader macro in the SX parser."""
- from shared.sx.parser import register_reader_macro
- register_reader_macro("z3", z3_translate)
diff --git a/shared/sx/ref/run_py_sx.py b/shared/sx/ref/run_py_sx.py
deleted file mode 100644
index 54927cf..0000000
--- a/shared/sx/ref/run_py_sx.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env python3
-"""
-Bootstrap runner: execute py.sx against spec files to produce sx_ref.py.
-
-This is the G1 bootstrapper — py.sx (SX-to-Python translator written in SX)
-is loaded into the Python evaluator, which then uses it to translate the
-spec .sx files into Python.
-
-The output should be identical to: python bootstrap_py.py
-
-Usage:
- python run_py_sx.py > sx_ref_g1.py
-"""
-from __future__ import annotations
-
-import os
-import 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.types import Symbol
-from shared.sx.ref.platform_py import (
- PREAMBLE, PLATFORM_PY, PRIMITIVES_PY_PRE, PRIMITIVES_PY_POST,
- PLATFORM_DEPS_PY, FIXUPS_PY, CONTINUATIONS_PY,
- _assemble_primitives_py, public_api_py,
-)
-
-
-def load_py_sx(evaluator_env: dict) -> dict:
- """Load py.sx into an evaluator environment and return it."""
- py_sx_path = os.path.join(_HERE, "py.sx")
- with open(py_sx_path) as f:
- source = f.read()
-
- exprs = parse_all(source)
-
- # Import the evaluator
- from shared.sx.ref.sx_ref import evaluate, make_env
-
- env = make_env()
- for expr in exprs:
- evaluate(expr, env)
-
- return env
-
-
-def extract_defines(source: str) -> list[tuple[str, list]]:
- """Parse .sx source, return list of (name, define-expr) for top-level defines."""
- exprs = parse_all(source)
- defines = []
- for expr in exprs:
- if isinstance(expr, list) and expr and isinstance(expr[0], Symbol):
- if expr[0].name == "define":
- name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1])
- defines.append((name, expr))
- return defines
-
-
-def main():
- from shared.sx.ref.sx_ref import evaluate
-
- # Load py.sx into evaluator
- env = load_py_sx({})
-
- # Get the py-translate-file function
- py_translate_file = env.get("py-translate-file")
- if py_translate_file is None:
- print("ERROR: py-translate-file not found in py.sx environment", file=sys.stderr)
- sys.exit(1)
-
- # Same file list and order as bootstrap_py.py compile_ref_to_py()
- sx_files = [
- ("eval.sx", "eval"),
- ("forms.sx", "forms (server definition forms)"),
- ("render.sx", "render (core)"),
- ("adapter-html.sx", "adapter-html"),
- ("adapter-sx.sx", "adapter-sx"),
- ("deps.sx", "deps (component dependency analysis)"),
- ("signals.sx", "signals (reactive signal runtime)"),
- ]
-
- # Build output — static sections are identical
- parts = []
- parts.append(PREAMBLE)
- parts.append(PLATFORM_PY)
- parts.append(PRIMITIVES_PY_PRE)
- parts.append(_assemble_primitives_py(None))
- parts.append(PRIMITIVES_PY_POST)
- parts.append(PLATFORM_DEPS_PY)
-
- # Translate each spec file using py.sx
- for filename, label in sx_files:
- filepath = os.path.join(_HERE, filename)
- if not os.path.exists(filepath):
- continue
- with open(filepath) as f:
- src = f.read()
- defines = extract_defines(src)
-
- # Convert defines to SX-compatible format: list of [name, expr] pairs
- sx_defines = [[name, expr] for name, expr in defines]
-
- parts.append(f"\n# === Transpiled from {label} ===\n")
- # Bind defines as data in env to avoid evaluator trying to execute AST
- env["_defines"] = sx_defines
- result = evaluate(
- [Symbol("py-translate-file"), Symbol("_defines")],
- env,
- )
- parts.append(result)
-
- parts.append(FIXUPS_PY)
- parts.append(public_api_py(True, True, True))
-
- print("\n".join(parts))
-
-
-if __name__ == "__main__":
- main()
diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py
deleted file mode 100644
index 70a81cf..0000000
--- a/shared/sx/ref/sx_ref.py
+++ /dev/null
@@ -1,5993 +0,0 @@
-"""
-sx_ref.py -- Generated from reference SX evaluator specification.
-
-Bootstrap-compiled from shared/sx/ref/{eval,render,adapter-html,adapter-sx}.sx
-Compare against hand-written evaluator.py / html.py for correctness verification.
-
-DO NOT EDIT -- regenerate with: python run_py_sx.py
-"""
-from __future__ import annotations
-
-import math
-from typing import Any
-
-
-# =========================================================================
-# Types (reuse existing types)
-# =========================================================================
-
-from shared.sx.types import (
- NIL, Symbol, Keyword, Lambda, Component, Island, Continuation, Macro,
- HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
-)
-from shared.sx.parser import SxExpr
-from shared.sx.env import Env as _Env, MergedEnv as _MergedEnv
-
-
-# =========================================================================
-# Platform interface -- Python implementation
-# =========================================================================
-
-class _Thunk:
- """Deferred evaluation for TCO."""
- __slots__ = ("expr", "env")
- def __init__(self, expr, env):
- self.expr = expr
- self.env = env
-
-
-class _RawHTML:
- """Marker for pre-rendered HTML that should not be escaped."""
- __slots__ = ("html",)
- def __init__(self, html: str):
- self.html = html
-
-
-class _Spread:
- """Attribute injection value — merges attrs onto parent element."""
- __slots__ = ("attrs",)
- def __init__(self, attrs: dict):
- self.attrs = dict(attrs) if attrs else {}
-
-
-# Unified scope stacks — backing store for provide/context/emit!/collect!
-# Each entry: {"value": v, "emitted": [], "dedup": bool}
-_scope_stacks: dict[str, list[dict]] = {}
-
-
-def _collect_reset():
- """Reset all scope stacks (call at start of each render pass)."""
- global _scope_stacks
- _scope_stacks = {}
-
-
-def scope_push(name, value=None):
- """Push a scope with name, value, and empty accumulator."""
- _scope_stacks.setdefault(name, []).append({"value": value, "emitted": [], "dedup": False})
-
-
-def scope_pop(name):
- """Pop the most recent scope for name."""
- if name in _scope_stacks and _scope_stacks[name]:
- _scope_stacks[name].pop()
-
-
-# Aliases — provide-push!/provide-pop! map to scope-push!/scope-pop!
-provide_push = scope_push
-provide_pop = scope_pop
-
-
-def sx_context(name, *default):
- """Read value from nearest enclosing scope. Error if no scope and no default."""
- if name in _scope_stacks and _scope_stacks[name]:
- return _scope_stacks[name][-1]["value"]
- if default:
- return default[0]
- raise RuntimeError(f"No provider for: {name}")
-
-
-def sx_emit(name, value):
- """Append value to nearest enclosing scope's accumulator. Respects dedup flag."""
- if name in _scope_stacks and _scope_stacks[name]:
- entry = _scope_stacks[name][-1]
- if entry["dedup"] and value in entry["emitted"]:
- return NIL
- entry["emitted"].append(value)
- return NIL
-
-
-def sx_emitted(name):
- """Return list of values emitted into nearest matching scope."""
- if name in _scope_stacks and _scope_stacks[name]:
- return list(_scope_stacks[name][-1]["emitted"])
- return []
-
-
-def sx_truthy(x):
- """SX truthiness: everything is truthy except False, None, and NIL."""
- if x is False:
- return False
- if x is None or x is NIL:
- return False
- return True
-
-
-def sx_str(*args):
- """SX str: concatenate string representations, skipping nil."""
- parts = []
- for a in args:
- if a is None or a is NIL:
- continue
- parts.append(str(a))
- return "".join(parts)
-
-
-def sx_and(*args):
- """SX and: return last truthy value or first falsy."""
- result = True
- for a in args:
- if not sx_truthy(a):
- return a
- result = a
- return result
-
-
-def sx_or(*args):
- """SX or: return first truthy value or last value."""
- for a in args:
- if sx_truthy(a):
- return a
- return args[-1] if args else False
-
-
-def _sx_begin(*args):
- """Evaluate all args (for side effects), return last."""
- return args[-1] if args else NIL
-
-
-
-def _sx_case(match_val, pairs):
- """Case dispatch: pairs is [(test_val, body_fn), ...]. None test = else."""
- for test, body_fn in pairs:
- if test is None: # :else clause
- return body_fn()
- if match_val == test:
- return body_fn()
- return NIL
-
-
-def _sx_fn(f):
- """Identity wrapper for multi-expression lambda bodies."""
- return f
-
-
-def type_of(x):
- if x is None or x is NIL:
- return "nil"
- if isinstance(x, bool):
- return "boolean"
- if isinstance(x, (int, float)):
- return "number"
- if isinstance(x, SxExpr):
- return "sx-expr"
- if isinstance(x, str):
- return "string"
- if isinstance(x, Symbol):
- return "symbol"
- if isinstance(x, Keyword):
- return "keyword"
- if isinstance(x, _Thunk):
- return "thunk"
- if isinstance(x, Lambda):
- return "lambda"
- if isinstance(x, Component):
- return "component"
- if isinstance(x, Island):
- return "island"
- if isinstance(x, _Spread):
- return "spread"
- if isinstance(x, Macro):
- return "macro"
- if isinstance(x, _RawHTML):
- return "raw-html"
- if isinstance(x, Continuation):
- return "continuation"
- if isinstance(x, list):
- return "list"
- if isinstance(x, dict):
- return "dict"
- return "unknown"
-
-
-def symbol_name(s):
- return s.name
-
-
-def keyword_name(k):
- return k.name
-
-
-def make_symbol(n):
- return Symbol(n)
-
-
-def make_keyword(n):
- return Keyword(n)
-
-
-def _ensure_env(env):
- """Wrap plain dict in Env if needed."""
- if isinstance(env, _Env):
- return env
- return _Env(env if isinstance(env, dict) else {})
-
-
-def make_lambda(params, body, env):
- return Lambda(params=list(params), body=body, closure=_ensure_env(env))
-
-
-def make_component(name, params, has_children, body, env, affinity="auto"):
- return Component(name=name, params=list(params), has_children=has_children,
- body=body, closure=dict(env), affinity=str(affinity) if affinity else "auto")
-
-
-def make_island(name, params, has_children, body, env):
- return Island(name=name, params=list(params), has_children=has_children,
- body=body, closure=dict(env))
-
-
-def make_macro(params, rest_param, body, env, name=None):
- return Macro(params=list(params), rest_param=rest_param, body=body,
- closure=dict(env), name=name)
-
-
-def make_handler_def(name, params, body, env, opts=None):
- path = opts.get('path') if opts else None
- method = str(opts.get('method', 'get')) if opts else 'get'
- csrf = opts.get('csrf', True) if opts else True
- returns = str(opts.get('returns', 'element')) if opts else 'element'
- if isinstance(csrf, str):
- csrf = csrf.lower() not in ('false', 'nil', 'no')
- return HandlerDef(name=name, params=list(params), body=body, closure=dict(env),
- path=path, method=method.lower(), csrf=csrf, returns=returns)
-
-
-def make_query_def(name, params, doc, body, env):
- return QueryDef(name=name, params=list(params), doc=doc, body=body, closure=dict(env))
-
-
-def make_action_def(name, params, doc, body, env):
- return ActionDef(name=name, params=list(params), doc=doc, body=body, closure=dict(env))
-
-
-def make_page_def(name, slots, env):
- path = slots.get("path", "")
- auth_val = slots.get("auth", "public")
- if isinstance(auth_val, Keyword):
- auth = auth_val.name
- elif isinstance(auth_val, list):
- auth = [item.name if isinstance(item, Keyword) else str(item) for item in auth_val]
- else:
- auth = str(auth_val) if auth_val else "public"
- layout = slots.get("layout")
- if isinstance(layout, Keyword):
- layout = layout.name
- cache = None
- stream_val = slots.get("stream")
- stream = bool(trampoline(eval_expr(stream_val, env))) if stream_val is not None else False
- return PageDef(
- name=name, path=path, auth=auth, layout=layout, cache=cache,
- data_expr=slots.get("data"), content_expr=slots.get("content"),
- filter_expr=slots.get("filter"), aside_expr=slots.get("aside"),
- menu_expr=slots.get("menu"), stream=stream,
- fallback_expr=slots.get("fallback"), shell_expr=slots.get("shell"),
- closure=dict(env),
- )
-
-
-def make_thunk(expr, env):
- return _Thunk(expr, env)
-
-
-def make_spread(attrs):
- return _Spread(attrs if isinstance(attrs, dict) else {})
-
-
-def is_spread(x):
- return isinstance(x, _Spread)
-
-
-def spread_attrs(s):
- return s.attrs if isinstance(s, _Spread) else {}
-
-
-def sx_collect(bucket, value):
- """Add value to named scope accumulator (deduplicated). Lazily creates root scope."""
- if bucket not in _scope_stacks or not _scope_stacks[bucket]:
- _scope_stacks.setdefault(bucket, []).append({"value": None, "emitted": [], "dedup": True})
- entry = _scope_stacks[bucket][-1]
- if value not in entry["emitted"]:
- entry["emitted"].append(value)
-
-
-def sx_collected(bucket):
- """Return all values collected in named scope accumulator."""
- return sx_emitted(bucket)
-
-
-def sx_clear_collected(bucket):
- """Clear nearest scope's accumulator for name."""
- if bucket in _scope_stacks and _scope_stacks[bucket]:
- _scope_stacks[bucket][-1]["emitted"] = []
-
-
-def lambda_params(f):
- return f.params
-
-
-def lambda_body(f):
- return f.body
-
-
-def lambda_closure(f):
- return f.closure
-
-
-def lambda_name(f):
- return f.name
-
-
-def set_lambda_name(f, n):
- f.name = n
-
-
-def component_params(c):
- return c.params
-
-
-def component_body(c):
- return c.body
-
-
-def component_closure(c):
- return c.closure
-
-
-def component_has_children(c):
- return c.has_children
-
-
-def component_name(c):
- return c.name
-
-
-def component_affinity(c):
- return getattr(c, 'affinity', 'auto')
-
-
-def component_param_types(c):
- return getattr(c, 'param_types', None)
-
-
-def component_set_param_types(c, d):
- c.param_types = d
-
-
-def macro_params(m):
- return m.params
-
-
-def macro_rest_param(m):
- return m.rest_param
-
-
-def macro_body(m):
- return m.body
-
-
-def macro_closure(m):
- return m.closure
-
-
-def is_thunk(x):
- return isinstance(x, _Thunk)
-
-
-def thunk_expr(t):
- return t.expr
-
-
-def thunk_env(t):
- return t.env
-
-
-def is_callable(x):
- return callable(x) or isinstance(x, Lambda)
-
-
-def is_lambda(x):
- return isinstance(x, Lambda)
-
-
-def is_component(x):
- return isinstance(x, Component)
-
-
-def is_macro(x):
- return isinstance(x, Macro)
-
-
-def is_island(x):
- return isinstance(x, Island)
-
-
-def is_identical(a, b):
- return a is b
-
-
-
-
-def json_serialize(obj):
- import json
- return json.dumps(obj)
-
-
-def is_empty_dict(d):
- if not isinstance(d, dict):
- return True
- return len(d) == 0
-
-
-# DOM event primitives — no-ops on server (browser-only).
-def dom_listen(el, name, handler):
- return lambda: None
-
-def dom_dispatch(el, name, detail=None):
- return False
-
-def event_detail(e):
- return None
-
-
-def env_has(env, name):
- return name in env
-
-
-def env_get(env, name):
- return env.get(name, NIL)
-
-
-def env_set(env, name, val):
- env[name] = val
-
-
-def env_extend(env):
- return _ensure_env(env).extend()
-
-
-def env_merge(base, overlay):
- base = _ensure_env(base)
- overlay = _ensure_env(overlay)
- if base is overlay:
- # Same env — just extend with empty local scope for params
- return base.extend()
- # Check if base is an ancestor of overlay — if so, no need to merge
- # (common for self-recursive calls where closure == caller's ancestor)
- p = overlay
- depth = 0
- while p is not None and depth < 100:
- if p is base:
- return base.extend()
- p = getattr(p, '_parent', None)
- depth += 1
- # MergedEnv: reads walk base then overlay; set! walks base only
- return _MergedEnv({}, primary=base, secondary=overlay)
-
-
-def dict_set(d, k, v):
- d[k] = v
-
-
-def dict_get(d, k):
- v = d.get(k)
- return v if v is not None else NIL
-
-
-def dict_has(d, k):
- return k in d
-
-
-def dict_delete(d, k):
- d.pop(k, None)
-
-
-def is_render_expr(expr):
- """Placeholder — overridden by transpiled version from render.sx."""
- return False
-
-
-# Render dispatch -- set by adapter
-_render_expr_fn = None
-
-# Render mode flag -- set by render-to-html/aser, checked by eval-list
-# When false, render expressions (HTML tags, components) fall through to eval-call
-_render_mode = False
-
-
-def render_active_p():
- return _render_mode
-
-
-def set_render_active_b(val):
- global _render_mode
- _render_mode = bool(val)
-
-
-def render_expr(expr, env):
- if _render_expr_fn:
- return _render_expr_fn(expr, env)
- # No adapter — fall through to eval_call so components still evaluate
- return eval_call(first(expr), rest(expr), env)
-
-
-def strip_prefix(s, prefix):
- return s[len(prefix):] if s.startswith(prefix) else s
-
-
-def debug_log(*args):
- import sys
- print(*args, file=sys.stderr)
-
-
-def error(msg):
- raise EvalError(msg)
-
-
-def inspect(x):
- return repr(x)
-
-
-def escape_html(s):
- s = str(s)
- return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
-
-
-def escape_attr(s):
- return escape_html(s)
-
-
-def raw_html_content(x):
- return x.html
-
-
-def make_raw_html(s):
- return _RawHTML(s)
-
-
-def sx_expr_source(x):
- return x.source if isinstance(x, SxExpr) else str(x)
-
-
-try:
- from shared.sx.types import EvalError
-except ImportError:
- class EvalError(Exception):
- pass
-
-
-def _sx_append(lst, item):
- """Append item to list, return the item (for expression context)."""
- lst.append(item)
- return item
-
-
-def _sx_dict_set(d, k, v):
- """Set key in dict, return the value (for expression context)."""
- d[k] = v
- return v
-
-
-def _sx_set_attr(obj, attr, val):
- """Set attribute on object, return the value."""
- setattr(obj, attr, val)
- return val
-
-
-def _sx_cell_set(cells, name, val):
- """Set a mutable cell value. Returns the value."""
- cells[name] = val
- return val
-
-
-def escape_string(s):
- """Escape a string for SX serialization."""
- return (str(s)
- .replace("\\", "\\\\")
- .replace('"', '\\"')
- .replace("\n", "\\n")
- .replace("\t", "\\t")
- .replace(""] = lambda a, b: a > b
-PRIMITIVES["<="] = lambda a, b: a <= b
-PRIMITIVES[">="] = lambda a, b: a >= b
-
-
-# core.logic
-PRIMITIVES["not"] = lambda x: not sx_truthy(x)
-
-
-# core.predicates
-PRIMITIVES["nil?"] = lambda x: x is None or x is NIL
-PRIMITIVES["number?"] = lambda x: isinstance(x, (int, float)) and not isinstance(x, bool)
-PRIMITIVES["string?"] = lambda x: isinstance(x, str)
-PRIMITIVES["list?"] = lambda x: isinstance(x, _b_list)
-PRIMITIVES["dict?"] = lambda x: isinstance(x, _b_dict)
-PRIMITIVES["boolean?"] = lambda x: isinstance(x, bool)
-PRIMITIVES["symbol?"] = lambda x: isinstance(x, Symbol)
-PRIMITIVES["keyword?"] = lambda x: isinstance(x, Keyword)
-PRIMITIVES["continuation?"] = lambda x: isinstance(x, Continuation)
-PRIMITIVES["empty?"] = lambda c: (
- c is None or c is NIL or
- (isinstance(c, (_b_list, str, _b_dict)) and _b_len(c) == 0)
-)
-PRIMITIVES["contains?"] = lambda c, k: (
- str(k) in c if isinstance(c, str) else
- k in c
-)
-PRIMITIVES["odd?"] = lambda n: n % 2 != 0
-PRIMITIVES["even?"] = lambda n: n % 2 == 0
-PRIMITIVES["zero?"] = lambda n: n == 0
-
-
-# core.strings
-PRIMITIVES["str"] = sx_str
-PRIMITIVES["char-from-code"] = lambda n: chr(_b_int(n))
-PRIMITIVES["upper"] = lambda s: str(s).upper()
-PRIMITIVES["lower"] = lambda s: str(s).lower()
-PRIMITIVES["trim"] = lambda s: str(s).strip()
-PRIMITIVES["split"] = lambda s, sep=" ": str(s).split(sep)
-PRIMITIVES["join"] = lambda sep, coll: sep.join(str(x) for x in coll)
-PRIMITIVES["replace"] = lambda s, old, new: s.replace(old, new)
-PRIMITIVES["index-of"] = lambda s, needle, start=0: str(s).find(needle, start)
-PRIMITIVES["starts-with?"] = lambda s, p: str(s).startswith(p)
-PRIMITIVES["ends-with?"] = lambda s, p: str(s).endswith(p)
-PRIMITIVES["slice"] = lambda c, a, b=None: c[int(a):] if (b is None or b is NIL) else c[int(a):int(b)]
-PRIMITIVES["concat"] = lambda *args: _b_sum((a for a in args if a), [])
-
-
-# core.collections
-PRIMITIVES["list"] = lambda *args: _b_list(args)
-PRIMITIVES["dict"] = lambda *args: {args[i]: args[i+1] for i in _b_range(0, _b_len(args)-1, 2)}
-PRIMITIVES["range"] = lambda a, b, step=1: _b_list(_b_range(_b_int(a), _b_int(b), _b_int(step)))
-PRIMITIVES["get"] = lambda c, k, default=NIL: c.get(k, default) if isinstance(c, _b_dict) else (c[k] if isinstance(c, (_b_list, str)) and isinstance(k, _b_int) and 0 <= k < _b_len(c) else (c.get(k, default) if hasattr(c, 'get') else default))
-PRIMITIVES["len"] = lambda c: _b_len(c) if c is not None and c is not NIL else 0
-PRIMITIVES["first"] = lambda c: c[0] if c and _b_len(c) > 0 else NIL
-PRIMITIVES["last"] = lambda c: c[-1] if c and _b_len(c) > 0 else NIL
-PRIMITIVES["rest"] = lambda c: c[1:] if c else []
-PRIMITIVES["nth"] = lambda c, n: c[n] if c and 0 <= n < _b_len(c) else NIL
-PRIMITIVES["cons"] = lambda x, c: [x] + (c or [])
-PRIMITIVES["append"] = lambda c, x: (c or []) + (x if isinstance(x, list) else [x])
-PRIMITIVES["chunk-every"] = lambda c, n: [c[i:i+n] for i in _b_range(0, _b_len(c), n)]
-PRIMITIVES["zip-pairs"] = lambda c: [[c[i], c[i+1]] for i in _b_range(_b_len(c)-1)]
-
-
-# core.dict
-PRIMITIVES["keys"] = lambda d: _b_list((d or {}).keys())
-PRIMITIVES["vals"] = lambda d: _b_list((d or {}).values())
-PRIMITIVES["merge"] = lambda *args: _sx_merge_dicts(*args)
-PRIMITIVES["has-key?"] = lambda d, k: isinstance(d, _b_dict) and k in d
-PRIMITIVES["assoc"] = lambda d, *kvs: _sx_assoc(d, *kvs)
-PRIMITIVES["dissoc"] = lambda d, *ks: {k: v for k, v in d.items() if k not in ks}
-PRIMITIVES["into"] = lambda target, coll: (_b_list(coll) if isinstance(target, _b_list) else {p[0]: p[1] for p in coll if isinstance(p, _b_list) and _b_len(p) >= 2})
-PRIMITIVES["zip"] = lambda *colls: [_b_list(t) for t in _b_zip(*colls)]
-
-def _sx_merge_dicts(*args):
- out = {}
- for d in args:
- if d and d is not NIL and isinstance(d, _b_dict):
- out.update(d)
- return out
-
-def _sx_assoc(d, *kvs):
- out = _b_dict(d) if d and d is not NIL else {}
- for i in _b_range(0, _b_len(kvs) - 1, 2):
- out[kvs[i]] = kvs[i + 1]
- return out
-
-
-# stdlib.format
-PRIMITIVES["format-decimal"] = lambda v, p=2: f"{float(v):.{p}f}"
-PRIMITIVES["parse-int"] = lambda v, d=0: _sx_parse_int(v, d)
-PRIMITIVES["parse-datetime"] = lambda s: str(s) if s else NIL
-
-def _sx_parse_int(v, default=0):
- if v is None or v is NIL:
- return default
- s = str(v).strip()
- # Match JS parseInt: extract leading integer portion
- import re as _re
- m = _re.match(r'^[+-]?\d+', s)
- if m:
- return _b_int(m.group())
- return default
-
-
-# stdlib.text
-PRIMITIVES["pluralize"] = lambda n, s="", p="s": s if n == 1 else p
-PRIMITIVES["escape"] = escape_html
-PRIMITIVES["strip-tags"] = lambda s: _strip_tags(str(s))
-
-import re as _re
-def _strip_tags(s):
- return _re.sub(r"<[^>]+>", "", s)
-
-
-# stdlib.style — stubs (CSSX needs full runtime)
-
-
-# stdlib.debug
-PRIMITIVES["assert"] = lambda cond, msg="Assertion failed": (_ for _ in ()).throw(RuntimeError(f"Assertion error: {msg}")) if not sx_truthy(cond) else True
-
-
-# stdlib.spread — spread + collect + scope primitives
-PRIMITIVES["make-spread"] = make_spread
-PRIMITIVES["spread?"] = is_spread
-PRIMITIVES["spread-attrs"] = spread_attrs
-PRIMITIVES["collect!"] = sx_collect
-PRIMITIVES["collected"] = sx_collected
-PRIMITIVES["clear-collected!"] = sx_clear_collected
-# scope — unified render-time dynamic scope
-PRIMITIVES["scope-push!"] = scope_push
-PRIMITIVES["scope-pop!"] = scope_pop
-# provide-push!/provide-pop! — aliases for scope-push!/scope-pop!
-PRIMITIVES["provide-push!"] = provide_push
-PRIMITIVES["provide-pop!"] = provide_pop
-PRIMITIVES["context"] = sx_context
-PRIMITIVES["emit!"] = sx_emit
-PRIMITIVES["emitted"] = sx_emitted
-
-
-def is_primitive(name):
- if name in PRIMITIVES:
- return True
- from shared.sx.primitives import get_primitive as _ext_get
- return _ext_get(name) is not None
-
-def get_primitive(name):
- p = PRIMITIVES.get(name)
- if p is not None:
- return p
- from shared.sx.primitives import get_primitive as _ext_get
- return _ext_get(name)
-
-# Higher-order helpers used by transpiled code
-def map(fn, coll):
- return [fn(x) for x in coll]
-
-def map_indexed(fn, coll):
- return [fn(i, item) for i, item in enumerate(coll)]
-
-def filter(fn, coll):
- return [x for x in coll if sx_truthy(fn(x))]
-
-def reduce(fn, init, coll):
- acc = init
- for item in coll:
- acc = fn(acc, item)
- return acc
-
-def some(fn, coll):
- for item in coll:
- r = fn(item)
- if sx_truthy(r):
- return r
- return NIL
-
-def every_p(fn, coll):
- for item in coll:
- if not sx_truthy(fn(item)):
- return False
- return True
-
-def for_each(fn, coll):
- for item in coll:
- fn(item)
- return NIL
-
-def for_each_indexed(fn, coll):
- for i, item in enumerate(coll):
- fn(i, item)
- return NIL
-
-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"]
-rest = PRIMITIVES["rest"]
-nth = PRIMITIVES["nth"]
-len = PRIMITIVES["len"]
-is_nil = PRIMITIVES["nil?"]
-empty_p = PRIMITIVES["empty?"]
-contains_p = PRIMITIVES["contains?"]
-starts_with_p = PRIMITIVES["starts-with?"]
-ends_with_p = PRIMITIVES["ends-with?"]
-slice = PRIMITIVES["slice"]
-get = PRIMITIVES["get"]
-append = PRIMITIVES["append"]
-cons = PRIMITIVES["cons"]
-keys = PRIMITIVES["keys"]
-join = PRIMITIVES["join"]
-range = PRIMITIVES["range"]
-apply = lambda f, args: f(*args)
-assoc = PRIMITIVES["assoc"]
-concat = PRIMITIVES["concat"]
-split = PRIMITIVES["split"]
-length = PRIMITIVES["len"]
-merge = PRIMITIVES["merge"]
-trim = PRIMITIVES["trim"]
-replace = PRIMITIVES["replace"]
-parse_int = PRIMITIVES["parse-int"]
-upper = PRIMITIVES["upper"]
-has_key_p = PRIMITIVES["has-key?"]
-dict_p = PRIMITIVES["dict?"]
-boolean_p = PRIMITIVES["boolean?"]
-symbol_p = PRIMITIVES["symbol?"]
-keyword_p = PRIMITIVES["keyword?"]
-number_p = PRIMITIVES["number?"]
-string_p = PRIMITIVES["string?"]
-list_p = PRIMITIVES["list?"]
-dissoc = PRIMITIVES["dissoc"]
-PRIMITIVES["char-code-at"] = lambda s, i: ord(s[int(i)]) if 0 <= int(i) < len(s) else 0
-PRIMITIVES["to-hex"] = lambda n: hex(int(n) & 0xFFFFFFFF)[2:]
-char_code_at = PRIMITIVES["char-code-at"]
-to_hex = PRIMITIVES["to-hex"]
-index_of = PRIMITIVES["index-of"]
-lower = PRIMITIVES["lower"]
-char_from_code = PRIMITIVES["char-from-code"]
-
-
-# =========================================================================
-# Platform interface — Parser
-# =========================================================================
-
-import re as _re_parser
-
-_IDENT_START_RE = _re_parser.compile(r"[a-zA-Z_~*+\-><=/!?&]")
-_IDENT_CHAR_RE = _re_parser.compile(r"[a-zA-Z0-9_~*+\-><=/!?.:&/#,]")
-
-
-def ident_start_p(ch):
- return bool(_IDENT_START_RE.match(ch))
-
-
-def ident_char_p(ch):
- return bool(_IDENT_CHAR_RE.match(ch))
-
-
-def parse_number(s):
- """Parse a numeric string to int or float."""
- try:
- if "." in s or "e" in s or "E" in s:
- return float(s)
- return int(s)
- except (ValueError, TypeError):
- return float(s)
-
-
-# Reader macro registry
-_reader_macros = {}
-
-
-def reader_macro_get(name):
- return _reader_macros.get(name, NIL)
-
-
-def reader_macro_set_b(name, handler):
- _reader_macros[name] = handler
- return NIL
-
-
-# =========================================================================
-# Platform: deps module — component dependency analysis
-# =========================================================================
-
-import re as _re
-
-def component_deps(c):
- """Return cached deps list for a component (may be empty)."""
- return list(c.deps) if hasattr(c, "deps") and c.deps else []
-
-def component_set_deps(c, deps):
- """Cache deps on a component."""
- c.deps = set(deps) if not isinstance(deps, set) else deps
-
-def component_css_classes(c):
- """Return pre-scanned CSS class list for a component."""
- return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else []
-
-def env_components(env):
- """Placeholder — overridden by transpiled version from deps.sx."""
- return [k for k, v in env.items()
- if isinstance(v, (Component, Macro))]
-
-def regex_find_all(pattern, source):
- """Return list of capture group 1 matches."""
- return [m.group(1) for m in _re.finditer(pattern, source)]
-
-def scan_css_classes(source):
- """Extract CSS class strings from SX source."""
- classes = set()
- for m in _re.finditer(r':class\s+"([^"]*)"', source):
- classes.update(m.group(1).split())
- for m in _re.finditer(r':class\s+\(str\s+((?:"[^"]*"\s*)+)\)', source):
- for s in _re.findall(r'"([^"]*)"', m.group(1)):
- classes.update(s.split())
- for m in _re.finditer(r';;\s*@css\s+(.+)', source):
- classes.update(m.group(1).split())
- return list(classes)
-
-def component_io_refs(c):
- """Return cached IO refs list, or NIL if not yet computed."""
- if not hasattr(c, "io_refs") or c.io_refs is None:
- return NIL
- return list(c.io_refs)
-
-def component_set_io_refs(c, refs):
- """Cache IO refs on a component."""
- c.io_refs = set(refs) if not isinstance(refs, set) else refs
-
-
-# =========================================================================
-# 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 {}
-
-
-# =========================================================================
-# Platform interface -- Async adapter
-# =========================================================================
-
-import contextvars
-import inspect as _inspect
-
-from shared.sx.primitives_io import (
- IO_PRIMITIVES, RequestContext, execute_io,
-)
-
-# Lazy imports to avoid circular dependency (html.py imports sx_ref.py)
-_css_class_collector_cv = None
-_svg_context_cv = None
-
-def _ensure_html_imports():
- global _css_class_collector_cv, _svg_context_cv
- if _css_class_collector_cv is None:
- from shared.sx.html import css_class_collector, _svg_context
- _css_class_collector_cv = css_class_collector
- _svg_context_cv = _svg_context
-
-# When True, async_aser expands known components server-side
-_expand_components_cv: contextvars.ContextVar[bool] = contextvars.ContextVar(
- "_expand_components_ref", default=False
-)
-
-
-class _AsyncThunk:
- __slots__ = ("expr", "env", "ctx")
- def __init__(self, expr, env, ctx):
- self.expr = expr
- self.env = env
- self.ctx = ctx
-
-
-def io_primitive_p(name):
- return name in IO_PRIMITIVES
-
-
-def expand_components_p():
- return _expand_components_cv.get()
-
-
-def svg_context_p():
- _ensure_html_imports()
- return _svg_context_cv.get(False)
-
-
-def svg_context_set(val):
- _ensure_html_imports()
- return _svg_context_cv.set(val)
-
-
-def svg_context_reset(token):
- _ensure_html_imports()
- _svg_context_cv.reset(token)
-
-
-def css_class_collect(val):
- _ensure_html_imports()
- collector = _css_class_collector_cv.get(None)
- if collector is not None:
- collector.update(str(val).split())
-
-
-def is_raw_html(x):
- return isinstance(x, _RawHTML)
-
-
-def make_sx_expr(s):
- return SxExpr(s)
-
-
-def is_sx_expr(x):
- return isinstance(x, SxExpr)
-
-
-# Predicate helpers used by adapter-async (these are in PRIMITIVES but
-# the bootstrapped code calls them as plain functions)
-def string_p(x):
- return isinstance(x, str)
-
-
-def list_p(x):
- return isinstance(x, _b_list)
-
-
-def number_p(x):
- return isinstance(x, (int, float)) and not isinstance(x, bool)
-
-
-def is_async_coroutine(x):
- return _inspect.iscoroutine(x)
-
-
-async def async_await(x):
- return await x
-
-
-async def _async_trampoline(val):
- while isinstance(val, _AsyncThunk):
- val = await async_eval(val.expr, val.env, val.ctx)
- return val
-
-
-async def async_eval(expr, env, ctx=None):
- """Evaluate with I/O primitives. Entry point for async evaluation."""
- if ctx is None:
- ctx = RequestContext()
- result = await _async_eval_inner(expr, env, ctx)
- while isinstance(result, _AsyncThunk):
- result = await _async_eval_inner(result.expr, result.env, result.ctx)
- return result
-
-
-async def _async_eval_inner(expr, env, ctx):
- """Intercept I/O primitives, delegate everything else to sync eval."""
- if isinstance(expr, list) and expr:
- head = expr[0]
- if isinstance(head, Symbol) and head.name in IO_PRIMITIVES:
- args_list, kwargs = await _parse_io_args(expr[1:], env, ctx)
- return await execute_io(head.name, args_list, kwargs, ctx)
- is_render = isinstance(expr, list) and is_render_expr(expr)
- result = eval_expr(expr, env)
- result = trampoline(result)
- if is_render and isinstance(result, str):
- return _RawHTML(result)
- return result
-
-
-async def _parse_io_args(exprs, env, ctx):
- """Parse and evaluate I/O node args (keyword + positional)."""
- from shared.sx.types import Keyword as _Kw
- args_list = []
- kwargs = {}
- i = 0
- while i < len(exprs):
- item = exprs[i]
- if isinstance(item, _Kw) and i + 1 < len(exprs):
- kwargs[item.name] = await async_eval(exprs[i + 1], env, ctx)
- i += 2
- else:
- args_list.append(await async_eval(item, env, ctx))
- i += 1
- return args_list, kwargs
-
-
-async def async_eval_to_sx(expr, env, ctx=None):
- """Evaluate and produce SX source string (wire format)."""
- if ctx is None:
- ctx = RequestContext()
- result = await async_aser(expr, env, ctx)
- if isinstance(result, SxExpr):
- return result
- if result is None or result is NIL:
- return SxExpr("")
- if isinstance(result, str):
- return SxExpr(result)
- return SxExpr(sx_serialize(result))
-
-
-async def async_eval_slot_to_sx(expr, env, ctx=None):
- """Like async_eval_to_sx but expands component calls server-side."""
- if ctx is None:
- ctx = RequestContext()
- token = _expand_components_cv.set(True)
- try:
- result = await async_eval_slot_inner(expr, env, ctx)
- if isinstance(result, SxExpr):
- return result
- if result is None or result is NIL:
- return SxExpr("")
- if isinstance(result, str):
- return SxExpr(result)
- return SxExpr(sx_serialize(result))
- finally:
- _expand_components_cv.reset(token)
-
-
-# === Transpiled from eval ===
-
-# trampoline
-def trampoline(val):
- result = val
- if sx_truthy(is_thunk(result)):
- return trampoline(eval_expr(thunk_expr(result), thunk_env(result)))
- else:
- return result
-
-# eval-expr
-def eval_expr(expr, env):
- _match = type_of(expr)
- if _match == 'number':
- return expr
- elif _match == 'string':
- return expr
- elif _match == 'boolean':
- return expr
- elif _match == 'nil':
- return NIL
- elif _match == 'symbol':
- name = symbol_name(expr)
- if sx_truthy(env_has(env, name)):
- return env_get(env, name)
- elif sx_truthy(is_primitive(name)):
- return get_primitive(name)
- elif sx_truthy((name == 'true')):
- return True
- elif sx_truthy((name == 'false')):
- return False
- elif sx_truthy((name == 'nil')):
- return NIL
- else:
- debug_log('Undefined symbol:', name, 'primitive?:', is_primitive(name))
- return error(sx_str('Undefined symbol: ', name))
- elif _match == 'keyword':
- return keyword_name(expr)
- elif _match == 'dict':
- return map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr)
- elif _match == 'list':
- if sx_truthy(empty_p(expr)):
- return []
- else:
- return eval_list(expr, env)
- else:
- return expr
-
-# eval-list
-def eval_list(expr, env):
- head = first(expr)
- args = rest(expr)
- if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))):
- return map(lambda x: trampoline(eval_expr(x, env)), expr)
- else:
- if sx_truthy((type_of(head) == 'symbol')):
- name = symbol_name(head)
- if sx_truthy((name == 'if')):
- return sf_if(args, env)
- elif sx_truthy((name == 'when')):
- return sf_when(args, env)
- elif sx_truthy((name == 'cond')):
- return sf_cond(args, env)
- elif sx_truthy((name == 'case')):
- return sf_case(args, env)
- elif sx_truthy((name == 'and')):
- return sf_and(args, env)
- elif sx_truthy((name == 'or')):
- return sf_or(args, env)
- elif sx_truthy((name == 'let')):
- return sf_let(args, env)
- elif sx_truthy((name == 'let*')):
- return sf_let(args, env)
- elif sx_truthy((name == 'letrec')):
- return sf_letrec(args, env)
- elif sx_truthy((name == 'lambda')):
- return sf_lambda(args, env)
- elif sx_truthy((name == 'fn')):
- return sf_lambda(args, env)
- elif sx_truthy((name == 'define')):
- return sf_define(args, env)
- elif sx_truthy((name == 'defcomp')):
- return sf_defcomp(args, env)
- elif sx_truthy((name == 'defisland')):
- return sf_defisland(args, env)
- elif sx_truthy((name == 'defmacro')):
- return sf_defmacro(args, env)
- elif sx_truthy((name == 'defstyle')):
- return sf_defstyle(args, env)
- elif sx_truthy((name == 'defhandler')):
- return sf_defhandler(args, env)
- elif sx_truthy((name == 'defpage')):
- return sf_defpage(args, env)
- elif sx_truthy((name == 'defquery')):
- return sf_defquery(args, env)
- elif sx_truthy((name == 'defaction')):
- return sf_defaction(args, env)
- elif sx_truthy((name == 'deftype')):
- return sf_deftype(args, env)
- elif sx_truthy((name == 'defeffect')):
- return sf_defeffect(args, env)
- elif sx_truthy((name == 'begin')):
- return sf_begin(args, env)
- elif sx_truthy((name == 'do')):
- return sf_begin(args, env)
- elif sx_truthy((name == 'quote')):
- return sf_quote(args, env)
- elif sx_truthy((name == 'quasiquote')):
- return sf_quasiquote(args, env)
- elif sx_truthy((name == '->')):
- return sf_thread_first(args, env)
- elif sx_truthy((name == 'set!')):
- return sf_set_bang(args, env)
- elif sx_truthy((name == 'reset')):
- return sf_reset(args, env)
- elif sx_truthy((name == 'shift')):
- return sf_shift(args, env)
- elif sx_truthy((name == 'dynamic-wind')):
- return sf_dynamic_wind(args, env)
- elif sx_truthy((name == 'scope')):
- return sf_scope(args, env)
- elif sx_truthy((name == 'provide')):
- return sf_provide(args, env)
- elif sx_truthy((name == 'map')):
- return ho_map(args, env)
- elif sx_truthy((name == 'map-indexed')):
- return ho_map_indexed(args, env)
- elif sx_truthy((name == 'filter')):
- return ho_filter(args, env)
- elif sx_truthy((name == 'reduce')):
- return ho_reduce(args, env)
- elif sx_truthy((name == 'some')):
- return ho_some(args, env)
- elif sx_truthy((name == 'every?')):
- return ho_every(args, env)
- elif sx_truthy((name == 'for-each')):
- return ho_for_each(args, env)
- elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
- mac = env_get(env, name)
- return make_thunk(expand_macro(mac, args, env), env)
- elif sx_truthy((render_active_p() if not sx_truthy(render_active_p()) else is_render_expr(expr))):
- return render_expr(expr, env)
- else:
- return eval_call(head, args, env)
- else:
- return eval_call(head, args, env)
-
-# eval-call
-def eval_call(head, args, env):
- f = trampoline(eval_expr(head, env))
- evaluated_args = map(lambda a: trampoline(eval_expr(a, env)), args)
- if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))):
- return apply(f, evaluated_args)
- elif sx_truthy(is_lambda(f)):
- return call_lambda(f, evaluated_args, env)
- elif sx_truthy(is_component(f)):
- return call_component(f, args, env)
- elif sx_truthy(is_island(f)):
- return call_component(f, args, env)
- else:
- return error(sx_str('Not callable: ', inspect(f)))
-
-# call-lambda
-def call_lambda(f, args, caller_env):
- params = lambda_params(f)
- local = env_merge(lambda_closure(f), caller_env)
- if sx_truthy((len(args) > len(params))):
- return error(sx_str((lambda_name(f) if sx_truthy(lambda_name(f)) else 'lambda'), ' expects ', len(params), ' args, got ', len(args)))
- else:
- for pair in zip(params, args):
- local[first(pair)] = nth(pair, 1)
- for p in slice(params, len(args)):
- local[p] = NIL
- return make_thunk(lambda_body(f), local)
-
-# call-component
-def call_component(comp, raw_args, env):
- parsed = parse_keyword_args(raw_args, env)
- kwargs = first(parsed)
- children = nth(parsed, 1)
- local = env_merge(component_closure(comp), env)
- for p in component_params(comp):
- local[p] = (dict_get(kwargs, p) if sx_truthy(dict_get(kwargs, p)) else NIL)
- if sx_truthy(component_has_children(comp)):
- local['children'] = children
- return make_thunk(component_body(comp), local)
-
-# parse-keyword-args
-def parse_keyword_args(raw_args, env):
- kwargs = {}
- children = []
- i = 0
- reduce(lambda state, arg: (lambda idx: (lambda skip: (assoc(state, 'skip', False, 'i', (idx + 1)) if sx_truthy(skip) else (_sx_begin(_sx_dict_set(kwargs, keyword_name(arg), trampoline(eval_expr(nth(raw_args, (idx + 1)), env))), assoc(state, 'skip', True, 'i', (idx + 1))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((idx + 1) < len(raw_args)))) else _sx_begin(_sx_append(children, trampoline(eval_expr(arg, env))), assoc(state, 'i', (idx + 1))))))(get(state, 'skip')))(get(state, 'i')), {'i': 0, 'skip': False}, raw_args)
- return [kwargs, children]
-
-# sf-if
-def sf_if(args, env):
- condition = trampoline(eval_expr(first(args), env))
- if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))):
- return make_thunk(nth(args, 1), env)
- else:
- if sx_truthy((len(args) > 2)):
- return make_thunk(nth(args, 2), env)
- else:
- return NIL
-
-# sf-when
-def sf_when(args, env):
- condition = trampoline(eval_expr(first(args), env))
- if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))):
- for e in slice(args, 1, (len(args) - 1)):
- trampoline(eval_expr(e, env))
- return make_thunk(last(args), env)
- else:
- return NIL
-
-# cond-scheme?
-def cond_scheme_p(clauses):
- return every_p(lambda c: ((type_of(c) == 'list') if not sx_truthy((type_of(c) == 'list')) else (len(c) == 2)), clauses)
-
-# sf-cond
-def sf_cond(args, env):
- if sx_truthy(cond_scheme_p(args)):
- return sf_cond_scheme(args, env)
- else:
- return sf_cond_clojure(args, env)
-
-# sf-cond-scheme
-def sf_cond_scheme(clauses, env):
- if sx_truthy(empty_p(clauses)):
- return NIL
- else:
- clause = first(clauses)
- test = first(clause)
- body = nth(clause, 1)
- if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))):
- return make_thunk(body, env)
- else:
- if sx_truthy(trampoline(eval_expr(test, env))):
- return make_thunk(body, env)
- else:
- return sf_cond_scheme(rest(clauses), env)
-
-# sf-cond-clojure
-def sf_cond_clojure(clauses, env):
- if sx_truthy((len(clauses) < 2)):
- return NIL
- else:
- test = first(clauses)
- body = nth(clauses, 1)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))):
- return make_thunk(body, env)
- else:
- if sx_truthy(trampoline(eval_expr(test, env))):
- return make_thunk(body, env)
- else:
- return sf_cond_clojure(slice(clauses, 2), env)
-
-# sf-case
-def sf_case(args, env):
- match_val = trampoline(eval_expr(first(args), env))
- clauses = rest(args)
- return sf_case_loop(match_val, clauses, env)
-
-# sf-case-loop
-def sf_case_loop(match_val, clauses, env):
- if sx_truthy((len(clauses) < 2)):
- return NIL
- else:
- test = first(clauses)
- body = nth(clauses, 1)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))):
- return make_thunk(body, env)
- else:
- if sx_truthy((match_val == trampoline(eval_expr(test, env)))):
- return make_thunk(body, env)
- else:
- return sf_case_loop(match_val, slice(clauses, 2), env)
-
-# sf-and
-def sf_and(args, env):
- if sx_truthy(empty_p(args)):
- return True
- else:
- val = trampoline(eval_expr(first(args), env))
- if sx_truthy((not sx_truthy(val))):
- return val
- else:
- if sx_truthy((len(args) == 1)):
- return val
- else:
- return sf_and(rest(args), env)
-
-# sf-or
-def sf_or(args, env):
- if sx_truthy(empty_p(args)):
- return False
- else:
- val = trampoline(eval_expr(first(args), env))
- if sx_truthy(val):
- return val
- else:
- return sf_or(rest(args), env)
-
-# sf-let
-def sf_let(args, env):
- if sx_truthy((type_of(first(args)) == 'symbol')):
- return sf_named_let(args, env)
- else:
- bindings = first(args)
- body = rest(args)
- local = env_extend(env)
- if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))):
- for binding in bindings:
- vname = (symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))
- local[vname] = trampoline(eval_expr(nth(binding, 1), local))
- else:
- i = 0
- reduce(lambda acc, pair_idx: (lambda vname: (lambda val_expr: _sx_dict_set(local, vname, trampoline(eval_expr(val_expr, local))))(nth(bindings, ((pair_idx * 2) + 1))))((symbol_name(nth(bindings, (pair_idx * 2))) if sx_truthy((type_of(nth(bindings, (pair_idx * 2))) == 'symbol')) else nth(bindings, (pair_idx * 2)))), NIL, range(0, (len(bindings) / 2)))
- for e in slice(body, 0, (len(body) - 1)):
- trampoline(eval_expr(e, local))
- return make_thunk(last(body), local)
-
-# sf-named-let
-def sf_named_let(args, env):
- loop_name = symbol_name(first(args))
- bindings = nth(args, 1)
- body = slice(args, 2)
- params = []
- inits = []
- if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))):
- for binding in bindings:
- params.append((symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding)))
- inits.append(nth(binding, 1))
- else:
- reduce(lambda acc, pair_idx: _sx_begin(_sx_append(params, (symbol_name(nth(bindings, (pair_idx * 2))) if sx_truthy((type_of(nth(bindings, (pair_idx * 2))) == 'symbol')) else nth(bindings, (pair_idx * 2)))), _sx_append(inits, nth(bindings, ((pair_idx * 2) + 1)))), NIL, range(0, (len(bindings) / 2)))
- loop_body = (first(body) if sx_truthy((len(body) == 1)) else cons(make_symbol('begin'), body))
- loop_fn = make_lambda(params, loop_body, env)
- loop_fn.name = loop_name
- lambda_closure(loop_fn)[loop_name] = loop_fn
- init_vals = map(lambda e: trampoline(eval_expr(e, env)), inits)
- return call_lambda(loop_fn, init_vals, env)
-
-# sf-lambda
-def sf_lambda(args, env):
- params_expr = first(args)
- body_exprs = rest(args)
- body = (first(body_exprs) if sx_truthy((len(body_exprs) == 1)) else cons(make_symbol('begin'), body_exprs))
- param_names = map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else (symbol_name(first(p)) if sx_truthy(((type_of(p) == 'list') if not sx_truthy((type_of(p) == 'list')) else ((len(p) == 3) if not sx_truthy((len(p) == 3)) else ((type_of(nth(p, 1)) == 'keyword') if not sx_truthy((type_of(nth(p, 1)) == 'keyword')) else (keyword_name(nth(p, 1)) == 'as'))))) else p)), params_expr)
- return make_lambda(param_names, body, env)
-
-# sf-define
-def sf_define(args, env):
- name_sym = first(args)
- has_effects = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))
- val_idx = (3 if sx_truthy(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))) else 1)
- value = trampoline(eval_expr(nth(args, val_idx), env))
- if sx_truthy((is_lambda(value) if not sx_truthy(is_lambda(value)) else is_nil(lambda_name(value)))):
- value.name = symbol_name(name_sym)
- env[symbol_name(name_sym)] = value
- if sx_truthy(has_effects):
- effects_raw = nth(args, 2)
- effect_list = (map(lambda e: (symbol_name(e) if sx_truthy((type_of(e) == 'symbol')) else sx_str(e)), effects_raw) if sx_truthy((type_of(effects_raw) == 'list')) else [sx_str(effects_raw)])
- effect_anns = (env_get(env, '*effect-annotations*') if sx_truthy(env_has(env, '*effect-annotations*')) else {})
- effect_anns[symbol_name(name_sym)] = effect_list
- env['*effect-annotations*'] = effect_anns
- return value
-
-# sf-defcomp
-def sf_defcomp(args, env):
- name_sym = first(args)
- params_raw = nth(args, 1)
- body = last(args)
- comp_name = strip_prefix(symbol_name(name_sym), '~')
- parsed = parse_comp_params(params_raw)
- params = first(parsed)
- has_children = nth(parsed, 1)
- param_types = nth(parsed, 2)
- affinity = defcomp_kwarg(args, 'affinity', 'auto')
- comp = make_component(comp_name, params, has_children, body, env, affinity)
- effects = defcomp_kwarg(args, 'effects', NIL)
- if sx_truthy(((not sx_truthy(is_nil(param_types))) if not sx_truthy((not sx_truthy(is_nil(param_types)))) else (not sx_truthy(empty_p(keys(param_types)))))):
- component_set_param_types(comp, param_types)
- if sx_truthy((not sx_truthy(is_nil(effects)))):
- effect_list = (map(lambda e: (symbol_name(e) if sx_truthy((type_of(e) == 'symbol')) else sx_str(e)), effects) if sx_truthy((type_of(effects) == 'list')) else [sx_str(effects)])
- effect_anns = (env_get(env, '*effect-annotations*') if sx_truthy(env_has(env, '*effect-annotations*')) else {})
- effect_anns[symbol_name(name_sym)] = effect_list
- env['*effect-annotations*'] = effect_anns
- env[symbol_name(name_sym)] = comp
- return comp
-
-# defcomp-kwarg
-def defcomp_kwarg(args, key, default_):
- _cells = {}
- end = (len(args) - 1)
- _cells['result'] = default_
- for i in range(2, end, 1):
- if sx_truthy(((type_of(nth(args, i)) == 'keyword') if not sx_truthy((type_of(nth(args, i)) == 'keyword')) else ((keyword_name(nth(args, i)) == key) if not sx_truthy((keyword_name(nth(args, i)) == key)) else ((i + 1) < end)))):
- val = nth(args, (i + 1))
- _cells['result'] = (keyword_name(val) if sx_truthy((type_of(val) == 'keyword')) else val)
- return _cells['result']
-
-# parse-comp-params
-def parse_comp_params(params_expr):
- _cells = {}
- params = []
- param_types = {}
- _cells['has_children'] = False
- _cells['in_key'] = False
- for p in params_expr:
- if sx_truthy(((type_of(p) == 'list') if not sx_truthy((type_of(p) == 'list')) else ((len(p) == 3) if not sx_truthy((len(p) == 3)) else ((type_of(first(p)) == 'symbol') if not sx_truthy((type_of(first(p)) == 'symbol')) else ((type_of(nth(p, 1)) == 'keyword') if not sx_truthy((type_of(nth(p, 1)) == 'keyword')) else (keyword_name(nth(p, 1)) == 'as')))))):
- name = symbol_name(first(p))
- ptype = nth(p, 2)
- type_val = (symbol_name(ptype) if sx_truthy((type_of(ptype) == 'symbol')) else ptype)
- if sx_truthy((not sx_truthy(_cells['has_children']))):
- params.append(name)
- param_types[name] = type_val
- else:
- if sx_truthy((type_of(p) == 'symbol')):
- name = symbol_name(p)
- if sx_truthy((name == '&key')):
- _cells['in_key'] = True
- elif sx_truthy((name == '&rest')):
- _cells['has_children'] = True
- elif sx_truthy((name == '&children')):
- _cells['has_children'] = True
- elif sx_truthy(_cells['has_children']):
- NIL
- elif sx_truthy(_cells['in_key']):
- params.append(name)
- else:
- params.append(name)
- return [params, _cells['has_children'], param_types]
-
-# sf-defisland
-def sf_defisland(args, env):
- name_sym = first(args)
- params_raw = nth(args, 1)
- body = last(args)
- comp_name = strip_prefix(symbol_name(name_sym), '~')
- parsed = parse_comp_params(params_raw)
- params = first(parsed)
- has_children = nth(parsed, 1)
- island = make_island(comp_name, params, has_children, body, env)
- env[symbol_name(name_sym)] = island
- return island
-
-# sf-defmacro
-def sf_defmacro(args, env):
- name_sym = first(args)
- params_raw = nth(args, 1)
- body = nth(args, 2)
- parsed = parse_macro_params(params_raw)
- params = first(parsed)
- rest_param = nth(parsed, 1)
- mac = make_macro(params, rest_param, body, env, symbol_name(name_sym))
- env[symbol_name(name_sym)] = mac
- return mac
-
-# parse-macro-params
-def parse_macro_params(params_expr):
- _cells = {}
- params = []
- _cells['rest_param'] = NIL
- reduce(lambda state, p: (assoc(state, 'in-rest', True) if sx_truthy(((type_of(p) == 'symbol') if not sx_truthy((type_of(p) == 'symbol')) else (symbol_name(p) == '&rest'))) else (_sx_begin(_sx_cell_set(_cells, 'rest_param', (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else p)), state) if sx_truthy(get(state, 'in-rest')) else _sx_begin(_sx_append(params, (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else p)), state))), {'in-rest': False}, params_expr)
- return [params, _cells['rest_param']]
-
-# sf-defstyle
-def sf_defstyle(args, env):
- name_sym = first(args)
- value = trampoline(eval_expr(nth(args, 1), env))
- env[symbol_name(name_sym)] = value
- return value
-
-# make-type-def
-def make_type_def(name, params, body):
- return {'name': name, 'params': params, 'body': body}
-
-# normalize-type-body
-def normalize_type_body(body):
- if sx_truthy(is_nil(body)):
- return 'nil'
- elif sx_truthy((type_of(body) == 'symbol')):
- return symbol_name(body)
- elif sx_truthy((type_of(body) == 'string')):
- return body
- elif sx_truthy((type_of(body) == 'keyword')):
- return keyword_name(body)
- elif sx_truthy((type_of(body) == 'dict')):
- return map_dict(lambda k, v: normalize_type_body(v), body)
- elif sx_truthy((type_of(body) == 'list')):
- if sx_truthy(empty_p(body)):
- return 'any'
- else:
- head = first(body)
- head_name = (symbol_name(head) if sx_truthy((type_of(head) == 'symbol')) else sx_str(head))
- if sx_truthy((head_name == 'union')):
- return cons('or', map(normalize_type_body, rest(body)))
- else:
- return cons(head_name, map(normalize_type_body, rest(body)))
- else:
- return sx_str(body)
-
-# sf-deftype
-def sf_deftype(args, env):
- name_or_form = first(args)
- body_expr = nth(args, 1)
- type_name = NIL
- type_params = []
- if sx_truthy((type_of(name_or_form) == 'symbol')):
- type_name = symbol_name(name_or_form)
- else:
- if sx_truthy((type_of(name_or_form) == 'list')):
- type_name = symbol_name(first(name_or_form))
- type_params = map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else sx_str(p)), rest(name_or_form))
- body = normalize_type_body(body_expr)
- registry = (env_get(env, '*type-registry*') if sx_truthy(env_has(env, '*type-registry*')) else {})
- registry[type_name] = make_type_def(type_name, type_params, body)
- env['*type-registry*'] = registry
- return NIL
-
-# sf-defeffect
-def sf_defeffect(args, env):
- effect_name = (symbol_name(first(args)) if sx_truthy((type_of(first(args)) == 'symbol')) else sx_str(first(args)))
- registry = (env_get(env, '*effect-registry*') if sx_truthy(env_has(env, '*effect-registry*')) else [])
- if sx_truthy((not sx_truthy(contains_p(registry, effect_name)))):
- registry.append(effect_name)
- env['*effect-registry*'] = registry
- return NIL
-
-# sf-begin
-def sf_begin(args, env):
- if sx_truthy(empty_p(args)):
- return NIL
- else:
- for e in slice(args, 0, (len(args) - 1)):
- trampoline(eval_expr(e, env))
- return make_thunk(last(args), env)
-
-# sf-quote
-def sf_quote(args, env):
- if sx_truthy(empty_p(args)):
- return NIL
- else:
- return first(args)
-
-# sf-quasiquote
-def sf_quasiquote(args, env):
- return qq_expand(first(args), env)
-
-# qq-expand
-def qq_expand(template, env):
- if sx_truthy((not sx_truthy((type_of(template) == 'list')))):
- return template
- else:
- if sx_truthy(empty_p(template)):
- return []
- else:
- head = first(template)
- if sx_truthy(((type_of(head) == 'symbol') if not sx_truthy((type_of(head) == 'symbol')) else (symbol_name(head) == 'unquote'))):
- return trampoline(eval_expr(nth(template, 1), env))
- else:
- return reduce(lambda result, item: ((lambda spliced: (concat(result, spliced) if sx_truthy((type_of(spliced) == 'list')) else (result if sx_truthy(is_nil(spliced)) else concat(result, [spliced]))))(trampoline(eval_expr(nth(item, 1), env))) if sx_truthy(((type_of(item) == 'list') if not sx_truthy((type_of(item) == 'list')) else ((len(item) == 2) if not sx_truthy((len(item) == 2)) else ((type_of(first(item)) == 'symbol') if not sx_truthy((type_of(first(item)) == 'symbol')) else (symbol_name(first(item)) == 'splice-unquote'))))) else concat(result, [qq_expand(item, env)])), [], template)
-
-# sf-thread-first
-def sf_thread_first(args, env):
- val = trampoline(eval_expr(first(args), env))
- return reduce(lambda result, form: ((lambda f: (lambda rest_args: (lambda all_args: (apply(f, all_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, all_args, env)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(cons(result, rest_args)))(map(lambda a: trampoline(eval_expr(a, env)), rest(form))))(trampoline(eval_expr(first(form), env))) if sx_truthy((type_of(form) == 'list')) else (lambda f: (f(result) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, [result], env)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(trampoline(eval_expr(form, env)))), val, rest(args))
-
-# sf-set!
-def sf_set_bang(args, env):
- name = symbol_name(first(args))
- value = trampoline(eval_expr(nth(args, 1), env))
- env[name] = value
- return value
-
-# sf-letrec
-def sf_letrec(args, env):
- bindings = first(args)
- body = rest(args)
- local = env_extend(env)
- names = []
- val_exprs = []
- if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))):
- for binding in bindings:
- vname = (symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))
- names.append(vname)
- val_exprs.append(nth(binding, 1))
- local[vname] = NIL
- else:
- reduce(lambda acc, pair_idx: (lambda vname: (lambda val_expr: _sx_begin(_sx_append(names, vname), _sx_append(val_exprs, val_expr), _sx_dict_set(local, vname, NIL)))(nth(bindings, ((pair_idx * 2) + 1))))((symbol_name(nth(bindings, (pair_idx * 2))) if sx_truthy((type_of(nth(bindings, (pair_idx * 2))) == 'symbol')) else nth(bindings, (pair_idx * 2)))), NIL, range(0, (len(bindings) / 2)))
- values = map(lambda e: trampoline(eval_expr(e, local)), val_exprs)
- for pair in zip(names, values):
- local[first(pair)] = nth(pair, 1)
- for val in values:
- if sx_truthy(is_lambda(val)):
- for n in names:
- lambda_closure(val)[n] = env_get(local, n)
- for e in slice(body, 0, (len(body) - 1)):
- trampoline(eval_expr(e, local))
- return make_thunk(last(body), local)
-
-# sf-dynamic-wind
-def sf_dynamic_wind(args, env):
- before = trampoline(eval_expr(first(args), env))
- body = trampoline(eval_expr(nth(args, 1), env))
- after = trampoline(eval_expr(nth(args, 2), env))
- return dynamic_wind_call(before, body, after, env)
-
-# sf-scope
-def sf_scope(args, env):
- _cells = {}
- name = trampoline(eval_expr(first(args), env))
- rest = slice(args, 1)
- val = NIL
- body_exprs = NIL
- if sx_truthy(((len(rest) >= 2) if not sx_truthy((len(rest) >= 2)) else ((type_of(first(rest)) == 'keyword') if not sx_truthy((type_of(first(rest)) == 'keyword')) else (keyword_name(first(rest)) == 'value')))):
- val = trampoline(eval_expr(nth(rest, 1), env))
- body_exprs = slice(rest, 2)
- else:
- body_exprs = rest
- scope_push(name, val)
- _cells['result'] = NIL
- for e in body_exprs:
- _cells['result'] = trampoline(eval_expr(e, env))
- scope_pop(name)
- return _cells['result']
-
-# sf-provide
-def sf_provide(args, env):
- _cells = {}
- name = trampoline(eval_expr(first(args), env))
- val = trampoline(eval_expr(nth(args, 1), env))
- body_exprs = slice(args, 2)
- _cells['result'] = NIL
- scope_push(name, val)
- for e in body_exprs:
- _cells['result'] = trampoline(eval_expr(e, env))
- scope_pop(name)
- return _cells['result']
-
-# expand-macro
-def expand_macro(mac, raw_args, env):
- local = env_merge(macro_closure(mac), env)
- for pair in map_indexed(lambda i, p: [p, i], macro_params(mac)):
- local[first(pair)] = (nth(raw_args, nth(pair, 1)) if sx_truthy((nth(pair, 1) < len(raw_args))) else NIL)
- if sx_truthy(macro_rest_param(mac)):
- local[macro_rest_param(mac)] = slice(raw_args, len(macro_params(mac)))
- return trampoline(eval_expr(macro_body(mac), local))
-
-# call-fn
-def call_fn(f, args, env):
- if sx_truthy(is_lambda(f)):
- return trampoline(call_lambda(f, args, env))
- elif sx_truthy(is_callable(f)):
- return apply(f, args)
- else:
- return error(sx_str('Not callable in HO form: ', inspect(f)))
-
-# ho-map
-def ho_map(args, env):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- return map(lambda item: call_fn(f, [item], env), coll)
-
-# ho-map-indexed
-def ho_map_indexed(args, env):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- return map_indexed(lambda i, item: call_fn(f, [i, item], env), coll)
-
-# ho-filter
-def ho_filter(args, env):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- return filter(lambda item: call_fn(f, [item], env), coll)
-
-# ho-reduce
-def ho_reduce(args, env):
- f = trampoline(eval_expr(first(args), env))
- init = trampoline(eval_expr(nth(args, 1), env))
- coll = trampoline(eval_expr(nth(args, 2), env))
- return reduce(lambda acc, item: call_fn(f, [acc, item], env), init, coll)
-
-# ho-some
-def ho_some(args, env):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- return some(lambda item: call_fn(f, [item], env), coll)
-
-# ho-every
-def ho_every(args, env):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- return every_p(lambda item: call_fn(f, [item], env), coll)
-
-# ho-for-each
-def ho_for_each(args, env):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- for item in coll:
- call_fn(f, [item], env)
- return NIL
-
-
-# === Transpiled from forms (server definition forms) ===
-
-# parse-key-params
-def parse_key_params(params_expr):
- _cells = {}
- params = []
- _cells['in_key'] = False
- for p in params_expr:
- if sx_truthy((type_of(p) == 'symbol')):
- name = symbol_name(p)
- if sx_truthy((name == '&key')):
- _cells['in_key'] = True
- elif sx_truthy(_cells['in_key']):
- params.append(name)
- else:
- params.append(name)
- return params
-
-# parse-handler-args
-def parse_handler_args(args):
- _cells = {}
- 'Parse defhandler args after the name symbol.\n Scans for :keyword value option pairs, then a list (params), then body.\n Returns dict with keys: opts, params, body.'
- opts = {}
- _cells['params'] = []
- _cells['body'] = NIL
- _cells['i'] = 0
- n = len(args)
- _cells['done'] = False
- for idx in range(0, n):
- if sx_truthy(((not sx_truthy(_cells['done'])) if not sx_truthy((not sx_truthy(_cells['done']))) else (idx == _cells['i']))):
- arg = nth(args, idx)
- if sx_truthy((type_of(arg) == 'keyword')):
- if sx_truthy(((idx + 1) < n)):
- val = nth(args, (idx + 1))
- opts[keyword_name(arg)] = (keyword_name(val) if sx_truthy((type_of(val) == 'keyword')) else val)
- _cells['i'] = (idx + 2)
- elif sx_truthy((type_of(arg) == 'list')):
- _cells['params'] = parse_key_params(arg)
- if sx_truthy(((idx + 1) < n)):
- _cells['body'] = nth(args, (idx + 1))
- _cells['done'] = True
- else:
- _cells['body'] = arg
- _cells['done'] = True
- return {'opts': opts, 'params': _cells['params'], 'body': _cells['body']}
-
-# sf-defhandler
-def sf_defhandler(args, env):
- name_sym = first(args)
- name = symbol_name(name_sym)
- parsed = parse_handler_args(rest(args))
- opts = get(parsed, 'opts')
- params = get(parsed, 'params')
- body = get(parsed, 'body')
- hdef = make_handler_def(name, params, body, env, opts)
- env[sx_str('handler:', name)] = hdef
- return hdef
-
-# sf-defquery
-def sf_defquery(args, env):
- name_sym = first(args)
- params_raw = nth(args, 1)
- name = symbol_name(name_sym)
- params = parse_key_params(params_raw)
- has_doc = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))
- doc = (nth(args, 2) if sx_truthy(has_doc) else '')
- body = (nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))
- qdef = make_query_def(name, params, doc, body, env)
- env[sx_str('query:', name)] = qdef
- return qdef
-
-# sf-defaction
-def sf_defaction(args, env):
- name_sym = first(args)
- params_raw = nth(args, 1)
- name = symbol_name(name_sym)
- params = parse_key_params(params_raw)
- has_doc = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))
- doc = (nth(args, 2) if sx_truthy(has_doc) else '')
- body = (nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))
- adef = make_action_def(name, params, doc, body, env)
- env[sx_str('action:', name)] = adef
- return adef
-
-# sf-defpage
-def sf_defpage(args, env):
- name_sym = first(args)
- name = symbol_name(name_sym)
- slots = {}
- i = 1
- max_i = len(args)
- for idx in range(1, max_i, 2):
- if sx_truthy(((idx < max_i) if not sx_truthy((idx < max_i)) else (type_of(nth(args, idx)) == 'keyword'))):
- if sx_truthy(((idx + 1) < max_i)):
- slots[keyword_name(nth(args, idx))] = nth(args, (idx + 1))
- pdef = make_page_def(name, slots, env)
- env[sx_str('page:', name)] = pdef
- return pdef
-
-# stream-chunk-id
-def stream_chunk_id(chunk):
- if sx_truthy(has_key_p(chunk, 'stream-id')):
- return get(chunk, 'stream-id')
- else:
- return 'stream-content'
-
-# stream-chunk-bindings
-def stream_chunk_bindings(chunk):
- return dissoc(chunk, 'stream-id')
-
-# normalize-binding-key
-def normalize_binding_key(key):
- return replace(key, '_', '-')
-
-# bind-stream-chunk
-def bind_stream_chunk(chunk, base_env):
- env = merge({}, base_env)
- bindings = stream_chunk_bindings(chunk)
- for key in keys(bindings):
- env[normalize_binding_key(key)] = get(bindings, key)
- return env
-
-# validate-stream-data
-def validate_stream_data(data):
- return ((type_of(data) == 'list') if not sx_truthy((type_of(data) == 'list')) else every_p(lambda item: (type_of(item) == 'dict'), data))
-
-
-# === Transpiled from render (core) ===
-
-# HTML_TAGS
-HTML_TAGS = ['html', 'head', 'body', 'title', 'meta', 'link', 'script', 'style', 'noscript', 'header', 'nav', 'main', 'section', 'article', 'aside', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hgroup', 'div', 'p', 'blockquote', 'pre', 'figure', 'figcaption', 'address', 'details', 'summary', 'a', 'span', 'em', 'strong', 'small', 'b', 'i', 'u', 's', 'mark', 'sub', 'sup', 'abbr', 'cite', 'code', 'time', 'br', 'wbr', 'hr', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption', 'colgroup', 'col', 'form', 'input', 'textarea', 'select', 'option', 'optgroup', 'button', 'label', 'fieldset', 'legend', 'output', 'datalist', 'img', 'video', 'audio', 'source', 'picture', 'canvas', 'iframe', 'svg', 'math', 'path', 'circle', 'ellipse', 'rect', 'line', 'polyline', 'polygon', 'text', 'tspan', 'g', 'defs', 'use', 'clipPath', 'mask', 'pattern', 'linearGradient', 'radialGradient', 'stop', 'filter', 'feGaussianBlur', 'feOffset', 'feBlend', 'feColorMatrix', 'feComposite', 'feMerge', 'feMergeNode', 'feTurbulence', 'feComponentTransfer', 'feFuncR', 'feFuncG', 'feFuncB', 'feFuncA', 'feDisplacementMap', 'feFlood', 'feImage', 'feMorphology', 'feSpecularLighting', 'feDiffuseLighting', 'fePointLight', 'feSpotLight', 'feDistantLight', 'animate', 'animateTransform', 'foreignObject', 'template', 'slot', 'dialog', 'menu']
-
-# VOID_ELEMENTS
-VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']
-
-# BOOLEAN_ATTRS
-BOOLEAN_ATTRS = ['async', 'autofocus', 'autoplay', 'checked', 'controls', 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'inert', 'ismap', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'playsinline', 'readonly', 'required', 'reversed', 'selected']
-
-# definition-form?
-def is_definition_form(name):
- return ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defisland') if sx_truthy((name == 'defisland')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))
-
-# parse-element-args
-def parse_element_args(args, env):
- attrs = {}
- children = []
- reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(attrs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
- return [attrs, children]
-
-# render-attrs
-def render_attrs(attrs):
- return join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"')))))(dict_get(attrs, key)), keys(attrs)))
-
-# eval-cond
-def eval_cond(clauses, env):
- if sx_truthy(cond_scheme_p(clauses)):
- return eval_cond_scheme(clauses, env)
- else:
- return eval_cond_clojure(clauses, env)
-
-# eval-cond-scheme
-def eval_cond_scheme(clauses, env):
- if sx_truthy(empty_p(clauses)):
- return NIL
- else:
- clause = first(clauses)
- test = first(clause)
- body = nth(clause, 1)
- if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))):
- return body
- else:
- if sx_truthy(trampoline(eval_expr(test, env))):
- return body
- else:
- return eval_cond_scheme(rest(clauses), env)
-
-# eval-cond-clojure
-def eval_cond_clojure(clauses, env):
- if sx_truthy((len(clauses) < 2)):
- return NIL
- else:
- test = first(clauses)
- body = nth(clauses, 1)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))):
- return body
- else:
- if sx_truthy(trampoline(eval_expr(test, env))):
- return body
- else:
- return eval_cond_clojure(slice(clauses, 2), env)
-
-# process-bindings
-def process_bindings(bindings, env):
- local = env_extend(env)
- for pair in bindings:
- if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))):
- name = (symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair)))
- local[name] = trampoline(eval_expr(nth(pair, 1), local))
- return local
-
-# is-render-expr?
-def is_render_expr(expr):
- if sx_truthy(((not sx_truthy((type_of(expr) == 'list'))) if sx_truthy((not sx_truthy((type_of(expr) == 'list')))) else empty_p(expr))):
- return False
- else:
- h = first(expr)
- if sx_truthy((not sx_truthy((type_of(h) == 'symbol')))):
- return False
- else:
- n = symbol_name(h)
- return ((n == '<>') if sx_truthy((n == '<>')) else ((n == 'raw!') if sx_truthy((n == 'raw!')) else (starts_with_p(n, '~') if sx_truthy(starts_with_p(n, '~')) else (starts_with_p(n, 'html:') if sx_truthy(starts_with_p(n, 'html:')) else (contains_p(HTML_TAGS, n) if sx_truthy(contains_p(HTML_TAGS, n)) else ((index_of(n, '-') > 0) if not sx_truthy((index_of(n, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword'))))))))
-
-# merge-spread-attrs
-def merge_spread_attrs(target, spread_dict):
- for key in keys(spread_dict):
- val = dict_get(spread_dict, key)
- if sx_truthy((key == 'class')):
- existing = dict_get(target, 'class')
- target['class'] = (sx_str(existing, ' ', val) if sx_truthy((existing if not sx_truthy(existing) else (not sx_truthy((existing == ''))))) else val)
- else:
- if sx_truthy((key == 'style')):
- existing = dict_get(target, 'style')
- target['style'] = (sx_str(existing, ';', val) if sx_truthy((existing if not sx_truthy(existing) else (not sx_truthy((existing == ''))))) else val)
- else:
- target[key] = val
- return NIL
-
-
-# === Transpiled from parser ===
-
-# sx-parse
-def sx_parse(source):
- _cells = {}
- _cells['pos'] = 0
- len_src = len(source)
- def skip_comment():
- while True:
- if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (not sx_truthy((nth(source, _cells['pos']) == '\n'))))):
- _cells['pos'] = (_cells['pos'] + 1)
- continue
- break
- def skip_ws():
- while True:
- if sx_truthy((_cells['pos'] < len_src)):
- ch = nth(source, _cells['pos'])
- if sx_truthy(((ch == ' ') if sx_truthy((ch == ' ')) else ((ch == '\t') if sx_truthy((ch == '\t')) else ((ch == '\n') if sx_truthy((ch == '\n')) else (ch == '\r'))))):
- _cells['pos'] = (_cells['pos'] + 1)
- continue
- elif sx_truthy((ch == ';')):
- _cells['pos'] = (_cells['pos'] + 1)
- skip_comment()
- continue
- else:
- break
- break
- def hex_digit_value(ch):
- return index_of('0123456789abcdef', lower(ch))
- def read_string():
- _cells['pos'] = (_cells['pos'] + 1)
- _cells['buf'] = ''
- while True:
- if sx_truthy((_cells['pos'] >= len_src)):
- error('Unterminated string')
- break
- else:
- ch = nth(source, _cells['pos'])
- if sx_truthy((ch == '"')):
- _cells['pos'] = (_cells['pos'] + 1)
- break
- elif sx_truthy((ch == '\\')):
- _cells['pos'] = (_cells['pos'] + 1)
- esc = nth(source, _cells['pos'])
- if sx_truthy((esc == 'u')):
- _cells['pos'] = (_cells['pos'] + 1)
- d0 = hex_digit_value(nth(source, _cells['pos']))
- _ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
- d1 = hex_digit_value(nth(source, _cells['pos']))
- _ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
- d2 = hex_digit_value(nth(source, _cells['pos']))
- _ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
- d3 = hex_digit_value(nth(source, _cells['pos']))
- _ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
- _cells['buf'] = sx_str(_cells['buf'], char_from_code(((d0 * 4096) + (d1 * 256))))
- continue
- else:
- _cells['buf'] = sx_str(_cells['buf'], ('\n' if sx_truthy((esc == 'n')) else ('\t' if sx_truthy((esc == 't')) else ('\r' if sx_truthy((esc == 'r')) else esc))))
- _cells['pos'] = (_cells['pos'] + 1)
- continue
- else:
- _cells['buf'] = sx_str(_cells['buf'], ch)
- _cells['pos'] = (_cells['pos'] + 1)
- continue
- return _cells['buf']
- def read_ident():
- start = _cells['pos']
- while True:
- if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else ident_char_p(nth(source, _cells['pos'])))):
- _cells['pos'] = (_cells['pos'] + 1)
- continue
- break
- return slice(source, start, _cells['pos'])
- def read_keyword():
- _cells['pos'] = (_cells['pos'] + 1)
- return make_keyword(read_ident())
- def read_number():
- start = _cells['pos']
- if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (nth(source, _cells['pos']) == '-'))):
- _cells['pos'] = (_cells['pos'] + 1)
- def read_digits():
- while True:
- if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (lambda c: ((c >= '0') if not sx_truthy((c >= '0')) else (c <= '9')))(nth(source, _cells['pos'])))):
- _cells['pos'] = (_cells['pos'] + 1)
- continue
- break
- read_digits()
- if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (nth(source, _cells['pos']) == '.'))):
- _cells['pos'] = (_cells['pos'] + 1)
- read_digits()
- if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else ((nth(source, _cells['pos']) == 'e') if sx_truthy((nth(source, _cells['pos']) == 'e')) else (nth(source, _cells['pos']) == 'E')))):
- _cells['pos'] = (_cells['pos'] + 1)
- if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else ((nth(source, _cells['pos']) == '+') if sx_truthy((nth(source, _cells['pos']) == '+')) else (nth(source, _cells['pos']) == '-')))):
- _cells['pos'] = (_cells['pos'] + 1)
- read_digits()
- return parse_number(slice(source, start, _cells['pos']))
- def read_symbol():
- name = read_ident()
- if sx_truthy((name == 'true')):
- return True
- elif sx_truthy((name == 'false')):
- return False
- elif sx_truthy((name == 'nil')):
- return NIL
- else:
- return make_symbol(name)
- def read_list(close_ch):
- items = []
- while True:
- skip_ws()
- if sx_truthy((_cells['pos'] >= len_src)):
- error('Unterminated list')
- break
- else:
- if sx_truthy((nth(source, _cells['pos']) == close_ch)):
- _cells['pos'] = (_cells['pos'] + 1)
- break
- else:
- items.append(read_expr())
- continue
- return items
- def read_map():
- result = {}
- while True:
- skip_ws()
- if sx_truthy((_cells['pos'] >= len_src)):
- error('Unterminated map')
- break
- else:
- if sx_truthy((nth(source, _cells['pos']) == '}')):
- _cells['pos'] = (_cells['pos'] + 1)
- break
- else:
- key_expr = read_expr()
- key_str = (keyword_name(key_expr) if sx_truthy((type_of(key_expr) == 'keyword')) else sx_str(key_expr))
- val_expr = read_expr()
- result[key_str] = val_expr
- continue
- return result
- def read_raw_string():
- _cells['buf'] = ''
- while True:
- if sx_truthy((_cells['pos'] >= len_src)):
- error('Unterminated raw string')
- break
- else:
- ch = nth(source, _cells['pos'])
- if sx_truthy((ch == '|')):
- _cells['pos'] = (_cells['pos'] + 1)
- break
- else:
- _cells['buf'] = sx_str(_cells['buf'], ch)
- _cells['pos'] = (_cells['pos'] + 1)
- continue
- return _cells['buf']
- def read_expr():
- skip_ws()
- if sx_truthy((_cells['pos'] >= len_src)):
- return error('Unexpected end of input')
- else:
- ch = nth(source, _cells['pos'])
- if sx_truthy((ch == '(')):
- _cells['pos'] = (_cells['pos'] + 1)
- return read_list(')')
- elif sx_truthy((ch == '[')):
- _cells['pos'] = (_cells['pos'] + 1)
- return read_list(']')
- elif sx_truthy((ch == '{')):
- _cells['pos'] = (_cells['pos'] + 1)
- return read_map()
- elif sx_truthy((ch == '"')):
- return read_string()
- elif sx_truthy((ch == ':')):
- return read_keyword()
- elif sx_truthy((ch == "'")):
- _cells['pos'] = (_cells['pos'] + 1)
- return [make_symbol('quote'), read_expr()]
- elif sx_truthy((ch == '`')):
- _cells['pos'] = (_cells['pos'] + 1)
- return [make_symbol('quasiquote'), read_expr()]
- elif sx_truthy((ch == ',')):
- _cells['pos'] = (_cells['pos'] + 1)
- if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (nth(source, _cells['pos']) == '@'))):
- _cells['pos'] = (_cells['pos'] + 1)
- return [make_symbol('splice-unquote'), read_expr()]
- else:
- return [make_symbol('unquote'), read_expr()]
- elif sx_truthy((ch == '#')):
- _cells['pos'] = (_cells['pos'] + 1)
- if sx_truthy((_cells['pos'] >= len_src)):
- return error('Unexpected end of input after #')
- else:
- dispatch_ch = nth(source, _cells['pos'])
- if sx_truthy((dispatch_ch == ';')):
- _cells['pos'] = (_cells['pos'] + 1)
- read_expr()
- return read_expr()
- elif sx_truthy((dispatch_ch == '|')):
- _cells['pos'] = (_cells['pos'] + 1)
- return read_raw_string()
- elif sx_truthy((dispatch_ch == "'")):
- _cells['pos'] = (_cells['pos'] + 1)
- return [make_symbol('quote'), read_expr()]
- elif sx_truthy(ident_start_p(dispatch_ch)):
- macro_name = read_ident()
- handler = reader_macro_get(macro_name)
- if sx_truthy(handler):
- return handler(read_expr())
- else:
- return error(sx_str('Unknown reader macro: #', macro_name))
- else:
- return error(sx_str('Unknown reader macro: #', dispatch_ch))
- elif sx_truthy((((ch >= '0') if not sx_truthy((ch >= '0')) else (ch <= '9')) if sx_truthy(((ch >= '0') if not sx_truthy((ch >= '0')) else (ch <= '9'))) else ((ch == '-') if not sx_truthy((ch == '-')) else (((_cells['pos'] + 1) < len_src) if not sx_truthy(((_cells['pos'] + 1) < len_src)) else (lambda next_ch: ((next_ch >= '0') if not sx_truthy((next_ch >= '0')) else (next_ch <= '9')))(nth(source, (_cells['pos'] + 1))))))):
- return read_number()
- elif sx_truthy(((ch == '.') if not sx_truthy((ch == '.')) else (((_cells['pos'] + 2) < len_src) if not sx_truthy(((_cells['pos'] + 2) < len_src)) else ((nth(source, (_cells['pos'] + 1)) == '.') if not sx_truthy((nth(source, (_cells['pos'] + 1)) == '.')) else (nth(source, (_cells['pos'] + 2)) == '.'))))):
- _cells['pos'] = (_cells['pos'] + 3)
- return make_symbol('...')
- elif sx_truthy(ident_start_p(ch)):
- return read_symbol()
- else:
- return error(sx_str('Unexpected character: ', ch))
- exprs = []
- while True:
- skip_ws()
- if sx_truthy((_cells['pos'] < len_src)):
- exprs.append(read_expr())
- continue
- break
- return exprs
-
-# sx-serialize
-def sx_serialize(val):
- _match = type_of(val)
- if _match == 'nil':
- return 'nil'
- elif _match == 'boolean':
- if sx_truthy(val):
- return 'true'
- else:
- return 'false'
- elif _match == 'number':
- return sx_str(val)
- elif _match == 'string':
- return sx_str('"', escape_string(val), '"')
- elif _match == 'symbol':
- return symbol_name(val)
- elif _match == 'keyword':
- return sx_str(':', keyword_name(val))
- elif _match == 'list':
- return sx_str('(', join(' ', map(sx_serialize, val)), ')')
- elif _match == 'dict':
- return sx_serialize_dict(val)
- elif _match == 'sx-expr':
- return sx_expr_source(val)
- elif _match == 'spread':
- return sx_str('(make-spread ', sx_serialize_dict(spread_attrs(val)), ')')
- else:
- return sx_str(val)
-
-# sx-serialize-dict
-def sx_serialize_dict(d):
- return sx_str('{', join(' ', reduce(lambda acc, key: concat(acc, [sx_str(':', key), sx_serialize(dict_get(d, key))]), [], keys(d))), '}')
-
-# serialize
-serialize = sx_serialize
-
-
-# === Transpiled from adapter-html ===
-
-# render-to-html
-def render_to_html(expr, env):
- set_render_active_b(True)
- _match = type_of(expr)
- if _match == 'nil':
- return ''
- elif _match == 'string':
- return escape_html(expr)
- elif _match == 'number':
- return sx_str(expr)
- elif _match == 'boolean':
- if sx_truthy(expr):
- return 'true'
- else:
- return 'false'
- elif _match == 'list':
- if sx_truthy(empty_p(expr)):
- return ''
- else:
- return render_list_to_html(expr, env)
- elif _match == 'symbol':
- return render_value_to_html(trampoline(eval_expr(expr, env)), env)
- elif _match == 'keyword':
- return escape_html(keyword_name(expr))
- elif _match == 'raw-html':
- return raw_html_content(expr)
- elif _match == 'spread':
- sx_emit('element-attrs', spread_attrs(expr))
- return ''
- else:
- return render_value_to_html(trampoline(eval_expr(expr, env)), env)
-
-# render-value-to-html
-def render_value_to_html(val, env):
- _match = type_of(val)
- if _match == 'nil':
- return ''
- elif _match == 'string':
- return escape_html(val)
- elif _match == 'number':
- return sx_str(val)
- elif _match == 'boolean':
- if sx_truthy(val):
- return 'true'
- else:
- return 'false'
- elif _match == 'list':
- return render_list_to_html(val, env)
- elif _match == 'raw-html':
- return raw_html_content(val)
- elif _match == 'spread':
- sx_emit('element-attrs', spread_attrs(val))
- return ''
- else:
- return escape_html(sx_str(val))
-
-# RENDER_HTML_FORMS
-RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each', 'scope', 'provide']
-
-# render-html-form?
-def is_render_html_form(name):
- return contains_p(RENDER_HTML_FORMS, name)
-
-# render-list-to-html
-def render_list_to_html(expr, env):
- if sx_truthy(empty_p(expr)):
- return ''
- else:
- head = first(expr)
- if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
- return join('', map(lambda x: render_value_to_html(x, env), expr))
- else:
- name = symbol_name(head)
- args = rest(expr)
- if sx_truthy((name == '<>')):
- return join('', map(lambda x: render_to_html(x, env), args))
- elif sx_truthy((name == 'raw!')):
- return join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args))
- elif sx_truthy((name == 'lake')):
- return render_html_lake(args, env)
- elif sx_truthy((name == 'marsh')):
- return render_html_marsh(args, env)
- elif sx_truthy(contains_p(HTML_TAGS, name)):
- return render_html_element(name, args, env)
- elif sx_truthy((starts_with_p(name, '~') if not sx_truthy(starts_with_p(name, '~')) else (env_has(env, name) if not sx_truthy(env_has(env, name)) else is_island(env_get(env, name))))):
- return render_html_island(env_get(env, name), args, env)
- elif sx_truthy(starts_with_p(name, '~')):
- val = env_get(env, name)
- if sx_truthy(is_component(val)):
- return render_html_component(val, args, env)
- elif sx_truthy(is_macro(val)):
- return render_to_html(expand_macro(val, args, env), env)
- else:
- return error(sx_str('Unknown component: ', name))
- elif sx_truthy(is_render_html_form(name)):
- return dispatch_html_form(name, expr, env)
- elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
- return render_to_html(expand_macro(env_get(env, name), args, env), env)
- else:
- return render_value_to_html(trampoline(eval_expr(expr, env)), env)
-
-# dispatch-html-form
-def dispatch_html_form(name, expr, env):
- if sx_truthy((name == 'if')):
- cond_val = trampoline(eval_expr(nth(expr, 1), env))
- if sx_truthy(cond_val):
- return render_to_html(nth(expr, 2), env)
- else:
- if sx_truthy((len(expr) > 3)):
- return render_to_html(nth(expr, 3), env)
- else:
- return ''
- elif sx_truthy((name == 'when')):
- if sx_truthy((not sx_truthy(trampoline(eval_expr(nth(expr, 1), env))))):
- return ''
- else:
- if sx_truthy((len(expr) == 3)):
- return render_to_html(nth(expr, 2), env)
- else:
- return join('', map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr))))
- elif sx_truthy((name == 'cond')):
- branch = eval_cond(rest(expr), env)
- if sx_truthy(branch):
- return render_to_html(branch, env)
- else:
- return ''
- elif sx_truthy((name == 'case')):
- return render_to_html(trampoline(eval_expr(expr, env)), env)
- elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
- local = process_bindings(nth(expr, 1), env)
- if sx_truthy((len(expr) == 3)):
- return render_to_html(nth(expr, 2), local)
- else:
- return join('', map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr))))
- elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
- if sx_truthy((len(expr) == 2)):
- return render_to_html(nth(expr, 1), env)
- else:
- return join('', map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr))))
- elif sx_truthy(is_definition_form(name)):
- trampoline(eval_expr(expr, env))
- return ''
- elif sx_truthy((name == 'map')):
- f = trampoline(eval_expr(nth(expr, 1), env))
- coll = trampoline(eval_expr(nth(expr, 2), env))
- return join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll))
- elif sx_truthy((name == 'map-indexed')):
- f = trampoline(eval_expr(nth(expr, 1), env))
- coll = trampoline(eval_expr(nth(expr, 2), env))
- return join('', map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll))
- elif sx_truthy((name == 'filter')):
- return render_to_html(trampoline(eval_expr(expr, env)), env)
- elif sx_truthy((name == 'for-each')):
- f = trampoline(eval_expr(nth(expr, 1), env))
- coll = trampoline(eval_expr(nth(expr, 2), env))
- return join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll))
- elif sx_truthy((name == 'scope')):
- scope_name = trampoline(eval_expr(nth(expr, 1), env))
- rest_args = slice(expr, 2)
- scope_val = NIL
- body_exprs = NIL
- if sx_truthy(((len(rest_args) >= 2) if not sx_truthy((len(rest_args) >= 2)) else ((type_of(first(rest_args)) == 'keyword') if not sx_truthy((type_of(first(rest_args)) == 'keyword')) else (keyword_name(first(rest_args)) == 'value')))):
- scope_val = trampoline(eval_expr(nth(rest_args, 1), env))
- body_exprs = slice(rest_args, 2)
- else:
- body_exprs = rest_args
- scope_push(scope_name, scope_val)
- result = (render_to_html(first(body_exprs), env) if sx_truthy((len(body_exprs) == 1)) else join('', map(lambda e: render_to_html(e, env), body_exprs)))
- scope_pop(scope_name)
- return result
- elif sx_truthy((name == 'provide')):
- prov_name = trampoline(eval_expr(nth(expr, 1), env))
- prov_val = trampoline(eval_expr(nth(expr, 2), env))
- body_start = 3
- body_count = (len(expr) - 3)
- scope_push(prov_name, prov_val)
- result = (render_to_html(nth(expr, body_start), env) if sx_truthy((body_count == 1)) else join('', map(lambda i: render_to_html(nth(expr, i), env), range(body_start, (body_start + body_count)))))
- scope_pop(prov_name)
- return result
- else:
- return render_value_to_html(trampoline(eval_expr(expr, env)), env)
-
-# render-lambda-html
-def render_lambda_html(f, args, env):
- local = env_merge(lambda_closure(f), env)
- for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f))
- return render_to_html(lambda_body(f), local)
-
-# render-html-component
-def render_html_component(comp, args, env):
- kwargs = {}
- children = []
- reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(kwargs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
- local = env_merge(component_closure(comp), env)
- for p in component_params(comp):
- local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
- if sx_truthy(component_has_children(comp)):
- local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))
- return render_to_html(component_body(comp), local)
-
-# render-html-element
-def render_html_element(tag, args, env):
- parsed = parse_element_args(args, env)
- attrs = first(parsed)
- children = nth(parsed, 1)
- is_void = contains_p(VOID_ELEMENTS, tag)
- if sx_truthy(is_void):
- return sx_str('<', tag, render_attrs(attrs), ' />')
- else:
- scope_push('element-attrs', NIL)
- content = join('', map(lambda c: render_to_html(c, env), children))
- for spread_dict in sx_emitted('element-attrs'):
- merge_spread_attrs(attrs, spread_dict)
- scope_pop('element-attrs')
- return sx_str('<', tag, render_attrs(attrs), '>', content, '', tag, '>')
-
-# render-html-lake
-def render_html_lake(args, env):
- _cells = {}
- _cells['lake_id'] = NIL
- _cells['lake_tag'] = 'div'
- children = []
- reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'lake_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'lake_tag', kval) if sx_truthy((kname == 'tag')) else NIL)), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
- lake_attrs = {'data-sx-lake': (_cells['lake_id'] if sx_truthy(_cells['lake_id']) else '')}
- scope_push('element-attrs', NIL)
- content = join('', map(lambda c: render_to_html(c, env), children))
- for spread_dict in sx_emitted('element-attrs'):
- merge_spread_attrs(lake_attrs, spread_dict)
- scope_pop('element-attrs')
- return sx_str('<', _cells['lake_tag'], render_attrs(lake_attrs), '>', content, '', _cells['lake_tag'], '>')
-
-# render-html-marsh
-def render_html_marsh(args, env):
- _cells = {}
- _cells['marsh_id'] = NIL
- _cells['marsh_tag'] = 'div'
- children = []
- reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'marsh_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'marsh_tag', kval) if sx_truthy((kname == 'tag')) else (NIL if sx_truthy((kname == 'transform')) else NIL))), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
- marsh_attrs = {'data-sx-marsh': (_cells['marsh_id'] if sx_truthy(_cells['marsh_id']) else '')}
- scope_push('element-attrs', NIL)
- content = join('', map(lambda c: render_to_html(c, env), children))
- for spread_dict in sx_emitted('element-attrs'):
- merge_spread_attrs(marsh_attrs, spread_dict)
- scope_pop('element-attrs')
- return sx_str('<', _cells['marsh_tag'], render_attrs(marsh_attrs), '>', content, '', _cells['marsh_tag'], '>')
-
-# render-html-island
-def render_html_island(island, args, env):
- kwargs = {}
- children = []
- reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(kwargs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
- local = env_merge(component_closure(island), env)
- island_name = component_name(island)
- for p in component_params(island):
- local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
- if sx_truthy(component_has_children(island)):
- local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))
- body_html = render_to_html(component_body(island), local)
- state_sx = serialize_island_state(kwargs)
- return sx_str('', body_html, '')
-
-# serialize-island-state
-def serialize_island_state(kwargs):
- if sx_truthy(is_empty_dict(kwargs)):
- return NIL
- else:
- return sx_serialize(kwargs)
-
-
-# === Transpiled from adapter-sx ===
-
-# render-to-sx
-def render_to_sx(expr, env):
- result = aser(expr, env)
- if sx_truthy((type_of(result) == 'string')):
- return result
- else:
- return serialize(result)
-
-# aser
-def aser(expr, env):
- set_render_active_b(True)
- result = _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else aser_list(expr, env))), ('spread', lambda: _sx_begin(sx_emit('element-attrs', spread_attrs(expr)), NIL)), (None, lambda: expr)])
- if sx_truthy(is_spread(result)):
- sx_emit('element-attrs', spread_attrs(result))
- return NIL
- else:
- return result
-
-# aser-list
-def aser_list(expr, env):
- head = first(expr)
- args = rest(expr)
- if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
- return map(lambda x: aser(x, env), expr)
- else:
- name = symbol_name(head)
- if sx_truthy((name == '<>')):
- return aser_fragment(args, env)
- elif sx_truthy(starts_with_p(name, '~')):
- return aser_call(name, args, env)
- elif sx_truthy((name == 'lake')):
- return aser_call(name, args, env)
- elif sx_truthy((name == 'marsh')):
- return aser_call(name, args, env)
- elif sx_truthy(contains_p(HTML_TAGS, name)):
- return aser_call(name, args, env)
- elif sx_truthy((is_special_form(name) if sx_truthy(is_special_form(name)) else is_ho_form(name))):
- return aser_special(name, expr, env)
- elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
- return aser(expand_macro(env_get(env, name), args, env), env)
- else:
- f = trampoline(eval_expr(head, env))
- evaled_args = map(lambda a: trampoline(eval_expr(a, env)), args)
- if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))):
- return apply(f, evaled_args)
- elif sx_truthy(is_lambda(f)):
- return trampoline(call_lambda(f, evaled_args, env))
- elif sx_truthy(is_component(f)):
- return aser_call(sx_str('~', component_name(f)), args, env)
- elif sx_truthy(is_island(f)):
- return aser_call(sx_str('~', component_name(f)), args, env)
- else:
- return error(sx_str('Not callable: ', inspect(f)))
-
-# aser-fragment
-def aser_fragment(children, env):
- parts = []
- for c in children:
- result = aser(c, env)
- if sx_truthy((type_of(result) == 'list')):
- for item in result:
- if sx_truthy((not sx_truthy(is_nil(item)))):
- parts.append(serialize(item))
- else:
- if sx_truthy((not sx_truthy(is_nil(result)))):
- parts.append(serialize(result))
- if sx_truthy(empty_p(parts)):
- return ''
- else:
- return sx_str('(<> ', join(' ', parts), ')')
-
-# aser-call
-def aser_call(name, args, env):
- _cells = {}
- attr_parts = []
- child_parts = []
- _cells['skip'] = False
- _cells['i'] = 0
- scope_push('element-attrs', NIL)
- for arg in args:
- if sx_truthy(_cells['skip']):
- _cells['skip'] = False
- _cells['i'] = (_cells['i'] + 1)
- else:
- if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
- val = aser(nth(args, (_cells['i'] + 1)), env)
- if sx_truthy((not sx_truthy(is_nil(val)))):
- attr_parts.append(sx_str(':', keyword_name(arg)))
- attr_parts.append(serialize(val))
- _cells['skip'] = True
- _cells['i'] = (_cells['i'] + 1)
- else:
- val = aser(arg, env)
- if sx_truthy((not sx_truthy(is_nil(val)))):
- if sx_truthy((type_of(val) == 'list')):
- for item in val:
- if sx_truthy((not sx_truthy(is_nil(item)))):
- child_parts.append(serialize(item))
- else:
- child_parts.append(serialize(val))
- _cells['i'] = (_cells['i'] + 1)
- for spread_dict in sx_emitted('element-attrs'):
- for k in keys(spread_dict):
- v = dict_get(spread_dict, k)
- attr_parts.append(sx_str(':', k))
- attr_parts.append(serialize(v))
- scope_pop('element-attrs')
- parts = concat([name], attr_parts, child_parts)
- return sx_str('(', join(' ', parts), ')')
-
-# SPECIAL_FORM_NAMES
-SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland', 'deftype', 'defeffect', 'scope', 'provide']
-
-# HO_FORM_NAMES
-HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each']
-
-# special-form?
-def is_special_form(name):
- return contains_p(SPECIAL_FORM_NAMES, name)
-
-# ho-form?
-def is_ho_form(name):
- return contains_p(HO_FORM_NAMES, name)
-
-# aser-special
-def aser_special(name, expr, env):
- _cells = {}
- args = rest(expr)
- if sx_truthy((name == 'if')):
- if sx_truthy(trampoline(eval_expr(first(args), env))):
- return aser(nth(args, 1), env)
- else:
- if sx_truthy((len(args) > 2)):
- return aser(nth(args, 2), env)
- else:
- return NIL
- elif sx_truthy((name == 'when')):
- if sx_truthy((not sx_truthy(trampoline(eval_expr(first(args), env))))):
- return NIL
- else:
- _cells['result'] = NIL
- for body in rest(args):
- _cells['result'] = aser(body, env)
- return _cells['result']
- elif sx_truthy((name == 'cond')):
- branch = eval_cond(args, env)
- if sx_truthy(branch):
- return aser(branch, env)
- else:
- return NIL
- elif sx_truthy((name == 'case')):
- match_val = trampoline(eval_expr(first(args), env))
- clauses = rest(args)
- return eval_case_aser(match_val, clauses, env)
- elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
- local = process_bindings(first(args), env)
- _cells['result'] = NIL
- for body in rest(args):
- _cells['result'] = aser(body, local)
- return _cells['result']
- elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
- _cells['result'] = NIL
- for body in args:
- _cells['result'] = aser(body, env)
- return _cells['result']
- elif sx_truthy((name == 'and')):
- _cells['result'] = True
- some(_sx_fn(lambda arg: (
- _sx_cell_set(_cells, 'result', trampoline(eval_expr(arg, env))),
- (not sx_truthy(_cells['result']))
-)[-1]), args)
- return _cells['result']
- elif sx_truthy((name == 'or')):
- _cells['result'] = False
- some(_sx_fn(lambda arg: (
- _sx_cell_set(_cells, 'result', trampoline(eval_expr(arg, env))),
- _cells['result']
-)[-1]), args)
- return _cells['result']
- elif sx_truthy((name == 'map')):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- return map(lambda item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else cek_call(f, [item])), coll)
- elif sx_truthy((name == 'map-indexed')):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- return map_indexed(lambda i, item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), i), _sx_dict_set(local, nth(lambda_params(f), 1), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else cek_call(f, [i, item])), coll)
- elif sx_truthy((name == 'for-each')):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- results = []
- for item in coll:
- if sx_truthy(is_lambda(f)):
- local = env_merge(lambda_closure(f), env)
- local[first(lambda_params(f))] = item
- results.append(aser(lambda_body(f), local))
- else:
- cek_call(f, [item])
- if sx_truthy(empty_p(results)):
- return NIL
- else:
- return results
- elif sx_truthy((name == 'defisland')):
- trampoline(eval_expr(expr, env))
- return serialize(expr)
- elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'defrelation') if sx_truthy((name == 'defrelation')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect')))))))))))):
- trampoline(eval_expr(expr, env))
- return NIL
- elif sx_truthy((name == 'scope')):
- scope_name = trampoline(eval_expr(first(args), env))
- rest_args = rest(args)
- scope_val = NIL
- body_args = NIL
- if sx_truthy(((len(rest_args) >= 2) if not sx_truthy((len(rest_args) >= 2)) else ((type_of(first(rest_args)) == 'keyword') if not sx_truthy((type_of(first(rest_args)) == 'keyword')) else (keyword_name(first(rest_args)) == 'value')))):
- scope_val = trampoline(eval_expr(nth(rest_args, 1), env))
- body_args = slice(rest_args, 2)
- else:
- body_args = rest_args
- scope_push(scope_name, scope_val)
- _cells['result'] = NIL
- for body in body_args:
- _cells['result'] = aser(body, env)
- scope_pop(scope_name)
- return _cells['result']
- elif sx_truthy((name == 'provide')):
- prov_name = trampoline(eval_expr(first(args), env))
- prov_val = trampoline(eval_expr(nth(args, 1), env))
- _cells['result'] = NIL
- scope_push(prov_name, prov_val)
- for body in slice(args, 2):
- _cells['result'] = aser(body, env)
- scope_pop(prov_name)
- return _cells['result']
- else:
- return trampoline(eval_expr(expr, env))
-
-# eval-case-aser
-def eval_case_aser(match_val, clauses, env):
- if sx_truthy((len(clauses) < 2)):
- return NIL
- else:
- test = first(clauses)
- body = nth(clauses, 1)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == ':else') if sx_truthy((symbol_name(test) == ':else')) else (symbol_name(test) == 'else'))))):
- return aser(body, env)
- else:
- if sx_truthy((match_val == trampoline(eval_expr(test, env)))):
- return aser(body, env)
- else:
- return eval_case_aser(match_val, slice(clauses, 2), env)
-
-
-# === Transpiled from deps (component dependency analysis) ===
-
-# scan-refs
-def scan_refs(node):
- refs = []
- scan_refs_walk(node, refs)
- return refs
-
-# scan-refs-walk
-def scan_refs_walk(node, refs):
- if sx_truthy((type_of(node) == 'symbol')):
- name = symbol_name(node)
- if sx_truthy(starts_with_p(name, '~')):
- if sx_truthy((not sx_truthy(contains_p(refs, name)))):
- return _sx_append(refs, name)
- return NIL
- return NIL
- elif sx_truthy((type_of(node) == 'list')):
- for item in node:
- scan_refs_walk(item, refs)
- return NIL
- elif sx_truthy((type_of(node) == 'dict')):
- for key in keys(node):
- scan_refs_walk(dict_get(node, key), refs)
- return NIL
- else:
- return NIL
-
-# transitive-deps-walk
-def transitive_deps_walk(n, seen, env):
- if sx_truthy((not sx_truthy(contains_p(seen, n)))):
- seen.append(n)
- val = env_get(env, n)
- if sx_truthy(((type_of(val) == 'component') if sx_truthy((type_of(val) == 'component')) else (type_of(val) == 'island'))):
- for ref in scan_refs(component_body(val)):
- transitive_deps_walk(ref, seen, env)
- return NIL
- elif sx_truthy((type_of(val) == 'macro')):
- for ref in scan_refs(macro_body(val)):
- transitive_deps_walk(ref, seen, env)
- return NIL
- else:
- return NIL
- return NIL
-
-# transitive-deps
-def transitive_deps(name, env):
- seen = []
- key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
- transitive_deps_walk(key, seen, env)
- return filter(lambda x: (not sx_truthy((x == key))), seen)
-
-# compute-all-deps
-def compute_all_deps(env):
- for name in env_components(env):
- val = env_get(env, name)
- if sx_truthy(((type_of(val) == 'component') if sx_truthy((type_of(val) == 'component')) else (type_of(val) == 'island'))):
- component_set_deps(val, transitive_deps(name, env))
- return NIL
-
-# scan-components-from-source
-def scan_components_from_source(source):
- matches = regex_find_all('\\(~([a-zA-Z_][a-zA-Z0-9_\\-:/]*)', source)
- return map(lambda m: sx_str('~', m), matches)
-
-# components-needed
-def components_needed(page_source, env):
- direct = scan_components_from_source(page_source)
- all_needed = []
- for name in direct:
- if sx_truthy((not sx_truthy(contains_p(all_needed, name)))):
- all_needed.append(name)
- val = env_get(env, name)
- deps = (component_deps(val) if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(empty_p(component_deps(val)))))) else transitive_deps(name, env))
- for dep in deps:
- if sx_truthy((not sx_truthy(contains_p(all_needed, dep)))):
- all_needed.append(dep)
- return all_needed
-
-# page-component-bundle
-def page_component_bundle(page_source, env):
- return components_needed(page_source, env)
-
-# page-css-classes
-def page_css_classes(page_source, env):
- needed = components_needed(page_source, env)
- classes = []
- for name in needed:
- val = env_get(env, name)
- if sx_truthy((type_of(val) == 'component')):
- for cls in component_css_classes(val):
- if sx_truthy((not sx_truthy(contains_p(classes, cls)))):
- classes.append(cls)
- for cls in scan_css_classes(page_source):
- if sx_truthy((not sx_truthy(contains_p(classes, cls)))):
- classes.append(cls)
- return classes
-
-# scan-io-refs-walk
-def scan_io_refs_walk(node, io_names, refs):
- if sx_truthy((type_of(node) == 'symbol')):
- name = symbol_name(node)
- if sx_truthy(contains_p(io_names, name)):
- if sx_truthy((not sx_truthy(contains_p(refs, name)))):
- return _sx_append(refs, name)
- return NIL
- return NIL
- elif sx_truthy((type_of(node) == 'list')):
- for item in node:
- scan_io_refs_walk(item, io_names, refs)
- return NIL
- elif sx_truthy((type_of(node) == 'dict')):
- for key in keys(node):
- scan_io_refs_walk(dict_get(node, key), io_names, refs)
- return NIL
- else:
- return NIL
-
-# scan-io-refs
-def scan_io_refs(node, io_names):
- refs = []
- scan_io_refs_walk(node, io_names, refs)
- return refs
-
-# transitive-io-refs-walk
-def transitive_io_refs_walk(n, seen, all_refs, env, io_names):
- if sx_truthy((not sx_truthy(contains_p(seen, n)))):
- seen.append(n)
- val = env_get(env, n)
- if sx_truthy((type_of(val) == 'component')):
- for ref in scan_io_refs(component_body(val), io_names):
- if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))):
- all_refs.append(ref)
- for dep in scan_refs(component_body(val)):
- transitive_io_refs_walk(dep, seen, all_refs, env, io_names)
- return NIL
- elif sx_truthy((type_of(val) == 'macro')):
- for ref in scan_io_refs(macro_body(val), io_names):
- if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))):
- all_refs.append(ref)
- for dep in scan_refs(macro_body(val)):
- transitive_io_refs_walk(dep, seen, all_refs, env, io_names)
- return NIL
- else:
- return NIL
- return NIL
-
-# transitive-io-refs
-def transitive_io_refs(name, env, io_names):
- all_refs = []
- seen = []
- key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
- transitive_io_refs_walk(key, seen, all_refs, env, io_names)
- return all_refs
-
-# compute-all-io-refs
-def compute_all_io_refs(env, io_names):
- for name in env_components(env):
- val = env_get(env, name)
- if sx_truthy((type_of(val) == 'component')):
- component_set_io_refs(val, transitive_io_refs(name, env, io_names))
- return NIL
-
-# component-io-refs-cached
-def component_io_refs_cached(name, env, io_names):
- key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
- val = env_get(env, key)
- if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else ((not sx_truthy(is_nil(component_io_refs(val)))) if not sx_truthy((not sx_truthy(is_nil(component_io_refs(val))))) else (not sx_truthy(empty_p(component_io_refs(val))))))):
- return component_io_refs(val)
- else:
- return transitive_io_refs(name, env, io_names)
-
-# component-pure?
-def component_pure_p(name, env, io_names):
- key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
- val = env_get(env, key)
- if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(is_nil(component_io_refs(val)))))):
- return empty_p(component_io_refs(val))
- else:
- return empty_p(transitive_io_refs(name, env, io_names))
-
-# render-target
-def render_target(name, env, io_names):
- key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
- val = env_get(env, key)
- if sx_truthy((not sx_truthy((type_of(val) == 'component')))):
- return 'server'
- else:
- affinity = component_affinity(val)
- if sx_truthy((affinity == 'server')):
- return 'server'
- elif sx_truthy((affinity == 'client')):
- return 'client'
- elif sx_truthy((not sx_truthy(component_pure_p(name, env, io_names)))):
- return 'server'
- else:
- return 'client'
-
-# page-render-plan
-def page_render_plan(page_source, env, io_names):
- needed = components_needed(page_source, env)
- comp_targets = {}
- server_list = []
- client_list = []
- io_deps = []
- for name in needed:
- target = render_target(name, env, io_names)
- comp_targets[name] = target
- if sx_truthy((target == 'server')):
- server_list.append(name)
- for io_ref in component_io_refs_cached(name, env, io_names):
- if sx_truthy((not sx_truthy(contains_p(io_deps, io_ref)))):
- io_deps.append(io_ref)
- else:
- client_list.append(name)
- return {'components': comp_targets, 'server': server_list, 'client': client_list, 'io-deps': io_deps}
-
-# env-components
-def env_components(env):
- return filter(lambda k: (lambda v: (is_component(v) if sx_truthy(is_component(v)) else is_macro(v)))(env_get(env, k)), keys(env))
-
-
-# === Transpiled from frames (CEK continuation frames) ===
-
-# make-cek-state
-def make_cek_state(control, env, kont):
- return {'control': control, 'env': env, 'kont': kont, 'phase': 'eval', 'value': NIL}
-
-# make-cek-value
-def make_cek_value(value, env, kont):
- return {'control': NIL, 'env': env, 'kont': kont, 'phase': 'continue', 'value': value}
-
-# cek-terminal?
-def cek_terminal_p(state):
- return ((get(state, 'phase') == 'continue') if not sx_truthy((get(state, 'phase') == 'continue')) else empty_p(get(state, 'kont')))
-
-# cek-control
-def cek_control(s):
- return get(s, 'control')
-
-# cek-env
-def cek_env(s):
- return get(s, 'env')
-
-# cek-kont
-def cek_kont(s):
- return get(s, 'kont')
-
-# cek-phase
-def cek_phase(s):
- return get(s, 'phase')
-
-# cek-value
-def cek_value(s):
- return get(s, 'value')
-
-# make-if-frame
-def make_if_frame(then_expr, else_expr, env):
- return {'type': 'if', 'then': then_expr, 'else': else_expr, 'env': env}
-
-# make-when-frame
-def make_when_frame(body_exprs, env):
- return {'type': 'when', 'body': body_exprs, 'env': env}
-
-# make-begin-frame
-def make_begin_frame(remaining, env):
- return {'type': 'begin', 'remaining': remaining, 'env': env}
-
-# make-let-frame
-def make_let_frame(name, remaining, body, local):
- return {'type': 'let', 'name': name, 'remaining': remaining, 'body': body, 'env': local}
-
-# make-define-frame
-def make_define_frame(name, env, has_effects, effect_list):
- return {'type': 'define', 'name': name, 'env': env, 'has-effects': has_effects, 'effect-list': effect_list}
-
-# make-set-frame
-def make_set_frame(name, env):
- return {'type': 'set', 'name': name, 'env': env}
-
-# make-arg-frame
-def make_arg_frame(f, evaled, remaining, env, raw_args):
- return {'type': 'arg', 'f': f, 'evaled': evaled, 'remaining': remaining, 'env': env, 'raw-args': raw_args}
-
-# make-call-frame
-def make_call_frame(f, args, env):
- return {'type': 'call', 'f': f, 'args': args, 'env': env}
-
-# make-cond-frame
-def make_cond_frame(remaining, env, scheme_p):
- return {'type': 'cond', 'remaining': remaining, 'env': env, 'scheme': scheme_p}
-
-# make-case-frame
-def make_case_frame(match_val, remaining, env):
- return {'type': 'case', 'match-val': match_val, 'remaining': remaining, 'env': env}
-
-# make-thread-frame
-def make_thread_frame(remaining, env):
- return {'type': 'thread', 'remaining': remaining, 'env': env}
-
-# make-map-frame
-def make_map_frame(f, remaining, results, env):
- return {'type': 'map', 'f': f, 'remaining': remaining, 'results': results, 'env': env, 'indexed': False}
-
-# make-map-indexed-frame
-def make_map_indexed_frame(f, remaining, results, env):
- return {'type': 'map', 'f': f, 'remaining': remaining, 'results': results, 'env': env, 'indexed': True}
-
-# make-filter-frame
-def make_filter_frame(f, remaining, results, current_item, env):
- return {'type': 'filter', 'f': f, 'remaining': remaining, 'results': results, 'current-item': current_item, 'env': env}
-
-# make-reduce-frame
-def make_reduce_frame(f, remaining, env):
- return {'type': 'reduce', 'f': f, 'remaining': remaining, 'env': env}
-
-# make-for-each-frame
-def make_for_each_frame(f, remaining, env):
- return {'type': 'for-each', 'f': f, 'remaining': remaining, 'env': env}
-
-# make-some-frame
-def make_some_frame(f, remaining, env):
- return {'type': 'some', 'f': f, 'remaining': remaining, 'env': env}
-
-# make-every-frame
-def make_every_frame(f, remaining, env):
- return {'type': 'every', 'f': f, 'remaining': remaining, 'env': env}
-
-# make-scope-frame
-def make_scope_frame(name, remaining, env):
- return {'type': 'scope', 'name': name, 'remaining': remaining, 'env': env}
-
-# make-reset-frame
-def make_reset_frame(env):
- return {'type': 'reset', 'env': env}
-
-# make-dict-frame
-def make_dict_frame(remaining, results, env):
- return {'type': 'dict', 'remaining': remaining, 'results': results, 'env': env}
-
-# make-and-frame
-def make_and_frame(remaining, env):
- return {'type': 'and', 'remaining': remaining, 'env': env}
-
-# make-or-frame
-def make_or_frame(remaining, env):
- return {'type': 'or', 'remaining': remaining, 'env': env}
-
-# make-dynamic-wind-frame
-def make_dynamic_wind_frame(phase, body_thunk, after_thunk, env):
- return {'type': 'dynamic-wind', 'phase': phase, 'body-thunk': body_thunk, 'after-thunk': after_thunk, 'env': env}
-
-# make-reactive-reset-frame
-def make_reactive_reset_frame(env, update_fn, first_render_p):
- return {'type': 'reactive-reset', 'env': env, 'update-fn': update_fn, 'first-render': first_render_p}
-
-# make-deref-frame
-def make_deref_frame(env):
- return {'type': 'deref', 'env': env}
-
-# frame-type
-def frame_type(f):
- return get(f, 'type')
-
-# kont-push
-def kont_push(frame, kont):
- return cons(frame, kont)
-
-# kont-top
-def kont_top(kont):
- return first(kont)
-
-# kont-pop
-def kont_pop(kont):
- return rest(kont)
-
-# kont-empty?
-def kont_empty_p(kont):
- return empty_p(kont)
-
-# kont-capture-to-reset
-def kont_capture_to_reset(kont):
- def scan(k, captured):
- if sx_truthy(empty_p(k)):
- return error('shift without enclosing reset')
- else:
- frame = first(k)
- if sx_truthy(((frame_type(frame) == 'reset') if sx_truthy((frame_type(frame) == 'reset')) else (frame_type(frame) == 'reactive-reset'))):
- return [captured, rest(k)]
- else:
- return scan(rest(k), append(captured, [frame]))
- return scan(kont, [])
-
-# has-reactive-reset-frame?
-def has_reactive_reset_frame_p(kont):
- if sx_truthy(empty_p(kont)):
- return False
- else:
- if sx_truthy((frame_type(first(kont)) == 'reactive-reset')):
- return True
- else:
- return has_reactive_reset_frame_p(rest(kont))
-
-# kont-capture-to-reactive-reset
-def kont_capture_to_reactive_reset(kont):
- def scan(k, captured):
- if sx_truthy(empty_p(k)):
- return error('reactive deref without enclosing reactive-reset')
- else:
- frame = first(k)
- if sx_truthy((frame_type(frame) == 'reactive-reset')):
- return [captured, frame, rest(k)]
- else:
- return scan(rest(k), append(captured, [frame]))
- return scan(kont, [])
-
-
-# === Transpiled from page-helpers (pure data transformation helpers) ===
-
-# special-form-category-map
-special_form_category_map = {'if': 'Control Flow', 'when': 'Control Flow', 'cond': 'Control Flow', 'case': 'Control Flow', 'and': 'Control Flow', 'or': 'Control Flow', 'let': 'Binding', 'let*': 'Binding', 'letrec': 'Binding', 'define': 'Binding', 'set!': 'Binding', 'lambda': 'Functions & Components', 'fn': 'Functions & Components', 'defcomp': 'Functions & Components', 'defmacro': 'Functions & Components', 'begin': 'Sequencing & Threading', 'do': 'Sequencing & Threading', '->': 'Sequencing & Threading', 'quote': 'Quoting', 'quasiquote': 'Quoting', 'reset': 'Continuations', 'shift': 'Continuations', 'dynamic-wind': 'Guards', 'map': 'Higher-Order Forms', 'map-indexed': 'Higher-Order Forms', 'filter': 'Higher-Order Forms', 'reduce': 'Higher-Order Forms', 'some': 'Higher-Order Forms', 'every?': 'Higher-Order Forms', 'for-each': 'Higher-Order Forms', 'defstyle': 'Domain Definitions', 'defhandler': 'Domain Definitions', 'defpage': 'Domain Definitions', 'defquery': 'Domain Definitions', 'defaction': 'Domain Definitions'}
-
-# extract-define-kwargs
-def extract_define_kwargs(expr):
- result = {}
- items = slice(expr, 2)
- n = len(items)
- for idx in range(0, n):
- if sx_truthy((((idx + 1) < n) if not sx_truthy(((idx + 1) < n)) else (type_of(nth(items, idx)) == 'keyword'))):
- key = keyword_name(nth(items, idx))
- val = nth(items, (idx + 1))
- result[key] = (sx_str('(', join(' ', map(serialize, val)), ')') if sx_truthy((type_of(val) == 'list')) else sx_str(val))
- return result
-
-# categorize-special-forms
-def categorize_special_forms(parsed_exprs):
- categories = {}
- for expr in parsed_exprs:
- if sx_truthy(((type_of(expr) == 'list') if not sx_truthy((type_of(expr) == 'list')) else ((len(expr) >= 2) if not sx_truthy((len(expr) >= 2)) else ((type_of(first(expr)) == 'symbol') if not sx_truthy((type_of(first(expr)) == 'symbol')) else (symbol_name(first(expr)) == 'define-special-form'))))):
- name = nth(expr, 1)
- kwargs = extract_define_kwargs(expr)
- category = (get(special_form_category_map, name) if sx_truthy(get(special_form_category_map, name)) else 'Other')
- if sx_truthy((not sx_truthy(has_key_p(categories, category)))):
- categories[category] = []
- get(categories, category).append({'name': name, 'syntax': (get(kwargs, 'syntax') if sx_truthy(get(kwargs, 'syntax')) else ''), 'doc': (get(kwargs, 'doc') if sx_truthy(get(kwargs, 'doc')) else ''), 'tail-position': (get(kwargs, 'tail-position') if sx_truthy(get(kwargs, 'tail-position')) else ''), 'example': (get(kwargs, 'example') if sx_truthy(get(kwargs, 'example')) else '')})
- return categories
-
-# build-ref-items-with-href
-def build_ref_items_with_href(items, base_path, detail_keys, n_fields):
- return map(lambda item: ((lambda name: (lambda field2: (lambda field3: {'name': name, 'desc': field2, 'exists': field3, 'href': (sx_str(base_path, name) if sx_truthy((field3 if not sx_truthy(field3) else some(lambda k: (k == name), detail_keys))) else NIL)})(nth(item, 2)))(nth(item, 1)))(nth(item, 0)) if sx_truthy((n_fields == 3)) else (lambda name: (lambda desc: {'name': name, 'desc': desc, 'href': (sx_str(base_path, name) if sx_truthy(some(lambda k: (k == name), detail_keys)) else NIL)})(nth(item, 1)))(nth(item, 0))), items)
-
-# build-reference-data
-def build_reference_data(slug, raw_data, detail_keys):
- _match = slug
- if _match == 'attributes':
- return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3)}
- elif _match == 'headers':
- return {'req-headers': build_ref_items_with_href(get(raw_data, 'req-headers'), '/geography/hypermedia/reference/headers/', detail_keys, 3), 'resp-headers': build_ref_items_with_href(get(raw_data, 'resp-headers'), '/geography/hypermedia/reference/headers/', detail_keys, 3)}
- elif _match == 'events':
- return {'events-list': build_ref_items_with_href(get(raw_data, 'events-list'), '/geography/hypermedia/reference/events/', detail_keys, 2)}
- elif _match == 'js-api':
- return {'js-api-list': map(lambda item: {'name': nth(item, 0), 'desc': nth(item, 1)}, get(raw_data, 'js-api-list'))}
- else:
- return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3)}
-
-# build-attr-detail
-def build_attr_detail(slug, detail):
- if sx_truthy(is_nil(detail)):
- return {'attr-not-found': True}
- else:
- return {'attr-not-found': NIL, 'attr-title': slug, 'attr-description': get(detail, 'description'), 'attr-example': get(detail, 'example'), 'attr-handler': get(detail, 'handler'), 'attr-demo': get(detail, 'demo'), 'attr-wire-id': (sx_str('ref-wire-', replace(replace(slug, ':', '-'), '*', 'star')) if sx_truthy(has_key_p(detail, 'handler')) else NIL)}
-
-# build-header-detail
-def build_header_detail(slug, detail):
- if sx_truthy(is_nil(detail)):
- return {'header-not-found': True}
- else:
- return {'header-not-found': NIL, 'header-title': slug, 'header-direction': get(detail, 'direction'), 'header-description': get(detail, 'description'), 'header-example': get(detail, 'example'), 'header-demo': get(detail, 'demo')}
-
-# build-event-detail
-def build_event_detail(slug, detail):
- if sx_truthy(is_nil(detail)):
- return {'event-not-found': True}
- else:
- return {'event-not-found': NIL, 'event-title': slug, 'event-description': get(detail, 'description'), 'event-example': get(detail, 'example'), 'event-demo': get(detail, 'demo')}
-
-# build-component-source
-def build_component_source(comp_data):
- comp_type = get(comp_data, 'type')
- name = get(comp_data, 'name')
- params = get(comp_data, 'params')
- has_children = get(comp_data, 'has-children')
- body_sx = get(comp_data, 'body-sx')
- affinity = get(comp_data, 'affinity')
- if sx_truthy((comp_type == 'not-found')):
- return sx_str(';; component ', name, ' not found')
- else:
- param_strs = ((['&rest', 'children'] if sx_truthy(has_children) else []) if sx_truthy(empty_p(params)) else (append(cons('&key', params), ['&rest', 'children']) if sx_truthy(has_children) else cons('&key', params)))
- params_sx = sx_str('(', join(' ', param_strs), ')')
- form_name = ('defisland' if sx_truthy((comp_type == 'island')) else 'defcomp')
- affinity_str = (sx_str(' :affinity ', affinity) if sx_truthy(((comp_type == 'component') if not sx_truthy((comp_type == 'component')) else ((not sx_truthy(is_nil(affinity))) if not sx_truthy((not sx_truthy(is_nil(affinity)))) else (not sx_truthy((affinity == 'auto')))))) else '')
- return sx_str('(', form_name, ' ', name, ' ', params_sx, affinity_str, '\n ', body_sx, ')')
-
-# build-bundle-analysis
-def build_bundle_analysis(pages_raw, components_raw, total_components, total_macros, pure_count, io_count):
- _cells = {}
- pages_data = []
- for page in pages_raw:
- needed_names = get(page, 'needed-names')
- n = len(needed_names)
- pct = (round(((n / total_components) * 100)) if sx_truthy((total_components > 0)) else 0)
- savings = (100 - pct)
- _cells['pure_in_page'] = 0
- _cells['io_in_page'] = 0
- page_io_refs = []
- comp_details = []
- for comp_name in needed_names:
- info = get(components_raw, comp_name)
- if sx_truthy((not sx_truthy(is_nil(info)))):
- if sx_truthy(get(info, 'is-pure')):
- _cells['pure_in_page'] = (_cells['pure_in_page'] + 1)
- else:
- _cells['io_in_page'] = (_cells['io_in_page'] + 1)
- for ref in (get(info, 'io-refs') if sx_truthy(get(info, 'io-refs')) else []):
- if sx_truthy((not sx_truthy(some(lambda r: (r == ref), page_io_refs)))):
- page_io_refs.append(ref)
- comp_details.append({'name': comp_name, 'is-pure': get(info, 'is-pure'), 'affinity': get(info, 'affinity'), 'render-target': get(info, 'render-target'), 'io-refs': (get(info, 'io-refs') if sx_truthy(get(info, 'io-refs')) else []), 'deps': (get(info, 'deps') if sx_truthy(get(info, 'deps')) else []), 'source': get(info, 'source')})
- pages_data.append({'name': get(page, 'name'), 'path': get(page, 'path'), 'direct': get(page, 'direct'), 'needed': n, 'pct': pct, 'savings': savings, 'io-refs': len(page_io_refs), 'pure-in-page': _cells['pure_in_page'], 'io-in-page': _cells['io_in_page'], 'components': comp_details})
- return {'pages': pages_data, 'total-components': total_components, 'total-macros': total_macros, 'pure-count': pure_count, 'io-count': io_count}
-
-# build-routing-analysis
-def build_routing_analysis(pages_raw):
- _cells = {}
- pages_data = []
- _cells['client_count'] = 0
- _cells['server_count'] = 0
- for page in pages_raw:
- has_data = get(page, 'has-data')
- content_src = (get(page, 'content-src') if sx_truthy(get(page, 'content-src')) else '')
- _cells['mode'] = NIL
- _cells['reason'] = ''
- if sx_truthy(has_data):
- _cells['mode'] = 'server'
- _cells['reason'] = 'Has :data expression — needs server IO'
- _cells['server_count'] = (_cells['server_count'] + 1)
- elif sx_truthy(empty_p(content_src)):
- _cells['mode'] = 'server'
- _cells['reason'] = 'No content expression'
- _cells['server_count'] = (_cells['server_count'] + 1)
- else:
- _cells['mode'] = 'client'
- _cells['client_count'] = (_cells['client_count'] + 1)
- pages_data.append({'name': get(page, 'name'), 'path': get(page, 'path'), 'mode': _cells['mode'], 'has-data': has_data, 'content-expr': (sx_str(slice(content_src, 0, 80), '...') if sx_truthy((len(content_src) > 80)) else content_src), 'reason': _cells['reason']})
- return {'pages': pages_data, 'total-pages': (_cells['client_count'] + _cells['server_count']), 'client-count': _cells['client_count'], 'server-count': _cells['server_count']}
-
-# build-affinity-analysis
-def build_affinity_analysis(demo_components, page_plans):
- return {'components': demo_components, 'page-plans': page_plans}
-
-
-# === Transpiled from router (client-side route matching) ===
-
-# split-path-segments
-def split_path_segments(path):
- trimmed = (slice(path, 1) if sx_truthy(starts_with_p(path, '/')) else path)
- trimmed2 = (slice(trimmed, 0, (len(trimmed) - 1)) if sx_truthy(((not sx_truthy(empty_p(trimmed))) if not sx_truthy((not sx_truthy(empty_p(trimmed)))) else ends_with_p(trimmed, '/'))) else trimmed)
- if sx_truthy(empty_p(trimmed2)):
- return []
- else:
- return split(trimmed2, '/')
-
-# make-route-segment
-def make_route_segment(seg):
- if sx_truthy((starts_with_p(seg, '<') if not sx_truthy(starts_with_p(seg, '<')) else ends_with_p(seg, '>'))):
- param_name = slice(seg, 1, (len(seg) - 1))
- d = {}
- d['type'] = 'param'
- d['value'] = param_name
- return d
- else:
- d = {}
- d['type'] = 'literal'
- d['value'] = seg
- return d
-
-# parse-route-pattern
-def parse_route_pattern(pattern):
- segments = split_path_segments(pattern)
- return map(make_route_segment, segments)
-
-# match-route-segments
-def match_route_segments(path_segs, parsed_segs):
- _cells = {}
- if sx_truthy((not sx_truthy((len(path_segs) == len(parsed_segs))))):
- return NIL
- else:
- params = {}
- _cells['matched'] = True
- for_each_indexed(lambda i, parsed_seg: ((lambda path_seg: (lambda seg_type: ((_sx_cell_set(_cells, 'matched', False) if sx_truthy((not sx_truthy((path_seg == get(parsed_seg, 'value'))))) else NIL) if sx_truthy((seg_type == 'literal')) else (_sx_dict_set(params, get(parsed_seg, 'value'), path_seg) if sx_truthy((seg_type == 'param')) else _sx_cell_set(_cells, 'matched', False))))(get(parsed_seg, 'type')))(nth(path_segs, i)) if sx_truthy(_cells['matched']) else NIL), parsed_segs)
- if sx_truthy(_cells['matched']):
- return params
- else:
- return NIL
-
-# match-route
-def match_route(path, pattern):
- path_segs = split_path_segments(path)
- parsed_segs = parse_route_pattern(pattern)
- return match_route_segments(path_segs, parsed_segs)
-
-# find-matching-route
-def find_matching_route(path, routes):
- _cells = {}
- match_path = ((sx_url_to_path(path) if sx_truthy(sx_url_to_path(path)) else path) if sx_truthy(starts_with_p(path, '/(')) else path)
- path_segs = split_path_segments(match_path)
- _cells['result'] = NIL
- for route in routes:
- if sx_truthy(is_nil(_cells['result'])):
- params = match_route_segments(path_segs, get(route, 'parsed'))
- if sx_truthy((not sx_truthy(is_nil(params)))):
- matched = merge(route, {})
- matched['params'] = params
- _cells['result'] = matched
- return _cells['result']
-
-# _fn-to-segment
-def _fn_to_segment(name):
- _match = name
- if _match == 'doc':
- return 'docs'
- elif _match == 'spec':
- return 'specs'
- elif _match == 'bootstrapper':
- return 'bootstrappers'
- elif _match == 'test':
- return 'testing'
- elif _match == 'example':
- return 'examples'
- elif _match == 'protocol':
- return 'protocols'
- elif _match == 'essay':
- return 'essays'
- elif _match == 'plan':
- return 'plans'
- elif _match == 'reference-detail':
- return 'reference'
- else:
- return name
-
-# sx-url-to-path
-def sx_url_to_path(url):
- if sx_truthy((not sx_truthy((starts_with_p(url, '/(') if not sx_truthy(starts_with_p(url, '/(')) else ends_with_p(url, ')'))))):
- return NIL
- else:
- inner = slice(url, 2, (len(url) - 1))
- s = replace(replace(replace(inner, '.', '/'), '(', ''), ')', '')
- segs = filter(lambda s: (not sx_truthy(empty_p(s))), split(s, '/'))
- return sx_str('/', join('/', map(_fn_to_segment, segs)))
-
-# _count-leading-dots
-def _count_leading_dots(s):
- if sx_truthy(empty_p(s)):
- return 0
- else:
- if sx_truthy(starts_with_p(s, '.')):
- return (1 + _count_leading_dots(slice(s, 1)))
- else:
- return 0
-
-# _strip-trailing-close
-def _strip_trailing_close(s):
- if sx_truthy(ends_with_p(s, ')')):
- return _strip_trailing_close(slice(s, 0, (len(s) - 1)))
- else:
- return s
-
-# _index-of-safe
-def _index_of_safe(s, needle):
- idx = index_of(s, needle)
- if sx_truthy((is_nil(idx) if sx_truthy(is_nil(idx)) else (idx < 0))):
- return NIL
- else:
- return idx
-
-# _last-index-of
-def _last_index_of(s, needle):
- idx = _index_of_safe(s, needle)
- if sx_truthy(is_nil(idx)):
- return NIL
- else:
- rest_idx = _last_index_of(slice(s, (idx + 1)), needle)
- if sx_truthy(is_nil(rest_idx)):
- return idx
- else:
- return ((idx + 1) + rest_idx)
-
-# _pop-sx-url-level
-def _pop_sx_url_level(url):
- stripped = _strip_trailing_close(url)
- close_count = (len(url) - len(_strip_trailing_close(url)))
- if sx_truthy((close_count <= 1)):
- return '/'
- else:
- last_dp = _last_index_of(stripped, '.(')
- if sx_truthy(is_nil(last_dp)):
- return '/'
- else:
- return sx_str(slice(stripped, 0, last_dp), slice(url, (len(url) - (close_count - 1))))
-
-# _pop-sx-url-levels
-def _pop_sx_url_levels(url, n):
- if sx_truthy((n <= 0)):
- return url
- else:
- return _pop_sx_url_levels(_pop_sx_url_level(url), (n - 1))
-
-# _split-pos-kw
-def _split_pos_kw(tokens, i, pos, kw):
- if sx_truthy((i >= len(tokens))):
- return {'positional': join('.', pos), 'keywords': kw}
- else:
- tok = nth(tokens, i)
- if sx_truthy(starts_with_p(tok, ':')):
- val = (nth(tokens, (i + 1)) if sx_truthy(((i + 1) < len(tokens))) else '')
- return _split_pos_kw(tokens, (i + 2), pos, append(kw, [[tok, val]]))
- else:
- return _split_pos_kw(tokens, (i + 1), append(pos, [tok]), kw)
-
-# _parse-relative-body
-def _parse_relative_body(body):
- if sx_truthy(empty_p(body)):
- return {'positional': '', 'keywords': []}
- else:
- return _split_pos_kw(split(body, '.'), 0, [], [])
-
-# _extract-innermost
-def _extract_innermost(url):
- stripped = _strip_trailing_close(url)
- suffix = slice(url, len(_strip_trailing_close(url)))
- last_dp = _last_index_of(stripped, '.(')
- if sx_truthy(is_nil(last_dp)):
- return {'before': '/(', 'content': slice(stripped, 2), 'suffix': suffix}
- else:
- return {'before': slice(stripped, 0, (last_dp + 2)), 'content': slice(stripped, (last_dp + 2)), 'suffix': suffix}
-
-# _find-kw-in-tokens
-def _find_kw_in_tokens(tokens, i, kw):
- if sx_truthy((i >= len(tokens))):
- return NIL
- else:
- if sx_truthy(((nth(tokens, i) == kw) if not sx_truthy((nth(tokens, i) == kw)) else ((i + 1) < len(tokens)))):
- return nth(tokens, (i + 1))
- else:
- return _find_kw_in_tokens(tokens, (i + 1), kw)
-
-# _find-keyword-value
-def _find_keyword_value(content, kw):
- return _find_kw_in_tokens(split(content, '.'), 0, kw)
-
-# _replace-kw-in-tokens
-def _replace_kw_in_tokens(tokens, i, kw, value):
- if sx_truthy((i >= len(tokens))):
- return []
- else:
- if sx_truthy(((nth(tokens, i) == kw) if not sx_truthy((nth(tokens, i) == kw)) else ((i + 1) < len(tokens)))):
- return append([kw, value], _replace_kw_in_tokens(tokens, (i + 2), kw, value))
- else:
- return cons(nth(tokens, i), _replace_kw_in_tokens(tokens, (i + 1), kw, value))
-
-# _set-keyword-in-content
-def _set_keyword_in_content(content, kw, value):
- current = _find_keyword_value(content, kw)
- if sx_truthy(is_nil(current)):
- return sx_str(content, '.', kw, '.', value)
- else:
- return join('.', _replace_kw_in_tokens(split(content, '.'), 0, kw, value))
-
-# _is-delta-value?
-def _is_delta_value_p(s):
- return ((not sx_truthy(empty_p(s))) if not sx_truthy((not sx_truthy(empty_p(s)))) else ((len(s) > 1) if not sx_truthy((len(s) > 1)) else (starts_with_p(s, '+') if sx_truthy(starts_with_p(s, '+')) else starts_with_p(s, '-'))))
-
-# _apply-delta
-def _apply_delta(current_str, delta_str):
- cur = parse_int(current_str, NIL)
- delta = parse_int(delta_str, NIL)
- if sx_truthy((is_nil(cur) if sx_truthy(is_nil(cur)) else is_nil(delta))):
- return delta_str
- else:
- return sx_str((cur + delta))
-
-# _apply-kw-pairs
-def _apply_kw_pairs(content, kw_pairs):
- if sx_truthy(empty_p(kw_pairs)):
- return content
- else:
- pair = first(kw_pairs)
- kw = first(pair)
- raw_val = nth(pair, 1)
- actual_val = ((lambda current: (raw_val if sx_truthy(is_nil(current)) else _apply_delta(current, raw_val)))(_find_keyword_value(content, kw)) if sx_truthy(_is_delta_value_p(raw_val)) else raw_val)
- return _apply_kw_pairs(_set_keyword_in_content(content, kw, actual_val), rest(kw_pairs))
-
-# _apply-keywords-to-url
-def _apply_keywords_to_url(url, kw_pairs):
- if sx_truthy(empty_p(kw_pairs)):
- return url
- else:
- parts = _extract_innermost(url)
- new_content = _apply_kw_pairs(get(parts, 'content'), kw_pairs)
- return sx_str(get(parts, 'before'), new_content, get(parts, 'suffix'))
-
-# _normalize-relative
-def _normalize_relative(url):
- if sx_truthy(starts_with_p(url, '(')):
- return url
- else:
- return sx_str('(', url, ')')
-
-# resolve-relative-url
-def resolve_relative_url(current, relative):
- canonical = _normalize_relative(relative)
- rel_inner = slice(canonical, 1, (len(canonical) - 1))
- dots = _count_leading_dots(rel_inner)
- body = slice(rel_inner, _count_leading_dots(rel_inner))
- if sx_truthy((dots == 0)):
- return current
- else:
- parsed = _parse_relative_body(body)
- pos_body = get(parsed, 'positional')
- kw_pairs = get(parsed, 'keywords')
- after_nav = ((current if sx_truthy(empty_p(pos_body)) else (lambda stripped: (lambda suffix: sx_str(stripped, '.', pos_body, suffix))(slice(current, len(_strip_trailing_close(current)))))(_strip_trailing_close(current))) if sx_truthy((dots == 1)) else (lambda base: (base if sx_truthy(empty_p(pos_body)) else (sx_str('/(', pos_body, ')') if sx_truthy((base == '/')) else (lambda stripped: (lambda suffix: sx_str(stripped, '.(', pos_body, ')', suffix))(slice(base, len(_strip_trailing_close(base)))))(_strip_trailing_close(base)))))(_pop_sx_url_levels(current, (dots - 1))))
- return _apply_keywords_to_url(after_nav, kw_pairs)
-
-# relative-sx-url?
-def relative_sx_url_p(url):
- return ((starts_with_p(url, '(') if not sx_truthy(starts_with_p(url, '(')) else (not sx_truthy(starts_with_p(url, '/(')))) if sx_truthy((starts_with_p(url, '(') if not sx_truthy(starts_with_p(url, '(')) else (not sx_truthy(starts_with_p(url, '/('))))) else starts_with_p(url, '.'))
-
-# _url-special-forms
-def _url_special_forms():
- return ['!source', '!inspect', '!diff', '!search', '!raw', '!json']
-
-# url-special-form?
-def url_special_form_p(name):
- return (starts_with_p(name, '!') if not sx_truthy(starts_with_p(name, '!')) else contains_p(_url_special_forms(), name))
-
-# parse-sx-url
-def parse_sx_url(url):
- if sx_truthy((url == '/')):
- return {'type': 'home', 'raw': url}
- elif sx_truthy(relative_sx_url_p(url)):
- return {'type': 'relative', 'raw': url}
- elif sx_truthy((starts_with_p(url, '/(!') if not sx_truthy(starts_with_p(url, '/(!')) else ends_with_p(url, ')'))):
- inner = slice(url, 2, (len(url) - 1))
- dot_pos = _index_of_safe(inner, '.')
- paren_pos = _index_of_safe(inner, '(')
- end_pos = (len(inner) if sx_truthy((is_nil(dot_pos) if not sx_truthy(is_nil(dot_pos)) else is_nil(paren_pos))) else (paren_pos if sx_truthy(is_nil(dot_pos)) else (dot_pos if sx_truthy(is_nil(paren_pos)) else min(dot_pos, paren_pos))))
- form_name = slice(inner, 0, end_pos)
- rest_part = slice(inner, end_pos)
- inner_expr = (slice(rest_part, 1) if sx_truthy(starts_with_p(rest_part, '.')) else rest_part)
- return {'type': 'special-form', 'form': form_name, 'inner': inner_expr, 'raw': url}
- elif sx_truthy((starts_with_p(url, '/(~') if not sx_truthy(starts_with_p(url, '/(~')) else ends_with_p(url, ')'))):
- name = slice(url, 2, (len(url) - 1))
- return {'type': 'direct-component', 'name': name, 'raw': url}
- elif sx_truthy((starts_with_p(url, '/(') if not sx_truthy(starts_with_p(url, '/(')) else ends_with_p(url, ')'))):
- return {'type': 'absolute', 'raw': url}
- else:
- return {'type': 'path', 'raw': url}
-
-# url-special-form-name
-def url_special_form_name(url):
- parsed = parse_sx_url(url)
- if sx_truthy((get(parsed, 'type') == 'special-form')):
- return get(parsed, 'form')
- else:
- return NIL
-
-# url-special-form-inner
-def url_special_form_inner(url):
- parsed = parse_sx_url(url)
- if sx_truthy((get(parsed, 'type') == 'special-form')):
- return get(parsed, 'inner')
- else:
- return NIL
-
-# url-to-expr
-def url_to_expr(url_path):
- if sx_truthy(((url_path == '/') if sx_truthy((url_path == '/')) else empty_p(url_path))):
- return []
- else:
- trimmed = (slice(url_path, 1) if sx_truthy(starts_with_p(url_path, '/')) else url_path)
- sx_source = replace(trimmed, '.', ' ')
- exprs = sx_parse(sx_source)
- if sx_truthy(empty_p(exprs)):
- return []
- else:
- return first(exprs)
-
-# auto-quote-unknowns
-def auto_quote_unknowns(expr, env):
- if sx_truthy((not sx_truthy(list_p(expr)))):
- return expr
- else:
- if sx_truthy(empty_p(expr)):
- return expr
- else:
- return cons(first(expr), map(lambda child: (auto_quote_unknowns(child, env) if sx_truthy(list_p(child)) else ((lambda name: (child if sx_truthy((env_has(env, name) if sx_truthy(env_has(env, name)) else (starts_with_p(name, ':') if sx_truthy(starts_with_p(name, ':')) else (starts_with_p(name, '~') if sx_truthy(starts_with_p(name, '~')) else starts_with_p(name, '!'))))) else name))(symbol_name(child)) if sx_truthy((type_of(child) == 'symbol')) else child)), rest(expr)))
-
-# prepare-url-expr
-def prepare_url_expr(url_path, env):
- expr = url_to_expr(url_path)
- if sx_truthy(empty_p(expr)):
- return expr
- else:
- return auto_quote_unknowns(expr, env)
-
-
-# === Transpiled from cek (explicit CEK machine evaluator) ===
-
-# cek-run
-def cek_run(state):
- if sx_truthy(cek_terminal_p(state)):
- return cek_value(state)
- else:
- return cek_run(cek_step(state))
-
-# cek-step
-def cek_step(state):
- if sx_truthy((cek_phase(state) == 'eval')):
- return step_eval(state)
- else:
- return step_continue(state)
-
-# step-eval
-def step_eval(state):
- expr = cek_control(state)
- env = cek_env(state)
- kont = cek_kont(state)
- _match = type_of(expr)
- if _match == 'number':
- return make_cek_value(expr, env, kont)
- elif _match == 'string':
- return make_cek_value(expr, env, kont)
- elif _match == 'boolean':
- return make_cek_value(expr, env, kont)
- elif _match == 'nil':
- return make_cek_value(NIL, env, kont)
- elif _match == 'symbol':
- name = symbol_name(expr)
- val = (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name)))))))
- return make_cek_value(val, env, kont)
- elif _match == 'keyword':
- return make_cek_value(keyword_name(expr), env, kont)
- elif _match == 'dict':
- ks = keys(expr)
- if sx_truthy(empty_p(ks)):
- return make_cek_value({}, env, kont)
- else:
- first_key = first(ks)
- remaining_entries = []
- for k in rest(ks):
- remaining_entries.append([k, get(expr, k)])
- return make_cek_state(get(expr, first_key), env, kont_push(make_dict_frame(remaining_entries, [[first_key]], env), kont))
- elif _match == 'list':
- if sx_truthy(empty_p(expr)):
- return make_cek_value([], env, kont)
- else:
- return step_eval_list(expr, env, kont)
- else:
- return make_cek_value(expr, env, kont)
-
-# step-eval-list
-def step_eval_list(expr, env, kont):
- head = first(expr)
- args = rest(expr)
- if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))):
- if sx_truthy(empty_p(expr)):
- return make_cek_value([], env, kont)
- else:
- return make_cek_state(first(expr), env, kont_push(make_map_frame(NIL, rest(expr), [], env), kont))
- else:
- if sx_truthy((type_of(head) == 'symbol')):
- name = symbol_name(head)
- if sx_truthy((name == 'if')):
- return step_sf_if(args, env, kont)
- elif sx_truthy((name == 'when')):
- return step_sf_when(args, env, kont)
- elif sx_truthy((name == 'cond')):
- return step_sf_cond(args, env, kont)
- elif sx_truthy((name == 'case')):
- return step_sf_case(args, env, kont)
- elif sx_truthy((name == 'and')):
- return step_sf_and(args, env, kont)
- elif sx_truthy((name == 'or')):
- return step_sf_or(args, env, kont)
- elif sx_truthy((name == 'let')):
- return step_sf_let(args, env, kont)
- elif sx_truthy((name == 'let*')):
- return step_sf_let(args, env, kont)
- elif sx_truthy((name == 'lambda')):
- return step_sf_lambda(args, env, kont)
- elif sx_truthy((name == 'fn')):
- return step_sf_lambda(args, env, kont)
- elif sx_truthy((name == 'define')):
- return step_sf_define(args, env, kont)
- elif sx_truthy((name == 'defcomp')):
- return make_cek_value(sf_defcomp(args, env), env, kont)
- elif sx_truthy((name == 'defisland')):
- return make_cek_value(sf_defisland(args, env), env, kont)
- elif sx_truthy((name == 'defmacro')):
- return make_cek_value(sf_defmacro(args, env), env, kont)
- elif sx_truthy((name == 'defstyle')):
- return make_cek_value(sf_defstyle(args, env), env, kont)
- elif sx_truthy((name == 'defhandler')):
- return make_cek_value(sf_defhandler(args, env), env, kont)
- elif sx_truthy((name == 'defpage')):
- return make_cek_value(sf_defpage(args, env), env, kont)
- elif sx_truthy((name == 'defquery')):
- return make_cek_value(sf_defquery(args, env), env, kont)
- elif sx_truthy((name == 'defaction')):
- return make_cek_value(sf_defaction(args, env), env, kont)
- elif sx_truthy((name == 'deftype')):
- return make_cek_value(sf_deftype(args, env), env, kont)
- elif sx_truthy((name == 'defeffect')):
- return make_cek_value(sf_defeffect(args, env), env, kont)
- elif sx_truthy((name == 'begin')):
- return step_sf_begin(args, env, kont)
- elif sx_truthy((name == 'do')):
- return step_sf_begin(args, env, kont)
- elif sx_truthy((name == 'quote')):
- return make_cek_value((NIL if sx_truthy(empty_p(args)) else first(args)), env, kont)
- elif sx_truthy((name == 'quasiquote')):
- return make_cek_value(qq_expand(first(args), env), env, kont)
- elif sx_truthy((name == '->')):
- return step_sf_thread_first(args, env, kont)
- elif sx_truthy((name == 'set!')):
- return step_sf_set_b(args, env, kont)
- elif sx_truthy((name == 'letrec')):
- return make_cek_value(sf_letrec(args, env), env, kont)
- elif sx_truthy((name == 'reset')):
- return step_sf_reset(args, env, kont)
- elif sx_truthy((name == 'shift')):
- return step_sf_shift(args, env, kont)
- elif sx_truthy((name == 'deref')):
- return step_sf_deref(args, env, kont)
- elif sx_truthy((name == 'scope')):
- return step_sf_scope(args, env, kont)
- elif sx_truthy((name == 'provide')):
- return step_sf_provide(args, env, kont)
- elif sx_truthy((name == 'dynamic-wind')):
- return make_cek_value(sf_dynamic_wind(args, env), env, kont)
- elif sx_truthy((name == 'map')):
- return step_ho_map(args, env, kont)
- elif sx_truthy((name == 'map-indexed')):
- return step_ho_map_indexed(args, env, kont)
- elif sx_truthy((name == 'filter')):
- return step_ho_filter(args, env, kont)
- elif sx_truthy((name == 'reduce')):
- return step_ho_reduce(args, env, kont)
- elif sx_truthy((name == 'some')):
- return step_ho_some(args, env, kont)
- elif sx_truthy((name == 'every?')):
- return step_ho_every(args, env, kont)
- elif sx_truthy((name == 'for-each')):
- return step_ho_for_each(args, env, kont)
- elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
- mac = env_get(env, name)
- return make_cek_state(expand_macro(mac, args, env), env, kont)
- elif sx_truthy((render_active_p() if not sx_truthy(render_active_p()) else is_render_expr(expr))):
- return make_cek_value(render_expr(expr, env), env, kont)
- else:
- return step_eval_call(head, args, env, kont)
- else:
- return step_eval_call(head, args, env, kont)
-
-# step-sf-if
-def step_sf_if(args, env, kont):
- return make_cek_state(first(args), env, kont_push(make_if_frame(nth(args, 1), (nth(args, 2) if sx_truthy((len(args) > 2)) else NIL), env), kont))
-
-# step-sf-when
-def step_sf_when(args, env, kont):
- return make_cek_state(first(args), env, kont_push(make_when_frame(rest(args), env), kont))
-
-# step-sf-begin
-def step_sf_begin(args, env, kont):
- if sx_truthy(empty_p(args)):
- return make_cek_value(NIL, env, kont)
- else:
- if sx_truthy((len(args) == 1)):
- return make_cek_state(first(args), env, kont)
- else:
- return make_cek_state(first(args), env, kont_push(make_begin_frame(rest(args), env), kont))
-
-# step-sf-let
-def step_sf_let(args, env, kont):
- if sx_truthy((type_of(first(args)) == 'symbol')):
- return make_cek_value(sf_named_let(args, env), env, kont)
- else:
- bindings = first(args)
- body = rest(args)
- local = env_extend(env)
- if sx_truthy(empty_p(bindings)):
- return step_sf_begin(body, local, kont)
- else:
- first_binding = (first(bindings) if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))) else [first(bindings), nth(bindings, 1)])
- rest_bindings = (rest(bindings) if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))) else (lambda pairs: _sx_begin(reduce(lambda acc, i: _sx_append(pairs, [nth(bindings, (i * 2)), nth(bindings, ((i * 2) + 1))]), NIL, range(1, (len(bindings) / 2))), pairs))([]))
- vname = (symbol_name(first(first_binding)) if sx_truthy((type_of(first(first_binding)) == 'symbol')) else first(first_binding))
- return make_cek_state(nth(first_binding, 1), local, kont_push(make_let_frame(vname, rest_bindings, body, local), kont))
-
-# step-sf-define
-def step_sf_define(args, env, kont):
- name_sym = first(args)
- has_effects = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))
- val_idx = (3 if sx_truthy(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))) else 1)
- effect_list = (nth(args, 2) if sx_truthy(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))) else NIL)
- return make_cek_state(nth(args, val_idx), env, kont_push(make_define_frame(symbol_name(name_sym), env, has_effects, effect_list), kont))
-
-# step-sf-set!
-def step_sf_set_b(args, env, kont):
- return make_cek_state(nth(args, 1), env, kont_push(make_set_frame(symbol_name(first(args)), env), kont))
-
-# step-sf-and
-def step_sf_and(args, env, kont):
- if sx_truthy(empty_p(args)):
- return make_cek_value(True, env, kont)
- else:
- return make_cek_state(first(args), env, kont_push(make_and_frame(rest(args), env), kont))
-
-# step-sf-or
-def step_sf_or(args, env, kont):
- if sx_truthy(empty_p(args)):
- return make_cek_value(False, env, kont)
- else:
- return make_cek_state(first(args), env, kont_push(make_or_frame(rest(args), env), kont))
-
-# step-sf-cond
-def step_sf_cond(args, env, kont):
- scheme_p = cond_scheme_p(args)
- if sx_truthy(scheme_p):
- if sx_truthy(empty_p(args)):
- return make_cek_value(NIL, env, kont)
- else:
- clause = first(args)
- test = first(clause)
- if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))):
- return make_cek_state(nth(clause, 1), env, kont)
- else:
- return make_cek_state(test, env, kont_push(make_cond_frame(args, env, True), kont))
- else:
- if sx_truthy((len(args) < 2)):
- return make_cek_value(NIL, env, kont)
- else:
- test = first(args)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))):
- return make_cek_state(nth(args, 1), env, kont)
- else:
- return make_cek_state(test, env, kont_push(make_cond_frame(args, env, False), kont))
-
-# step-sf-case
-def step_sf_case(args, env, kont):
- return make_cek_state(first(args), env, kont_push(make_case_frame(NIL, rest(args), env), kont))
-
-# step-sf-thread-first
-def step_sf_thread_first(args, env, kont):
- return make_cek_state(first(args), env, kont_push(make_thread_frame(rest(args), env), kont))
-
-# step-sf-lambda
-def step_sf_lambda(args, env, kont):
- return make_cek_value(sf_lambda(args, env), env, kont)
-
-# step-sf-scope
-def step_sf_scope(args, env, kont):
- return make_cek_value(sf_scope(args, env), env, kont)
-
-# step-sf-provide
-def step_sf_provide(args, env, kont):
- return make_cek_value(sf_provide(args, env), env, kont)
-
-# step-sf-reset
-def step_sf_reset(args, env, kont):
- return make_cek_state(first(args), env, kont_push(make_reset_frame(env), kont))
-
-# step-sf-shift
-def step_sf_shift(args, env, kont):
- k_name = symbol_name(first(args))
- body = nth(args, 1)
- captured_result = kont_capture_to_reset(kont)
- captured = first(captured_result)
- rest_kont = nth(captured_result, 1)
- k = make_cek_continuation(captured, rest_kont)
- shift_env = env_extend(env)
- shift_env[k_name] = k
- return make_cek_state(body, shift_env, rest_kont)
-
-# step-sf-deref
-def step_sf_deref(args, env, kont):
- return make_cek_state(first(args), env, kont_push(make_deref_frame(env), kont))
-
-# cek-call
-def cek_call(f, args):
- a = ([] if sx_truthy(is_nil(args)) else args)
- if sx_truthy(is_nil(f)):
- return NIL
- elif sx_truthy(is_lambda(f)):
- return cek_run(continue_with_call(f, a, {}, a, []))
- elif sx_truthy(is_callable(f)):
- return apply(f, a)
- else:
- return NIL
-
-# reactive-shift-deref
-def reactive_shift_deref(sig, env, kont):
- _cells = {}
- scan_result = kont_capture_to_reactive_reset(kont)
- captured_frames = first(scan_result)
- reset_frame = nth(scan_result, 1)
- remaining_kont = nth(scan_result, 2)
- update_fn = get(reset_frame, 'update-fn')
- _cells['sub_disposers'] = []
- subscriber = _sx_fn(lambda : (
- for_each(lambda d: cek_call(d, NIL), _cells['sub_disposers']),
- _sx_cell_set(_cells, 'sub_disposers', []),
- (lambda new_reset: (lambda new_kont: with_island_scope(lambda d: _sx_append(_cells['sub_disposers'], d), lambda : cek_run(make_cek_value(signal_value(sig), env, new_kont))))(concat(captured_frames, [new_reset], remaining_kont)))(make_reactive_reset_frame(env, update_fn, False))
-)[-1])
- signal_add_sub(sig, subscriber)
- register_in_scope(_sx_fn(lambda : (
- signal_remove_sub(sig, subscriber),
- for_each(lambda d: cek_call(d, NIL), _cells['sub_disposers'])
-)[-1]))
- initial_kont = concat(captured_frames, [reset_frame], remaining_kont)
- return make_cek_value(signal_value(sig), env, initial_kont)
-
-# step-eval-call
-def step_eval_call(head, args, env, kont):
- return make_cek_state(head, env, kont_push(make_arg_frame(NIL, [], args, env, args), kont))
-
-# step-ho-map
-def step_ho_map(args, env, kont):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- if sx_truthy(empty_p(coll)):
- return make_cek_value([], env, kont)
- else:
- return continue_with_call(f, [first(coll)], env, [], kont_push(make_map_frame(f, rest(coll), [], env), kont))
-
-# step-ho-map-indexed
-def step_ho_map_indexed(args, env, kont):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- if sx_truthy(empty_p(coll)):
- return make_cek_value([], env, kont)
- else:
- return continue_with_call(f, [0, first(coll)], env, [], kont_push(make_map_indexed_frame(f, rest(coll), [], env), kont))
-
-# step-ho-filter
-def step_ho_filter(args, env, kont):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- if sx_truthy(empty_p(coll)):
- return make_cek_value([], env, kont)
- else:
- return continue_with_call(f, [first(coll)], env, [], kont_push(make_filter_frame(f, rest(coll), [], first(coll), env), kont))
-
-# step-ho-reduce
-def step_ho_reduce(args, env, kont):
- f = trampoline(eval_expr(first(args), env))
- init = trampoline(eval_expr(nth(args, 1), env))
- coll = trampoline(eval_expr(nth(args, 2), env))
- if sx_truthy(empty_p(coll)):
- return make_cek_value(init, env, kont)
- else:
- return continue_with_call(f, [init, first(coll)], env, [], kont_push(make_reduce_frame(f, rest(coll), env), kont))
-
-# step-ho-some
-def step_ho_some(args, env, kont):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- if sx_truthy(empty_p(coll)):
- return make_cek_value(False, env, kont)
- else:
- return continue_with_call(f, [first(coll)], env, [], kont_push(make_some_frame(f, rest(coll), env), kont))
-
-# step-ho-every
-def step_ho_every(args, env, kont):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- if sx_truthy(empty_p(coll)):
- return make_cek_value(True, env, kont)
- else:
- return continue_with_call(f, [first(coll)], env, [], kont_push(make_every_frame(f, rest(coll), env), kont))
-
-# step-ho-for-each
-def step_ho_for_each(args, env, kont):
- f = trampoline(eval_expr(first(args), env))
- coll = trampoline(eval_expr(nth(args, 1), env))
- if sx_truthy(empty_p(coll)):
- return make_cek_value(NIL, env, kont)
- else:
- return continue_with_call(f, [first(coll)], env, [], kont_push(make_for_each_frame(f, rest(coll), env), kont))
-
-# step-continue
-def step_continue(state):
- value = cek_value(state)
- env = cek_env(state)
- kont = cek_kont(state)
- if sx_truthy(kont_empty_p(kont)):
- return state
- else:
- frame = kont_top(kont)
- rest_k = kont_pop(kont)
- ft = frame_type(frame)
- if sx_truthy((ft == 'if')):
- if sx_truthy((value if not sx_truthy(value) else (not sx_truthy(is_nil(value))))):
- return make_cek_state(get(frame, 'then'), get(frame, 'env'), rest_k)
- else:
- if sx_truthy(is_nil(get(frame, 'else'))):
- return make_cek_value(NIL, env, rest_k)
- else:
- return make_cek_state(get(frame, 'else'), get(frame, 'env'), rest_k)
- elif sx_truthy((ft == 'when')):
- if sx_truthy((value if not sx_truthy(value) else (not sx_truthy(is_nil(value))))):
- body = get(frame, 'body')
- fenv = get(frame, 'env')
- if sx_truthy(empty_p(body)):
- return make_cek_value(NIL, fenv, rest_k)
- else:
- if sx_truthy((len(body) == 1)):
- return make_cek_state(first(body), fenv, rest_k)
- else:
- return make_cek_state(first(body), fenv, kont_push(make_begin_frame(rest(body), fenv), rest_k))
- else:
- return make_cek_value(NIL, env, rest_k)
- elif sx_truthy((ft == 'begin')):
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(value, fenv, rest_k)
- else:
- if sx_truthy((len(remaining) == 1)):
- return make_cek_state(first(remaining), fenv, rest_k)
- else:
- return make_cek_state(first(remaining), fenv, kont_push(make_begin_frame(rest(remaining), fenv), rest_k))
- elif sx_truthy((ft == 'let')):
- name = get(frame, 'name')
- remaining = get(frame, 'remaining')
- body = get(frame, 'body')
- local = get(frame, 'env')
- local[name] = value
- if sx_truthy(empty_p(remaining)):
- return step_sf_begin(body, local, rest_k)
- else:
- next_binding = first(remaining)
- vname = (symbol_name(first(next_binding)) if sx_truthy((type_of(first(next_binding)) == 'symbol')) else first(next_binding))
- return make_cek_state(nth(next_binding, 1), local, kont_push(make_let_frame(vname, rest(remaining), body, local), rest_k))
- elif sx_truthy((ft == 'define')):
- name = get(frame, 'name')
- fenv = get(frame, 'env')
- has_effects = get(frame, 'has-effects')
- effect_list = get(frame, 'effect-list')
- if sx_truthy((is_lambda(value) if not sx_truthy(is_lambda(value)) else is_nil(lambda_name(value)))):
- value.name = name
- fenv[name] = value
- if sx_truthy(has_effects):
- effect_names = (map(lambda e: (symbol_name(e) if sx_truthy((type_of(e) == 'symbol')) else sx_str(e)), effect_list) if sx_truthy((type_of(effect_list) == 'list')) else [sx_str(effect_list)])
- effect_anns = (env_get(fenv, '*effect-annotations*') if sx_truthy(env_has(fenv, '*effect-annotations*')) else {})
- effect_anns[name] = effect_names
- fenv['*effect-annotations*'] = effect_anns
- return make_cek_value(value, fenv, rest_k)
- elif sx_truthy((ft == 'set')):
- name = get(frame, 'name')
- fenv = get(frame, 'env')
- fenv[name] = value
- return make_cek_value(value, env, rest_k)
- elif sx_truthy((ft == 'and')):
- if sx_truthy((not sx_truthy(value))):
- return make_cek_value(value, env, rest_k)
- else:
- remaining = get(frame, 'remaining')
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(value, env, rest_k)
- else:
- return make_cek_state(first(remaining), get(frame, 'env'), (rest_k if sx_truthy((len(remaining) == 1)) else kont_push(make_and_frame(rest(remaining), get(frame, 'env')), rest_k)))
- elif sx_truthy((ft == 'or')):
- if sx_truthy(value):
- return make_cek_value(value, env, rest_k)
- else:
- remaining = get(frame, 'remaining')
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(False, env, rest_k)
- else:
- return make_cek_state(first(remaining), get(frame, 'env'), (rest_k if sx_truthy((len(remaining) == 1)) else kont_push(make_or_frame(rest(remaining), get(frame, 'env')), rest_k)))
- elif sx_truthy((ft == 'cond')):
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- scheme_p = get(frame, 'scheme')
- if sx_truthy(scheme_p):
- if sx_truthy(value):
- return make_cek_state(nth(first(remaining), 1), fenv, rest_k)
- else:
- next_clauses = rest(remaining)
- if sx_truthy(empty_p(next_clauses)):
- return make_cek_value(NIL, fenv, rest_k)
- else:
- next_clause = first(next_clauses)
- next_test = first(next_clause)
- if sx_truthy((((type_of(next_test) == 'symbol') if not sx_truthy((type_of(next_test) == 'symbol')) else ((symbol_name(next_test) == 'else') if sx_truthy((symbol_name(next_test) == 'else')) else (symbol_name(next_test) == ':else'))) if sx_truthy(((type_of(next_test) == 'symbol') if not sx_truthy((type_of(next_test) == 'symbol')) else ((symbol_name(next_test) == 'else') if sx_truthy((symbol_name(next_test) == 'else')) else (symbol_name(next_test) == ':else')))) else ((type_of(next_test) == 'keyword') if not sx_truthy((type_of(next_test) == 'keyword')) else (keyword_name(next_test) == 'else')))):
- return make_cek_state(nth(next_clause, 1), fenv, rest_k)
- else:
- return make_cek_state(next_test, fenv, kont_push(make_cond_frame(next_clauses, fenv, True), rest_k))
- else:
- if sx_truthy(value):
- return make_cek_state(nth(remaining, 1), fenv, rest_k)
- else:
- next = slice(remaining, 2)
- if sx_truthy((len(next) < 2)):
- return make_cek_value(NIL, fenv, rest_k)
- else:
- next_test = first(next)
- if sx_truthy((((type_of(next_test) == 'keyword') if not sx_truthy((type_of(next_test) == 'keyword')) else (keyword_name(next_test) == 'else')) if sx_truthy(((type_of(next_test) == 'keyword') if not sx_truthy((type_of(next_test) == 'keyword')) else (keyword_name(next_test) == 'else'))) else ((type_of(next_test) == 'symbol') if not sx_truthy((type_of(next_test) == 'symbol')) else ((symbol_name(next_test) == 'else') if sx_truthy((symbol_name(next_test) == 'else')) else (symbol_name(next_test) == ':else'))))):
- return make_cek_state(nth(next, 1), fenv, rest_k)
- else:
- return make_cek_state(next_test, fenv, kont_push(make_cond_frame(next, fenv, False), rest_k))
- elif sx_truthy((ft == 'case')):
- match_val = get(frame, 'match-val')
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- if sx_truthy(is_nil(match_val)):
- return sf_case_step_loop(value, remaining, fenv, rest_k)
- else:
- return sf_case_step_loop(match_val, remaining, fenv, rest_k)
- elif sx_truthy((ft == 'thread')):
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(value, fenv, rest_k)
- else:
- form = first(remaining)
- rest_forms = rest(remaining)
- result = ((lambda f: (lambda rargs: (lambda all_args: (apply(f, all_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, all_args, fenv)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(cons(value, rargs)))(map(lambda a: trampoline(eval_expr(a, fenv)), rest(form))))(trampoline(eval_expr(first(form), fenv))) if sx_truthy((type_of(form) == 'list')) else (lambda f: (f(value) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, [value], fenv)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(trampoline(eval_expr(form, fenv))))
- if sx_truthy(empty_p(rest_forms)):
- return make_cek_value(result, fenv, rest_k)
- else:
- return make_cek_value(result, fenv, kont_push(make_thread_frame(rest_forms, fenv), rest_k))
- elif sx_truthy((ft == 'arg')):
- f = get(frame, 'f')
- evaled = get(frame, 'evaled')
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- raw_args = get(frame, 'raw-args')
- if sx_truthy(is_nil(f)):
- if sx_truthy(empty_p(remaining)):
- return continue_with_call(value, [], fenv, raw_args, rest_k)
- else:
- return make_cek_state(first(remaining), fenv, kont_push(make_arg_frame(value, [], rest(remaining), fenv, raw_args), rest_k))
- else:
- new_evaled = append(evaled, [value])
- if sx_truthy(empty_p(remaining)):
- return continue_with_call(f, new_evaled, fenv, raw_args, rest_k)
- else:
- return make_cek_state(first(remaining), fenv, kont_push(make_arg_frame(f, new_evaled, rest(remaining), fenv, raw_args), rest_k))
- elif sx_truthy((ft == 'dict')):
- remaining = get(frame, 'remaining')
- results = get(frame, 'results')
- fenv = get(frame, 'env')
- last_result = last(results)
- completed = append(slice(results, 0, (len(results) - 1)), [[first(last_result), value]])
- if sx_truthy(empty_p(remaining)):
- d = {}
- for pair in completed:
- d[first(pair)] = nth(pair, 1)
- return make_cek_value(d, fenv, rest_k)
- else:
- next_entry = first(remaining)
- return make_cek_state(nth(next_entry, 1), fenv, kont_push(make_dict_frame(rest(remaining), append(completed, [[first(next_entry)]]), fenv), rest_k))
- elif sx_truthy((ft == 'reset')):
- return make_cek_value(value, env, rest_k)
- elif sx_truthy((ft == 'deref')):
- val = value
- fenv = get(frame, 'env')
- if sx_truthy((not sx_truthy(is_signal(val)))):
- return make_cek_value(val, fenv, rest_k)
- else:
- if sx_truthy(has_reactive_reset_frame_p(rest_k)):
- return reactive_shift_deref(val, fenv, rest_k)
- else:
- ctx = sx_context('sx-reactive', NIL)
- if sx_truthy(ctx):
- dep_list = get(ctx, 'deps')
- notify_fn = get(ctx, 'notify')
- if sx_truthy((not sx_truthy(contains_p(dep_list, val)))):
- dep_list.append(val)
- signal_add_sub(val, notify_fn)
- return make_cek_value(signal_value(val), fenv, rest_k)
- elif sx_truthy((ft == 'reactive-reset')):
- update_fn = get(frame, 'update-fn')
- first_p = get(frame, 'first-render')
- if sx_truthy((update_fn if not sx_truthy(update_fn) else (not sx_truthy(first_p)))):
- cek_call(update_fn, [value])
- return make_cek_value(value, env, rest_k)
- elif sx_truthy((ft == 'scope')):
- name = get(frame, 'name')
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- if sx_truthy(empty_p(remaining)):
- scope_pop(name)
- return make_cek_value(value, fenv, rest_k)
- else:
- return make_cek_state(first(remaining), fenv, kont_push(make_scope_frame(name, rest(remaining), fenv), rest_k))
- elif sx_truthy((ft == 'map')):
- f = get(frame, 'f')
- remaining = get(frame, 'remaining')
- results = get(frame, 'results')
- indexed = get(frame, 'indexed')
- fenv = get(frame, 'env')
- new_results = append(results, [value])
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(new_results, fenv, rest_k)
- else:
- call_args = ([len(new_results), first(remaining)] if sx_truthy(indexed) else [first(remaining)])
- next_frame = (make_map_indexed_frame(f, rest(remaining), new_results, fenv) if sx_truthy(indexed) else make_map_frame(f, rest(remaining), new_results, fenv))
- return continue_with_call(f, call_args, fenv, [], kont_push(next_frame, rest_k))
- elif sx_truthy((ft == 'filter')):
- f = get(frame, 'f')
- remaining = get(frame, 'remaining')
- results = get(frame, 'results')
- current_item = get(frame, 'current-item')
- fenv = get(frame, 'env')
- new_results = (append(results, [current_item]) if sx_truthy(value) else results)
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(new_results, fenv, rest_k)
- else:
- return continue_with_call(f, [first(remaining)], fenv, [], kont_push(make_filter_frame(f, rest(remaining), new_results, first(remaining), fenv), rest_k))
- elif sx_truthy((ft == 'reduce')):
- f = get(frame, 'f')
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(value, fenv, rest_k)
- else:
- return continue_with_call(f, [value, first(remaining)], fenv, [], kont_push(make_reduce_frame(f, rest(remaining), fenv), rest_k))
- elif sx_truthy((ft == 'for-each')):
- f = get(frame, 'f')
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(NIL, fenv, rest_k)
- else:
- return continue_with_call(f, [first(remaining)], fenv, [], kont_push(make_for_each_frame(f, rest(remaining), fenv), rest_k))
- elif sx_truthy((ft == 'some')):
- f = get(frame, 'f')
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- if sx_truthy(value):
- return make_cek_value(value, fenv, rest_k)
- else:
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(False, fenv, rest_k)
- else:
- return continue_with_call(f, [first(remaining)], fenv, [], kont_push(make_some_frame(f, rest(remaining), fenv), rest_k))
- elif sx_truthy((ft == 'every')):
- f = get(frame, 'f')
- remaining = get(frame, 'remaining')
- fenv = get(frame, 'env')
- if sx_truthy((not sx_truthy(value))):
- return make_cek_value(False, fenv, rest_k)
- else:
- if sx_truthy(empty_p(remaining)):
- return make_cek_value(True, fenv, rest_k)
- else:
- return continue_with_call(f, [first(remaining)], fenv, [], kont_push(make_every_frame(f, rest(remaining), fenv), rest_k))
- else:
- return error(sx_str('Unknown frame type: ', ft))
-
-# continue-with-call
-def continue_with_call(f, args, env, raw_args, kont):
- if sx_truthy(continuation_p(f)):
- arg = (NIL if sx_truthy(empty_p(args)) else first(args))
- cont_data = continuation_data(f)
- captured = get(cont_data, 'captured')
- rest_k = get(cont_data, 'rest-kont')
- return make_cek_value(arg, env, concat(captured, rest_k))
- elif sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))):
- return make_cek_value(apply(f, args), env, kont)
- elif sx_truthy(is_lambda(f)):
- params = lambda_params(f)
- local = env_merge(lambda_closure(f), env)
- if sx_truthy((len(args) > len(params))):
- return error(sx_str((lambda_name(f) if sx_truthy(lambda_name(f)) else 'lambda'), ' expects ', len(params), ' args, got ', len(args)))
- else:
- for pair in zip(params, args):
- local[first(pair)] = nth(pair, 1)
- for p in slice(params, len(args)):
- local[p] = NIL
- return make_cek_state(lambda_body(f), local, kont)
- elif sx_truthy((is_component(f) if sx_truthy(is_component(f)) else is_island(f))):
- parsed = parse_keyword_args(raw_args, env)
- kwargs = first(parsed)
- children = nth(parsed, 1)
- local = env_merge(component_closure(f), env)
- for p in component_params(f):
- local[p] = (dict_get(kwargs, p) if sx_truthy(dict_get(kwargs, p)) else NIL)
- if sx_truthy(component_has_children(f)):
- local['children'] = children
- return make_cek_state(component_body(f), local, kont)
- else:
- return error(sx_str('Not callable: ', inspect(f)))
-
-# sf-case-step-loop
-def sf_case_step_loop(match_val, clauses, env, kont):
- if sx_truthy((len(clauses) < 2)):
- return make_cek_value(NIL, env, kont)
- else:
- test = first(clauses)
- body = nth(clauses, 1)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))):
- return make_cek_state(body, env, kont)
- else:
- test_val = trampoline(eval_expr(test, env))
- if sx_truthy((match_val == test_val)):
- return make_cek_state(body, env, kont)
- else:
- return sf_case_step_loop(match_val, slice(clauses, 2), env, kont)
-
-# eval-expr-cek
-def eval_expr_cek(expr, env):
- return cek_run(make_cek_state(expr, env, []))
-
-# trampoline-cek
-def trampoline_cek(val):
- if sx_truthy(is_thunk(val)):
- return eval_expr_cek(thunk_expr(val), thunk_env(val))
- else:
- return val
-
-# freeze-registry
-freeze_registry = {}
-
-# freeze-signal
-def freeze_signal(name, sig):
- scope_name = sx_context('sx-freeze-scope', NIL)
- if sx_truthy(scope_name):
- entries = (get(freeze_registry, scope_name) if sx_truthy(get(freeze_registry, scope_name)) else [])
- entries.append({'name': name, 'signal': sig})
- return _sx_dict_set(freeze_registry, scope_name, entries)
- return NIL
-
-# freeze-scope
-def freeze_scope(name, body_fn):
- scope_push('sx-freeze-scope', name)
- freeze_registry[name] = []
- cek_call(body_fn, NIL)
- scope_pop('sx-freeze-scope')
- return NIL
-
-# cek-freeze-scope
-def cek_freeze_scope(name):
- entries = (get(freeze_registry, name) if sx_truthy(get(freeze_registry, name)) else [])
- signals_dict = {}
- for entry in entries:
- signals_dict[get(entry, 'name')] = signal_value(get(entry, 'signal'))
- return {'name': name, 'signals': signals_dict}
-
-# cek-freeze-all
-def cek_freeze_all():
- return map(lambda name: cek_freeze_scope(name), keys(freeze_registry))
-
-# cek-thaw-scope
-def cek_thaw_scope(name, frozen):
- entries = (get(freeze_registry, name) if sx_truthy(get(freeze_registry, name)) else [])
- values = get(frozen, 'signals')
- if sx_truthy(values):
- for entry in entries:
- sig_name = get(entry, 'name')
- sig = get(entry, 'signal')
- val = get(values, sig_name)
- if sx_truthy((not sx_truthy(is_nil(val)))):
- reset_b(sig, val)
- return NIL
- return NIL
-
-# cek-thaw-all
-def cek_thaw_all(frozen_list):
- for frozen in frozen_list:
- cek_thaw_scope(get(frozen, 'name'), frozen)
- return NIL
-
-# freeze-to-sx
-def freeze_to_sx(name):
- return sx_serialize(cek_freeze_scope(name))
-
-# thaw-from-sx
-def thaw_from_sx(sx_text):
- parsed = sx_parse(sx_text)
- if sx_truthy((not sx_truthy(empty_p(parsed)))):
- frozen = first(parsed)
- return cek_thaw_scope(get(frozen, 'name'), frozen)
- return NIL
-
-# content-store
-content_store = {}
-
-# content-hash
-def content_hash(sx_text):
- _cells = {}
- _cells['hash'] = 5381
- for i in range(0, len(sx_text)):
- _cells['hash'] = (((_cells['hash'] * 33) + char_code_at(sx_text, i)) % 4294967296)
- return to_hex(_cells['hash'])
-
-# content-put
-def content_put(sx_text):
- cid = content_hash(sx_text)
- content_store[cid] = sx_text
- return cid
-
-# content-get
-def content_get(cid):
- return get(content_store, cid)
-
-# freeze-to-cid
-def freeze_to_cid(scope_name):
- sx_text = freeze_to_sx(scope_name)
- return content_put(sx_text)
-
-# thaw-from-cid
-def thaw_from_cid(cid):
- sx_text = content_get(cid)
- if sx_truthy(sx_text):
- thaw_from_sx(sx_text)
- return True
- return NIL
-
-
-# === Transpiled from signals (reactive signal runtime) ===
-
-# make-signal
-def make_signal(value):
- return {'__signal': True, 'value': value, 'subscribers': [], 'deps': []}
-
-# signal?
-def is_signal(x):
- return (dict_p(x) if not sx_truthy(dict_p(x)) else has_key_p(x, '__signal'))
-
-# signal-value
-def signal_value(s):
- return get(s, 'value')
-
-# signal-set-value!
-def signal_set_value(s, v):
- return _sx_dict_set(s, 'value', v)
-
-# signal-subscribers
-def signal_subscribers(s):
- return get(s, 'subscribers')
-
-# signal-add-sub!
-def signal_add_sub(s, f):
- if sx_truthy((not sx_truthy(contains_p(get(s, 'subscribers'), f)))):
- return _sx_append(get(s, 'subscribers'), f)
- return NIL
-
-# signal-remove-sub!
-def signal_remove_sub(s, f):
- return _sx_dict_set(s, 'subscribers', filter(lambda sub: (not sx_truthy(is_identical(sub, f))), get(s, 'subscribers')))
-
-# signal-deps
-def signal_deps(s):
- return get(s, 'deps')
-
-# signal-set-deps!
-def signal_set_deps(s, deps):
- return _sx_dict_set(s, 'deps', deps)
-
-# signal
-def signal(initial_value):
- return make_signal(initial_value)
-
-# deref
-def deref(s):
- if sx_truthy((not sx_truthy(is_signal(s)))):
- return s
- else:
- ctx = sx_context('sx-reactive', NIL)
- if sx_truthy(ctx):
- dep_list = get(ctx, 'deps')
- notify_fn = get(ctx, 'notify')
- if sx_truthy((not sx_truthy(contains_p(dep_list, s)))):
- dep_list.append(s)
- signal_add_sub(s, notify_fn)
- return signal_value(s)
-
-# reset!
-def reset_b(s, value):
- if sx_truthy(is_signal(s)):
- old = signal_value(s)
- if sx_truthy((not sx_truthy(is_identical(old, value)))):
- signal_set_value(s, value)
- return notify_subscribers(s)
- return NIL
- return NIL
-
-# swap!
-def swap_b(s, f, *args):
- if sx_truthy(is_signal(s)):
- old = signal_value(s)
- new_val = apply(f, cons(old, args))
- if sx_truthy((not sx_truthy(is_identical(old, new_val)))):
- signal_set_value(s, new_val)
- return notify_subscribers(s)
- return NIL
- return NIL
-
-# computed
-def computed(compute_fn):
- s = make_signal(NIL)
- deps = []
- compute_ctx = NIL
- recompute = _sx_fn(lambda : (
- for_each(lambda dep: signal_remove_sub(dep, recompute), signal_deps(s)),
- signal_set_deps(s, []),
- (lambda ctx: _sx_begin(scope_push('sx-reactive', ctx), (lambda new_val: _sx_begin(scope_pop('sx-reactive'), signal_set_deps(s, get(ctx, 'deps')), (lambda old: _sx_begin(signal_set_value(s, new_val), (notify_subscribers(s) if sx_truthy((not sx_truthy(is_identical(old, new_val)))) else NIL)))(signal_value(s))))(cek_call(compute_fn, NIL))))({'deps': [], 'notify': recompute})
-)[-1])
- recompute()
- register_in_scope(lambda : dispose_computed(s))
- return s
-
-# effect
-def effect(effect_fn):
- _cells = {}
- _cells['deps'] = []
- _cells['disposed'] = False
- _cells['cleanup_fn'] = NIL
- run_effect = lambda : (_sx_begin((cek_call(_cells['cleanup_fn'], NIL) if sx_truthy(_cells['cleanup_fn']) else NIL), for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']), _sx_cell_set(_cells, 'deps', []), (lambda ctx: _sx_begin(scope_push('sx-reactive', ctx), (lambda result: _sx_begin(scope_pop('sx-reactive'), _sx_cell_set(_cells, 'deps', get(ctx, 'deps')), (_sx_cell_set(_cells, 'cleanup_fn', result) if sx_truthy(is_callable(result)) else NIL)))(cek_call(effect_fn, NIL))))({'deps': [], 'notify': run_effect})) if sx_truthy((not sx_truthy(_cells['disposed']))) else NIL)
- run_effect()
- dispose_fn = _sx_fn(lambda : (
- _sx_cell_set(_cells, 'disposed', True),
- (cek_call(_cells['cleanup_fn'], NIL) if sx_truthy(_cells['cleanup_fn']) else NIL),
- for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']),
- _sx_cell_set(_cells, 'deps', [])
-)[-1])
- register_in_scope(dispose_fn)
- return dispose_fn
-
-# *batch-depth*
-_batch_depth = 0
-
-# *batch-queue*
-_batch_queue = []
-
-# batch
-def batch(thunk):
- _batch_depth = (_batch_depth + 1)
- cek_call(thunk, NIL)
- _batch_depth = (_batch_depth - 1)
- if sx_truthy((_batch_depth == 0)):
- queue = _batch_queue
- _batch_queue = []
- seen = []
- pending = []
- for s in queue:
- for sub in signal_subscribers(s):
- if sx_truthy((not sx_truthy(contains_p(seen, sub)))):
- seen.append(sub)
- pending.append(sub)
- for sub in pending:
- sub()
- return NIL
- return NIL
-
-# notify-subscribers
-def notify_subscribers(s):
- if sx_truthy((_batch_depth > 0)):
- if sx_truthy((not sx_truthy(contains_p(_batch_queue, s)))):
- return _sx_append(_batch_queue, s)
- return NIL
- else:
- return flush_subscribers(s)
-
-# flush-subscribers
-def flush_subscribers(s):
- for sub in signal_subscribers(s):
- sub()
- return NIL
-
-# dispose-computed
-def dispose_computed(s):
- if sx_truthy(is_signal(s)):
- for dep in signal_deps(s):
- signal_remove_sub(dep, NIL)
- return signal_set_deps(s, [])
- return NIL
-
-# with-island-scope
-def with_island_scope(scope_fn, body_fn):
- scope_push('sx-island-scope', scope_fn)
- result = body_fn()
- scope_pop('sx-island-scope')
- return result
-
-# register-in-scope
-def register_in_scope(disposable):
- collector = sx_context('sx-island-scope', NIL)
- if sx_truthy(collector):
- return cek_call(collector, [disposable])
- return NIL
-
-# with-marsh-scope
-def with_marsh_scope(marsh_el, body_fn):
- disposers = []
- with_island_scope(lambda d: _sx_append(disposers, d), body_fn)
- return dom_set_data(marsh_el, 'sx-marsh-disposers', disposers)
-
-# dispose-marsh-scope
-def dispose_marsh_scope(marsh_el):
- disposers = dom_get_data(marsh_el, 'sx-marsh-disposers')
- if sx_truthy(disposers):
- for d in disposers:
- cek_call(d, NIL)
- return dom_set_data(marsh_el, 'sx-marsh-disposers', NIL)
- return NIL
-
-# *store-registry*
-_store_registry = {}
-
-# def-store
-def def_store(name, init_fn):
- registry = _store_registry
- if sx_truthy((not sx_truthy(has_key_p(registry, name)))):
- _store_registry = assoc(registry, name, cek_call(init_fn, NIL))
- return get(_store_registry, name)
-
-# use-store
-def use_store(name):
- if sx_truthy(has_key_p(_store_registry, name)):
- return get(_store_registry, name)
- else:
- return error(sx_str('Store not found: ', name, '. Call (def-store ...) before (use-store ...).'))
-
-# clear-stores
-def clear_stores():
- return _sx_cell_set(_cells, '_store_registry', {})
-
-# emit-event
-def emit_event(el, event_name, detail):
- return dom_dispatch(el, event_name, detail)
-
-# on-event
-def on_event(el, event_name, handler):
- return dom_listen(el, event_name, handler)
-
-# bridge-event
-def bridge_event(el, event_name, target_signal, transform_fn):
- return effect(lambda : (lambda remove: remove)(dom_listen(el, event_name, lambda e: (lambda detail: (lambda new_val: reset_b(target_signal, new_val))((cek_call(transform_fn, [detail]) if sx_truthy(transform_fn) else detail)))(event_detail(e)))))
-
-# resource
-def resource(fetch_fn):
- state = signal({'loading': True, 'data': NIL, 'error': NIL})
- promise_then(cek_call(fetch_fn, NIL), lambda data: reset_b(state, {'loading': False, 'data': data, 'error': NIL}), lambda err: reset_b(state, {'loading': False, 'data': NIL, 'error': err}))
- return state
-
-
-# === Transpiled from adapter-async ===
-
-# async-render
-async def async_render(expr, env, ctx):
- _match = type_of(expr)
- if _match == 'nil':
- return ''
- elif _match == 'boolean':
- return ''
- elif _match == 'string':
- return escape_html(expr)
- elif _match == 'number':
- return escape_html(sx_str(expr))
- elif _match == 'raw-html':
- return raw_html_content(expr)
- elif _match == 'spread':
- sx_emit('element-attrs', spread_attrs(expr))
- return ''
- elif _match == 'symbol':
- val = (await async_eval(expr, env, ctx))
- return (await async_render(val, env, ctx))
- elif _match == 'keyword':
- return escape_html(keyword_name(expr))
- elif _match == 'list':
- if sx_truthy(empty_p(expr)):
- return ''
- else:
- return (await async_render_list(expr, env, ctx))
- elif _match == 'dict':
- return ''
- else:
- return escape_html(sx_str(expr))
-
-# async-render-list
-async def async_render_list(expr, env, ctx):
- head = first(expr)
- if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
- if sx_truthy((is_lambda(head) if sx_truthy(is_lambda(head)) else (type_of(head) == 'list'))):
- return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
- else:
- return join('', (await async_map_render(expr, env, ctx)))
- else:
- name = symbol_name(head)
- args = rest(expr)
- if sx_truthy(io_primitive_p(name)):
- return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
- elif sx_truthy((name == 'raw!')):
- return (await async_render_raw(args, env, ctx))
- elif sx_truthy((name == '<>')):
- return join('', (await async_map_render(args, env, ctx)))
- elif sx_truthy(starts_with_p(name, 'html:')):
- return (await async_render_element(slice(name, 5), args, env, ctx))
- elif sx_truthy(async_render_form_p(name)):
- if sx_truthy((contains_p(HTML_TAGS, name) if not sx_truthy(contains_p(HTML_TAGS, name)) else (((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')) if sx_truthy(((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword'))) else svg_context_p()))):
- return (await async_render_element(name, args, env, ctx))
- else:
- return (await dispatch_async_render_form(name, expr, env, ctx))
- elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
- return (await async_render(trampoline(expand_macro(env_get(env, name), args, env)), env, ctx))
- elif sx_truthy(contains_p(HTML_TAGS, name)):
- return (await async_render_element(name, args, env, ctx))
- elif sx_truthy((starts_with_p(name, '~') if not sx_truthy(starts_with_p(name, '~')) else (env_has(env, name) if not sx_truthy(env_has(env, name)) else is_island(env_get(env, name))))):
- return (await async_render_island(env_get(env, name), args, env, ctx))
- elif sx_truthy(starts_with_p(name, '~')):
- val = (env_get(env, name) if sx_truthy(env_has(env, name)) else NIL)
- if sx_truthy(is_component(val)):
- return (await async_render_component(val, args, env, ctx))
- elif sx_truthy(is_macro(val)):
- return (await async_render(trampoline(expand_macro(val, args, env)), env, ctx))
- else:
- return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
- elif sx_truthy(((index_of(name, '-') > 0) if not sx_truthy((index_of(name, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')))):
- return (await async_render_element(name, args, env, ctx))
- elif sx_truthy(svg_context_p()):
- return (await async_render_element(name, args, env, ctx))
- else:
- return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
-
-# async-render-raw
-async def async_render_raw(args, env, ctx):
- parts = []
- for arg in args:
- val = (await async_eval(arg, env, ctx))
- if sx_truthy(is_raw_html(val)):
- parts.append(raw_html_content(val))
- elif sx_truthy((type_of(val) == 'string')):
- parts.append(val)
- elif sx_truthy(((not sx_truthy(is_nil(val))) if not sx_truthy((not sx_truthy(is_nil(val)))) else (not sx_truthy((val == False))))):
- parts.append(sx_str(val))
- return join('', parts)
-
-# async-render-element
-async def async_render_element(tag, args, env, ctx):
- attrs = {}
- children = []
- (await async_parse_element_args(args, attrs, children, env, ctx))
- class_val = dict_get(attrs, 'class')
- if sx_truthy(((not sx_truthy(is_nil(class_val))) if not sx_truthy((not sx_truthy(is_nil(class_val)))) else (not sx_truthy((class_val == False))))):
- css_class_collect(sx_str(class_val))
- if sx_truthy(contains_p(VOID_ELEMENTS, tag)):
- return sx_str('<', tag, render_attrs(attrs), '>')
- else:
- token = (svg_context_set(True) if sx_truthy(((tag == 'svg') if sx_truthy((tag == 'svg')) else (tag == 'math'))) else NIL)
- content_parts = []
- scope_push('element-attrs', NIL)
- for c in children:
- content_parts.append((await async_render(c, env, ctx)))
- for spread_dict in sx_emitted('element-attrs'):
- merge_spread_attrs(attrs, spread_dict)
- scope_pop('element-attrs')
- if sx_truthy(token):
- svg_context_reset(token)
- return sx_str('<', tag, render_attrs(attrs), '>', join('', content_parts), '', tag, '>')
-
-# async-parse-element-args
-async def async_parse_element_args(args, attrs, children, env, ctx):
- _cells = {}
- _cells['skip'] = False
- _cells['i'] = 0
- for arg in args:
- if sx_truthy(_cells['skip']):
- _cells['skip'] = False
- _cells['i'] = (_cells['i'] + 1)
- else:
- if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
- val = (await async_eval(nth(args, (_cells['i'] + 1)), env, ctx))
- attrs[keyword_name(arg)] = val
- _cells['skip'] = True
- _cells['i'] = (_cells['i'] + 1)
- else:
- children.append(arg)
- _cells['i'] = (_cells['i'] + 1)
- return NIL
-
-# async-render-component
-async def async_render_component(comp, args, env, ctx):
- kwargs = {}
- children = []
- (await async_parse_kw_args(args, kwargs, children, env, ctx))
- local = env_merge(component_closure(comp), env)
- for p in component_params(comp):
- local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
- if sx_truthy(component_has_children(comp)):
- parts = []
- for c in children:
- parts.append((await async_render(c, env, ctx)))
- local['children'] = make_raw_html(join('', parts))
- return (await async_render(component_body(comp), local, ctx))
-
-# async-render-island
-async def async_render_island(island, args, env, ctx):
- kwargs = {}
- children = []
- (await async_parse_kw_args(args, kwargs, children, env, ctx))
- local = env_merge(component_closure(island), env)
- island_name = component_name(island)
- for p in component_params(island):
- local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
- if sx_truthy(component_has_children(island)):
- parts = []
- for c in children:
- parts.append((await async_render(c, env, ctx)))
- local['children'] = make_raw_html(join('', parts))
- body_html = (await async_render(component_body(island), local, ctx))
- state_json = serialize_island_state(kwargs)
- return sx_str('', body_html, '')
-
-# async-render-lambda
-async def async_render_lambda(f, args, env, ctx):
- local = env_merge(lambda_closure(f), env)
- for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f))
- return (await async_render(lambda_body(f), local, ctx))
-
-# async-parse-kw-args
-async def async_parse_kw_args(args, kwargs, children, env, ctx):
- _cells = {}
- _cells['skip'] = False
- _cells['i'] = 0
- for arg in args:
- if sx_truthy(_cells['skip']):
- _cells['skip'] = False
- _cells['i'] = (_cells['i'] + 1)
- else:
- if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
- val = (await async_eval(nth(args, (_cells['i'] + 1)), env, ctx))
- kwargs[keyword_name(arg)] = val
- _cells['skip'] = True
- _cells['i'] = (_cells['i'] + 1)
- else:
- children.append(arg)
- _cells['i'] = (_cells['i'] + 1)
- return NIL
-
-# async-map-render
-async def async_map_render(exprs, env, ctx):
- results = []
- for x in exprs:
- results.append((await async_render(x, env, ctx)))
- return results
-
-# ASYNC_RENDER_FORMS
-ASYNC_RENDER_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each', 'scope', 'provide']
-
-# async-render-form?
-def async_render_form_p(name):
- return contains_p(ASYNC_RENDER_FORMS, name)
-
-# dispatch-async-render-form
-async def dispatch_async_render_form(name, expr, env, ctx):
- if sx_truthy((name == 'if')):
- cond_val = (await async_eval(nth(expr, 1), env, ctx))
- if sx_truthy(cond_val):
- return (await async_render(nth(expr, 2), env, ctx))
- else:
- if sx_truthy((len(expr) > 3)):
- return (await async_render(nth(expr, 3), env, ctx))
- else:
- return ''
- elif sx_truthy((name == 'when')):
- if sx_truthy((not sx_truthy((await async_eval(nth(expr, 1), env, ctx))))):
- return ''
- else:
- if sx_truthy((len(expr) == 3)):
- return (await async_render(nth(expr, 2), env, ctx))
- else:
- return join('', (await async_map_render(slice(expr, 2), env, ctx)))
- elif sx_truthy((name == 'cond')):
- clauses = rest(expr)
- if sx_truthy(cond_scheme_p(clauses)):
- return (await async_render_cond_scheme(clauses, env, ctx))
- else:
- return (await async_render_cond_clojure(clauses, env, ctx))
- elif sx_truthy((name == 'case')):
- return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
- elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
- local = (await async_process_bindings(nth(expr, 1), env, ctx))
- if sx_truthy((len(expr) == 3)):
- return (await async_render(nth(expr, 2), local, ctx))
- else:
- return join('', (await async_map_render(slice(expr, 2), local, ctx)))
- elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
- if sx_truthy((len(expr) == 2)):
- return (await async_render(nth(expr, 1), env, ctx))
- else:
- return join('', (await async_map_render(rest(expr), env, ctx)))
- elif sx_truthy(is_definition_form(name)):
- (await async_eval(expr, env, ctx))
- return ''
- elif sx_truthy((name == 'map')):
- f = (await async_eval(nth(expr, 1), env, ctx))
- coll = (await async_eval(nth(expr, 2), env, ctx))
- return join('', (await async_map_fn_render(f, coll, env, ctx)))
- elif sx_truthy((name == 'map-indexed')):
- f = (await async_eval(nth(expr, 1), env, ctx))
- coll = (await async_eval(nth(expr, 2), env, ctx))
- return join('', (await async_map_indexed_fn_render(f, coll, env, ctx)))
- elif sx_truthy((name == 'filter')):
- return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
- elif sx_truthy((name == 'for-each')):
- f = (await async_eval(nth(expr, 1), env, ctx))
- coll = (await async_eval(nth(expr, 2), env, ctx))
- return join('', (await async_map_fn_render(f, coll, env, ctx)))
- elif sx_truthy((name == 'scope')):
- scope_name = (await async_eval(nth(expr, 1), env, ctx))
- rest_args = slice(expr, 2)
- scope_val = NIL
- body_exprs = NIL
- if sx_truthy(((len(rest_args) >= 2) if not sx_truthy((len(rest_args) >= 2)) else ((type_of(first(rest_args)) == 'keyword') if not sx_truthy((type_of(first(rest_args)) == 'keyword')) else (keyword_name(first(rest_args)) == 'value')))):
- scope_val = (await async_eval(nth(rest_args, 1), env, ctx))
- body_exprs = slice(rest_args, 2)
- else:
- body_exprs = rest_args
- scope_push(scope_name, scope_val)
- result = ((await async_render(first(body_exprs), env, ctx)) if sx_truthy((len(body_exprs) == 1)) else join('', (await async_map_render(body_exprs, env, ctx))))
- scope_pop(scope_name)
- return result
- elif sx_truthy((name == 'provide')):
- prov_name = (await async_eval(nth(expr, 1), env, ctx))
- prov_val = (await async_eval(nth(expr, 2), env, ctx))
- body_start = 3
- body_count = (len(expr) - 3)
- scope_push(prov_name, prov_val)
- result = ((await async_render(nth(expr, body_start), env, ctx)) if sx_truthy((body_count == 1)) else join('', (await async_map_render(slice(expr, body_start), env, ctx))))
- scope_pop(prov_name)
- return result
- else:
- return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
-
-# async-render-cond-scheme
-async def async_render_cond_scheme(clauses, env, ctx):
- if sx_truthy(empty_p(clauses)):
- return ''
- else:
- clause = first(clauses)
- test = first(clause)
- body = nth(clause, 1)
- if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))):
- return (await async_render(body, env, ctx))
- else:
- if sx_truthy((await async_eval(test, env, ctx))):
- return (await async_render(body, env, ctx))
- else:
- return (await async_render_cond_scheme(rest(clauses), env, ctx))
-
-# async-render-cond-clojure
-async def async_render_cond_clojure(clauses, env, ctx):
- if sx_truthy((len(clauses) < 2)):
- return ''
- else:
- test = first(clauses)
- body = nth(clauses, 1)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))):
- return (await async_render(body, env, ctx))
- else:
- if sx_truthy((await async_eval(test, env, ctx))):
- return (await async_render(body, env, ctx))
- else:
- return (await async_render_cond_clojure(slice(clauses, 2), env, ctx))
-
-# async-process-bindings
-async def async_process_bindings(bindings, env, ctx):
- local = env_extend(env)
- if sx_truthy(((type_of(bindings) == 'list') if not sx_truthy((type_of(bindings) == 'list')) else (not sx_truthy(empty_p(bindings))))):
- if sx_truthy((type_of(first(bindings)) == 'list')):
- for pair in bindings:
- if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))):
- name = (symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair)))
- local[name] = (await async_eval(nth(pair, 1), local, ctx))
- else:
- (await async_process_bindings_flat(bindings, local, ctx))
- return local
-
-# async-process-bindings-flat
-async def async_process_bindings_flat(bindings, local, ctx):
- _cells = {}
- _cells['skip'] = False
- _cells['i'] = 0
- for item in bindings:
- if sx_truthy(_cells['skip']):
- _cells['skip'] = False
- _cells['i'] = (_cells['i'] + 1)
- else:
- name = (symbol_name(item) if sx_truthy((type_of(item) == 'symbol')) else sx_str(item))
- if sx_truthy(((_cells['i'] + 1) < len(bindings))):
- local[name] = (await async_eval(nth(bindings, (_cells['i'] + 1)), local, ctx))
- _cells['skip'] = True
- _cells['i'] = (_cells['i'] + 1)
- return NIL
-
-# async-map-fn-render
-async def async_map_fn_render(f, coll, env, ctx):
- results = []
- for item in coll:
- if sx_truthy(is_lambda(f)):
- results.append((await async_render_lambda(f, [item], env, ctx)))
- else:
- r = (await async_invoke(f, item))
- results.append((await async_render(r, env, ctx)))
- return results
-
-# async-map-indexed-fn-render
-async def async_map_indexed_fn_render(f, coll, env, ctx):
- _cells = {}
- results = []
- _cells['i'] = 0
- for item in coll:
- if sx_truthy(is_lambda(f)):
- results.append((await async_render_lambda(f, [_cells['i'], item], env, ctx)))
- else:
- r = (await async_invoke(f, _cells['i'], item))
- results.append((await async_render(r, env, ctx)))
- _cells['i'] = (_cells['i'] + 1)
- return results
-
-# async-invoke
-async def async_invoke(f, *args):
- r = apply(f, args)
- if sx_truthy(is_async_coroutine(r)):
- return (await async_await(r))
- else:
- return r
-
-# async-aser
-async def async_aser(expr, env, ctx):
- t = type_of(expr)
- result = NIL
- if sx_truthy((t == 'number')):
- result = expr
- elif sx_truthy((t == 'string')):
- result = expr
- elif sx_truthy((t == 'boolean')):
- result = expr
- elif sx_truthy((t == 'nil')):
- result = NIL
- elif sx_truthy((t == 'symbol')):
- name = symbol_name(expr)
- result = (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name)))))))
- elif sx_truthy((t == 'keyword')):
- result = keyword_name(expr)
- elif sx_truthy((t == 'dict')):
- result = (await async_aser_dict(expr, env, ctx))
- elif sx_truthy((t == 'spread')):
- sx_emit('element-attrs', spread_attrs(expr))
- result = NIL
- elif sx_truthy((t == 'list')):
- result = ([] if sx_truthy(empty_p(expr)) else (await async_aser_list(expr, env, ctx)))
- else:
- result = expr
- if sx_truthy(is_spread(result)):
- sx_emit('element-attrs', spread_attrs(result))
- return NIL
- else:
- return result
-
-# async-aser-dict
-async def async_aser_dict(expr, env, ctx):
- result = {}
- for key in keys(expr):
- result[key] = (await async_aser(dict_get(expr, key), env, ctx))
- return result
-
-# async-aser-list
-async def async_aser_list(expr, env, ctx):
- head = first(expr)
- args = rest(expr)
- if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
- if sx_truthy((is_lambda(head) if sx_truthy(is_lambda(head)) else (type_of(head) == 'list'))):
- return (await async_aser_eval_call(head, args, env, ctx))
- else:
- return (await async_aser_map_list(expr, env, ctx))
- else:
- name = symbol_name(head)
- if sx_truthy(io_primitive_p(name)):
- return (await async_eval(expr, env, ctx))
- elif sx_truthy((name == '<>')):
- return (await async_aser_fragment(args, env, ctx))
- elif sx_truthy((name == 'raw!')):
- return (await async_aser_call('raw!', args, env, ctx))
- elif sx_truthy(starts_with_p(name, 'html:')):
- return (await async_aser_call(slice(name, 5), args, env, ctx))
- elif sx_truthy(starts_with_p(name, '~')):
- val = (env_get(env, name) if sx_truthy(env_has(env, name)) else NIL)
- if sx_truthy(is_macro(val)):
- return (await async_aser(trampoline(expand_macro(val, args, env)), env, ctx))
- elif sx_truthy((is_component(val) if not sx_truthy(is_component(val)) else (expand_components_p() if sx_truthy(expand_components_p()) else (component_affinity(val) == 'server')))):
- return (await async_aser_component(val, args, env, ctx))
- else:
- return (await async_aser_call(name, args, env, ctx))
- elif sx_truthy(async_aser_form_p(name)):
- if sx_truthy((contains_p(HTML_TAGS, name) if not sx_truthy(contains_p(HTML_TAGS, name)) else (((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')) if sx_truthy(((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword'))) else svg_context_p()))):
- return (await async_aser_call(name, args, env, ctx))
- else:
- return (await dispatch_async_aser_form(name, expr, env, ctx))
- elif sx_truthy(contains_p(HTML_TAGS, name)):
- return (await async_aser_call(name, args, env, ctx))
- elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
- return (await async_aser(trampoline(expand_macro(env_get(env, name), args, env)), env, ctx))
- elif sx_truthy(((index_of(name, '-') > 0) if not sx_truthy((index_of(name, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')))):
- return (await async_aser_call(name, args, env, ctx))
- elif sx_truthy(svg_context_p()):
- return (await async_aser_call(name, args, env, ctx))
- else:
- return (await async_aser_eval_call(head, args, env, ctx))
-
-# async-aser-eval-call
-async def async_aser_eval_call(head, args, env, ctx):
- f = (await async_eval(head, env, ctx))
- evaled_args = (await async_eval_args(args, env, ctx))
- if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else (not sx_truthy(is_component(f)))))):
- r = apply(f, evaled_args)
- if sx_truthy(is_async_coroutine(r)):
- return (await async_await(r))
- else:
- return r
- elif sx_truthy(is_lambda(f)):
- local = env_merge(lambda_closure(f), env)
- for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(evaled_args, i)), lambda_params(f))
- return (await async_aser(lambda_body(f), local, ctx))
- elif sx_truthy(is_component(f)):
- return (await async_aser_call(sx_str('~', component_name(f)), args, env, ctx))
- elif sx_truthy(is_island(f)):
- return (await async_aser_call(sx_str('~', component_name(f)), args, env, ctx))
- else:
- return error(sx_str('Not callable: ', inspect(f)))
-
-# async-eval-args
-async def async_eval_args(args, env, ctx):
- results = []
- for a in args:
- results.append((await async_eval(a, env, ctx)))
- return results
-
-# async-aser-map-list
-async def async_aser_map_list(exprs, env, ctx):
- results = []
- for x in exprs:
- results.append((await async_aser(x, env, ctx)))
- return results
-
-# async-aser-fragment
-async def async_aser_fragment(children, env, ctx):
- parts = []
- for c in children:
- result = (await async_aser(c, env, ctx))
- if sx_truthy((type_of(result) == 'list')):
- for item in result:
- if sx_truthy((not sx_truthy(is_nil(item)))):
- parts.append(serialize(item))
- else:
- if sx_truthy((not sx_truthy(is_nil(result)))):
- parts.append(serialize(result))
- if sx_truthy(empty_p(parts)):
- return make_sx_expr('')
- else:
- return make_sx_expr(sx_str('(<> ', join(' ', parts), ')'))
-
-# async-aser-component
-async def async_aser_component(comp, args, env, ctx):
- kwargs = {}
- children = []
- (await async_parse_aser_kw_args(args, kwargs, children, env, ctx))
- local = env_merge(component_closure(comp), env)
- for p in component_params(comp):
- local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
- if sx_truthy(component_has_children(comp)):
- child_parts = []
- for c in children:
- result = (await async_aser(c, env, ctx))
- if sx_truthy(list_p(result)):
- for item in result:
- if sx_truthy((not sx_truthy(is_nil(item)))):
- child_parts.append(serialize(item))
- else:
- if sx_truthy((not sx_truthy(is_nil(result)))):
- child_parts.append(serialize(result))
- local['children'] = make_sx_expr(sx_str('(<> ', join(' ', child_parts), ')'))
- return (await async_aser(component_body(comp), local, ctx))
-
-# async-parse-aser-kw-args
-async def async_parse_aser_kw_args(args, kwargs, children, env, ctx):
- _cells = {}
- _cells['skip'] = False
- _cells['i'] = 0
- for arg in args:
- if sx_truthy(_cells['skip']):
- _cells['skip'] = False
- _cells['i'] = (_cells['i'] + 1)
- else:
- if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
- val = (await async_aser(nth(args, (_cells['i'] + 1)), env, ctx))
- kwargs[keyword_name(arg)] = val
- _cells['skip'] = True
- _cells['i'] = (_cells['i'] + 1)
- else:
- children.append(arg)
- _cells['i'] = (_cells['i'] + 1)
- return NIL
-
-# async-aser-call
-async def async_aser_call(name, args, env, ctx):
- _cells = {}
- token = (svg_context_set(True) if sx_truthy(((name == 'svg') if sx_truthy((name == 'svg')) else (name == 'math'))) else NIL)
- attr_parts = []
- child_parts = []
- _cells['skip'] = False
- _cells['i'] = 0
- scope_push('element-attrs', NIL)
- for arg in args:
- if sx_truthy(_cells['skip']):
- _cells['skip'] = False
- _cells['i'] = (_cells['i'] + 1)
- else:
- if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
- val = (await async_aser(nth(args, (_cells['i'] + 1)), env, ctx))
- if sx_truthy((not sx_truthy(is_nil(val)))):
- attr_parts.append(sx_str(':', keyword_name(arg)))
- if sx_truthy((type_of(val) == 'list')):
- live = filter(lambda v: (not sx_truthy(is_nil(v))), val)
- if sx_truthy(empty_p(live)):
- attr_parts.append('nil')
- else:
- items = map(serialize, live)
- if sx_truthy(some(lambda v: is_sx_expr(v), live)):
- attr_parts.append(sx_str('(<> ', join(' ', items), ')'))
- else:
- attr_parts.append(sx_str('(list ', join(' ', items), ')'))
- else:
- attr_parts.append(serialize(val))
- _cells['skip'] = True
- _cells['i'] = (_cells['i'] + 1)
- else:
- result = (await async_aser(arg, env, ctx))
- if sx_truthy((not sx_truthy(is_nil(result)))):
- if sx_truthy((type_of(result) == 'list')):
- for item in result:
- if sx_truthy((not sx_truthy(is_nil(item)))):
- child_parts.append(serialize(item))
- else:
- child_parts.append(serialize(result))
- _cells['i'] = (_cells['i'] + 1)
- for spread_dict in sx_emitted('element-attrs'):
- for k in keys(spread_dict):
- v = dict_get(spread_dict, k)
- attr_parts.append(sx_str(':', k))
- attr_parts.append(serialize(v))
- scope_pop('element-attrs')
- if sx_truthy(token):
- svg_context_reset(token)
- parts = concat([name], attr_parts, child_parts)
- return make_sx_expr(sx_str('(', join(' ', parts), ')'))
-
-# ASYNC_ASER_FORM_NAMES
-ASYNC_ASER_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'begin', 'do', 'quote', '->', 'set!', 'defisland', 'deftype', 'defeffect', 'scope', 'provide']
-
-# ASYNC_ASER_HO_NAMES
-ASYNC_ASER_HO_NAMES = ['map', 'map-indexed', 'filter', 'for-each']
-
-# async-aser-form?
-def async_aser_form_p(name):
- return (contains_p(ASYNC_ASER_FORM_NAMES, name) if sx_truthy(contains_p(ASYNC_ASER_FORM_NAMES, name)) else contains_p(ASYNC_ASER_HO_NAMES, name))
-
-# dispatch-async-aser-form
-async def dispatch_async_aser_form(name, expr, env, ctx):
- _cells = {}
- args = rest(expr)
- if sx_truthy((name == 'if')):
- cond_val = (await async_eval(first(args), env, ctx))
- if sx_truthy(cond_val):
- return (await async_aser(nth(args, 1), env, ctx))
- else:
- if sx_truthy((len(args) > 2)):
- return (await async_aser(nth(args, 2), env, ctx))
- else:
- return NIL
- elif sx_truthy((name == 'when')):
- if sx_truthy((not sx_truthy((await async_eval(first(args), env, ctx))))):
- return NIL
- else:
- _cells['result'] = NIL
- for body in rest(args):
- _cells['result'] = (await async_aser(body, env, ctx))
- return _cells['result']
- elif sx_truthy((name == 'cond')):
- if sx_truthy(cond_scheme_p(args)):
- return (await async_aser_cond_scheme(args, env, ctx))
- else:
- return (await async_aser_cond_clojure(args, env, ctx))
- elif sx_truthy((name == 'case')):
- match_val = (await async_eval(first(args), env, ctx))
- return (await async_aser_case_loop(match_val, rest(args), env, ctx))
- elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
- local = (await async_process_bindings(first(args), env, ctx))
- _cells['result'] = NIL
- for body in rest(args):
- _cells['result'] = (await async_aser(body, local, ctx))
- return _cells['result']
- elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
- _cells['result'] = NIL
- for body in args:
- _cells['result'] = (await async_aser(body, env, ctx))
- return _cells['result']
- elif sx_truthy((name == 'and')):
- _cells['result'] = True
- _cells['stop'] = False
- for arg in args:
- if sx_truthy((not sx_truthy(_cells['stop']))):
- _cells['result'] = (await async_eval(arg, env, ctx))
- if sx_truthy((not sx_truthy(_cells['result']))):
- _cells['stop'] = True
- return _cells['result']
- elif sx_truthy((name == 'or')):
- _cells['result'] = False
- _cells['stop'] = False
- for arg in args:
- if sx_truthy((not sx_truthy(_cells['stop']))):
- _cells['result'] = (await async_eval(arg, env, ctx))
- if sx_truthy(_cells['result']):
- _cells['stop'] = True
- return _cells['result']
- elif sx_truthy(((name == 'lambda') if sx_truthy((name == 'lambda')) else (name == 'fn'))):
- return sf_lambda(args, env)
- elif sx_truthy((name == 'quote')):
- if sx_truthy(empty_p(args)):
- return NIL
- else:
- return first(args)
- elif sx_truthy((name == '->')):
- return (await async_aser_thread_first(args, env, ctx))
- elif sx_truthy((name == 'set!')):
- value = (await async_eval(nth(args, 1), env, ctx))
- env[symbol_name(first(args))] = value
- return value
- elif sx_truthy((name == 'map')):
- return (await async_aser_ho_map(args, env, ctx))
- elif sx_truthy((name == 'map-indexed')):
- return (await async_aser_ho_map_indexed(args, env, ctx))
- elif sx_truthy((name == 'filter')):
- return (await async_eval(expr, env, ctx))
- elif sx_truthy((name == 'for-each')):
- return (await async_aser_ho_for_each(args, env, ctx))
- elif sx_truthy((name == 'defisland')):
- (await async_eval(expr, env, ctx))
- return serialize(expr)
- elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))))):
- (await async_eval(expr, env, ctx))
- return NIL
- elif sx_truthy((name == 'scope')):
- scope_name = (await async_eval(first(args), env, ctx))
- rest_args = rest(args)
- scope_val = NIL
- body_args = NIL
- if sx_truthy(((len(rest_args) >= 2) if not sx_truthy((len(rest_args) >= 2)) else ((type_of(first(rest_args)) == 'keyword') if not sx_truthy((type_of(first(rest_args)) == 'keyword')) else (keyword_name(first(rest_args)) == 'value')))):
- scope_val = (await async_eval(nth(rest_args, 1), env, ctx))
- body_args = slice(rest_args, 2)
- else:
- body_args = rest_args
- scope_push(scope_name, scope_val)
- _cells['result'] = NIL
- for body in body_args:
- _cells['result'] = (await async_aser(body, env, ctx))
- scope_pop(scope_name)
- return _cells['result']
- elif sx_truthy((name == 'provide')):
- prov_name = (await async_eval(first(args), env, ctx))
- prov_val = (await async_eval(nth(args, 1), env, ctx))
- _cells['result'] = NIL
- scope_push(prov_name, prov_val)
- for body in slice(args, 2):
- _cells['result'] = (await async_aser(body, env, ctx))
- scope_pop(prov_name)
- return _cells['result']
- else:
- return (await async_eval(expr, env, ctx))
-
-# async-aser-cond-scheme
-async def async_aser_cond_scheme(clauses, env, ctx):
- if sx_truthy(empty_p(clauses)):
- return NIL
- else:
- clause = first(clauses)
- test = first(clause)
- body = nth(clause, 1)
- if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))):
- return (await async_aser(body, env, ctx))
- else:
- if sx_truthy((await async_eval(test, env, ctx))):
- return (await async_aser(body, env, ctx))
- else:
- return (await async_aser_cond_scheme(rest(clauses), env, ctx))
-
-# async-aser-cond-clojure
-async def async_aser_cond_clojure(clauses, env, ctx):
- if sx_truthy((len(clauses) < 2)):
- return NIL
- else:
- test = first(clauses)
- body = nth(clauses, 1)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))):
- return (await async_aser(body, env, ctx))
- else:
- if sx_truthy((await async_eval(test, env, ctx))):
- return (await async_aser(body, env, ctx))
- else:
- return (await async_aser_cond_clojure(slice(clauses, 2), env, ctx))
-
-# async-aser-case-loop
-async def async_aser_case_loop(match_val, clauses, env, ctx):
- if sx_truthy((len(clauses) < 2)):
- return NIL
- else:
- test = first(clauses)
- body = nth(clauses, 1)
- if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == ':else') if sx_truthy((symbol_name(test) == ':else')) else (symbol_name(test) == 'else'))))):
- return (await async_aser(body, env, ctx))
- else:
- if sx_truthy((match_val == (await async_eval(test, env, ctx)))):
- return (await async_aser(body, env, ctx))
- else:
- return (await async_aser_case_loop(match_val, slice(clauses, 2), env, ctx))
-
-# async-aser-thread-first
-async def async_aser_thread_first(args, env, ctx):
- _cells = {}
- _cells['result'] = (await async_eval(first(args), env, ctx))
- for form in rest(args):
- if sx_truthy((type_of(form) == 'list')):
- f = (await async_eval(first(form), env, ctx))
- fn_args = cons(_cells['result'], (await async_eval_args(rest(form), env, ctx)))
- _cells['result'] = (await async_invoke_or_lambda(f, fn_args, env, ctx))
- else:
- f = (await async_eval(form, env, ctx))
- _cells['result'] = (await async_invoke_or_lambda(f, [_cells['result']], env, ctx))
- return _cells['result']
-
-# async-invoke-or-lambda
-async def async_invoke_or_lambda(f, args, env, ctx):
- if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else (not sx_truthy(is_component(f)))))):
- r = apply(f, args)
- if sx_truthy(is_async_coroutine(r)):
- return (await async_await(r))
- else:
- return r
- elif sx_truthy(is_lambda(f)):
- local = env_merge(lambda_closure(f), env)
- for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f))
- return (await async_eval(lambda_body(f), local, ctx))
- else:
- return error(sx_str('-> form not callable: ', inspect(f)))
-
-# async-aser-ho-map
-async def async_aser_ho_map(args, env, ctx):
- f = (await async_eval(first(args), env, ctx))
- coll = (await async_eval(nth(args, 1), env, ctx))
- results = []
- for item in coll:
- if sx_truthy(is_lambda(f)):
- local = env_merge(lambda_closure(f), env)
- local[first(lambda_params(f))] = item
- results.append((await async_aser(lambda_body(f), local, ctx)))
- else:
- results.append((await async_invoke(f, item)))
- return results
-
-# async-aser-ho-map-indexed
-async def async_aser_ho_map_indexed(args, env, ctx):
- _cells = {}
- f = (await async_eval(first(args), env, ctx))
- coll = (await async_eval(nth(args, 1), env, ctx))
- results = []
- _cells['i'] = 0
- for item in coll:
- if sx_truthy(is_lambda(f)):
- local = env_merge(lambda_closure(f), env)
- local[first(lambda_params(f))] = _cells['i']
- local[nth(lambda_params(f), 1)] = item
- results.append((await async_aser(lambda_body(f), local, ctx)))
- else:
- results.append((await async_invoke(f, _cells['i'], item)))
- _cells['i'] = (_cells['i'] + 1)
- return results
-
-# async-aser-ho-for-each
-async def async_aser_ho_for_each(args, env, ctx):
- f = (await async_eval(first(args), env, ctx))
- coll = (await async_eval(nth(args, 1), env, ctx))
- results = []
- for item in coll:
- if sx_truthy(is_lambda(f)):
- local = env_merge(lambda_closure(f), env)
- local[first(lambda_params(f))] = item
- results.append((await async_aser(lambda_body(f), local, ctx)))
- else:
- results.append((await async_invoke(f, item)))
- return results
-
-# async-eval-slot-inner
-async def async_eval_slot_inner(expr, env, ctx):
- result = NIL
- if sx_truthy((list_p(expr) if not sx_truthy(list_p(expr)) else (not sx_truthy(empty_p(expr))))):
- head = first(expr)
- if sx_truthy(((type_of(head) == 'symbol') if not sx_truthy((type_of(head) == 'symbol')) else starts_with_p(symbol_name(head), '~'))):
- name = symbol_name(head)
- val = (env_get(env, name) if sx_truthy(env_has(env, name)) else NIL)
- if sx_truthy(is_component(val)):
- result = (await async_aser_component(val, rest(expr), env, ctx))
- else:
- result = (await async_maybe_expand_result((await async_aser(expr, env, ctx)), env, ctx))
- else:
- result = (await async_maybe_expand_result((await async_aser(expr, env, ctx)), env, ctx))
- else:
- result = (await async_maybe_expand_result((await async_aser(expr, env, ctx)), env, ctx))
- if sx_truthy(is_sx_expr(result)):
- return result
- else:
- if sx_truthy(is_nil(result)):
- return make_sx_expr('')
- else:
- if sx_truthy(string_p(result)):
- return make_sx_expr(result)
- else:
- return make_sx_expr(serialize(result))
-
-# async-maybe-expand-result
-async def async_maybe_expand_result(result, env, ctx):
- raw = (trim(sx_str(result)) if sx_truthy(is_sx_expr(result)) else (trim(result) if sx_truthy(string_p(result)) else NIL))
- if sx_truthy((raw if not sx_truthy(raw) else starts_with_p(raw, '(~'))):
- parsed = sx_parse(raw)
- if sx_truthy((parsed if not sx_truthy(parsed) else (not sx_truthy(empty_p(parsed))))):
- return (await async_eval_slot_inner(first(parsed), env, ctx))
- else:
- return result
- else:
- return result
-
-
-# =========================================================================
-# Fixups -- wire up render adapter dispatch
-# =========================================================================
-
-def _setup_html_adapter():
- global _render_expr_fn
- _render_expr_fn = lambda expr, env: render_list_to_html(expr, env)
-
-def _setup_sx_adapter():
- global _render_expr_fn
- _render_expr_fn = lambda expr, env: aser_list(expr, env)
-
-
-# Wrap aser_call and aser_fragment to return SxExpr
-# so serialize() won't double-quote them
-_orig_aser_call = None
-_orig_aser_fragment = None
-
-def _wrap_aser_outputs():
- global aser_call, aser_fragment, _orig_aser_call, _orig_aser_fragment
- _orig_aser_call = aser_call
- _orig_aser_fragment = aser_fragment
- def _aser_call_wrapped(name, args, env):
- result = _orig_aser_call(name, args, env)
- return SxExpr(result) if isinstance(result, str) else result
- def _aser_fragment_wrapped(children, env):
- result = _orig_aser_fragment(children, env)
- return SxExpr(result) if isinstance(result, str) else result
- aser_call = _aser_call_wrapped
- aser_fragment = _aser_fragment_wrapped
-
-
-# Override sf_set_bang to walk the Env scope chain so that (set! var val)
-# updates the variable in its defining scope, not just the local copy.
-def sf_set_bang(args, env):
- name = symbol_name(first(args))
- value = trampoline(eval_expr(nth(args, 1), env))
- env = _ensure_env(env)
- try:
- env.set(name, value)
- except KeyError:
- # Not found in chain — define locally (matches prior behavior)
- env[name] = value
- return value
-
-
-# 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)
-
-# CEK is the canonical evaluator — override eval_expr to use it.
-# The tree-walk evaluator (eval_expr from eval.sx) is superseded.
-_tree_walk_eval_expr = eval_expr
-
-def eval_expr(expr, env):
- """Evaluate expr using the CEK machine."""
- return cek_run(make_cek_state(expr, env, []))
-
-# CEK never produces thunks — trampoline becomes identity
-_tree_walk_trampoline = trampoline
-
-def trampoline(val):
- """In CEK mode, values are immediate — resolve any legacy thunks."""
- if is_thunk(val):
- return eval_expr(thunk_expr(val), thunk_env(val))
- return val
-
-
-# =========================================================================
-# Public API
-# =========================================================================
-
-# Wrap aser outputs to return SxExpr
-_wrap_aser_outputs()
-
-# Set HTML as default adapter
-_setup_html_adapter()
-
-def evaluate(expr, env=None):
- """Evaluate expr in env and return the result."""
- if env is None:
- env = _Env()
- elif isinstance(env, dict):
- env = _Env(env)
- result = eval_expr(expr, env)
- while is_thunk(result):
- result = eval_expr(thunk_expr(result), thunk_env(result))
- return result
-
-
-def render(expr, env=None):
- """Render expr to HTML string."""
- global _render_mode
- if env is None:
- env = {}
- try:
- _render_mode = True
- return render_to_html(expr, env)
- finally:
- _render_mode = 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)
diff --git a/shared/sx/ref/z3.sx b/shared/sx/ref/z3.sx
deleted file mode 100644
index 5cbd68b..0000000
--- a/shared/sx/ref/z3.sx
+++ /dev/null
@@ -1,358 +0,0 @@
-;; ==========================================================================
-;; z3.sx — SX spec to SMT-LIB translator, written in SX
-;;
-;; Translates define-primitive, define-io-primitive, and define-special-form
-;; declarations from the SX spec into SMT-LIB verification conditions for
-;; Z3 and other theorem provers.
-;;
-;; This is the first self-hosted bootstrapper: the SX evaluator (itself
-;; bootstrapped from eval.sx) executes this file against the spec to
-;; produce output in a different language. Same pattern as bootstrap_js.py
-;; and bootstrap_py.py, but written in SX instead of Python.
-;;
-;; Usage (from SX):
-;; (z3-translate expr) — translate one define-* form
-;; (z3-translate-file exprs) — translate a list of parsed expressions
-;;
-;; Usage (as reader macro):
-;; #z3(define-primitive "inc" :params (n) :returns "number" :body (+ n 1))
-;; → "; inc — ...\n(declare-fun inc (Int) Int)\n..."
-;; ==========================================================================
-
-
-;; --------------------------------------------------------------------------
-;; Type mapping: SX type names → SMT-LIB sorts
-;; --------------------------------------------------------------------------
-
-(define z3-sort
- (fn ((sx-type :as string))
- (case sx-type
- "number" "Int"
- "boolean" "Bool"
- "string" "String"
- "list" "(List Value)"
- "dict" "(Array String Value)"
- :else "Value")))
-
-
-;; --------------------------------------------------------------------------
-;; Name translation: SX identifiers → SMT-LIB identifiers
-;; --------------------------------------------------------------------------
-
-(define z3-name
- (fn ((name :as string))
- (cond
- (= name "!=") "neq"
- (= name "+") "+"
- (= name "-") "-"
- (= name "*") "*"
- (= name "/") "/"
- (= name "=") "="
- (= name "<") "<"
- (= name ">") ">"
- (= name "<=") "<="
- (= name ">=") ">="
- :else (replace (replace (replace name "-" "_") "?" "_p") "!" "_bang"))))
-
-(define z3-sym
- (fn (sym)
- (let ((name (symbol-name sym)))
- (cond
- (ends-with? name "?")
- (str "is_" (replace (slice name 0 (- (string-length name) 1)) "-" "_"))
- :else
- (replace (replace name "-" "_") "!" "_bang")))))
-
-
-;; --------------------------------------------------------------------------
-;; Expression translation: SX body expressions → SMT-LIB s-expressions
-;; --------------------------------------------------------------------------
-
-;; Operators that pass through unchanged
-(define z3-identity-ops
- (list "+" "-" "*" "/" "=" "!=" "<" ">" "<=" ">=" "and" "or" "not" "mod"))
-
-;; Operators that get renamed
-(define z3-rename-op
- (fn ((op :as string))
- (case op
- "if" "ite"
- "str" "str.++"
- :else nil)))
-
-(define z3-expr
- (fn (expr)
- (cond
- ;; Numbers
- (number? expr)
- (str expr)
-
- ;; Strings
- (string? expr)
- (str "\"" expr "\"")
-
- ;; Booleans
- (= expr true) "true"
- (= expr false) "false"
-
- ;; Nil
- (nil? expr)
- "nil_val"
-
- ;; Symbols
- (= (type-of expr) "symbol")
- (z3-sym expr)
-
- ;; Lists (function calls / special forms)
- (list? expr)
- (if (empty? expr)
- "()"
- (let ((head (first expr))
- (args (rest expr)))
- (if (not (= (type-of head) "symbol"))
- (str expr)
- (let ((op (symbol-name head)))
- (cond
- ;; Identity ops: same syntax in both languages
- (some (fn (x) (= x op)) z3-identity-ops)
- (str "(" op " " (join " " (map z3-expr args)) ")")
-
- ;; Renamed ops
- (not (nil? (z3-rename-op op)))
- (str "(" (z3-rename-op op) " " (join " " (map z3-expr args)) ")")
-
- ;; max → ite
- (and (= op "max") (= (len args) 2))
- (let ((a (z3-expr (nth args 0)))
- (b (z3-expr (nth args 1))))
- (str "(ite (>= " a " " b ") " a " " b ")"))
-
- ;; min → ite
- (and (= op "min") (= (len args) 2))
- (let ((a (z3-expr (nth args 0)))
- (b (z3-expr (nth args 1))))
- (str "(ite (<= " a " " b ") " a " " b ")"))
-
- ;; empty? → length check
- (= op "empty?")
- (str "(= (len " (z3-expr (first args)) ") 0)")
-
- ;; first/rest → list ops
- (= op "first")
- (str "(head " (z3-expr (first args)) ")")
- (= op "rest")
- (str "(tail " (z3-expr (first args)) ")")
-
- ;; reduce with initial value
- (and (= op "reduce") (>= (len args) 3))
- (str "(reduce " (z3-expr (nth args 0)) " "
- (z3-expr (nth args 2)) " "
- (z3-expr (nth args 1)) ")")
-
- ;; fn (lambda)
- (= op "fn")
- (let ((params (first args))
- (body (nth args 1)))
- (str "(lambda (("
- (join " " (map (fn (p) (str "(" (z3-sym p) " Int)")) params))
- ")) " (z3-expr body) ")"))
-
- ;; native-* → strip prefix
- (starts-with? op "native-")
- (str "(" (slice op 7 (string-length op)) " "
- (join " " (map z3-expr args)) ")")
-
- ;; Generic function call
- :else
- (str "(" (z3-name op) " "
- (join " " (map z3-expr args)) ")"))))))
-
- ;; Fallback
- :else (str expr))))
-
-
-;; --------------------------------------------------------------------------
-;; Keyword argument extraction from define-* forms
-;; --------------------------------------------------------------------------
-
-(define z3-extract-kwargs
- (fn ((expr :as list))
- ;; Returns a dict of keyword args from a define-* form
- ;; (define-primitive "name" :params (...) :returns "type" ...) → {:params ... :returns ...}
- (let ((result {})
- (items (rest (rest expr)))) ;; skip head and name
- (z3-extract-kwargs-loop items result))))
-
-(define z3-extract-kwargs-loop
- (fn ((items :as list) (result :as dict))
- (if (or (empty? items) (< (len items) 2))
- result
- (if (= (type-of (first items)) "keyword")
- (z3-extract-kwargs-loop
- (rest (rest items))
- (assoc result (keyword-name (first items)) (nth items 1)))
- (z3-extract-kwargs-loop (rest items) result)))))
-
-
-;; --------------------------------------------------------------------------
-;; Parameter processing
-;; --------------------------------------------------------------------------
-
-(define z3-params-to-sorts
- (fn ((params :as list))
- ;; Convert SX param list to list of (name sort) pairs, skipping &rest/&key
- (z3-params-loop params false (list))))
-
-(define z3-params-loop
- (fn ((params :as list) (skip-next :as boolean) (acc :as list))
- (if (empty? params)
- acc
- (let ((p (first params))
- (rest-p (rest params)))
- (cond
- ;; &rest or &key marker — skip it and the next param
- (and (= (type-of p) "symbol")
- (or (= (symbol-name p) "&rest")
- (= (symbol-name p) "&key")))
- (z3-params-loop rest-p true acc)
- ;; Skipping the param after &rest/&key
- skip-next
- (z3-params-loop rest-p false acc)
- ;; Normal parameter
- (= (type-of p) "symbol")
- (z3-params-loop rest-p false
- (append acc (list (list (symbol-name p) "Int"))))
- ;; Something else — skip
- :else
- (z3-params-loop rest-p false acc))))))
-
-(define z3-has-rest?
- (fn ((params :as list))
- (some (fn (p) (and (= (type-of p) "symbol") (= (symbol-name p) "&rest")))
- params)))
-
-
-;; --------------------------------------------------------------------------
-;; define-primitive → SMT-LIB
-;; --------------------------------------------------------------------------
-
-(define z3-translate-primitive
- (fn ((expr :as list))
- (let ((name (nth expr 1))
- (kwargs (z3-extract-kwargs expr))
- (params (or (get kwargs "params") (list)))
- (returns (or (get kwargs "returns") "any"))
- (doc (or (get kwargs "doc") ""))
- (body (get kwargs "body"))
- (pairs (z3-params-to-sorts params))
- (has-rest (z3-has-rest? params))
- (smt-name (z3-name name)))
-
- (str
- ;; Comment header
- "; " name " — " doc "\n"
-
- ;; Declaration
- (if has-rest
- (str "; (variadic — modeled as uninterpreted)\n"
- "(declare-fun " smt-name " (Int Int) " (z3-sort returns) ")")
- (str "(declare-fun " smt-name " ("
- (join " " (map (fn (pair) (nth pair 1)) pairs))
- ") " (z3-sort returns) ")"))
- "\n"
-
- ;; Assertion (if body exists and not variadic)
- (if (and (not (nil? body)) (not has-rest))
- (if (empty? pairs)
- ;; No params — simple assertion
- (str "(assert (= (" smt-name ") " (z3-expr body) "))\n")
- ;; With params — forall
- (let ((bindings (join " " (map (fn (pair) (str "(" (nth pair 0) " Int)")) pairs)))
- (call-args (join " " (map (fn (pair) (nth pair 0)) pairs))))
- (str "(assert (forall ((" bindings "))\n"
- " (= (" smt-name " " call-args ") " (z3-expr body) ")))\n")))
- "")
-
- ;; Check satisfiability
- "(check-sat)"))))
-
-
-;; --------------------------------------------------------------------------
-;; define-io-primitive → SMT-LIB
-;; --------------------------------------------------------------------------
-
-(define z3-translate-io
- (fn ((expr :as list))
- (let ((name (nth expr 1))
- (kwargs (z3-extract-kwargs expr))
- (doc (or (get kwargs "doc") ""))
- (smt-name (replace (replace name "-" "_") "?" "_p")))
- (str "; IO primitive: " name " — " doc "\n"
- "; (uninterpreted — IO cannot be verified statically)\n"
- "(declare-fun " smt-name " () Value)"))))
-
-
-;; --------------------------------------------------------------------------
-;; define-special-form → SMT-LIB
-;; --------------------------------------------------------------------------
-
-(define z3-translate-special-form
- (fn ((expr :as list))
- (let ((name (nth expr 1))
- (kwargs (z3-extract-kwargs expr))
- (doc (or (get kwargs "doc") "")))
- (case name
- "if"
- (str "; Special form: if — " doc "\n"
- "(assert (forall ((c Bool) (t Value) (e Value))\n"
- " (= (sx_if c t e) (ite c t e))))\n"
- "(check-sat)")
- "when"
- (str "; Special form: when — " doc "\n"
- "(assert (forall ((c Bool) (body Value))\n"
- " (= (sx_when c body) (ite c body nil_val))))\n"
- "(check-sat)")
- :else
- (str "; Special form: " name " — " doc "\n"
- "; (not directly expressible in SMT-LIB)")))))
-
-
-;; --------------------------------------------------------------------------
-;; Top-level dispatch
-;; --------------------------------------------------------------------------
-
-(define z3-translate
- (fn (expr)
- (if (not (list? expr))
- "; Cannot translate: not a list form"
- (if (< (len expr) 2)
- "; Cannot translate: too short"
- (let ((head (first expr)))
- (if (not (= (type-of head) "symbol"))
- "; Cannot translate: head is not a symbol"
- (case (symbol-name head)
- "define-primitive" (z3-translate-primitive expr)
- "define-io-primitive" (z3-translate-io expr)
- "define-special-form" (z3-translate-special-form expr)
- :else (z3-expr expr))))))))
-
-
-;; --------------------------------------------------------------------------
-;; Batch translation: process a list of parsed expressions
-;; --------------------------------------------------------------------------
-
-(define z3-translate-file
- (fn ((exprs :as list))
- ;; Filter to translatable forms and translate each
- (let ((translatable
- (filter
- (fn (expr)
- (and (list? expr)
- (>= (len expr) 2)
- (= (type-of (first expr)) "symbol")
- (let ((name (symbol-name (first expr))))
- (or (= name "define-primitive")
- (= name "define-io-primitive")
- (= name "define-special-form")))))
- exprs)))
- (join "\n\n" (map z3-translate translatable)))))
diff --git a/shared/sx/resolver.py b/shared/sx/resolver.py
index 2b90c6f..dbede3b 100644
--- a/shared/sx/resolver.py
+++ b/shared/sx/resolver.py
@@ -31,7 +31,10 @@ import asyncio
from typing import Any
from .types import Component, Keyword, Lambda, NIL, Symbol
-from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline
+# sx_ref.py removed — stub so module loads. OCaml bridge handles evaluation.
+def _not_available(*a, **kw):
+ raise RuntimeError("sx_ref.py has been removed — use SX_USE_OCAML=1")
+_raw_eval = _trampoline = _not_available
def _eval(expr, env):
"""Evaluate and unwrap thunks — all resolver.py _eval calls are non-tail."""
diff --git a/sx/sxc/pages/__init__.py b/sx/sxc/pages/__init__.py
index 48f8f0c..9150667 100644
--- a/sx/sxc/pages/__init__.py
+++ b/sx/sxc/pages/__init__.py
@@ -21,17 +21,7 @@ def _load_sx_page_files() -> None:
load_service_components(service_root, service_name="sx")
load_sx_dir(_sxc_dir)
watch_sx_dir(_sxc_dir)
- # Register page helpers as primitives so the CEK machine can find them
- # during nested async component expansion (e.g. highlight inside ~docs/code
- # inside a plan component inside ~layouts/doc). Without this, the env_merge
- # chain loses page helpers because component closures don't capture them.
- from shared.sx.ref.sx_ref import PRIMITIVES
- helpers = get_page_helpers("sx")
- for name, fn in helpers.items():
- PRIMITIVES[name] = fn
-
- # helper is registered as an IO primitive in primitives_io.py,
- # intercepted by async_eval before hitting the CEK machine.
- import logging; logging.getLogger("sx.pages").info("Injected %d page helpers as primitives: %s", len(helpers), list(helpers.keys())[:5])
+ # Page helpers are accessed via the OCaml IO bridge (helper "name" args...)
+ # — no Python-side PRIMITIVES registration needed.
load_page_dir(os.path.dirname(__file__), "sx")
diff --git a/sx/sxc/pages/sx_router.py b/sx/sxc/pages/sx_router.py
index 23dead4..77fe60e 100644
--- a/sx/sxc/pages/sx_router.py
+++ b/sx/sxc/pages/sx_router.py
@@ -93,7 +93,7 @@ async def eval_sx_url(raw_path: str) -> Any:
from shared.sx.helpers import full_page_sx, oob_page_sx, sx_response, sx_page
from shared.sx.page import get_template_context
from shared.browser.app.utils.htmx import is_htmx_request
- from shared.sx.ref.sx_ref import prepare_url_expr
+ from shared.sx.pages import prepare_url_expr
path = unquote(raw_path).strip()