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, '') - -# 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, '') - -# 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, '') - -# 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), '') - -# 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()