From 29c90a625b40ef9558310e3316867ad7d0eaab86 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 11 Mar 2026 11:15:48 +0000 Subject: [PATCH 1/6] Delete evaluator.py shim: all imports go directly to bootstrapped sx_ref.py EvalError moved to types.py. All 27 files updated to import eval_expr, trampoline, call_lambda, etc. directly from shared.sx.ref.sx_ref instead of through the evaluator.py indirection layer. 320/320 spec tests pass. Co-Authored-By: Claude Opus 4.6 --- shared/sx/__init__.py | 7 +-- shared/sx/async_eval.py | 21 ++++--- shared/sx/evaluator.py | 94 ---------------------------- shared/sx/handlers.py | 5 +- shared/sx/html.py | 2 +- shared/sx/jinja_bridge.py | 5 +- shared/sx/pages.py | 2 +- shared/sx/parser.py | 2 +- shared/sx/query_registry.py | 4 +- shared/sx/ref/bootstrap_test.py | 2 +- shared/sx/ref/platform_py.py | 2 +- shared/sx/ref/reader_z3.py | 6 +- shared/sx/ref/run_js_sx.py | 4 +- shared/sx/ref/run_py_sx.py | 4 +- shared/sx/ref/sx_ref.py | 2 +- shared/sx/resolver.py | 2 +- shared/sx/tests/run.py | 2 +- shared/sx/tests/test_bootstrapper.py | 2 +- shared/sx/tests/test_deps.py | 2 +- shared/sx/tests/test_io_detection.py | 2 +- shared/sx/tests/test_io_proxy.py | 4 +- shared/sx/tests/test_page_data.py | 14 ++--- shared/sx/tests/test_parity.py | 6 +- shared/sx/tests/test_sx_js.py | 2 +- shared/sx/tests/test_sx_spec.py | 2 +- shared/sx/types.py | 9 +++ sx/sxc/pages/helpers.py | 14 ++--- 27 files changed, 65 insertions(+), 158 deletions(-) delete mode 100644 shared/sx/evaluator.py diff --git a/shared/sx/__init__.py b/shared/sx/__init__.py index 20d357c8..9322cfb2 100644 --- a/shared/sx/__init__.py +++ b/shared/sx/__init__.py @@ -31,11 +31,8 @@ from .parser import ( parse_all, serialize, ) -from .evaluator import ( - EvalError, - evaluate, - make_env, -) +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 beccab34..78c8e526 100644 --- a/shared/sx/async_eval.py +++ b/shared/sx/async_eval.py @@ -53,7 +53,8 @@ from .types import Component, Island, Keyword, Lambda, Macro, NIL, Symbol _expand_components: contextvars.ContextVar[bool] = contextvars.ContextVar( "_expand_components", default=False ) -from .evaluator import _expand_macro, EvalError +from .ref.sx_ref import expand_macro as _expand_macro +from .types import EvalError from .primitives import _PRIMITIVES from .primitives_io import IO_PRIMITIVES, RequestContext, execute_io from .parser import SxExpr, serialize @@ -420,23 +421,23 @@ async def _asf_define(expr, env, ctx): async def _asf_defcomp(expr, env, ctx): - from .evaluator import _sf_defcomp - return _sf_defcomp(expr, env) + from .ref.sx_ref import sf_defcomp + return sf_defcomp(expr[1:], env) async def _asf_defstyle(expr, env, ctx): - from .evaluator import _sf_defstyle - return _sf_defstyle(expr, env) + from .ref.sx_ref import sf_defstyle + return sf_defstyle(expr[1:], env) async def _asf_defmacro(expr, env, ctx): - from .evaluator import _sf_defmacro - return _sf_defmacro(expr, env) + from .ref.sx_ref import sf_defmacro + return sf_defmacro(expr[1:], env) async def _asf_defhandler(expr, env, ctx): - from .evaluator import _sf_defhandler - return _sf_defhandler(expr, env) + from .ref.sx_ref import sf_defhandler + return sf_defhandler(expr[1:], env) async def _asf_begin(expr, env, ctx): @@ -599,7 +600,7 @@ async def _asf_reset(expr, env, ctx): _ASYNC_RESET_RESUME.append(value if value is not None else NIL) try: # Sync re-evaluation; the async caller will trampoline - from .evaluator import _eval as sync_eval, _trampoline + from .ref.sx_ref import eval_expr as sync_eval, trampoline as _trampoline return _trampoline(sync_eval(body, env)) finally: _ASYNC_RESET_RESUME.pop() diff --git a/shared/sx/evaluator.py b/shared/sx/evaluator.py deleted file mode 100644 index 4e703528..00000000 --- a/shared/sx/evaluator.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -S-expression evaluator — thin shim over bootstrapped sx_ref.py. - -All evaluation logic lives in the spec (shared/sx/ref/eval.sx) and is -bootstrapped to Python (shared/sx/ref/sx_ref.py). This module re-exports -the public API and internal helpers under their historical names so that -existing callers don't need updating. - -Imports are lazy (inside functions/properties) to avoid circular imports -during bootstrapping: bootstrap_py.py → parser → __init__ → evaluator → sx_ref. -""" - -from __future__ import annotations - - -def _ref(): - """Lazy import of the bootstrapped evaluator.""" - from .ref import sx_ref - return sx_ref - - -# --------------------------------------------------------------------------- -# Public API — these are the most used, so we make them importable directly -# --------------------------------------------------------------------------- - -class EvalError(Exception): - """Error during expression evaluation. - - Delegates to the bootstrapped EvalError at runtime but is defined here - so imports don't fail during bootstrapping. - """ - pass - - -def evaluate(expr, env=None): - return _ref().evaluate(expr, env) - - -def make_env(**kwargs): - return _ref().make_env(**kwargs) - - -# --------------------------------------------------------------------------- -# Internal helpers — used by html.py, async_eval.py, handlers.py, etc. -# --------------------------------------------------------------------------- - -def _eval(expr, env): - return _ref().eval_expr(expr, env) - - -def _trampoline(val): - return _ref().trampoline(val) - - -def _call_lambda(fn, args, caller_env): - return _ref().call_lambda(fn, args, caller_env) - - -def _call_component(comp, raw_args, env): - return _ref().call_component(comp, raw_args, env) - - -def _expand_macro(macro, raw_args, env): - return _ref().expand_macro(macro, raw_args, env) - - -# --------------------------------------------------------------------------- -# Special-form wrappers: callers pass (expr, env) with expr[0] = head symbol. -# sx_ref.py special forms take (args, env) where args = expr[1:]. -# --------------------------------------------------------------------------- - -def _sf_defcomp(expr, env): - return _ref().sf_defcomp(expr[1:], env) - -def _sf_defisland(expr, env): - return _ref().sf_defisland(expr[1:], env) - -def _sf_defstyle(expr, env): - return _ref().sf_defstyle(expr[1:], env) - -def _sf_defmacro(expr, env): - return _ref().sf_defmacro(expr[1:], env) - -def _sf_defhandler(expr, env): - return _ref().sf_defhandler(expr[1:], env) - -def _sf_defpage(expr, env): - return _ref().sf_defpage(expr[1:], env) - -def _sf_defquery(expr, env): - return _ref().sf_defquery(expr[1:], env) - -def _sf_defaction(expr, env): - return _ref().sf_defaction(expr[1:], env) diff --git a/shared/sx/handlers.py b/shared/sx/handlers.py index 0aabd8a8..1a775d43 100644 --- a/shared/sx/handlers.py +++ b/shared/sx/handlers.py @@ -70,10 +70,7 @@ 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 - if os.environ.get("SX_USE_REF") == "1": - from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline - else: - from .evaluator import _eval as _raw_eval, _trampoline + 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 diff --git a/shared/sx/html.py b/shared/sx/html.py index 2341bd98..f7fc9d50 100644 --- a/shared/sx/html.py +++ b/shared/sx/html.py @@ -28,7 +28,7 @@ import contextvars from typing import Any from .types import Component, Island, Keyword, Lambda, Macro, NIL, Symbol -from .evaluator import _eval as _raw_eval, _call_component as _raw_call_component, _expand_macro, _trampoline +from .ref.sx_ref import eval_expr as _raw_eval, call_component as _raw_call_component, expand_macro as _expand_macro, trampoline as _trampoline 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 9da4a6c4..167eac57 100644 --- a/shared/sx/jinja_bridge.py +++ b/shared/sx/jinja_bridge.py @@ -229,10 +229,7 @@ def register_components(sx_source: str) -> None: (div :class "..." (div :class "..." title))))) ''') """ - if _os.environ.get("SX_USE_REF") == "1": - from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline - else: - from .evaluator import _eval as _raw_eval, _trampoline + 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 diff --git a/shared/sx/pages.py b/shared/sx/pages.py index f0df5ce0..51dc76de 100644 --- a/shared/sx/pages.py +++ b/shared/sx/pages.py @@ -127,7 +127,7 @@ def get_page_helpers(service: str) -> dict[str, Any]: def load_page_file(filepath: str, service_name: str) -> list[PageDef]: """Parse an .sx file, evaluate it, and register any PageDef values.""" from .parser import parse_all - from .evaluator import _eval as _raw_eval, _trampoline + 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 diff --git a/shared/sx/parser.py b/shared/sx/parser.py index c4ac622c..f4092936 100644 --- a/shared/sx/parser.py +++ b/shared/sx/parser.py @@ -41,7 +41,7 @@ def _resolve_sx_reader_macro(name: str): """ try: from .jinja_bridge import get_component_env - from .evaluator import _trampoline, _call_lambda + from .ref.sx_ref import trampoline as _trampoline, call_lambda as _call_lambda from .types import Lambda except ImportError: return None diff --git a/shared/sx/query_registry.py b/shared/sx/query_registry.py index e6d63e35..3edda12e 100644 --- a/shared/sx/query_registry.py +++ b/shared/sx/query_registry.py @@ -78,7 +78,7 @@ def clear(service: str | None = None) -> None: def load_query_file(filepath: str, service_name: str) -> list[QueryDef]: """Parse an .sx file and register any defquery definitions.""" from .parser import parse_all - from .evaluator import _eval as _raw_eval, _trampoline + 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 @@ -103,7 +103,7 @@ def load_query_file(filepath: str, service_name: str) -> list[QueryDef]: def load_action_file(filepath: str, service_name: str) -> list[ActionDef]: """Parse an .sx file and register any defaction definitions.""" from .parser import parse_all - from .evaluator import _eval as _raw_eval, _trampoline + 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 diff --git a/shared/sx/ref/bootstrap_test.py b/shared/sx/ref/bootstrap_test.py index 08eb64bc..0f1ef43c 100644 --- a/shared/sx/ref/bootstrap_test.py +++ b/shared/sx/ref/bootstrap_test.py @@ -143,7 +143,7 @@ def _emit_py(suites: list[dict], preamble: list) -> str: lines.append('') lines.append('import pytest') lines.append('from shared.sx.parser import parse_all') - lines.append('from shared.sx.evaluator import _eval, _trampoline') + 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}'''") diff --git a/shared/sx/ref/platform_py.py b/shared/sx/ref/platform_py.py index c21be752..74fafaef 100644 --- a/shared/sx/ref/platform_py.py +++ b/shared/sx/ref/platform_py.py @@ -600,7 +600,7 @@ def sx_expr_source(x): try: - from shared.sx.evaluator import EvalError + from shared.sx.types import EvalError except ImportError: class EvalError(Exception): pass diff --git a/shared/sx/ref/reader_z3.py b/shared/sx/ref/reader_z3.py index f5c76239..8ab2297a 100644 --- a/shared/sx/ref/reader_z3.py +++ b/shared/sx/ref/reader_z3.py @@ -39,7 +39,7 @@ def _get_z3_env() -> dict[str, Any]: return _z3_env from shared.sx.parser import parse_all - from shared.sx.evaluator import make_env, _eval, _trampoline + 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") @@ -60,7 +60,7 @@ def z3_translate(expr: Any) -> str: Delegates to z3-translate defined in z3.sx. """ - from shared.sx.evaluator import _trampoline, _call_lambda + 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)) @@ -72,7 +72,7 @@ def z3_translate_file(source: str) -> str: Delegates to z3-translate-file defined in z3.sx. """ from shared.sx.parser import parse_all - from shared.sx.evaluator import _trampoline, _call_lambda + from shared.sx.ref.sx_ref import trampoline as _trampoline, call_lambda as _call_lambda env = _get_z3_env() exprs = parse_all(source) diff --git a/shared/sx/ref/run_js_sx.py b/shared/sx/ref/run_js_sx.py index ebe478b6..9fd1e141 100644 --- a/shared/sx/ref/run_js_sx.py +++ b/shared/sx/ref/run_js_sx.py @@ -49,7 +49,7 @@ def load_js_sx() -> dict: exprs = parse_all(source) - from shared.sx.evaluator import evaluate, make_env + from shared.sx.ref.sx_ref import evaluate, make_env env = make_env() for expr in exprs: @@ -74,7 +74,7 @@ def compile_ref_to_js( spec_modules: List of spec modules (deps, router, signals). None = auto. """ from datetime import datetime, timezone - from shared.sx.evaluator import evaluate + from shared.sx.ref.sx_ref import evaluate ref_dir = _HERE env = load_js_sx() diff --git a/shared/sx/ref/run_py_sx.py b/shared/sx/ref/run_py_sx.py index ab170f5f..54927cf6 100644 --- a/shared/sx/ref/run_py_sx.py +++ b/shared/sx/ref/run_py_sx.py @@ -38,7 +38,7 @@ def load_py_sx(evaluator_env: dict) -> dict: exprs = parse_all(source) # Import the evaluator - from shared.sx.evaluator import evaluate, make_env + from shared.sx.ref.sx_ref import evaluate, make_env env = make_env() for expr in exprs: @@ -60,7 +60,7 @@ def extract_defines(source: str) -> list[tuple[str, list]]: def main(): - from shared.sx.evaluator import evaluate + from shared.sx.ref.sx_ref import evaluate # Load py.sx into evaluator env = load_py_sx({}) diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py index 427dced1..020daa3e 100644 --- a/shared/sx/ref/sx_ref.py +++ b/shared/sx/ref/sx_ref.py @@ -559,7 +559,7 @@ def sx_expr_source(x): try: - from shared.sx.evaluator import EvalError + from shared.sx.types import EvalError except ImportError: class EvalError(Exception): pass diff --git a/shared/sx/resolver.py b/shared/sx/resolver.py index 93622499..2b90c6fa 100644 --- a/shared/sx/resolver.py +++ b/shared/sx/resolver.py @@ -31,7 +31,7 @@ import asyncio from typing import Any from .types import Component, Keyword, Lambda, NIL, Symbol -from .evaluator import _eval as _raw_eval, _trampoline +from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline def _eval(expr, env): """Evaluate and unwrap thunks — all resolver.py _eval calls are non-tail.""" diff --git a/shared/sx/tests/run.py b/shared/sx/tests/run.py index c679d59a..abdc791a 100644 --- a/shared/sx/tests/run.py +++ b/shared/sx/tests/run.py @@ -20,7 +20,7 @@ _PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", "..")) sys.path.insert(0, _PROJECT) from shared.sx.parser import parse_all -from shared.sx.evaluator import _eval, _trampoline, _call_lambda +from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline, call_lambda as _call_lambda from shared.sx.types import Symbol, Keyword, Lambda, NIL, Component, Island # --- Test state --- diff --git a/shared/sx/tests/test_bootstrapper.py b/shared/sx/tests/test_bootstrapper.py index 4fb6416b..635943ea 100644 --- a/shared/sx/tests/test_bootstrapper.py +++ b/shared/sx/tests/test_bootstrapper.py @@ -21,7 +21,7 @@ class TestJsSxTranslation: def _translate(self, sx_source: str) -> str: """Translate a single SX expression to JS using js.sx.""" - from shared.sx.evaluator import evaluate + from shared.sx.ref.sx_ref import evaluate env = load_js_sx() expr = parse(sx_source) env["_def_expr"] = expr diff --git a/shared/sx/tests/test_deps.py b/shared/sx/tests/test_deps.py index c5ca5262..a648a1ea 100644 --- a/shared/sx/tests/test_deps.py +++ b/shared/sx/tests/test_deps.py @@ -18,7 +18,7 @@ from shared.sx.deps import ( def make_env(*sx_sources: str) -> dict: """Parse and evaluate component definitions into an env dict.""" - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline env: dict = {} for source in sx_sources: exprs = parse_all(source) diff --git a/shared/sx/tests/test_io_detection.py b/shared/sx/tests/test_io_detection.py index bac513ae..412c8996 100644 --- a/shared/sx/tests/test_io_detection.py +++ b/shared/sx/tests/test_io_detection.py @@ -23,7 +23,7 @@ from shared.sx.deps import ( def make_env(*sx_sources: str) -> dict: """Parse and evaluate component definitions into an env dict.""" - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline env: dict = {} for source in sx_sources: exprs = parse_all(source) diff --git a/shared/sx/tests/test_io_proxy.py b/shared/sx/tests/test_io_proxy.py index 1516797f..8ccfd366 100644 --- a/shared/sx/tests/test_io_proxy.py +++ b/shared/sx/tests/test_io_proxy.py @@ -20,7 +20,7 @@ from shared.sx.deps import ( def make_env(*sx_sources: str) -> dict: """Parse and evaluate component definitions into an env dict.""" - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline env: dict = {} for source in sx_sources: exprs = parse_all(source) @@ -282,7 +282,7 @@ class TestIoRoutingLogic: """ def _eval(self, src, env): - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline result = None for expr in parse_all(src): result = _trampoline(_eval(expr, env)) diff --git a/shared/sx/tests/test_page_data.py b/shared/sx/tests/test_page_data.py index 7cd22800..5789814d 100644 --- a/shared/sx/tests/test_page_data.py +++ b/shared/sx/tests/test_page_data.py @@ -156,7 +156,7 @@ class TestDataPageDeps: def test_deps_computed_for_data_page(self): from shared.sx.deps import components_needed from shared.sx.parser import parse_all as pa - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline # Define a component env = {} @@ -172,7 +172,7 @@ class TestDataPageDeps: def test_deps_transitive_for_data_page(self): from shared.sx.deps import components_needed from shared.sx.parser import parse_all as pa - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline env = {} source = """ @@ -205,7 +205,7 @@ class TestDataPipelineSimulation: def test_full_pipeline(self): from shared.sx.parser import parse_all as pa - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline # 1. Define a component that uses only pure primitives env = {} @@ -236,7 +236,7 @@ class TestDataPipelineSimulation: def test_pipeline_with_list_data(self): from shared.sx.parser import parse_all as pa - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline env = {} for expr in pa(''' @@ -262,7 +262,7 @@ class TestDataPipelineSimulation: def test_pipeline_data_isolation(self): """Different data for the same content produces different results.""" from shared.sx.parser import parse_all as pa - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline env = {} for expr in pa('(defcomp ~page (&key title count) (str title ": " count))'): @@ -298,7 +298,7 @@ class TestDataCache: def _make_env(self, current_time_ms=1000): """Create an env with cache functions and a controllable now-ms.""" from shared.sx.parser import parse_all as pa - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline env = {} # Mock now-ms as a callable that returns current_time_ms @@ -344,7 +344,7 @@ class TestDataCache: def _eval(self, src, env): from shared.sx.parser import parse_all as pa - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline result = None for expr in pa(src): result = _trampoline(_eval(expr, env)) diff --git a/shared/sx/tests/test_parity.py b/shared/sx/tests/test_parity.py index db809b02..0f079f51 100644 --- a/shared/sx/tests/test_parity.py +++ b/shared/sx/tests/test_parity.py @@ -18,7 +18,7 @@ from shared.sx.types import Symbol, Keyword, Lambda, Component, Macro, NIL def hw_eval(text, env=None): """Evaluate via hand-written evaluator.py.""" - from shared.sx.evaluator import evaluate as _evaluate, EvalError + from shared.sx.ref.sx_ref import evaluate as _evaluate if env is None: env = {} return _evaluate(parse(text), env) @@ -50,7 +50,7 @@ def ref_render(text, env=None): def hw_eval_multi(text, env=None): """Evaluate multiple expressions (e.g. defines then call).""" - from shared.sx.evaluator import evaluate as _evaluate + from shared.sx.ref.sx_ref import evaluate as _evaluate if env is None: env = {} result = None @@ -736,7 +736,7 @@ class TestParityDeps: class TestParityErrors: def test_undefined_symbol(self): - from shared.sx.evaluator import EvalError as HwError + from shared.sx.types import EvalError as HwError from shared.sx.ref.sx_ref import EvalError as RefError with pytest.raises(HwError): hw_eval("undefined_var") diff --git a/shared/sx/tests/test_sx_js.py b/shared/sx/tests/test_sx_js.py index 135adaec..43b59aae 100644 --- a/shared/sx/tests/test_sx_js.py +++ b/shared/sx/tests/test_sx_js.py @@ -12,7 +12,7 @@ import pytest from shared.sx.parser import parse, parse_all from shared.sx.html import render as py_render -from shared.sx.evaluator import evaluate +from shared.sx.ref.sx_ref import evaluate SX_JS = Path(__file__).resolve().parents[2] / "static" / "scripts" / "sx.js" SX_TEST_JS = Path(__file__).resolve().parents[2] / "static" / "scripts" / "sx-test.js" diff --git a/shared/sx/tests/test_sx_spec.py b/shared/sx/tests/test_sx_spec.py index db885cce..51592761 100644 --- a/shared/sx/tests/test_sx_spec.py +++ b/shared/sx/tests/test_sx_spec.py @@ -7,7 +7,7 @@ from __future__ import annotations import pytest from shared.sx.parser import parse_all -from shared.sx.evaluator import _eval, _trampoline +from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline _PREAMBLE = '''(define assert-equal (fn (expected actual) (assert (equal? expected actual) (str "Expected " (str expected) " but got " (str actual))))) diff --git a/shared/sx/types.py b/shared/sx/types.py index 97f0e2fc..4ade10f4 100644 --- a/shared/sx/types.py +++ b/shared/sx/types.py @@ -375,6 +375,15 @@ class _ShiftSignal(BaseException): self.env = env +# --------------------------------------------------------------------------- +# EvalError +# --------------------------------------------------------------------------- + +class EvalError(Exception): + """Error during expression evaluation.""" + pass + + # --------------------------------------------------------------------------- # Type alias # --------------------------------------------------------------------------- diff --git a/sx/sxc/pages/helpers.py b/sx/sxc/pages/helpers.py index 102147d5..f8c6deea 100644 --- a/sx/sxc/pages/helpers.py +++ b/sx/sxc/pages/helpers.py @@ -314,7 +314,7 @@ def _self_hosting_data(ref_dir: str) -> dict: import os from shared.sx.parser import parse_all from shared.sx.types import Symbol - from shared.sx.evaluator import evaluate, make_env + from shared.sx.ref.sx_ref import evaluate, make_env from shared.sx.ref.bootstrap_py import extract_defines, compile_ref_to_py, PyEmitter try: @@ -387,7 +387,7 @@ def _js_self_hosting_data(ref_dir: str) -> dict: """Run js.sx live: load into evaluator, translate all spec defines.""" import os from shared.sx.types import Symbol - from shared.sx.evaluator import evaluate + from shared.sx.ref.sx_ref import evaluate from shared.sx.ref.run_js_sx import load_js_sx from shared.sx.ref.platform_js import extract_defines @@ -661,7 +661,7 @@ def _run_spec_tests() -> dict: import os import time from shared.sx.parser import parse_all - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref") if not os.path.isdir(ref_dir): @@ -735,7 +735,7 @@ def _run_modular_tests(spec_name: str) -> dict: import os import time from shared.sx.parser import parse_all - from shared.sx.evaluator import _eval, _trampoline + from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline from shared.sx.types import Symbol, Keyword, Lambda, NIL ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref") @@ -817,7 +817,7 @@ def _run_modular_tests(spec_name: str) -> dict: def _call_sx(fn, args, caller_env): if isinstance(fn, Lambda): - from shared.sx.evaluator import _call_lambda + from shared.sx.ref.sx_ref import call_lambda as _call_lambda return _trampoline(_call_lambda(fn, list(args), caller_env)) return fn(*args) @@ -1165,9 +1165,9 @@ def _prove_data() -> dict: """ import time from shared.sx.parser import parse_all - from shared.sx.evaluator import evaluate + from shared.sx.ref.sx_ref import evaluate from shared.sx.primitives import all_primitives - from shared.sx.evaluator import _trampoline, _call_lambda + from shared.sx.ref.sx_ref import trampoline as _trampoline, call_lambda as _call_lambda env = all_primitives() From c95e19dcf2927e4a87b498dbc846c6398f010bb2 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 11 Mar 2026 14:30:12 +0000 Subject: [PATCH 2/6] Page helpers demo: defisland, map-in-children fix, _eval_slot ref evaluator - Add page-helpers-demo page with defisland ~demo-client-runner (pure SX, zero JS files) showing spec functions running on both server and client - Fix _aser_component children serialization: flatten list results from map instead of serialize(list) which wraps in parens creating ((div ...) ...) that re-parses as invalid function call. Fixed in adapter-async.sx spec and async_eval_ref.py - Switch _eval_slot to use async_eval_ref.py when SX_USE_REF=1 (was hardcoded to async_eval.py) - Add Island type support to async_eval_ref.py: import, SSR rendering, aser dispatch, thread-first, defisland in _ASER_FORMS - Add server affinity check: components with :affinity :server expand even when _expand_components is False - Add diagnostic _aser_stack context to EvalError messages - New spec files: adapter-async.sx, page-helpers.sx, platform_js.py - Bootstrappers: page-helpers module support, performance.now() timing - 0-arity lambda event handler fix in adapter-dom.sx Co-Authored-By: Claude Opus 4.6 --- shared/static/scripts/sx-browser.js | 155 +- shared/sx/pages.py | 6 +- shared/sx/ref/adapter-async.sx | 1198 ++++++++++ shared/sx/ref/adapter-dom.sx | 7 +- shared/sx/ref/async_eval_ref.py | 85 +- shared/sx/ref/bootstrap_py.py | 2 + shared/sx/ref/page-helpers.sx | 368 ++++ shared/sx/ref/platform_js.py | 3163 +++++++++++++++++++++++++++ shared/sx/ref/platform_py.py | 1 + shared/sx/ref/run_js_sx.py | 7 +- shared/sx/ref/sx_ref.py | 557 ++--- sx/sx/boundary.sx | 5 + sx/sx/nav-data.sx | 3 +- sx/sx/page-helpers-demo.sx | 265 +++ sx/sxc/pages/docs.sx | 22 + sx/sxc/pages/helpers.py | 521 ++--- 16 files changed, 5584 insertions(+), 781 deletions(-) create mode 100644 shared/sx/ref/adapter-async.sx create mode 100644 shared/sx/ref/page-helpers.sx create mode 100644 shared/sx/ref/platform_js.py create mode 100644 sx/sx/page-helpers-demo.sx diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 1a36e680..69090b80 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-11T04:41:27Z"; + var SX_VERSION = "2026-03-11T13:57:48Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -974,8 +974,8 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var head = first(template); return (isSxTruthy((isSxTruthy((typeOf(head) == "symbol")) && (symbolName(head) == "unquote"))) ? trampoline(evalExpr(nth(template, 1), env)) : reduce(function(result, item) { return (isSxTruthy((isSxTruthy((typeOf(item) == "list")) && isSxTruthy((len(item) == 2)) && isSxTruthy((typeOf(first(item)) == "symbol")) && (symbolName(first(item)) == "splice-unquote"))) ? (function() { var spliced = trampoline(evalExpr(nth(item, 1), env)); - return (isSxTruthy((typeOf(spliced) == "list")) ? concat(result, spliced) : (isSxTruthy(isNil(spliced)) ? result : append(result, spliced))); -})() : append(result, qqExpand(item, env))); }, [], template)); + return (isSxTruthy((typeOf(spliced) == "list")) ? concat(result, spliced) : (isSxTruthy(isNil(spliced)) ? result : concat(result, [spliced]))); +})() : concat(result, [qqExpand(item, env)])); }, [], template)); })())); }; // sf-thread-first @@ -1658,7 +1658,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme var attrExpr = nth(args, (get(state, "i") + 1)); (isSxTruthy(startsWith(attrName, "on-")) ? (function() { var attrVal = trampoline(evalExpr(attrExpr, env)); - return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), attrVal) : NIL); + return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), (isSxTruthy((isSxTruthy(isLambda(attrVal)) && (len(lambdaParams(attrVal)) == 0))) ? function(e) { return callLambda(attrVal, [], lambdaClosure(attrVal)); } : attrVal)) : NIL); })() : (isSxTruthy((attrName == "bind")) ? (function() { var attrVal = trampoline(evalExpr(attrExpr, env)); return (isSxTruthy(isSignal(attrVal)) ? bindInput(el, attrVal) : NIL); @@ -3433,6 +3433,125 @@ callExpr.push(dictGet(kwargs, k)); } } })(); }, keys(env)); }; + // === Transpiled from page-helpers (pure data transformation helpers) === + + // special-form-category-map + var specialFormCategoryMap = {"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 + var extractDefineKwargs = function(expr) { return (function() { + var result = {}; + var items = slice(expr, 2); + var n = len(items); + { var _c = range(0, n); for (var _i = 0; _i < _c.length; _i++) { var idx = _c[_i]; if (isSxTruthy((isSxTruthy(((idx + 1) < n)) && (typeOf(nth(items, idx)) == "keyword")))) { + (function() { + var key = keywordName(nth(items, idx)); + var val = nth(items, (idx + 1)); + return dictSet(result, key, (isSxTruthy((typeOf(val) == "list")) ? (String("(") + String(join(" ", map(serialize, val))) + String(")")) : (String(val)))); +})(); +} } } + return result; +})(); }; + + // categorize-special-forms + var categorizeSpecialForms = function(parsedExprs) { return (function() { + var categories = {}; + { var _c = parsedExprs; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(expr) == "list")) && isSxTruthy((len(expr) >= 2)) && isSxTruthy((typeOf(first(expr)) == "symbol")) && (symbolName(first(expr)) == "define-special-form")))) { + (function() { + var name = nth(expr, 1); + var kwargs = extractDefineKwargs(expr); + var category = sxOr(get(specialFormCategoryMap, name), "Other"); + if (isSxTruthy(!isSxTruthy(dictHas(categories, category)))) { + categories[category] = []; +} + return append_b(get(categories, category), {"name": name, "syntax": sxOr(get(kwargs, "syntax"), ""), "doc": sxOr(get(kwargs, "doc"), ""), "tail-position": sxOr(get(kwargs, "tail-position"), ""), "example": sxOr(get(kwargs, "example"), "")}); +})(); +} } } + return categories; +})(); }; + + // build-ref-items-with-href + var buildRefItemsWithHref = function(items, basePath, detailKeys, nFields) { return map(function(item) { return (isSxTruthy((nFields == 3)) ? (function() { + var name = nth(item, 0); + var field2 = nth(item, 1); + var field3 = nth(item, 2); + return {"name": name, "desc": field2, "exists": field3, "href": (isSxTruthy((isSxTruthy(field3) && some(function(k) { return (k == name); }, detailKeys))) ? (String(basePath) + String(name)) : NIL)}; +})() : (function() { + var name = nth(item, 0); + var desc = nth(item, 1); + return {"name": name, "desc": desc, "href": (isSxTruthy(some(function(k) { return (k == name); }, detailKeys)) ? (String(basePath) + String(name)) : NIL)}; +})()); }, items); }; + + // build-reference-data + var buildReferenceData = function(slug, rawData, detailKeys) { return (function() { var _m = slug; if (_m == "attributes") return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; if (_m == "headers") return {"req-headers": buildRefItemsWithHref(get(rawData, "req-headers"), "/hypermedia/reference/headers/", detailKeys, 3), "resp-headers": buildRefItemsWithHref(get(rawData, "resp-headers"), "/hypermedia/reference/headers/", detailKeys, 3)}; if (_m == "events") return {"events-list": buildRefItemsWithHref(get(rawData, "events-list"), "/hypermedia/reference/events/", detailKeys, 2)}; if (_m == "js-api") return {"js-api-list": map(function(item) { return {"name": nth(item, 0), "desc": nth(item, 1)}; }, get(rawData, "js-api-list"))}; return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; })(); }; + + // build-attr-detail + var buildAttrDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"attr-not-found": true} : {"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": (isSxTruthy(dictHas(detail, "handler")) ? (String("ref-wire-") + String(replace_(replace_(slug, ":", "-"), "*", "star"))) : NIL)}); }; + + // build-header-detail + var buildHeaderDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"header-not-found": true} : {"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 + var buildEventDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"event-not-found": true} : {"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 + var buildComponentSource = function(compData) { return (function() { + var compType = get(compData, "type"); + var name = get(compData, "name"); + var params = get(compData, "params"); + var hasChildren = get(compData, "has-children"); + var bodySx = get(compData, "body-sx"); + var affinity = get(compData, "affinity"); + return (isSxTruthy((compType == "not-found")) ? (String(";; component ") + String(name) + String(" not found")) : (function() { + var paramStrs = (isSxTruthy(isEmpty(params)) ? (isSxTruthy(hasChildren) ? ["&rest", "children"] : []) : (isSxTruthy(hasChildren) ? append(cons("&key", params), ["&rest", "children"]) : cons("&key", params))); + 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("\n ") + String(bodySx) + String(")")); +})()); +})(); }; + + // build-bundle-analysis + var buildBundleAnalysis = function(pagesRaw, componentsRaw, totalComponents, totalMacros, pureCount, ioCount) { return (function() { + var pagesData = []; + { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() { + var neededNames = get(page, "needed-names"); + var n = len(neededNames); + var pct = (isSxTruthy((totalComponents > 0)) ? round(((n / totalComponents) * 100)) : 0); + var savings = (100 - pct); + var pureInPage = 0; + var ioInPage = 0; + var pageIoRefs = []; + var compDetails = []; + { var _c = neededNames; for (var _i = 0; _i < _c.length; _i++) { var compName = _c[_i]; (function() { + var info = get(componentsRaw, compName); + return (isSxTruthy(!isSxTruthy(isNil(info))) ? ((isSxTruthy(get(info, "is-pure")) ? (pureInPage = (pureInPage + 1)) : ((ioInPage = (ioInPage + 1)), forEach(function(ref) { return (isSxTruthy(!isSxTruthy(some(function(r) { return (r == ref); }, pageIoRefs))) ? append_b(pageIoRefs, ref) : NIL); }, sxOr(get(info, "io-refs"), [])))), append_b(compDetails, {"name": compName, "is-pure": get(info, "is-pure"), "affinity": get(info, "affinity"), "render-target": get(info, "render-target"), "io-refs": sxOr(get(info, "io-refs"), []), "deps": sxOr(get(info, "deps"), []), "source": get(info, "source")})) : NIL); +})(); } } + return append_b(pagesData, {"name": get(page, "name"), "path": get(page, "path"), "direct": get(page, "direct"), "needed": n, "pct": pct, "savings": savings, "io-refs": len(pageIoRefs), "pure-in-page": pureInPage, "io-in-page": ioInPage, "components": compDetails}); +})(); } } + return {"pages": pagesData, "total-components": totalComponents, "total-macros": totalMacros, "pure-count": pureCount, "io-count": ioCount}; +})(); }; + + // build-routing-analysis + var buildRoutingAnalysis = function(pagesRaw) { return (function() { + var pagesData = []; + var clientCount = 0; + var serverCount = 0; + { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() { + var hasData = get(page, "has-data"); + var contentSrc = sxOr(get(page, "content-src"), ""); + var mode = NIL; + var reason = ""; + (isSxTruthy(hasData) ? ((mode = "server"), (reason = "Has :data expression — needs server IO"), (serverCount = (serverCount + 1))) : (isSxTruthy(isEmpty(contentSrc)) ? ((mode = "server"), (reason = "No content expression"), (serverCount = (serverCount + 1))) : ((mode = "client"), (clientCount = (clientCount + 1))))); + return append_b(pagesData, {"name": get(page, "name"), "path": get(page, "path"), "mode": mode, "has-data": hasData, "content-expr": (isSxTruthy((len(contentSrc) > 80)) ? (String(slice(contentSrc, 0, 80)) + String("...")) : contentSrc), "reason": reason}); +})(); } } + return {"pages": pagesData, "total-pages": (clientCount + serverCount), "client-count": clientCount, "server-count": serverCount}; +})(); }; + + // build-affinity-analysis + var buildAffinityAnalysis = function(demoComponents, pagePlans) { return {"components": demoComponents, "page-plans": pagePlans}; }; + + // === Transpiled from router (client-side route matching) === // split-path-segments @@ -3947,7 +4066,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { } } - function nowMs() { return Date.now(); } + function nowMs() { return (typeof performance !== "undefined") ? performance.now() : Date.now(); } function parseHeaderValue(s) { if (!s) return null; @@ -5060,6 +5179,10 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { if (typeof elementValue === "function") PRIMITIVES["element-value"] = elementValue; if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml; if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml; + if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent; + if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse; + if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs; + PRIMITIVES["sx-parse"] = sxParse; // Expose deps module functions as primitives so runtime-evaluated SX code // (e.g. test-deps.sx in browser) can call them @@ -5090,6 +5213,19 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { PRIMITIVES["render-target"] = renderTarget; PRIMITIVES["page-render-plan"] = pageRenderPlan; + // Expose page-helper functions as primitives + PRIMITIVES["categorize-special-forms"] = categorizeSpecialForms; + PRIMITIVES["extract-define-kwargs"] = extractDefineKwargs; + PRIMITIVES["build-reference-data"] = buildReferenceData; + PRIMITIVES["build-ref-items-with-href"] = buildRefItemsWithHref; + PRIMITIVES["build-attr-detail"] = buildAttrDetail; + PRIMITIVES["build-header-detail"] = buildHeaderDetail; + PRIMITIVES["build-event-detail"] = buildEventDetail; + PRIMITIVES["build-component-source"] = buildComponentSource; + PRIMITIVES["build-bundle-analysis"] = buildBundleAnalysis; + PRIMITIVES["build-routing-analysis"] = buildRoutingAnalysis; + PRIMITIVES["build-affinity-analysis"] = buildAffinityAnalysis; + // ========================================================================= // Async IO: Promise-aware rendering for client-side IO primitives // ========================================================================= @@ -5823,6 +5959,15 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { transitiveIoRefs: transitiveIoRefs, computeAllIoRefs: computeAllIoRefs, componentPure_p: componentPure_p, + categorizeSpecialForms: categorizeSpecialForms, + buildReferenceData: buildReferenceData, + buildAttrDetail: buildAttrDetail, + buildHeaderDetail: buildHeaderDetail, + buildEventDetail: buildEventDetail, + buildComponentSource: buildComponentSource, + buildBundleAnalysis: buildBundleAnalysis, + buildRoutingAnalysis: buildRoutingAnalysis, + buildAffinityAnalysis: buildAffinityAnalysis, splitPathSegments: splitPathSegments, parseRoutePattern: parseRoutePattern, matchRoute: matchRoute, diff --git a/shared/sx/pages.py b/shared/sx/pages.py index 51dc76de..2050a5b2 100644 --- a/shared/sx/pages.py +++ b/shared/sx/pages.py @@ -170,7 +170,11 @@ async def _eval_slot(expr: Any, env: dict, ctx: Any) -> str: Expands component calls (so IO in the body executes) but serializes the result as SX wire format, not HTML. """ - from .async_eval import async_eval_slot_to_sx + import os + if os.environ.get("SX_USE_REF") == "1": + from .ref.async_eval_ref import async_eval_slot_to_sx + else: + from .async_eval import async_eval_slot_to_sx return await async_eval_slot_to_sx(expr, env, ctx) diff --git a/shared/sx/ref/adapter-async.sx b/shared/sx/ref/adapter-async.sx new file mode 100644 index 00000000..f57dfe35 --- /dev/null +++ b/shared/sx/ref/adapter-async.sx @@ -0,0 +1,1198 @@ +;; ========================================================================== +;; adapter-async.sx — Async rendering and serialization adapter +;; +;; Async versions of adapter-html.sx (render) and adapter-sx.sx (aser) +;; for use with I/O-capable server environments (Python async, JS promises). +;; +;; Structurally identical to the sync adapters but uses async primitives: +;; async-eval — evaluate with I/O interception (platform primitive) +;; async-render — defined here, async HTML rendering +;; async-aser — defined here, async SX wire format +;; +;; All functions in this file are emitted as async by the bootstrapper. +;; Calls to other async functions receive await automatically. +;; +;; Depends on: +;; eval.sx — cond-scheme?, eval-cond-scheme, eval-cond-clojure, +;; expand-macro, env-merge, lambda?, component?, island?, +;; macro?, lambda-closure, lambda-params, lambda-body +;; render.sx — HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS, +;; render-attrs, definition-form?, process-bindings, eval-cond +;; +;; Platform primitives (provided by host): +;; (async-eval expr env ctx) — evaluate with I/O interception +;; (io-primitive? name) — check if name is I/O primitive +;; (execute-io name args kw ctx) — execute an I/O primitive +;; (expand-components?) — context var: expand components in aser? +;; (svg-context?) — context var: in SVG rendering context? +;; (svg-context-set! val) — set SVG context +;; (svg-context-reset! token) — reset SVG context +;; (css-class-collect! val) — collect CSS classes for bundling +;; (is-raw-html? x) — check if value is raw HTML marker +;; (raw-html-content x) — extract HTML string from marker +;; (make-raw-html s) — wrap string as raw HTML +;; (async-coroutine? x) — check if value is a coroutine +;; (async-await! x) — await a coroutine value +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; Async HTML renderer +;; -------------------------------------------------------------------------- + +(define-async async-render + (fn (expr env ctx) + (case (type-of expr) + "nil" "" + "boolean" "" + "string" (escape-html expr) + "number" (escape-html (str expr)) + "raw-html" (raw-html-content expr) + "symbol" (let ((val (async-eval expr env ctx))) + (async-render val env ctx)) + "keyword" (escape-html (keyword-name expr)) + "list" (if (empty? expr) "" (async-render-list expr env ctx)) + "dict" "" + :else (escape-html (str expr))))) + + +(define-async async-render-list + (fn (expr env ctx) + (let ((head (first expr))) + (if (not (= (type-of head) "symbol")) + ;; Non-symbol head — data list, render each item + (if (or (lambda? head) (= (type-of head) "list")) + ;; Lambda/list call — eval then render + (async-render (async-eval expr env ctx) env ctx) + ;; Data list + (join "" (async-map-render expr env ctx))) + + ;; Symbol head — dispatch + (let ((name (symbol-name head)) + (args (rest expr))) + (cond + ;; I/O primitive + (io-primitive? name) + (async-render (async-eval expr env ctx) env ctx) + + ;; raw! + (= name "raw!") + (async-render-raw args env ctx) + + ;; Fragment + (= name "<>") + (join "" (async-map-render args env ctx)) + + ;; html: prefix + (starts-with? name "html:") + (async-render-element (slice name 5) args env ctx) + + ;; Render-aware special form (but check HTML tag + keyword first) + (async-render-form? name) + (if (and (contains? HTML_TAGS name) + (or (and (> (len expr) 1) (= (type-of (nth expr 1)) "keyword")) + (svg-context?))) + (async-render-element name args env ctx) + (dispatch-async-render-form name expr env ctx)) + + ;; Macro + (and (env-has? env name) (macro? (env-get env name))) + (async-render + (trampoline (expand-macro (env-get env name) args env)) + env ctx) + + ;; HTML tag + (contains? HTML_TAGS name) + (async-render-element name args env ctx) + + ;; Island (~name) + (and (starts-with? name "~") + (env-has? env name) + (island? (env-get env name))) + (async-render-island (env-get env name) args env ctx) + + ;; Component (~name) + (starts-with? name "~") + (let ((val (if (env-has? env name) (env-get env name) nil))) + (cond + (component? val) (async-render-component val args env ctx) + (macro? val) (async-render (trampoline (expand-macro val args env)) env ctx) + :else (async-render (async-eval expr env ctx) env ctx))) + + ;; Custom element (has - and keyword arg) + (and (> (index-of name "-") 0) + (> (len expr) 1) + (= (type-of (nth expr 1)) "keyword")) + (async-render-element name args env ctx) + + ;; SVG context + (svg-context?) + (async-render-element name args env ctx) + + ;; Fallback — eval then render + :else + (async-render (async-eval expr env ctx) env ctx))))))) + + +;; -------------------------------------------------------------------------- +;; async-render-raw — handle (raw! ...) in async context +;; -------------------------------------------------------------------------- + +(define-async async-render-raw + (fn (args env ctx) + (let ((parts (list))) + (for-each + (fn (arg) + (let ((val (async-eval arg env ctx))) + (cond + (is-raw-html? val) (append! parts (raw-html-content val)) + (= (type-of val) "string") (append! parts val) + (and (not (nil? val)) (not (= val false))) + (append! parts (str val))))) + args) + (join "" parts)))) + + +;; -------------------------------------------------------------------------- +;; async-render-element — render an HTML element with async arg evaluation +;; -------------------------------------------------------------------------- + +(define-async async-render-element + (fn (tag args env ctx) + (let ((attrs (dict)) + (children (list))) + ;; Parse keyword attrs and children + (async-parse-element-args args attrs children env ctx) + ;; Collect CSS classes + (let ((class-val (dict-get attrs "class"))) + (when (and (not (nil? class-val)) (not (= class-val false))) + (css-class-collect! (str class-val)))) + ;; Build opening tag + (let ((opening (str "<" tag (render-attrs attrs) ">"))) + (if (contains? VOID_ELEMENTS tag) + opening + (let ((token (if (or (= tag "svg") (= tag "math")) + (svg-context-set! true) + nil)) + (child-html (join "" (async-map-render children env ctx)))) + (when token (svg-context-reset! token)) + (str opening child-html ""))))))) + + +;; -------------------------------------------------------------------------- +;; async-parse-element-args — parse :key val pairs + children, async eval +;; -------------------------------------------------------------------------- +;; Uses for-each + mutable state instead of reduce, because the bootstrapper +;; compiles inline for-each lambdas as for loops (which can contain await). + +(define-async async-parse-element-args + (fn (args attrs children env ctx) + (let ((skip false) + (i 0)) + (for-each + (fn (arg) + (if skip + (do (set! skip false) + (set! i (inc i))) + (if (and (= (type-of arg) "keyword") + (< (inc i) (len args))) + (let ((val (async-eval (nth args (inc i)) env ctx))) + (dict-set! attrs (keyword-name arg) val) + (set! skip true) + (set! i (inc i))) + (do + (append! children arg) + (set! i (inc i)))))) + args)))) + + +;; -------------------------------------------------------------------------- +;; async-render-component — expand and render a component asynchronously +;; -------------------------------------------------------------------------- + +(define-async async-render-component + (fn (comp args env ctx) + (let ((kwargs (dict)) + (children (list))) + ;; Parse keyword args and children + (async-parse-kw-args args kwargs children env ctx) + ;; Build env: closure + caller env + params + (let ((local (env-merge (component-closure comp) env))) + (for-each + (fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (component-params comp)) + (when (component-has-children? comp) + (env-set! local "children" + (make-raw-html + (join "" (async-map-render children env ctx))))) + (async-render (component-body comp) local ctx))))) + + +;; -------------------------------------------------------------------------- +;; async-render-island — SSR render of reactive island with hydration markers +;; -------------------------------------------------------------------------- + +(define-async async-render-island + (fn (island args env ctx) + (let ((kwargs (dict)) + (children (list))) + (async-parse-kw-args args kwargs children env ctx) + (let ((local (env-merge (component-closure island) env)) + (island-name (component-name island))) + (for-each + (fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (component-params island)) + (when (component-has-children? island) + (env-set! local "children" + (make-raw-html + (join "" (async-map-render children env ctx))))) + (let ((body-html (async-render (component-body island) local ctx)) + (state-json (serialize-island-state kwargs))) + (str "" + body-html + "")))))) + + +;; -------------------------------------------------------------------------- +;; async-render-lambda — render lambda body in HTML context +;; -------------------------------------------------------------------------- + +(define-async async-render-lambda + (fn (f args env ctx) + (let ((local (env-merge (lambda-closure f) env))) + (for-each-indexed + (fn (i p) (env-set! local p (nth args i))) + (lambda-params f)) + (async-render (lambda-body f) local ctx)))) + + +;; -------------------------------------------------------------------------- +;; async-parse-kw-args — parse keyword args and children with async eval +;; -------------------------------------------------------------------------- + +(define-async async-parse-kw-args + (fn (args kwargs children env ctx) + (let ((skip false) + (i 0)) + (for-each + (fn (arg) + (if skip + (do (set! skip false) + (set! i (inc i))) + (if (and (= (type-of arg) "keyword") + (< (inc i) (len args))) + (let ((val (async-eval (nth args (inc i)) env ctx))) + (dict-set! kwargs (keyword-name arg) val) + (set! skip true) + (set! i (inc i))) + (do + (append! children arg) + (set! i (inc i)))))) + args)))) + + +;; -------------------------------------------------------------------------- +;; async-map-render — map async-render over a list, return list of strings +;; -------------------------------------------------------------------------- +;; Bootstrapper emits this as: [await async_render(x, env, ctx) for x in exprs] + +(define-async async-map-render + (fn (exprs env ctx) + (let ((results (list))) + (for-each + (fn (x) (append! results (async-render x env ctx))) + exprs) + results))) + + +;; -------------------------------------------------------------------------- +;; Render-aware form classification +;; -------------------------------------------------------------------------- + +(define ASYNC_RENDER_FORMS + (list "if" "when" "cond" "case" "let" "let*" "begin" "do" + "define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler" + "map" "map-indexed" "filter" "for-each")) + +(define async-render-form? + (fn (name) + (contains? ASYNC_RENDER_FORMS name))) + + +;; -------------------------------------------------------------------------- +;; dispatch-async-render-form — async special form rendering for HTML output +;; -------------------------------------------------------------------------- +;; +;; Uses cond-scheme? from eval.sx (the FIXED version with every? check) +;; and eval-cond from render.sx for correct scheme/clojure classification. + +(define-async dispatch-async-render-form + (fn (name expr env ctx) + (cond + ;; if + (= name "if") + (let ((cond-val (async-eval (nth expr 1) env ctx))) + (if cond-val + (async-render (nth expr 2) env ctx) + (if (> (len expr) 3) + (async-render (nth expr 3) env ctx) + ""))) + + ;; when + (= name "when") + (if (not (async-eval (nth expr 1) env ctx)) + "" + (join "" (async-map-render (slice expr 2) env ctx))) + + ;; cond — uses cond-scheme? (every? check) from eval.sx + (= name "cond") + (let ((clauses (rest expr))) + (if (cond-scheme? clauses) + (async-render-cond-scheme clauses env ctx) + (async-render-cond-clojure clauses env ctx))) + + ;; case + (= name "case") + (async-render (async-eval expr env ctx) env ctx) + + ;; let / let* + (or (= name "let") (= name "let*")) + (let ((local (async-process-bindings (nth expr 1) env ctx))) + (join "" (async-map-render (slice expr 2) local ctx))) + + ;; begin / do + (or (= name "begin") (= name "do")) + (join "" (async-map-render (rest expr) env ctx)) + + ;; Definition forms + (definition-form? name) + (do (async-eval expr env ctx) "") + + ;; map + (= name "map") + (let ((f (async-eval (nth expr 1) env ctx)) + (coll (async-eval (nth expr 2) env ctx))) + (join "" + (async-map-fn-render f coll env ctx))) + + ;; map-indexed + (= name "map-indexed") + (let ((f (async-eval (nth expr 1) env ctx)) + (coll (async-eval (nth expr 2) env ctx))) + (join "" + (async-map-indexed-fn-render f coll env ctx))) + + ;; filter — eval fully then render + (= name "filter") + (async-render (async-eval expr env ctx) env ctx) + + ;; for-each (render variant) + (= name "for-each") + (let ((f (async-eval (nth expr 1) env ctx)) + (coll (async-eval (nth expr 2) env ctx))) + (join "" + (async-map-fn-render f coll env ctx))) + + ;; Fallback + :else + (async-render (async-eval expr env ctx) env ctx)))) + + +;; -------------------------------------------------------------------------- +;; async-render-cond-scheme — scheme-style cond for render mode +;; -------------------------------------------------------------------------- + +(define-async async-render-cond-scheme + (fn (clauses env ctx) + (if (empty? clauses) + "" + (let ((clause (first clauses)) + (test (first clause)) + (body (nth clause 1))) + (if (or (and (= (type-of test) "symbol") + (or (= (symbol-name test) "else") + (= (symbol-name test) ":else"))) + (and (= (type-of test) "keyword") + (= (keyword-name test) "else"))) + (async-render body env ctx) + (if (async-eval test env ctx) + (async-render body env ctx) + (async-render-cond-scheme (rest clauses) env ctx))))))) + + +;; -------------------------------------------------------------------------- +;; async-render-cond-clojure — clojure-style cond for render mode +;; -------------------------------------------------------------------------- + +(define-async async-render-cond-clojure + (fn (clauses env ctx) + (if (< (len clauses) 2) + "" + (let ((test (first clauses)) + (body (nth clauses 1))) + (if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else")) + (and (= (type-of test) "symbol") + (or (= (symbol-name test) "else") + (= (symbol-name test) ":else")))) + (async-render body env ctx) + (if (async-eval test env ctx) + (async-render body env ctx) + (async-render-cond-clojure (slice clauses 2) env ctx))))))) + + +;; -------------------------------------------------------------------------- +;; async-process-bindings — evaluate let-bindings asynchronously +;; -------------------------------------------------------------------------- + +(define-async async-process-bindings + (fn (bindings env ctx) + (let ((local (merge env))) + (if (and (= (type-of bindings) "list") (not (empty? bindings))) + (if (= (type-of (first bindings)) "list") + ;; Scheme-style: ((name val) ...) + (for-each + (fn (pair) + (when (and (= (type-of pair) "list") (>= (len pair) 2)) + (let ((name (if (= (type-of (first pair)) "symbol") + (symbol-name (first pair)) + (str (first pair))))) + (env-set! local name (async-eval (nth pair 1) local ctx))))) + bindings) + ;; Clojure-style: (name val name val ...) + (async-process-bindings-flat bindings local ctx))) + local))) + + +(define-async async-process-bindings-flat + (fn (bindings local ctx) + (let ((skip false) + (i 0)) + (for-each + (fn (item) + (if skip + (do (set! skip false) + (set! i (inc i))) + (do + (let ((name (if (= (type-of item) "symbol") + (symbol-name item) + (str item)))) + (when (< (inc i) (len bindings)) + (env-set! local name + (async-eval (nth bindings (inc i)) local ctx)))) + (set! skip true) + (set! i (inc i))))) + bindings)))) + + +;; -------------------------------------------------------------------------- +;; async-map-fn-render — map a lambda/callable over collection for render +;; -------------------------------------------------------------------------- + +(define-async async-map-fn-render + (fn (f coll env ctx) + (let ((results (list))) + (for-each + (fn (item) + (if (lambda? f) + (append! results (async-render-lambda f (list item) env ctx)) + (let ((r (async-invoke f item))) + (append! results (async-render r env ctx))))) + coll) + results))) + + +;; -------------------------------------------------------------------------- +;; async-map-indexed-fn-render — map-indexed variant for render +;; -------------------------------------------------------------------------- + +(define-async async-map-indexed-fn-render + (fn (f coll env ctx) + (let ((results (list)) + (i 0)) + (for-each + (fn (item) + (if (lambda? f) + (append! results (async-render-lambda f (list i item) env ctx)) + (let ((r (async-invoke f i item))) + (append! results (async-render r env ctx)))) + (set! i (inc i))) + coll) + results))) + + +;; -------------------------------------------------------------------------- +;; async-invoke — call a native callable, await if coroutine +;; -------------------------------------------------------------------------- + +(define-async async-invoke + (fn (f &rest args) + (let ((r (apply f args))) + (if (async-coroutine? r) + (async-await! r) + r)))) + + +;; ========================================================================== +;; Async SX wire format (aser) +;; ========================================================================== + +(define-async async-aser + (fn (expr env ctx) + (case (type-of expr) + "number" expr + "string" expr + "boolean" expr + "nil" nil + + "symbol" + (let ((name (symbol-name expr))) + (cond + (env-has? env name) (env-get env name) + (primitive? name) (get-primitive name) + (= name "true") true + (= name "false") false + (= name "nil") nil + :else (error (str "Undefined symbol: " name)))) + + "keyword" (keyword-name expr) + + "dict" (async-aser-dict expr env ctx) + + "list" + (if (empty? expr) + (list) + (async-aser-list expr env ctx)) + + :else expr))) + + +(define-async async-aser-dict + (fn (expr env ctx) + (let ((result (dict))) + (for-each + (fn (key) + (dict-set! result key (async-aser (dict-get expr key) env ctx))) + (keys expr)) + result))) + + +;; -------------------------------------------------------------------------- +;; async-aser-list — dispatch on list head for aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-list + (fn (expr env ctx) + (let ((head (first expr)) + (args (rest expr))) + (if (not (= (type-of head) "symbol")) + ;; Non-symbol head + (if (or (lambda? head) (= (type-of head) "list")) + ;; Function/list call — eval fully + (async-aser-eval-call head args env ctx) + ;; Data list — aser each + (async-aser-map-list expr env ctx)) + + ;; Symbol head — dispatch + (let ((name (symbol-name head))) + (cond + ;; I/O primitive + (io-primitive? name) + (async-eval expr env ctx) + + ;; Fragment + (= name "<>") + (async-aser-fragment args env ctx) + + ;; raw! + (= name "raw!") + (async-aser-call "raw!" args env ctx) + + ;; html: prefix + (starts-with? name "html:") + (async-aser-call (slice name 5) args env ctx) + + ;; Component call (~name) + (starts-with? name "~") + (let ((val (if (env-has? env name) (env-get env name) nil))) + (cond + (macro? val) + (async-aser (trampoline (expand-macro val args env)) env ctx) + (and (component? val) + (or (expand-components?) + (= (component-affinity val) "server"))) + (async-aser-component val args env ctx) + :else + (async-aser-call name args env ctx))) + + ;; Special/HO forms + (or (async-aser-form? name)) + (if (and (contains? HTML_TAGS name) + (or (and (> (len expr) 1) (= (type-of (nth expr 1)) "keyword")) + (svg-context?))) + (async-aser-call name args env ctx) + (dispatch-async-aser-form name expr env ctx)) + + ;; HTML tag + (contains? HTML_TAGS name) + (async-aser-call name args env ctx) + + ;; Macro + (and (env-has? env name) (macro? (env-get env name))) + (async-aser (trampoline (expand-macro (env-get env name) args env)) env ctx) + + ;; Custom element + (and (> (index-of name "-") 0) + (> (len expr) 1) + (= (type-of (nth expr 1)) "keyword")) + (async-aser-call name args env ctx) + + ;; SVG context + (svg-context?) + (async-aser-call name args env ctx) + + ;; Fallback — function/lambda call + :else + (async-aser-eval-call head args env ctx))))))) + + +;; -------------------------------------------------------------------------- +;; async-aser-eval-call — evaluate a function call fully in aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-eval-call + (fn (head args env ctx) + (let ((f (async-eval head env ctx)) + (evaled-args (async-eval-args args env ctx))) + (cond + (and (callable? f) (not (lambda? f)) (not (component? f))) + (async-invoke f evaled-args) + (lambda? f) + (let ((local (env-merge (lambda-closure f) env))) + (for-each-indexed + (fn (i p) (env-set! local p (nth evaled-args i))) + (lambda-params f)) + (async-aser (lambda-body f) local ctx)) + (component? f) + (async-aser-call (str "~" (component-name f)) args env ctx) + (island? f) + (async-aser-call (str "~" (component-name f)) args env ctx) + :else + (error (str "Not callable: " (inspect f))))))) + + +;; -------------------------------------------------------------------------- +;; async-eval-args — evaluate a list of args asynchronously +;; -------------------------------------------------------------------------- + +(define-async async-eval-args + (fn (args env ctx) + (let ((results (list))) + (for-each + (fn (a) (append! results (async-eval a env ctx))) + args) + results))) + + +;; -------------------------------------------------------------------------- +;; async-aser-map-list — aser each element of a list +;; -------------------------------------------------------------------------- + +(define-async async-aser-map-list + (fn (exprs env ctx) + (let ((results (list))) + (for-each + (fn (x) (append! results (async-aser x env ctx))) + exprs) + results))) + + +;; -------------------------------------------------------------------------- +;; async-aser-fragment — serialize (<> child1 child2 ...) in aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-fragment + (fn (children env ctx) + (let ((parts (list))) + (for-each + (fn (c) + (let ((result (async-aser c env ctx))) + (if (= (type-of result) "list") + (for-each + (fn (item) + (when (not (nil? item)) + (append! parts (serialize item)))) + result) + (when (not (nil? result)) + (append! parts (serialize result)))))) + children) + (if (empty? parts) + (make-sx-expr "") + (make-sx-expr (str "(<> " (join " " parts) ")")))))) + + +;; -------------------------------------------------------------------------- +;; async-aser-component — expand component server-side in aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-component + (fn (comp args env ctx) + (let ((kwargs (dict)) + (children (list))) + (async-parse-aser-kw-args args kwargs children env ctx) + (let ((local (env-merge (component-closure comp) env))) + (for-each + (fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (component-params comp)) + (when (component-has-children? comp) + (let ((child-parts (list))) + (for-each + (fn (c) + (let ((result (async-aser c env ctx))) + (if (list? result) + (for-each + (fn (item) + (when (not (nil? item)) + (append! child-parts (serialize item)))) + result) + (when (not (nil? result)) + (append! child-parts (serialize result)))))) + children) + (env-set! local "children" + (make-sx-expr (str "(<> " (join " " child-parts) ")"))))) + (async-aser (component-body comp) local ctx))))) + + +;; -------------------------------------------------------------------------- +;; async-parse-aser-kw-args — parse keyword args for aser mode +;; -------------------------------------------------------------------------- + +(define-async async-parse-aser-kw-args + (fn (args kwargs children env ctx) + (let ((skip false) + (i 0)) + (for-each + (fn (arg) + (if skip + (do (set! skip false) + (set! i (inc i))) + (if (and (= (type-of arg) "keyword") + (< (inc i) (len args))) + (let ((val (async-aser (nth args (inc i)) env ctx))) + (dict-set! kwargs (keyword-name arg) val) + (set! skip true) + (set! i (inc i))) + (do + (append! children arg) + (set! i (inc i)))))) + args)))) + + +;; -------------------------------------------------------------------------- +;; async-aser-call — serialize an SX call (tag or component) in aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-call + (fn (name args env ctx) + (let ((token (if (or (= name "svg") (= name "math")) + (svg-context-set! true) + nil)) + (parts (list name)) + (skip false) + (i 0)) + (for-each + (fn (arg) + (if skip + (do (set! skip false) + (set! i (inc i))) + (if (and (= (type-of arg) "keyword") + (< (inc i) (len args))) + (let ((val (async-aser (nth args (inc i)) env ctx))) + (when (not (nil? val)) + (append! parts (str ":" (keyword-name arg))) + (if (= (type-of val) "list") + (let ((live (filter (fn (v) (not (nil? v))) val))) + (if (empty? live) + (append! parts "nil") + (let ((items (map serialize live))) + (if (some (fn (v) (sx-expr? v)) live) + (append! parts (str "(<> " (join " " items) ")")) + (append! parts (str "(list " (join " " items) ")")))))) + (append! parts (serialize val)))) + (set! skip true) + (set! i (inc i))) + (let ((result (async-aser arg env ctx))) + (when (not (nil? result)) + (if (= (type-of result) "list") + (for-each + (fn (item) + (when (not (nil? item)) + (append! parts (serialize item)))) + result) + (append! parts (serialize result)))) + (set! i (inc i)))))) + args) + (when token (svg-context-reset! token)) + (make-sx-expr (str "(" (join " " parts) ")"))))) + + +;; -------------------------------------------------------------------------- +;; Aser form classification +;; -------------------------------------------------------------------------- + +(define ASYNC_ASER_FORM_NAMES + (list "if" "when" "cond" "case" "and" "or" + "let" "let*" "lambda" "fn" + "define" "defcomp" "defmacro" "defstyle" + "defhandler" "defpage" "defquery" "defaction" + "begin" "do" "quote" "->" "set!" "defisland")) + +(define ASYNC_ASER_HO_NAMES + (list "map" "map-indexed" "filter" "for-each")) + +(define async-aser-form? + (fn (name) + (or (contains? ASYNC_ASER_FORM_NAMES name) + (contains? ASYNC_ASER_HO_NAMES name)))) + + +;; -------------------------------------------------------------------------- +;; dispatch-async-aser-form — evaluate special/HO forms in aser mode +;; -------------------------------------------------------------------------- +;; +;; Uses cond-scheme? from eval.sx (the FIXED version with every? check). + +(define-async dispatch-async-aser-form + (fn (name expr env ctx) + (let ((args (rest expr))) + (cond + ;; if + (= name "if") + (let ((cond-val (async-eval (first args) env ctx))) + (if cond-val + (async-aser (nth args 1) env ctx) + (if (> (len args) 2) + (async-aser (nth args 2) env ctx) + nil))) + + ;; when + (= name "when") + (if (not (async-eval (first args) env ctx)) + nil + (let ((result nil)) + (for-each + (fn (body) (set! result (async-aser body env ctx))) + (rest args)) + result)) + + ;; cond — uses cond-scheme? (every? check) + (= name "cond") + (if (cond-scheme? args) + (async-aser-cond-scheme args env ctx) + (async-aser-cond-clojure args env ctx)) + + ;; case + (= name "case") + (let ((match-val (async-eval (first args) env ctx))) + (async-aser-case-loop match-val (rest args) env ctx)) + + ;; let / let* + (or (= name "let") (= name "let*")) + (let ((local (async-process-bindings (first args) env ctx)) + (result nil)) + (for-each + (fn (body) (set! result (async-aser body local ctx))) + (rest args)) + result) + + ;; begin / do + (or (= name "begin") (= name "do")) + (let ((result nil)) + (for-each + (fn (body) (set! result (async-aser body env ctx))) + args) + result) + + ;; and — short-circuit via flag to avoid 'some' with async lambda + (= name "and") + (let ((result true) + (stop false)) + (for-each (fn (arg) + (when (not stop) + (set! result (async-eval arg env ctx)) + (when (not result) + (set! stop true)))) + args) + result) + + ;; or — short-circuit via flag to avoid 'some' with async lambda + (= name "or") + (let ((result false) + (stop false)) + (for-each (fn (arg) + (when (not stop) + (set! result (async-eval arg env ctx)) + (when result + (set! stop true)))) + args) + result) + + ;; lambda / fn + (or (= name "lambda") (= name "fn")) + (sf-lambda args env) + + ;; quote + (= name "quote") + (if (empty? args) nil (first args)) + + ;; -> thread-first + (= name "->") + (async-aser-thread-first args env ctx) + + ;; set! + (= name "set!") + (let ((value (async-eval (nth args 1) env ctx))) + (env-set! env (symbol-name (first args)) value) + value) + + ;; map + (= name "map") + (async-aser-ho-map args env ctx) + + ;; map-indexed + (= name "map-indexed") + (async-aser-ho-map-indexed args env ctx) + + ;; filter + (= name "filter") + (async-eval expr env ctx) + + ;; for-each + (= name "for-each") + (async-aser-ho-for-each args env ctx) + + ;; defisland — evaluate AND serialize + (= name "defisland") + (do (async-eval expr env ctx) + (serialize expr)) + + ;; Definition forms — evaluate for side effects + (or (= name "define") (= name "defcomp") (= name "defmacro") + (= name "defstyle") (= name "defhandler") (= name "defpage") + (= name "defquery") (= name "defaction")) + (do (async-eval expr env ctx) nil) + + ;; Fallback + :else + (async-eval expr env ctx))))) + + +;; -------------------------------------------------------------------------- +;; async-aser-cond-scheme — scheme-style cond for aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-cond-scheme + (fn (clauses env ctx) + (if (empty? clauses) + nil + (let ((clause (first clauses)) + (test (first clause)) + (body (nth clause 1))) + (if (or (and (= (type-of test) "symbol") + (or (= (symbol-name test) "else") + (= (symbol-name test) ":else"))) + (and (= (type-of test) "keyword") + (= (keyword-name test) "else"))) + (async-aser body env ctx) + (if (async-eval test env ctx) + (async-aser body env ctx) + (async-aser-cond-scheme (rest clauses) env ctx))))))) + + +;; -------------------------------------------------------------------------- +;; async-aser-cond-clojure — clojure-style cond for aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-cond-clojure + (fn (clauses env ctx) + (if (< (len clauses) 2) + nil + (let ((test (first clauses)) + (body (nth clauses 1))) + (if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else")) + (and (= (type-of test) "symbol") + (or (= (symbol-name test) "else") + (= (symbol-name test) ":else")))) + (async-aser body env ctx) + (if (async-eval test env ctx) + (async-aser body env ctx) + (async-aser-cond-clojure (slice clauses 2) env ctx))))))) + + +;; -------------------------------------------------------------------------- +;; async-aser-case-loop — case dispatch for aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-case-loop + (fn (match-val clauses env ctx) + (if (< (len clauses) 2) + nil + (let ((test (first clauses)) + (body (nth clauses 1))) + (if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else")) + (and (= (type-of test) "symbol") + (or (= (symbol-name test) ":else") + (= (symbol-name test) "else")))) + (async-aser body env ctx) + (if (= match-val (async-eval test env ctx)) + (async-aser body env ctx) + (async-aser-case-loop match-val (slice clauses 2) env ctx))))))) + + +;; -------------------------------------------------------------------------- +;; async-aser-thread-first — -> form in aser mode +;; -------------------------------------------------------------------------- + +(define-async async-aser-thread-first + (fn (args env ctx) + (let ((result (async-eval (first args) env ctx))) + (for-each + (fn (form) + (if (= (type-of form) "list") + (let ((f (async-eval (first form) env ctx)) + (fn-args (cons result + (async-eval-args (rest form) env ctx)))) + (set! result (async-invoke-or-lambda f fn-args env ctx))) + (let ((f (async-eval form env ctx))) + (set! result (async-invoke-or-lambda f (list result) env ctx))))) + (rest args)) + result))) + + +;; -------------------------------------------------------------------------- +;; async-invoke-or-lambda — invoke a callable or lambda with args +;; -------------------------------------------------------------------------- + +(define-async async-invoke-or-lambda + (fn (f args env ctx) + (cond + (and (callable? f) (not (lambda? f)) (not (component? f))) + (let ((r (apply f args))) + (if (async-coroutine? r) + (async-await! r) + r)) + (lambda? f) + (let ((local (env-merge (lambda-closure f) env))) + (for-each-indexed + (fn (i p) (env-set! local p (nth args i))) + (lambda-params f)) + (async-eval (lambda-body f) local ctx)) + :else + (error (str "-> form not callable: " (inspect f)))))) + + +;; -------------------------------------------------------------------------- +;; Async aser HO forms (map, map-indexed, for-each) +;; -------------------------------------------------------------------------- + +(define-async async-aser-ho-map + (fn (args env ctx) + (let ((f (async-eval (first args) env ctx)) + (coll (async-eval (nth args 1) env ctx)) + (results (list))) + (for-each + (fn (item) + (if (lambda? f) + (let ((local (env-merge (lambda-closure f) env))) + (env-set! local (first (lambda-params f)) item) + (append! results (async-aser (lambda-body f) local ctx))) + (append! results (async-invoke f item)))) + coll) + results))) + + +(define-async async-aser-ho-map-indexed + (fn (args env ctx) + (let ((f (async-eval (first args) env ctx)) + (coll (async-eval (nth args 1) env ctx)) + (results (list)) + (i 0)) + (for-each + (fn (item) + (if (lambda? f) + (let ((local (env-merge (lambda-closure f) env))) + (env-set! local (first (lambda-params f)) i) + (env-set! local (nth (lambda-params f) 1) item) + (append! results (async-aser (lambda-body f) local ctx))) + (append! results (async-invoke f i item))) + (set! i (inc i))) + coll) + results))) + + +(define-async async-aser-ho-for-each + (fn (args env ctx) + (let ((f (async-eval (first args) env ctx)) + (coll (async-eval (nth args 1) env ctx)) + (results (list))) + (for-each + (fn (item) + (if (lambda? f) + (let ((local (env-merge (lambda-closure f) env))) + (env-set! local (first (lambda-params f)) item) + (append! results (async-aser (lambda-body f) local ctx))) + (append! results (async-invoke f item)))) + coll) + results))) + + +;; -------------------------------------------------------------------------- +;; Platform interface — async adapter +;; -------------------------------------------------------------------------- +;; +;; Async evaluation (provided by platform): +;; (async-eval expr env ctx) — evaluate with I/O interception +;; (execute-io name args kw ctx) — execute I/O primitive +;; (io-primitive? name) — check if name is I/O primitive +;; +;; From eval.sx: +;; cond-scheme?, eval-cond-scheme, eval-cond-clojure +;; eval-expr, trampoline, expand-macro, sf-lambda +;; env-has?, env-get, env-set!, env-merge +;; lambda?, component?, island?, macro?, callable? +;; lambda-closure, lambda-params, lambda-body +;; component-params, component-body, component-closure, +;; component-has-children?, component-name +;; inspect +;; +;; From render.sx: +;; HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS +;; render-attrs, definition-form?, cond-scheme? +;; escape-html, escape-attr, raw-html-content +;; +;; From adapter-html.sx: +;; serialize-island-state +;; +;; Context management (platform): +;; (expand-components?) — check if component expansion is enabled +;; (svg-context?) — check if in SVG context +;; (svg-context-set! val) — set SVG context (returns reset token) +;; (svg-context-reset! token) — reset SVG context +;; (css-class-collect! val) — collect CSS classes +;; +;; Raw HTML: +;; (is-raw-html? x) — check if raw HTML marker +;; (make-raw-html s) — wrap string as raw HTML +;; (raw-html-content x) — unwrap raw HTML +;; +;; SxExpr: +;; (make-sx-expr s) — wrap as SxExpr (wire format string) +;; (sx-expr? x) — check if SxExpr +;; +;; Async primitives: +;; (async-coroutine? x) — check if value is a coroutine +;; (async-await! x) — await a coroutine +;; -------------------------------------------------------------------------- diff --git a/shared/sx/ref/adapter-dom.sx b/shared/sx/ref/adapter-dom.sx index e659b994..3e84f4f2 100644 --- a/shared/sx/ref/adapter-dom.sx +++ b/shared/sx/ref/adapter-dom.sx @@ -186,10 +186,15 @@ (attr-expr (nth args (inc (get state "i"))))) (cond ;; Event handler: evaluate eagerly, bind listener + ;; If handler is a 0-arity lambda, wrap to ignore the event arg (starts-with? attr-name "on-") (let ((attr-val (trampoline (eval-expr attr-expr env)))) (when (callable? attr-val) - (dom-listen el (slice attr-name 3) attr-val))) + (dom-listen el (slice attr-name 3) + (if (and (lambda? attr-val) + (= (len (lambda-params attr-val)) 0)) + (fn (e) (call-lambda attr-val (list) (lambda-closure attr-val))) + attr-val)))) ;; Two-way input binding: :bind signal (= attr-name "bind") (let ((attr-val (trampoline (eval-expr attr-expr env)))) diff --git a/shared/sx/ref/async_eval_ref.py b/shared/sx/ref/async_eval_ref.py index af9c9037..eebe2b8b 100644 --- a/shared/sx/ref/async_eval_ref.py +++ b/shared/sx/ref/async_eval_ref.py @@ -26,7 +26,7 @@ import contextvars import inspect from typing import Any -from ..types import Component, Keyword, Lambda, Macro, NIL, Symbol +from ..types import Component, Island, Keyword, Lambda, Macro, NIL, Symbol from ..parser import SxExpr, serialize from ..primitives_io import IO_PRIMITIVES, RequestContext, execute_io from ..html import ( @@ -210,9 +210,11 @@ async def _arender_list(expr, env, ctx): if name in HTML_TAGS: return await _arender_element(name, expr[1:], env, ctx) - # Component + # Component / Island if name.startswith("~"): val = env.get(name) + if isinstance(val, Island): + return sx_ref.render_html_island(val, expr[1:], env) if isinstance(val, Component): return await _arender_component(val, expr[1:], env, ctx) @@ -455,6 +457,7 @@ _ASYNC_RENDER_FORMS = { "defcomp": _arsf_define, "defmacro": _arsf_define, "defhandler": _arsf_define, + "defisland": _arsf_define, "map": _arsf_map, "map-indexed": _arsf_map_indexed, "filter": _arsf_filter, @@ -505,6 +508,8 @@ async def _eval_slot_inner(expr, env, ctx): if isinstance(result, str): return SxExpr(result) return SxExpr(serialize(result)) + elif isinstance(comp, Island): + pass # Islands serialize as SX for client hydration result = await _aser(expr, env, ctx) result = await _maybe_expand_component_result(result, env, ctx) if isinstance(result, SxExpr): @@ -530,6 +535,9 @@ async def _maybe_expand_component_result(result, env, ctx): return result +_aser_stack: list[str] = [] # diagnostic: track expression context + + async def _aser(expr, env, ctx): """Evaluate for SX wire format — serialize rendering forms, evaluate control flow.""" if isinstance(expr, (int, float, bool)): @@ -553,7 +561,8 @@ async def _aser(expr, env, ctx): return False if name == "nil": return NIL - raise EvalError(f"Undefined symbol: {name}") + ctx_info = " → ".join(_aser_stack[-5:]) if _aser_stack else "(top)" + raise EvalError(f"Undefined symbol: {name} [aser context: {ctx_info}]") if isinstance(expr, Keyword): return expr.name @@ -590,7 +599,7 @@ async def _aser(expr, env, ctx): if name.startswith("html:"): return await _aser_call(name[5:], expr[1:], env, ctx) - # Component call + # Component / Island call if name.startswith("~"): val = env.get(name) if isinstance(val, Macro): @@ -598,7 +607,10 @@ async def _aser(expr, env, ctx): sx_ref.expand_macro(val, expr[1:], env) ) return await _aser(expanded, env, ctx) - if isinstance(val, Component) and _expand_components.get(): + if isinstance(val, Component) and ( + _expand_components.get() + or getattr(val, "render_target", None) == "server" + ): return await _aser_component(val, expr[1:], env, ctx) return await _aser_call(name, expr[1:], env, ctx) @@ -633,11 +645,11 @@ async def _aser(expr, env, ctx): if _svg_context.get(False): return await _aser_call(name, expr[1:], env, ctx) - # Function/lambda call + # Function/lambda call — fallback: evaluate head as callable fn = await async_eval(head, env, ctx) args = [await async_eval(a, env, ctx) for a in expr[1:]] - if callable(fn) and not isinstance(fn, (Lambda, Component)): + if callable(fn) and not isinstance(fn, (Lambda, Component, Island)): result = fn(*args) if inspect.iscoroutine(result): return await result @@ -650,7 +662,9 @@ async def _aser(expr, env, ctx): return await _aser(fn.body, local, ctx) if isinstance(fn, Component): return await _aser_call(f"~{fn.name}", expr[1:], env, ctx) - raise EvalError(f"Not callable: {fn!r}") + if isinstance(fn, Island): + return await _aser_call(f"~{fn.name}", expr[1:], env, ctx) + raise EvalError(f"Not callable in aser: {fn!r} (expr head: {head!r})") async def _aser_fragment(children, env, ctx): @@ -669,28 +683,41 @@ async def _aser_fragment(children, env, ctx): async def _aser_component(comp, args, env, ctx): - kwargs = {} - children = [] - i = 0 - while i < len(args): - arg = args[i] - if isinstance(arg, Keyword) and i + 1 < len(args): - kwargs[arg.name] = await _aser(args[i + 1], env, ctx) - i += 2 - else: - children.append(arg) - i += 1 - local = dict(comp.closure) - local.update(env) - for p in comp.params: - local[p] = kwargs.get(p, NIL) - if comp.has_children: - child_parts = [serialize(await _aser(c, env, ctx)) for c in children] - local["children"] = SxExpr("(<> " + " ".join(child_parts) + ")") - return await _aser(comp.body, local, ctx) + _aser_stack.append(f"~{comp.name}") + try: + kwargs = {} + children = [] + i = 0 + while i < len(args): + arg = args[i] + if isinstance(arg, Keyword) and i + 1 < len(args): + kwargs[arg.name] = await _aser(args[i + 1], env, ctx) + i += 2 + else: + children.append(arg) + i += 1 + local = dict(comp.closure) + local.update(env) + for p in comp.params: + local[p] = kwargs.get(p, NIL) + if comp.has_children: + child_parts = [] + for c in children: + result = await _aser(c, env, ctx) + if isinstance(result, list): + for item in result: + if item is not NIL and item is not None: + child_parts.append(serialize(item)) + elif result is not NIL and result is not None: + child_parts.append(serialize(result)) + local["children"] = SxExpr("(<> " + " ".join(child_parts) + ")") + return await _aser(comp.body, local, ctx) + finally: + _aser_stack.pop() async def _aser_call(name, args, env, ctx): + _aser_stack.append(name) token = None if name in ("svg", "math"): token = _svg_context.set(True) @@ -730,6 +757,7 @@ async def _aser_call(name, args, env, ctx): _merge_class_into_parts(parts, extra_class) return SxExpr("(" + " ".join(parts) + ")") finally: + _aser_stack.pop() if token is not None: _svg_context.reset(token) @@ -885,7 +913,7 @@ async def _assf_thread_first(expr, env, ctx): else: fn = await async_eval(form, env, ctx) fn_args = [result] - if callable(fn) and not isinstance(fn, (Lambda, Component)): + if callable(fn) and not isinstance(fn, (Lambda, Component, Island)): result = fn(*fn_args) if inspect.iscoroutine(result): result = await result @@ -981,6 +1009,7 @@ _ASER_FORMS = { "defcomp": _assf_define, "defmacro": _assf_define, "defhandler": _assf_define, + "defisland": _assf_define, "begin": _assf_begin, "do": _assf_begin, "quote": _assf_quote, diff --git a/shared/sx/ref/bootstrap_py.py b/shared/sx/ref/bootstrap_py.py index 20c2383b..875b381c 100644 --- a/shared/sx/ref/bootstrap_py.py +++ b/shared/sx/ref/bootstrap_py.py @@ -1196,6 +1196,8 @@ def compile_ref_to_py( spec_mod_set.add("deps") if "signals" in SPEC_MODULES: spec_mod_set.add("signals") + if "page-helpers" in SPEC_MODULES: + spec_mod_set.add("page-helpers") has_deps = "deps" in spec_mod_set # Core files always included, then selected adapters, then spec modules diff --git a/shared/sx/ref/page-helpers.sx b/shared/sx/ref/page-helpers.sx new file mode 100644 index 00000000..69e2ba1c --- /dev/null +++ b/shared/sx/ref/page-helpers.sx @@ -0,0 +1,368 @@ +;; ========================================================================== +;; page-helpers.sx — Pure data-transformation page helpers +;; +;; These functions take raw data (from Python I/O edge) and return +;; structured dicts for page rendering. No I/O — pure transformations +;; only. Bootstrapped to every host. +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; categorize-special-forms +;; +;; Parses define-special-form declarations from special-forms.sx AST, +;; categorizes each form by name lookup, returns dict of category → forms. +;; -------------------------------------------------------------------------- + +(define 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"}) + + +(define extract-define-kwargs + (fn (expr) + ;; Extract keyword args from a define-special-form expression. + ;; Returns dict of keyword-name → string value. + ;; Walks items pairwise: when item[i] is a keyword, item[i+1] is its value. + (let ((result {}) + (items (slice expr 2)) + (n (len items))) + (for-each + (fn (idx) + (when (and (< (+ idx 1) n) + (= (type-of (nth items idx)) "keyword")) + (let ((key (keyword-name (nth items idx))) + (val (nth items (+ idx 1)))) + (dict-set! result key + (if (= (type-of val) "list") + (str "(" (join " " (map serialize val)) ")") + (str val)))))) + (range 0 n)) + result))) + + +(define categorize-special-forms + (fn (parsed-exprs) + ;; parsed-exprs: result of parse-all on special-forms.sx + ;; Returns dict of category-name → list of form dicts. + (let ((categories {})) + (for-each + (fn (expr) + (when (and (= (type-of expr) "list") + (>= (len expr) 2) + (= (type-of (first expr)) "symbol") + (= (symbol-name (first expr)) "define-special-form")) + (let ((name (nth expr 1)) + (kwargs (extract-define-kwargs expr)) + (category (or (get special-form-category-map name) "Other"))) + (when (not (has-key? categories category)) + (dict-set! categories category (list))) + (append! (get categories category) + {"name" name + "syntax" (or (get kwargs "syntax") "") + "doc" (or (get kwargs "doc") "") + "tail-position" (or (get kwargs "tail-position") "") + "example" (or (get kwargs "example") "")})))) + parsed-exprs) + categories))) + + +;; -------------------------------------------------------------------------- +;; build-reference-data +;; +;; Takes a slug and raw reference data, returns structured dict for rendering. +;; -------------------------------------------------------------------------- + +(define build-ref-items-with-href + (fn (items base-path detail-keys n-fields) + ;; items: list of lists (tuples), each with n-fields elements + ;; base-path: e.g. "/hypermedia/reference/attributes/" + ;; detail-keys: list of strings (keys that have detail pages) + ;; n-fields: 2 or 3 (number of fields per tuple) + (map + (fn (item) + (if (= n-fields 3) + ;; [name, desc/value, exists/desc] + (let ((name (nth item 0)) + (field2 (nth item 1)) + (field3 (nth item 2))) + {"name" name + "desc" field2 + "exists" field3 + "href" (if (and field3 (some (fn (k) (= k name)) detail-keys)) + (str base-path name) + nil)}) + ;; [name, desc] + (let ((name (nth item 0)) + (desc (nth item 1))) + {"name" name + "desc" desc + "href" (if (some (fn (k) (= k name)) detail-keys) + (str base-path name) + nil)}))) + items))) + + +(define build-reference-data + (fn (slug raw-data detail-keys) + ;; slug: "attributes", "headers", "events", "js-api" + ;; raw-data: dict with the raw data lists for this slug + ;; detail-keys: list of names that have detail pages + (case slug + "attributes" + {"req-attrs" (build-ref-items-with-href + (get raw-data "req-attrs") + "/hypermedia/reference/attributes/" detail-keys 3) + "beh-attrs" (build-ref-items-with-href + (get raw-data "beh-attrs") + "/hypermedia/reference/attributes/" detail-keys 3) + "uniq-attrs" (build-ref-items-with-href + (get raw-data "uniq-attrs") + "/hypermedia/reference/attributes/" detail-keys 3)} + + "headers" + {"req-headers" (build-ref-items-with-href + (get raw-data "req-headers") + "/hypermedia/reference/headers/" detail-keys 3) + "resp-headers" (build-ref-items-with-href + (get raw-data "resp-headers") + "/hypermedia/reference/headers/" detail-keys 3)} + + "events" + {"events-list" (build-ref-items-with-href + (get raw-data "events-list") + "/hypermedia/reference/events/" detail-keys 2)} + + "js-api" + {"js-api-list" (map (fn (item) {"name" (nth item 0) "desc" (nth item 1)}) + (get raw-data "js-api-list"))} + + ;; default: attributes + :else + {"req-attrs" (build-ref-items-with-href + (get raw-data "req-attrs") + "/hypermedia/reference/attributes/" detail-keys 3) + "beh-attrs" (build-ref-items-with-href + (get raw-data "beh-attrs") + "/hypermedia/reference/attributes/" detail-keys 3) + "uniq-attrs" (build-ref-items-with-href + (get raw-data "uniq-attrs") + "/hypermedia/reference/attributes/" detail-keys 3)}))) + + +;; -------------------------------------------------------------------------- +;; build-attr-detail / build-header-detail / build-event-detail +;; +;; Lookup a slug in a detail dict, reshape for page rendering. +;; -------------------------------------------------------------------------- + +(define build-attr-detail + (fn (slug detail) + ;; detail: dict with "description", "example", "handler", "demo" keys or nil + (if (nil? detail) + {"attr-not-found" true} + {"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" (if (has-key? detail "handler") + (str "ref-wire-" + (replace (replace slug ":" "-") "*" "star")) + nil)}))) + + +(define build-header-detail + (fn (slug detail) + (if (nil? detail) + {"header-not-found" true} + {"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")}))) + + +(define build-event-detail + (fn (slug detail) + (if (nil? detail) + {"event-not-found" true} + {"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 +;; +;; Reconstruct defcomp/defisland source from component metadata. +;; -------------------------------------------------------------------------- + +(define build-component-source + (fn (comp-data) + ;; comp-data: dict with "type", "name", "params", "has-children", "body-sx", "affinity" + (let ((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 (= comp-type "not-found") + (str ";; component " name " not found") + (let ((param-strs (if (empty? params) + (if has-children + (list "&rest" "children") + (list)) + (if has-children + (append (cons "&key" params) (list "&rest" "children")) + (cons "&key" params)))) + (params-sx (str "(" (join " " param-strs) ")")) + (form-name (if (= comp-type "island") "defisland" "defcomp")) + (affinity-str (if (and (= comp-type "component") + (not (nil? affinity)) + (not (= affinity "auto"))) + (str " :affinity " affinity) + ""))) + (str "(" form-name " " name " " params-sx affinity-str "\n " body-sx ")")))))) + + +;; -------------------------------------------------------------------------- +;; build-bundle-analysis +;; +;; Compute per-page bundle stats from pre-extracted component data. +;; -------------------------------------------------------------------------- + +(define build-bundle-analysis + (fn (pages-raw components-raw total-components total-macros pure-count io-count) + ;; pages-raw: list of {:name :path :direct :needed-names} + ;; components-raw: dict of name → {:is-pure :affinity :render-target :io-refs :deps :source} + (let ((pages-data (list))) + (for-each + (fn (page) + (let ((needed-names (get page "needed-names")) + (n (len needed-names)) + (pct (if (> total-components 0) + (round (* (/ n total-components) 100)) + 0)) + (savings (- 100 pct)) + (pure-in-page 0) + (io-in-page 0) + (page-io-refs (list)) + (comp-details (list))) + ;; Walk needed components + (for-each + (fn (comp-name) + (let ((info (get components-raw comp-name))) + (when (not (nil? info)) + (if (get info "is-pure") + (set! pure-in-page (+ pure-in-page 1)) + (do + (set! io-in-page (+ io-in-page 1)) + (for-each + (fn (ref) (when (not (some (fn (r) (= r ref)) page-io-refs)) + (append! page-io-refs ref))) + (or (get info "io-refs") (list))))) + (append! comp-details + {"name" comp-name + "is-pure" (get info "is-pure") + "affinity" (get info "affinity") + "render-target" (get info "render-target") + "io-refs" (or (get info "io-refs") (list)) + "deps" (or (get info "deps") (list)) + "source" (get info "source")})))) + needed-names) + (append! pages-data + {"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" pure-in-page + "io-in-page" io-in-page + "components" comp-details}))) + pages-raw) + {"pages" pages-data + "total-components" total-components + "total-macros" total-macros + "pure-count" pure-count + "io-count" io-count}))) + + +;; -------------------------------------------------------------------------- +;; build-routing-analysis +;; +;; Classify pages by routing mode (client vs server). +;; -------------------------------------------------------------------------- + +(define build-routing-analysis + (fn (pages-raw) + ;; pages-raw: list of {:name :path :has-data :content-src} + (let ((pages-data (list)) + (client-count 0) + (server-count 0)) + (for-each + (fn (page) + (let ((has-data (get page "has-data")) + (content-src (or (get page "content-src") "")) + (mode nil) + (reason "")) + (cond + has-data + (do (set! mode "server") + (set! reason "Has :data expression — needs server IO") + (set! server-count (+ server-count 1))) + (empty? content-src) + (do (set! mode "server") + (set! reason "No content expression") + (set! server-count (+ server-count 1))) + :else + (do (set! mode "client") + (set! client-count (+ client-count 1)))) + (append! pages-data + {"name" (get page "name") + "path" (get page "path") + "mode" mode + "has-data" has-data + "content-expr" (if (> (len content-src) 80) + (str (slice content-src 0 80) "...") + content-src) + "reason" reason}))) + pages-raw) + {"pages" pages-data + "total-pages" (+ client-count server-count) + "client-count" client-count + "server-count" server-count}))) + + +;; -------------------------------------------------------------------------- +;; build-affinity-analysis +;; +;; Package component affinity info + page render plans for display. +;; -------------------------------------------------------------------------- + +(define build-affinity-analysis + (fn (demo-components page-plans) + {"components" demo-components + "page-plans" page-plans})) diff --git a/shared/sx/ref/platform_js.py b/shared/sx/ref/platform_js.py new file mode 100644 index 00000000..9dd28817 --- /dev/null +++ b/shared/sx/ref/platform_js.py @@ -0,0 +1,3163 @@ +""" +JS platform constants and functions for the SX bootstrap compiler. + +This module contains all platform-specific JS code (string constants, helper +functions, and configuration dicts) shared by bootstrap_js.py and run_js_sx.py. +The JSEmitter class, compile_ref_to_js function, and main entry point remain +in bootstrap_js.py. +""" +from __future__ import annotations + +from shared.sx.parser import parse_all +from shared.sx.types import Symbol + + +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 + +ADAPTER_FILES = { + "parser": ("parser.sx", "parser"), + "html": ("adapter-html.sx", "adapter-html"), + "sx": ("adapter-sx.sx", "adapter-sx"), + "dom": ("adapter-dom.sx", "adapter-dom"), + "engine": ("engine.sx", "engine"), + "orchestration": ("orchestration.sx","orchestration"), + "boot": ("boot.sx", "boot"), +} + +# Dependencies +ADAPTER_DEPS = { + "engine": ["dom"], + "orchestration": ["engine", "dom"], + "boot": ["dom", "engine", "orchestration", "parser"], + "parser": [], +} + +SPEC_MODULES = { + "deps": ("deps.sx", "deps (component dependency analysis)"), + "router": ("router.sx", "router (client-side route matching)"), + "signals": ("signals.sx", "signals (reactive signal runtime)"), + "page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"), +} + + +EXTENSION_NAMES = {"continuations"} +CONTINUATIONS_JS = ''' + // ========================================================================= + // Extension: Delimited continuations (shift/reset) + // ========================================================================= + + function Continuation(fn) { this.fn = fn; } + Continuation.prototype._continuation = true; + Continuation.prototype.call = function(value) { return this.fn(value !== undefined ? value : NIL); }; + + function ShiftSignal(kName, body, env) { + this.kName = kName; + this.body = body; + this.env = env; + } + + PRIMITIVES["continuation?"] = function(x) { return x != null && x._continuation === true; }; + + var _resetResume = []; + + function sfReset(args, env) { + var body = args[0]; + try { + return trampoline(evalExpr(body, env)); + } catch (e) { + if (e instanceof ShiftSignal) { + var sig = e; + var cont = new Continuation(function(value) { + if (value === undefined) value = NIL; + _resetResume.push(value); + try { + return trampoline(evalExpr(body, env)); + } finally { + _resetResume.pop(); + } + }); + var sigEnv = merge(sig.env); + sigEnv[sig.kName] = cont; + return trampoline(evalExpr(sig.body, sigEnv)); + } + throw e; + } + } + + function sfShift(args, env) { + if (_resetResume.length > 0) { + return _resetResume[_resetResume.length - 1]; + } + var kName = symbolName(args[0]); + var body = args[1]; + throw new ShiftSignal(kName, body, env); + } + + // Wrap evalList to intercept reset/shift + var _baseEvalList = evalList; + evalList = function(expr, env) { + var head = expr[0]; + if (isSym(head)) { + var name = head.name; + if (name === "reset") return sfReset(expr.slice(1), env); + if (name === "shift") return sfShift(expr.slice(1), env); + } + return _baseEvalList(expr, env); + }; + + // Wrap aserSpecial to handle reset/shift in SX wire mode + if (typeof aserSpecial === "function") { + var _baseAserSpecial = aserSpecial; + aserSpecial = function(name, expr, env) { + if (name === "reset") return sfReset(expr.slice(1), env); + if (name === "shift") return sfShift(expr.slice(1), env); + return _baseAserSpecial(name, expr, env); + }; + } + + // Wrap typeOf to recognize continuations + var _baseTypeOf = typeOf; + typeOf = function(x) { + if (x != null && x._continuation) return "continuation"; + return _baseTypeOf(x); + }; +''' + +ASYNC_IO_JS = ''' + // ========================================================================= + // Async IO: Promise-aware rendering for client-side IO primitives + // ========================================================================= + // + // IO primitives (query, current-user, etc.) return Promises on the client. + // asyncRenderToDom walks the component tree; when it encounters an IO + // primitive, it awaits the Promise and continues rendering. + // + // The sync evaluator/renderer is untouched. This is a separate async path + // used only when a page's component tree contains IO references. + + var IO_PRIMITIVES = {}; + + function registerIoPrimitive(name, fn) { + IO_PRIMITIVES[name] = fn; + } + + function isPromise(x) { + return x != null && typeof x === "object" && typeof x.then === "function"; + } + + // Async trampoline: resolves thunks, awaits Promises + function asyncTrampoline(val) { + if (isPromise(val)) return val.then(asyncTrampoline); + if (isThunk(val)) return asyncTrampoline(evalExpr(thunkExpr(val), thunkEnv(val))); + return val; + } + + // Async eval: like trampoline(evalExpr(...)) but handles IO primitives + function asyncEval(expr, env) { + // Intercept IO primitive calls at the AST level + if (Array.isArray(expr) && expr.length > 0) { + var head = expr[0]; + if (head && head._sym) { + var name = head.name; + if (IO_PRIMITIVES[name]) { + // Evaluate args, then call the IO primitive + return asyncEvalIoCall(name, expr.slice(1), env); + } + } + } + // Non-IO: use sync eval, but result might be a thunk + var result = evalExpr(expr, env); + return asyncTrampoline(result); + } + + function asyncEvalIoCall(name, rawArgs, env) { + // Parse keyword args and positional args, evaluating each (may be async) + var kwargs = {}; + var args = []; + var promises = []; + var i = 0; + while (i < rawArgs.length) { + var arg = rawArgs[i]; + if (arg && arg._kw && (i + 1) < rawArgs.length) { + var kName = arg.name; + var kVal = asyncEval(rawArgs[i + 1], env); + if (isPromise(kVal)) { + (function(k) { promises.push(kVal.then(function(v) { kwargs[k] = v; })); })(kName); + } else { + kwargs[kName] = kVal; + } + i += 2; + } else { + var aVal = asyncEval(arg, env); + if (isPromise(aVal)) { + (function(idx) { promises.push(aVal.then(function(v) { args[idx] = v; })); })(args.length); + args.push(null); // placeholder + } else { + args.push(aVal); + } + i++; + } + } + var ioFn = IO_PRIMITIVES[name]; + if (promises.length > 0) { + return Promise.all(promises).then(function() { return ioFn(args, kwargs); }); + } + return ioFn(args, kwargs); + } + + // Async render-to-dom: returns Promise or Node + function asyncRenderToDom(expr, env, ns) { + // Literals + if (expr === NIL || expr === null || expr === undefined) return null; + if (expr === true || expr === false) return null; + if (typeof expr === "string") return document.createTextNode(expr); + if (typeof expr === "number") return document.createTextNode(String(expr)); + + // Symbol -> async eval then render + if (expr && expr._sym) { + var val = asyncEval(expr, env); + if (isPromise(val)) return val.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(val, env, ns); + } + + // Keyword + if (expr && expr._kw) return document.createTextNode(expr.name); + + // DocumentFragment / DOM nodes pass through + if (expr instanceof DocumentFragment || (expr && expr.nodeType)) return expr; + + // Dict -> skip + if (expr && typeof expr === "object" && !Array.isArray(expr)) return null; + + // List + if (!Array.isArray(expr) || expr.length === 0) return null; + + var head = expr[0]; + if (!head) return null; + + // Symbol head + if (head._sym) { + var hname = head.name; + + // IO primitive + if (IO_PRIMITIVES[hname]) { + var ioResult = asyncEval(expr, env); + if (isPromise(ioResult)) return ioResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(ioResult, env, ns); + } + + // Fragment + if (hname === "<>") return asyncRenderChildren(expr.slice(1), env, ns); + + // raw! + if (hname === "raw!") { + return asyncEvalRaw(expr.slice(1), env); + } + + // Special forms that need async handling + if (hname === "if") return asyncRenderIf(expr, env, ns); + if (hname === "when") return asyncRenderWhen(expr, env, ns); + if (hname === "cond") return asyncRenderCond(expr, env, ns); + if (hname === "case") return asyncRenderCase(expr, env, ns); + if (hname === "let" || hname === "let*") return asyncRenderLet(expr, env, ns); + if (hname === "begin" || hname === "do") return asyncRenderChildren(expr.slice(1), env, ns); + if (hname === "map") return asyncRenderMap(expr, env, ns); + if (hname === "map-indexed") return asyncRenderMapIndexed(expr, env, ns); + if (hname === "for-each") return asyncRenderMap(expr, env, ns); + + // define/defcomp/defmacro — eval for side effects + if (hname === "define" || hname === "defcomp" || hname === "defmacro" || + hname === "defstyle" || hname === "defhandler") { + trampoline(evalExpr(expr, env)); + return null; + } + + // quote + if (hname === "quote") return null; + + // lambda/fn + if (hname === "lambda" || hname === "fn") { + trampoline(evalExpr(expr, env)); + return null; + } + + // and/or — eval and render result + if (hname === "and" || hname === "or" || hname === "->") { + var aoResult = asyncEval(expr, env); + if (isPromise(aoResult)) return aoResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(aoResult, env, ns); + } + + // set! + if (hname === "set!") { + asyncEval(expr, env); + return null; + } + + // Component or Island + if (hname.charAt(0) === "~") { + var comp = env[hname]; + if (comp && comp._island) return renderDomIsland(comp, expr.slice(1), env, ns); + if (comp && comp._component) return asyncRenderComponent(comp, expr.slice(1), env, ns); + if (comp && comp._macro) { + var expanded = trampoline(expandMacro(comp, expr.slice(1), env)); + return asyncRenderToDom(expanded, env, ns); + } + } + + // Macro + if (env[hname] && env[hname]._macro) { + var mac = env[hname]; + var expanded = trampoline(expandMacro(mac, expr.slice(1), env)); + return asyncRenderToDom(expanded, env, ns); + } + + // HTML tag + if (typeof renderDomElement === "function" && contains(HTML_TAGS, hname)) { + return asyncRenderElement(hname, expr.slice(1), env, ns); + } + + // html: prefix + if (hname.indexOf("html:") === 0) { + return asyncRenderElement(hname.slice(5), expr.slice(1), env, ns); + } + + // Custom element + if (hname.indexOf("-") >= 0 && expr.length > 1 && expr[1] && expr[1]._kw) { + return asyncRenderElement(hname, expr.slice(1), env, ns); + } + + // SVG context + if (ns) return asyncRenderElement(hname, expr.slice(1), env, ns); + + // Fallback: eval and render + var fResult = asyncEval(expr, env); + if (isPromise(fResult)) return fResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(fResult, env, ns); + } + + // Non-symbol head: eval call + var cResult = asyncEval(expr, env); + if (isPromise(cResult)) return cResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(cResult, env, ns); + } + + function asyncRenderChildren(exprs, env, ns) { + var frag = document.createDocumentFragment(); + var pending = []; + for (var i = 0; i < exprs.length; i++) { + var result = asyncRenderToDom(exprs[i], env, ns); + if (isPromise(result)) { + // Insert placeholder, replace when resolved + var placeholder = document.createComment("async"); + frag.appendChild(placeholder); + (function(ph) { + pending.push(result.then(function(node) { + if (node) ph.parentNode.replaceChild(node, ph); + else ph.parentNode.removeChild(ph); + })); + })(placeholder); + } else if (result) { + frag.appendChild(result); + } + } + if (pending.length > 0) { + return Promise.all(pending).then(function() { return frag; }); + } + return frag; + } + + function asyncRenderElement(tag, args, env, ns) { + var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns; + var el = domCreateElement(tag, newNs); + var pending = []; + var isVoid = contains(VOID_ELEMENTS, tag); + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + if (arg && arg._kw && (i + 1) < args.length) { + var attrName = arg.name; + var attrVal = asyncEval(args[i + 1], env); + i++; + if (isPromise(attrVal)) { + (function(an, av) { + pending.push(av.then(function(v) { + if (!isNil(v) && v !== false) { + if (contains(BOOLEAN_ATTRS, an)) { if (isSxTruthy(v)) el.setAttribute(an, ""); } + else if (v === true) el.setAttribute(an, ""); + else el.setAttribute(an, String(v)); + } + })); + })(attrName, attrVal); + } else { + if (!isNil(attrVal) && attrVal !== false) { + if (contains(BOOLEAN_ATTRS, attrName)) { + if (isSxTruthy(attrVal)) el.setAttribute(attrName, ""); + } else if (attrVal === true) { + el.setAttribute(attrName, ""); + } else { + el.setAttribute(attrName, String(attrVal)); + } + } + } + } else if (!isVoid) { + var child = asyncRenderToDom(arg, env, newNs); + if (isPromise(child)) { + var placeholder = document.createComment("async"); + el.appendChild(placeholder); + (function(ph) { + pending.push(child.then(function(node) { + if (node) ph.parentNode.replaceChild(node, ph); + else ph.parentNode.removeChild(ph); + })); + })(placeholder); + } else if (child) { + el.appendChild(child); + } + } + } + if (pending.length > 0) return Promise.all(pending).then(function() { return el; }); + return el; + } + + function asyncRenderComponent(comp, args, env, ns) { + var kwargs = {}; + var children = []; + var pending = []; + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + if (arg && arg._kw && (i + 1) < args.length) { + var kName = arg.name; + var kVal = asyncEval(args[i + 1], env); + if (isPromise(kVal)) { + (function(k) { pending.push(kVal.then(function(v) { kwargs[k] = v; })); })(kName); + } else { + kwargs[kName] = kVal; + } + i++; + } else { + children.push(arg); + } + } + + function doRender() { + var local = Object.create(componentClosure(comp)); + for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; + var params = componentParams(comp); + for (var j = 0; j < params.length; j++) { + local[params[j]] = params[j] in kwargs ? kwargs[params[j]] : NIL; + } + if (componentHasChildren(comp)) { + var childResult = asyncRenderChildren(children, env, ns); + if (isPromise(childResult)) { + return childResult.then(function(childFrag) { + local["children"] = childFrag; + return asyncRenderToDom(componentBody(comp), local, ns); + }); + } + local["children"] = childResult; + } + return asyncRenderToDom(componentBody(comp), local, ns); + } + + if (pending.length > 0) return Promise.all(pending).then(doRender); + return doRender(); + } + + function asyncRenderIf(expr, env, ns) { + var cond = asyncEval(expr[1], env); + if (isPromise(cond)) { + return cond.then(function(v) { + return isSxTruthy(v) + ? asyncRenderToDom(expr[2], env, ns) + : (expr.length > 3 ? asyncRenderToDom(expr[3], env, ns) : null); + }); + } + return isSxTruthy(cond) + ? asyncRenderToDom(expr[2], env, ns) + : (expr.length > 3 ? asyncRenderToDom(expr[3], env, ns) : null); + } + + function asyncRenderWhen(expr, env, ns) { + var cond = asyncEval(expr[1], env); + if (isPromise(cond)) { + return cond.then(function(v) { + return isSxTruthy(v) ? asyncRenderChildren(expr.slice(2), env, ns) : null; + }); + } + return isSxTruthy(cond) ? asyncRenderChildren(expr.slice(2), env, ns) : null; + } + + function asyncRenderCond(expr, env, ns) { + var clauses = expr.slice(1); + function step(idx) { + if (idx >= clauses.length) return null; + var clause = clauses[idx]; + if (!Array.isArray(clause) || clause.length < 2) return step(idx + 1); + var test = clause[0]; + if ((test && test._sym && (test.name === "else" || test.name === ":else")) || + (test && test._kw && test.name === "else")) { + return asyncRenderToDom(clause[1], env, ns); + } + var v = asyncEval(test, env); + if (isPromise(v)) return v.then(function(r) { return isSxTruthy(r) ? asyncRenderToDom(clause[1], env, ns) : step(idx + 1); }); + return isSxTruthy(v) ? asyncRenderToDom(clause[1], env, ns) : step(idx + 1); + } + return step(0); + } + + function asyncRenderCase(expr, env, ns) { + var matchVal = asyncEval(expr[1], env); + function doCase(mv) { + var clauses = expr.slice(2); + for (var i = 0; i < clauses.length - 1; i += 2) { + var test = clauses[i]; + if ((test && test._kw && test.name === "else") || + (test && test._sym && (test.name === "else" || test.name === ":else"))) { + return asyncRenderToDom(clauses[i + 1], env, ns); + } + var tv = trampoline(evalExpr(test, env)); + if (mv === tv || (typeof mv === "string" && typeof tv === "string" && mv === tv)) { + return asyncRenderToDom(clauses[i + 1], env, ns); + } + } + return null; + } + if (isPromise(matchVal)) return matchVal.then(doCase); + return doCase(matchVal); + } + + function asyncRenderLet(expr, env, ns) { + var bindings = expr[1]; + var local = Object.create(env); + for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; + function bindStep(idx) { + if (!Array.isArray(bindings)) return asyncRenderChildren(expr.slice(2), local, ns); + // Nested pairs: ((a 1) (b 2)) + if (bindings.length > 0 && Array.isArray(bindings[0])) { + if (idx >= bindings.length) return asyncRenderChildren(expr.slice(2), local, ns); + var b = bindings[idx]; + var vname = b[0]._sym ? b[0].name : String(b[0]); + var val = asyncEval(b[1], local); + if (isPromise(val)) return val.then(function(v) { local[vname] = v; return bindStep(idx + 1); }); + local[vname] = val; + return bindStep(idx + 1); + } + // Flat pairs: (a 1 b 2) + if (idx >= bindings.length) return asyncRenderChildren(expr.slice(2), local, ns); + var vn = bindings[idx]._sym ? bindings[idx].name : String(bindings[idx]); + var vv = asyncEval(bindings[idx + 1], local); + if (isPromise(vv)) return vv.then(function(v) { local[vn] = v; return bindStep(idx + 2); }); + local[vn] = vv; + return bindStep(idx + 2); + } + return bindStep(0); + } + + function asyncRenderMap(expr, env, ns) { + var fn = asyncEval(expr[1], env); + var coll = asyncEval(expr[2], env); + function doMap(f, c) { + if (!Array.isArray(c)) return null; + var frag = document.createDocumentFragment(); + var pending = []; + for (var i = 0; i < c.length; i++) { + var item = c[i]; + var result; + if (f && f._lambda) { + var lenv = Object.create(f.closure || env); + for (var k in env) if (env.hasOwnProperty(k)) lenv[k] = env[k]; + lenv[f.params[0]] = item; + result = asyncRenderToDom(f.body, lenv, null); + } else if (typeof f === "function") { + var r = f(item); + result = isPromise(r) ? r.then(function(v) { return asyncRenderToDom(v, env, null); }) : asyncRenderToDom(r, env, null); + } else { + result = asyncRenderToDom(item, env, null); + } + if (isPromise(result)) { + var ph = document.createComment("async"); + frag.appendChild(ph); + (function(p) { pending.push(result.then(function(n) { if (n) p.parentNode.replaceChild(n, p); else p.parentNode.removeChild(p); })); })(ph); + } else if (result) { + frag.appendChild(result); + } + } + if (pending.length) return Promise.all(pending).then(function() { return frag; }); + return frag; + } + if (isPromise(fn) || isPromise(coll)) { + return Promise.all([isPromise(fn) ? fn : Promise.resolve(fn), isPromise(coll) ? coll : Promise.resolve(coll)]) + .then(function(r) { return doMap(r[0], r[1]); }); + } + return doMap(fn, coll); + } + + function asyncRenderMapIndexed(expr, env, ns) { + var fn = asyncEval(expr[1], env); + var coll = asyncEval(expr[2], env); + function doMap(f, c) { + if (!Array.isArray(c)) return null; + var frag = document.createDocumentFragment(); + var pending = []; + for (var i = 0; i < c.length; i++) { + var item = c[i]; + var result; + if (f && f._lambda) { + var lenv = Object.create(f.closure || env); + for (var k in env) if (env.hasOwnProperty(k)) lenv[k] = env[k]; + lenv[f.params[0]] = i; + lenv[f.params[1]] = item; + result = asyncRenderToDom(f.body, lenv, null); + } else if (typeof f === "function") { + var r = f(i, item); + result = isPromise(r) ? r.then(function(v) { return asyncRenderToDom(v, env, null); }) : asyncRenderToDom(r, env, null); + } else { + result = asyncRenderToDom(item, env, null); + } + if (isPromise(result)) { + var ph = document.createComment("async"); + frag.appendChild(ph); + (function(p) { pending.push(result.then(function(n) { if (n) p.parentNode.replaceChild(n, p); else p.parentNode.removeChild(p); })); })(ph); + } else if (result) { + frag.appendChild(result); + } + } + if (pending.length) return Promise.all(pending).then(function() { return frag; }); + return frag; + } + if (isPromise(fn) || isPromise(coll)) { + return Promise.all([isPromise(fn) ? fn : Promise.resolve(fn), isPromise(coll) ? coll : Promise.resolve(coll)]) + .then(function(r) { return doMap(r[0], r[1]); }); + } + return doMap(fn, coll); + } + + function asyncEvalRaw(args, env) { + var parts = []; + var pending = []; + for (var i = 0; i < args.length; i++) { + var val = asyncEval(args[i], env); + if (isPromise(val)) { + (function(idx) { + pending.push(val.then(function(v) { parts[idx] = v; })); + })(parts.length); + parts.push(null); + } else { + parts.push(val); + } + } + function assemble() { + var html = ""; + for (var j = 0; j < parts.length; j++) { + var p = parts[j]; + if (p && p._rawHtml) html += p.html; + else if (typeof p === "string") html += p; + else if (p != null && !isNil(p)) html += String(p); + } + var el = document.createElement("span"); + el.innerHTML = html; + var frag = document.createDocumentFragment(); + while (el.firstChild) frag.appendChild(el.firstChild); + return frag; + } + if (pending.length) return Promise.all(pending).then(assemble); + return assemble(); + } + + // Async version of sxRenderWithEnv — returns Promise + function asyncSxRenderWithEnv(source, extraEnv) { + var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + var exprs = parse(source); + if (!_hasDom) return Promise.resolve(null); + return asyncRenderChildren(exprs, env, null); + } + + // IO proxy cache: key → { value, expires } + var _ioCache = {}; + var IO_CACHE_TTL = 300000; // 5 minutes + + // Register a server-proxied IO primitive: fetches from /sx/io/ + // Uses GET for short args, POST for long payloads (URL length safety). + // Results are cached client-side by (name + args) with a TTL. + function registerProxiedIo(name) { + registerIoPrimitive(name, function(args, kwargs) { + // Cache key: name + serialized args + var cacheKey = name; + for (var ci = 0; ci < args.length; ci++) cacheKey += "\0" + String(args[ci]); + for (var ck in kwargs) { + if (kwargs.hasOwnProperty(ck)) cacheKey += "\0" + ck + "=" + String(kwargs[ck]); + } + var cached = _ioCache[cacheKey]; + if (cached && cached.expires > Date.now()) return cached.value; + + var url = "/sx/io/" + encodeURIComponent(name); + var qs = []; + for (var i = 0; i < args.length; i++) { + qs.push("_arg" + i + "=" + encodeURIComponent(String(args[i]))); + } + for (var k in kwargs) { + if (kwargs.hasOwnProperty(k)) { + qs.push(encodeURIComponent(k) + "=" + encodeURIComponent(String(kwargs[k]))); + } + } + var queryStr = qs.join("&"); + var fetchOpts; + if (queryStr.length > 1500) { + // POST with JSON body for long payloads + var sArgs = []; + for (var j = 0; j < args.length; j++) sArgs.push(String(args[j])); + var sKwargs = {}; + for (var kk in kwargs) { + if (kwargs.hasOwnProperty(kk)) sKwargs[kk] = String(kwargs[kk]); + } + var postHeaders = { "SX-Request": "true", "Content-Type": "application/json" }; + var csrf = csrfToken(); + if (csrf && csrf !== NIL) postHeaders["X-CSRFToken"] = csrf; + fetchOpts = { + method: "POST", + headers: postHeaders, + body: JSON.stringify({ args: sArgs, kwargs: sKwargs }) + }; + } else { + if (queryStr) url += "?" + queryStr; + fetchOpts = { headers: { "SX-Request": "true" } }; + } + var result = fetch(url, fetchOpts) + .then(function(resp) { + if (!resp.ok) { + logWarn("sx:io " + name + " failed " + resp.status); + return NIL; + } + return resp.text(); + }) + .then(function(text) { + if (!text || text === "nil") return NIL; + try { + var exprs = parse(text); + var val = exprs.length === 1 ? exprs[0] : exprs; + _ioCache[cacheKey] = { value: val, expires: Date.now() + IO_CACHE_TTL }; + return val; + } catch (e) { + logWarn("sx:io " + name + " parse error: " + (e && e.message ? e.message : e)); + return NIL; + } + }) + .catch(function(e) { + logWarn("sx:io " + name + " network error: " + (e && e.message ? e.message : e)); + return NIL; + }); + // Cache the in-flight promise too (dedup concurrent calls for same args) + _ioCache[cacheKey] = { value: result, expires: Date.now() + IO_CACHE_TTL }; + return result; + }); + } + + // Register IO deps as proxied primitives (idempotent, called per-page) + function registerIoDeps(names) { + if (!names || !names.length) return; + var registered = 0; + for (var i = 0; i < names.length; i++) { + var name = names[i]; + if (!IO_PRIMITIVES[name]) { + registerProxiedIo(name); + registered++; + } + } + if (registered > 0) { + logInfo("sx:io registered " + registered + " proxied primitives: " + names.join(", ")); + } + } +''' + +PREAMBLE = '''\ +/** + * sx-ref.js — Generated from reference SX evaluator specification. + * + * Bootstrap-compiled from shared/sx/ref/{eval,render,primitives}.sx + * Compare against hand-written sx.js for correctness verification. + * + * DO NOT EDIT — regenerate with: python bootstrap_js.py + */ +;(function(global) { + "use strict"; + + // ========================================================================= + // Types + // ========================================================================= + + var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); + var SX_VERSION = "BUILD_TIMESTAMP"; + + function isNil(x) { return x === NIL || x === null || x === undefined; } + function isSxTruthy(x) { return x !== false && !isNil(x); } + + function Symbol(name) { this.name = name; } + Symbol.prototype.toString = function() { return this.name; }; + Symbol.prototype._sym = true; + + function Keyword(name) { this.name = name; } + Keyword.prototype.toString = function() { return ":" + this.name; }; + Keyword.prototype._kw = true; + + function Lambda(params, body, closure, name) { + this.params = params; + this.body = body; + this.closure = closure || {}; + this.name = name || null; + } + Lambda.prototype._lambda = true; + + function Component(name, params, hasChildren, body, closure, affinity) { + this.name = name; + this.params = params; + this.hasChildren = hasChildren; + this.body = body; + this.closure = closure || {}; + this.affinity = affinity || "auto"; + } + Component.prototype._component = true; + + function Island(name, params, hasChildren, body, closure) { + this.name = name; + this.params = params; + this.hasChildren = hasChildren; + this.body = body; + this.closure = closure || {}; + } + Island.prototype._island = true; + + function SxSignal(value) { + this.value = value; + this.subscribers = []; + this.deps = []; + } + SxSignal.prototype._signal = true; + + function TrackingCtx(notifyFn) { + this.notifyFn = notifyFn; + this.deps = []; + } + + var _trackingContext = null; + + function Macro(params, restParam, body, closure, name) { + this.params = params; + this.restParam = restParam; + this.body = body; + this.closure = closure || {}; + this.name = name || null; + } + Macro.prototype._macro = true; + + function Thunk(expr, env) { this.expr = expr; this.env = env; } + Thunk.prototype._thunk = true; + + function RawHTML(html) { this.html = html; } + RawHTML.prototype._raw = true; + + function isSym(x) { return x != null && x._sym === true; } + function isKw(x) { return x != null && x._kw === true; } + + function merge() { + var out = {}; + for (var i = 0; i < arguments.length; i++) { + var d = arguments[i]; + if (d) for (var k in d) out[k] = d[k]; + } + return out; + } + + function sxOr() { + for (var i = 0; i < arguments.length; i++) { + if (isSxTruthy(arguments[i])) return arguments[i]; + } + return arguments.length ? arguments[arguments.length - 1] : false; + }''' +PRIMITIVES_JS_MODULES: dict[str, str] = { + "core.arithmetic": ''' + // core.arithmetic + PRIMITIVES["+"] = function() { var s = 0; for (var i = 0; i < arguments.length; i++) s += arguments[i]; return s; }; + PRIMITIVES["-"] = function(a, b) { return arguments.length === 1 ? -a : a - b; }; + PRIMITIVES["*"] = function() { var s = 1; for (var i = 0; i < arguments.length; i++) s *= arguments[i]; return s; }; + PRIMITIVES["/"] = function(a, b) { return a / b; }; + PRIMITIVES["mod"] = function(a, b) { return a % b; }; + PRIMITIVES["inc"] = function(n) { return n + 1; }; + PRIMITIVES["dec"] = function(n) { return n - 1; }; + PRIMITIVES["abs"] = Math.abs; + PRIMITIVES["floor"] = Math.floor; + PRIMITIVES["ceil"] = Math.ceil; + PRIMITIVES["round"] = function(x, n) { + if (n === undefined || n === 0) return Math.round(x); + var f = Math.pow(10, n); return Math.round(x * f) / f; + }; + PRIMITIVES["min"] = Math.min; + PRIMITIVES["max"] = Math.max; + PRIMITIVES["sqrt"] = Math.sqrt; + PRIMITIVES["pow"] = Math.pow; + PRIMITIVES["clamp"] = function(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); }; +''', + + "core.comparison": ''' + // core.comparison + PRIMITIVES["="] = function(a, b) { return a === b; }; + PRIMITIVES["!="] = function(a, b) { return a !== b; }; + PRIMITIVES["<"] = function(a, b) { return a < b; }; + PRIMITIVES[">"] = function(a, b) { return a > b; }; + PRIMITIVES["<="] = function(a, b) { return a <= b; }; + PRIMITIVES[">="] = function(a, b) { return a >= b; }; +''', + + "core.logic": ''' + // core.logic + PRIMITIVES["not"] = function(x) { return !isSxTruthy(x); }; +''', + + "core.predicates": ''' + // core.predicates + PRIMITIVES["nil?"] = isNil; + PRIMITIVES["number?"] = function(x) { return typeof x === "number"; }; + PRIMITIVES["string?"] = function(x) { return typeof x === "string"; }; + PRIMITIVES["list?"] = Array.isArray; + PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; }; + PRIMITIVES["empty?"] = function(c) { return isNil(c) || (Array.isArray(c) ? c.length === 0 : typeof c === "string" ? c.length === 0 : Object.keys(c).length === 0); }; + PRIMITIVES["contains?"] = function(c, k) { + if (typeof c === "string") return c.indexOf(String(k)) !== -1; + if (Array.isArray(c)) return c.indexOf(k) !== -1; + return k in c; + }; + PRIMITIVES["odd?"] = function(n) { return n % 2 !== 0; }; + PRIMITIVES["even?"] = function(n) { return n % 2 === 0; }; + PRIMITIVES["zero?"] = function(n) { return n === 0; }; + PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; }; + PRIMITIVES["component-affinity"] = componentAffinity; +''', + + "core.strings": ''' + // core.strings + PRIMITIVES["str"] = function() { + var p = []; + for (var i = 0; i < arguments.length; i++) { + var v = arguments[i]; if (isNil(v)) continue; p.push(String(v)); + } + return p.join(""); + }; + PRIMITIVES["upper"] = function(s) { return String(s).toUpperCase(); }; + PRIMITIVES["lower"] = function(s) { return String(s).toLowerCase(); }; + PRIMITIVES["trim"] = function(s) { return String(s).trim(); }; + PRIMITIVES["split"] = function(s, sep) { return String(s).split(sep || " "); }; + PRIMITIVES["join"] = function(sep, coll) { return coll.join(sep); }; + PRIMITIVES["replace"] = function(s, old, nw) { return s.split(old).join(nw); }; + PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); }; + PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; }; + PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; }; + PRIMITIVES["slice"] = function(c, a, b) { return b !== undefined ? c.slice(a, b) : c.slice(a); }; + PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); }; + PRIMITIVES["string-length"] = function(s) { return String(s).length; }; + PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; }; + PRIMITIVES["concat"] = function() { + var out = []; + for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]); + return out; + }; +''', + + "core.collections": ''' + // core.collections + PRIMITIVES["list"] = function() { return Array.prototype.slice.call(arguments); }; + PRIMITIVES["dict"] = function() { + var d = {}; + for (var i = 0; i < arguments.length - 1; i += 2) d[arguments[i]] = arguments[i + 1]; + return d; + }; + PRIMITIVES["range"] = function(a, b, step) { + var r = []; step = step || 1; + for (var i = a; step > 0 ? i < b : i > b; i += step) r.push(i); + return r; + }; + PRIMITIVES["get"] = function(c, k, def) { var v = (c && c[k]); return v !== undefined ? v : (def !== undefined ? def : NIL); }; + PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; }; + PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; }; + PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; }; + PRIMITIVES["rest"] = function(c) { return c ? c.slice(1) : []; }; + PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; }; + PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); }; + PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); }; + PRIMITIVES["append!"] = function(arr, x) { arr.push(x); return arr; }; + PRIMITIVES["chunk-every"] = function(c, n) { + var r = []; for (var i = 0; i < c.length; i += n) r.push(c.slice(i, i + n)); return r; + }; + PRIMITIVES["zip-pairs"] = function(c) { + var r = []; for (var i = 0; i < c.length - 1; i++) r.push([c[i], c[i + 1]]); return r; + }; + PRIMITIVES["reverse"] = function(c) { return Array.isArray(c) ? c.slice().reverse() : String(c).split("").reverse().join(""); }; + PRIMITIVES["flatten"] = function(c) { + var out = []; + function walk(a) { for (var i = 0; i < a.length; i++) Array.isArray(a[i]) ? walk(a[i]) : out.push(a[i]); } + walk(c || []); return out; + }; +''', + + "core.dict": ''' + // core.dict + PRIMITIVES["keys"] = function(d) { return Object.keys(d || {}); }; + PRIMITIVES["vals"] = function(d) { var r = []; for (var k in d) r.push(d[k]); return r; }; + PRIMITIVES["merge"] = function() { + var out = {}; + for (var i = 0; i < arguments.length; i++) { var d = arguments[i]; if (d && !isNil(d)) for (var k in d) out[k] = d[k]; } + return out; + }; + PRIMITIVES["assoc"] = function(d) { + var out = {}; if (d && !isNil(d)) for (var k in d) out[k] = d[k]; + for (var i = 1; i < arguments.length - 1; i += 2) out[arguments[i]] = arguments[i + 1]; + return out; + }; + PRIMITIVES["dissoc"] = function(d) { + var out = {}; for (var k in d) out[k] = d[k]; + for (var i = 1; i < arguments.length; i++) delete out[arguments[i]]; + return out; + }; + PRIMITIVES["dict-set!"] = function(d, k, v) { d[k] = v; return v; }; + PRIMITIVES["has-key?"] = function(d, k) { return d !== null && d !== undefined && k in d; }; + PRIMITIVES["into"] = function(target, coll) { + if (Array.isArray(target)) return Array.isArray(coll) ? coll.slice() : Object.entries(coll); + var r = {}; for (var i = 0; i < coll.length; i++) { var p = coll[i]; if (Array.isArray(p) && p.length >= 2) r[p[0]] = p[1]; } + return r; + }; +''', + + "stdlib.format": ''' + // stdlib.format + PRIMITIVES["format-decimal"] = function(v, p) { return Number(v).toFixed(p || 2); }; + PRIMITIVES["parse-int"] = function(v, d) { var n = parseInt(v, 10); return isNaN(n) ? (d || 0) : n; }; + PRIMITIVES["format-date"] = function(s, fmt) { + if (!s) return ""; + try { + var d = new Date(s); + if (isNaN(d.getTime())) return String(s); + var months = ["January","February","March","April","May","June","July","August","September","October","November","December"]; + var short_months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; + return fmt.replace(/%-d/g, d.getDate()).replace(/%d/g, ("0"+d.getDate()).slice(-2)) + .replace(/%B/g, months[d.getMonth()]).replace(/%b/g, short_months[d.getMonth()]) + .replace(/%Y/g, d.getFullYear()).replace(/%m/g, ("0"+(d.getMonth()+1)).slice(-2)) + .replace(/%H/g, ("0"+d.getHours()).slice(-2)).replace(/%M/g, ("0"+d.getMinutes()).slice(-2)); + } catch (e) { return String(s); } + }; + PRIMITIVES["parse-datetime"] = function(s) { return s ? String(s) : NIL; }; +''', + + "stdlib.text": ''' + // stdlib.text + PRIMITIVES["pluralize"] = function(n, s, p) { + if (s || (p && p !== "s")) return n == 1 ? (s || "") : (p || "s"); + return n == 1 ? "" : "s"; + }; + PRIMITIVES["escape"] = function(s) { + return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"); + }; + PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); }; +''', + + "stdlib.debug": ''' + // stdlib.debug + PRIMITIVES["assert"] = function(cond, msg) { + if (!isSxTruthy(cond)) throw new Error("Assertion error: " + (msg || "Assertion failed")); + return true; + }; +''', +} +# Modules to include by default (all) +_ALL_JS_MODULES = list(PRIMITIVES_JS_MODULES.keys()) + +def _assemble_primitives_js(modules: list[str] | None = None) -> str: + """Assemble JS primitive code from selected modules. + + If modules is None, all modules are included. + Core modules are always included regardless of the list. + """ + if modules is None: + modules = _ALL_JS_MODULES + parts = [] + for mod in modules: + if mod in PRIMITIVES_JS_MODULES: + parts.append(PRIMITIVES_JS_MODULES[mod]) + return "\n".join(parts) + +PLATFORM_JS_PRE = ''' + // ========================================================================= + // Platform interface — JS implementation + // ========================================================================= + + function typeOf(x) { + if (isNil(x)) return "nil"; + if (typeof x === "number") return "number"; + if (typeof x === "string") return "string"; + if (typeof x === "boolean") return "boolean"; + if (x._sym) return "symbol"; + if (x._kw) return "keyword"; + if (x._thunk) return "thunk"; + if (x._lambda) return "lambda"; + if (x._component) return "component"; + if (x._island) return "island"; + if (x._signal) return "signal"; + if (x._macro) return "macro"; + if (x._raw) return "raw-html"; + if (typeof Node !== "undefined" && x instanceof Node) return "dom-node"; + if (Array.isArray(x)) return "list"; + if (typeof x === "object") return "dict"; + return "unknown"; + } + + function symbolName(s) { return s.name; } + function keywordName(k) { return k.name; } + function makeSymbol(n) { return new Symbol(n); } + function makeKeyword(n) { return new Keyword(n); } + + function makeLambda(params, body, env) { return new Lambda(params, body, merge(env)); } + function makeComponent(name, params, hasChildren, body, env, affinity) { + return new Component(name, params, hasChildren, body, merge(env), affinity); + } + function makeMacro(params, restParam, body, env, name) { + return new Macro(params, restParam, body, merge(env), name); + } + function makeThunk(expr, env) { return new Thunk(expr, env); } + + function lambdaParams(f) { return f.params; } + function lambdaBody(f) { return f.body; } + function lambdaClosure(f) { return f.closure; } + function lambdaName(f) { return f.name; } + function setLambdaName(f, n) { f.name = n; } + + function componentParams(c) { return c.params; } + function componentBody(c) { return c.body; } + function componentClosure(c) { return c.closure; } + function componentHasChildren(c) { return c.hasChildren; } + function componentName(c) { return c.name; } + function componentAffinity(c) { return c.affinity || "auto"; } + + function macroParams(m) { return m.params; } + function macroRestParam(m) { return m.restParam; } + function macroBody(m) { return m.body; } + function macroClosure(m) { return m.closure; } + + function isThunk(x) { return x != null && x._thunk === true; } + function thunkExpr(t) { return t.expr; } + function thunkEnv(t) { return t.env; } + + function isCallable(x) { return typeof x === "function" || (x != null && x._lambda === true); } + function isLambda(x) { return x != null && x._lambda === true; } + function isComponent(x) { return x != null && x._component === true; } + function isIsland(x) { return x != null && x._island === true; } + function isMacro(x) { return x != null && x._macro === true; } + function isIdentical(a, b) { return a === b; } + + // Island platform + function makeIsland(name, params, hasChildren, body, env) { + return new Island(name, params, hasChildren, body, merge(env)); + } + + // Signal platform + function makeSignal(value) { return new SxSignal(value); } + function isSignal(x) { return x != null && x._signal === true; } + function signalValue(s) { return s.value; } + function signalSetValue(s, v) { s.value = v; } + function signalSubscribers(s) { return s.subscribers.slice(); } + function signalAddSub(s, fn) { if (s.subscribers.indexOf(fn) < 0) s.subscribers.push(fn); } + function signalRemoveSub(s, fn) { var i = s.subscribers.indexOf(fn); if (i >= 0) s.subscribers.splice(i, 1); } + function signalDeps(s) { return s.deps.slice(); } + function signalSetDeps(s, deps) { s.deps = Array.isArray(deps) ? deps.slice() : []; } + function setTrackingContext(ctx) { _trackingContext = ctx; } + function getTrackingContext() { return _trackingContext || NIL; } + function makeTrackingContext(notifyFn) { return new TrackingCtx(notifyFn); } + function trackingContextDeps(ctx) { return ctx ? ctx.deps : []; } + function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); } + function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; } + + // invoke — call any callable (native fn or SX lambda) with args. + // Transpiled code emits direct calls f(args) which fail on SX lambdas + // from runtime-evaluated island bodies. invoke dispatches correctly. + function invoke() { + var f = arguments[0]; + var args = Array.prototype.slice.call(arguments, 1); + if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f))); + if (typeof f === 'function') return f.apply(null, args); + return NIL; + } + + // JSON / dict helpers for island state serialization + function jsonSerialize(obj) { + try { return JSON.stringify(obj); } catch(e) { return "{}"; } + } + function isEmptyDict(d) { + if (!d || typeof d !== "object") return true; + for (var k in d) if (d.hasOwnProperty(k)) return false; + return true; + } + + function envHas(env, name) { return name in env; } + function envGet(env, name) { return env[name]; } + function envSet(env, name, val) { env[name] = val; } + function envExtend(env) { return Object.create(env); } + function envMerge(base, overlay) { + var child = Object.create(base); + if (overlay) for (var k in overlay) if (overlay.hasOwnProperty(k)) child[k] = overlay[k]; + return child; + } + + function dictSet(d, k, v) { d[k] = v; return v; } + function dictGet(d, k) { var v = d[k]; return v !== undefined ? v : NIL; } + + // Render-expression detection — lets the evaluator delegate to the active adapter. + // Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements. + // Placeholder — overridden by transpiled version from render.sx + function isRenderExpr(expr) { return false; } + + // Render dispatch — call the active adapter's render function. + // Set by each adapter when loaded; defaults to identity (no rendering). + var _renderExprFn = null; + + // Render mode flag — set by render-to-html/aser, checked by eval-list. + // When false, render expressions fall through to evalCall. + var _renderMode = false; + function renderActiveP() { return _renderMode; } + function setRenderActiveB(val) { _renderMode = !!val; } + + function renderExpr(expr, env) { + if (_renderExprFn) return _renderExprFn(expr, env); + // No adapter loaded — fall through to evalCall + return evalCall(first(expr), rest(expr), env); + } + + function stripPrefix(s, prefix) { + return s.indexOf(prefix) === 0 ? s.slice(prefix.length) : s; + } + + function error(msg) { throw new Error(msg); } + function inspect(x) { return JSON.stringify(x); } + +''' + + +PLATFORM_JS_POST = ''' + function isPrimitive(name) { return name in PRIMITIVES; } + function getPrimitive(name) { return PRIMITIVES[name]; } + + // Higher-order helpers used by the transpiled code + function map(fn, coll) { return coll.map(fn); } + function mapIndexed(fn, coll) { return coll.map(function(item, i) { return fn(i, item); }); } + function filter(fn, coll) { return coll.filter(function(x) { return isSxTruthy(fn(x)); }); } + function reduce(fn, init, coll) { + var acc = init; + for (var i = 0; i < coll.length; i++) acc = fn(acc, coll[i]); + return acc; + } + function some(fn, coll) { + for (var i = 0; i < coll.length; i++) { var r = fn(coll[i]); if (isSxTruthy(r)) return r; } + return NIL; + } + function forEach(fn, coll) { for (var i = 0; i < coll.length; i++) fn(coll[i]); return NIL; } + function isEvery(fn, coll) { + for (var i = 0; i < coll.length; i++) { if (!isSxTruthy(fn(coll[i]))) return false; } + return true; + } + function mapDict(fn, d) { var r = {}; for (var k in d) r[k] = fn(k, d[k]); return r; } + + // List primitives used directly by transpiled code + var len = PRIMITIVES["len"]; + var first = PRIMITIVES["first"]; + var last = PRIMITIVES["last"]; + var rest = PRIMITIVES["rest"]; + var nth = PRIMITIVES["nth"]; + var cons = PRIMITIVES["cons"]; + var append = PRIMITIVES["append"]; + var isEmpty = PRIMITIVES["empty?"]; + var contains = PRIMITIVES["contains?"]; + var startsWith = PRIMITIVES["starts-with?"]; + var slice = PRIMITIVES["slice"]; + var concat = PRIMITIVES["concat"]; + var str = PRIMITIVES["str"]; + var join = PRIMITIVES["join"]; + var keys = PRIMITIVES["keys"]; + var get = PRIMITIVES["get"]; + var assoc = PRIMITIVES["assoc"]; + var range = PRIMITIVES["range"]; + function zip(a, b) { var r = []; for (var i = 0; i < Math.min(a.length, b.length); i++) r.push([a[i], b[i]]); return r; } + function append_b(arr, x) { arr.push(x); return arr; } + var apply = function(f, args) { + if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f))); + return f.apply(null, args); + }; + + // Additional primitive aliases used by adapter/engine transpiled code + var split = PRIMITIVES["split"]; + var trim = PRIMITIVES["trim"]; + var upper = PRIMITIVES["upper"]; + var lower = PRIMITIVES["lower"]; + var replace_ = function(s, old, nw) { return s.split(old).join(nw); }; + var endsWith = PRIMITIVES["ends-with?"]; + var parseInt_ = PRIMITIVES["parse-int"]; + var dict_fn = PRIMITIVES["dict"]; + + // HTML rendering helpers + function escapeHtml(s) { + return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,"""); + } + function escapeAttr(s) { return escapeHtml(s); } + function rawHtmlContent(r) { return r.html; } + function makeRawHtml(s) { return { _raw: true, html: s }; } + function sxExprSource(x) { return x && x.source ? x.source : String(x); } + + // Placeholders — overridden by transpiled spec from parser.sx / adapter-sx.sx + function serialize(val) { return String(val); } + function isSpecialForm(n) { return false; } + function isHoForm(n) { return false; } + + // processBindings and evalCond — now specced in render.sx, bootstrapped above + + function isDefinitionForm(name) { + return name === "define" || name === "defcomp" || name === "defmacro" || + name === "defstyle" || name === "defhandler"; + } + + function indexOf_(s, ch) { + return typeof s === "string" ? s.indexOf(ch) : -1; + } + + function dictHas(d, k) { return d != null && k in d; } + function dictDelete(d, k) { delete d[k]; } + + function forEachIndexed(fn, coll) { + for (var i = 0; i < coll.length; i++) fn(i, coll[i]); + return NIL; + } + + // ========================================================================= + // Performance overrides — evaluator hot path + // ========================================================================= + + // Override parseKeywordArgs: imperative loop instead of reduce+assoc + parseKeywordArgs = function(rawArgs, env) { + var kwargs = {}; + var children = []; + for (var i = 0; i < rawArgs.length; i++) { + var arg = rawArgs[i]; + if (arg && arg._kw && (i + 1) < rawArgs.length) { + kwargs[arg.name] = trampoline(evalExpr(rawArgs[i + 1], env)); + i++; + } else { + children.push(trampoline(evalExpr(arg, env))); + } + } + return [kwargs, children]; + }; + + // Override callComponent: use prototype chain env, imperative kwarg binding + callComponent = function(comp, rawArgs, env) { + var kwargs = {}; + var children = []; + for (var i = 0; i < rawArgs.length; i++) { + var arg = rawArgs[i]; + if (arg && arg._kw && (i + 1) < rawArgs.length) { + kwargs[arg.name] = trampoline(evalExpr(rawArgs[i + 1], env)); + i++; + } else { + children.push(trampoline(evalExpr(arg, env))); + } + } + var local = Object.create(componentClosure(comp)); + for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; + var params = componentParams(comp); + for (var j = 0; j < params.length; j++) { + var p = params[j]; + local[p] = p in kwargs ? kwargs[p] : NIL; + } + if (componentHasChildren(comp)) { + local["children"] = children; + } + return makeThunk(componentBody(comp), local); + };''' + + +PLATFORM_DEPS_JS = ''' + // ========================================================================= + // Platform: deps module — component dependency analysis + // ========================================================================= + + function componentDeps(c) { + return c.deps ? c.deps.slice() : []; + } + + function componentSetDeps(c, deps) { + c.deps = deps; + } + + function componentCssClasses(c) { + return c.cssClasses ? c.cssClasses.slice() : []; + } + + function envComponents(env) { + var names = []; + for (var k in env) { + var v = env[k]; + if (v && (v._component || v._macro)) names.push(k); + } + return names; + } + + function regexFindAll(pattern, source) { + var re = new RegExp(pattern, "g"); + var results = []; + var m; + while ((m = re.exec(source)) !== null) { + if (m[1] !== undefined) results.push(m[1]); + else results.push(m[0]); + } + return results; + } + + function scanCssClasses(source) { + var classes = {}; + var result = []; + var m; + var re1 = /:class\\s+"([^"]*)"/g; + while ((m = re1.exec(source)) !== null) { + var parts = m[1].split(/\\s+/); + for (var i = 0; i < parts.length; i++) { + if (parts[i] && !classes[parts[i]]) { + classes[parts[i]] = true; + result.push(parts[i]); + } + } + } + var re2 = /:class\\s+\\(str\\s+((?:"[^"]*"\\s*)+)\\)/g; + while ((m = re2.exec(source)) !== null) { + var re3 = /"([^"]*)"/g; + var m2; + while ((m2 = re3.exec(m[1])) !== null) { + var parts2 = m2[1].split(/\\s+/); + for (var j = 0; j < parts2.length; j++) { + if (parts2[j] && !classes[parts2[j]]) { + classes[parts2[j]] = true; + result.push(parts2[j]); + } + } + } + } + var re4 = /;;\\s*@css\\s+(.+)/g; + while ((m = re4.exec(source)) !== null) { + var parts3 = m[1].split(/\\s+/); + for (var k = 0; k < parts3.length; k++) { + if (parts3[k] && !classes[parts3[k]]) { + classes[parts3[k]] = true; + result.push(parts3[k]); + } + } + } + return result; + } + + function componentIoRefs(c) { + return c.ioRefs ? c.ioRefs.slice() : []; + } + + function componentSetIoRefs(c, refs) { + c.ioRefs = refs; + } +''' + + +PLATFORM_PARSER_JS = r""" + // ========================================================================= + // Platform interface — Parser + // ========================================================================= + // Character classification derived from the grammar: + // ident-start → [a-zA-Z_~*+\-><=/!?&] + // ident-char → ident-start + [0-9.:\/\[\]#,] + + var _identStartRe = /[a-zA-Z_~*+\-><=/!?&]/; + var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]/; + + function isIdentStart(ch) { return _identStartRe.test(ch); } + function isIdentChar(ch) { return _identCharRe.test(ch); } + function parseNumber(s) { return Number(s); } + function escapeString(s) { + return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t"); + } + function sxExprSource(e) { return typeof e === "string" ? e : String(e); } +""" + + +PLATFORM_DOM_JS = """ + // ========================================================================= + // Platform interface — DOM adapter (browser-only) + // ========================================================================= + + var _hasDom = typeof document !== "undefined"; + + // Register DOM adapter as the render dispatch target for the evaluator. + _renderExprFn = function(expr, env) { return renderToDom(expr, env, null); }; + _renderMode = true; // Browser always evaluates in render context. + + var SVG_NS = "http://www.w3.org/2000/svg"; + var MATH_NS = "http://www.w3.org/1998/Math/MathML"; + + function domCreateElement(tag, ns) { + if (!_hasDom) return null; + if (ns && ns !== NIL) return document.createElementNS(ns, tag); + return document.createElement(tag); + } + + function createTextNode(s) { + return _hasDom ? document.createTextNode(s) : null; + } + + function createComment(s) { + return _hasDom ? document.createComment(s || "") : null; + } + + function createFragment() { + return _hasDom ? document.createDocumentFragment() : null; + } + + function domAppend(parent, child) { + if (parent && child) parent.appendChild(child); + } + + function domPrepend(parent, child) { + if (parent && child) parent.insertBefore(child, parent.firstChild); + } + + function domSetAttr(el, name, val) { + if (el && el.setAttribute) el.setAttribute(name, val); + } + + function domGetAttr(el, name) { + if (!el || !el.getAttribute) return NIL; + var v = el.getAttribute(name); + return v === null ? NIL : v; + } + + function domRemoveAttr(el, name) { + if (el && el.removeAttribute) el.removeAttribute(name); + } + + function domHasAttr(el, name) { + return !!(el && el.hasAttribute && el.hasAttribute(name)); + } + + function domParseHtml(html) { + if (!_hasDom) return null; + var tpl = document.createElement("template"); + tpl.innerHTML = html; + return tpl.content; + } + + function domClone(node) { + return node && node.cloneNode ? node.cloneNode(true) : node; + } + + function domParent(el) { return el ? el.parentNode : null; } + function domId(el) { return el && el.id ? el.id : NIL; } + function domNodeType(el) { return el ? el.nodeType : 0; } + function domNodeName(el) { return el ? el.nodeName : ""; } + function domTextContent(el) { return el ? el.textContent || el.nodeValue || "" : ""; } + function domSetTextContent(el, s) { if (el) { if (el.nodeType === 3 || el.nodeType === 8) el.nodeValue = s; else el.textContent = s; } } + function domIsFragment(el) { return el ? el.nodeType === 11 : false; } + function domIsChildOf(child, parent) { return !!(parent && child && child.parentNode === parent); } + function domIsActiveElement(el) { return _hasDom && el === document.activeElement; } + function domIsInputElement(el) { + if (!el || !el.tagName) return false; + var t = el.tagName; + return t === "INPUT" || t === "TEXTAREA" || t === "SELECT"; + } + function domFirstChild(el) { return el ? el.firstChild : null; } + function domNextSibling(el) { return el ? el.nextSibling : null; } + + function domChildList(el) { + if (!el || !el.childNodes) return []; + return Array.prototype.slice.call(el.childNodes); + } + + function domAttrList(el) { + if (!el || !el.attributes) return []; + var r = []; + for (var i = 0; i < el.attributes.length; i++) { + r.push([el.attributes[i].name, el.attributes[i].value]); + } + return r; + } + + function domInsertBefore(parent, node, ref) { + if (parent && node) parent.insertBefore(node, ref || null); + } + + function domInsertAfter(ref, node) { + if (ref && ref.parentNode && node) { + ref.parentNode.insertBefore(node, ref.nextSibling); + } + } + + function domRemoveChild(parent, child) { + if (parent && child && child.parentNode === parent) parent.removeChild(child); + } + + function domReplaceChild(parent, newChild, oldChild) { + if (parent && newChild && oldChild) parent.replaceChild(newChild, oldChild); + } + + function domSetInnerHtml(el, html) { + if (el) el.innerHTML = html; + } + + function domInsertAdjacentHtml(el, pos, html) { + if (el && el.insertAdjacentHTML) el.insertAdjacentHTML(pos, html); + } + + function domGetStyle(el, prop) { + return el && el.style ? el.style[prop] || "" : ""; + } + + function domSetStyle(el, prop, val) { + if (el && el.style) el.style[prop] = val; + } + + function domGetProp(el, name) { return el ? el[name] : NIL; } + function domSetProp(el, name, val) { if (el) el[name] = val; } + + function domAddClass(el, cls) { + if (el && el.classList) el.classList.add(cls); + } + + function domRemoveClass(el, cls) { + if (el && el.classList) el.classList.remove(cls); + } + + function domDispatch(el, name, detail) { + if (!_hasDom || !el) return false; + var evt = new CustomEvent(name, { bubbles: true, cancelable: true, detail: detail || {} }); + return el.dispatchEvent(evt); + } + + function domListen(el, name, handler) { + if (!_hasDom || !el) return function() {}; + // Wrap SX lambdas from runtime-evaluated island code into native fns + var wrapped = isLambda(handler) + ? function(e) { try { invoke(handler, e); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } + : handler; + if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler)); + el.addEventListener(name, wrapped); + return function() { el.removeEventListener(name, wrapped); }; + } + + function eventDetail(e) { + return (e && e.detail != null) ? e.detail : nil; + } + + function domQuery(sel) { + return _hasDom ? document.querySelector(sel) : null; + } + + function domEnsureElement(sel) { + if (!_hasDom) return null; + var el = document.querySelector(sel); + if (el) return el; + // Parse #id selector → create div with that id, append to body + if (sel.charAt(0) === '#') { + el = document.createElement('div'); + el.id = sel.slice(1); + document.body.appendChild(el); + return el; + } + return null; + } + + function domQueryAll(root, sel) { + if (!root || !root.querySelectorAll) return []; + return Array.prototype.slice.call(root.querySelectorAll(sel)); + } + + function domTagName(el) { return el && el.tagName ? el.tagName : ""; } + + // Island DOM helpers + function domRemove(node) { + if (node && node.parentNode) node.parentNode.removeChild(node); + } + function domChildNodes(el) { + if (!el || !el.childNodes) return []; + return Array.prototype.slice.call(el.childNodes); + } + function domRemoveChildrenAfter(marker) { + if (!marker || !marker.parentNode) return; + var parent = marker.parentNode; + while (marker.nextSibling) parent.removeChild(marker.nextSibling); + } + function domSetData(el, key, val) { + if (el) { if (!el._sxData) el._sxData = {}; el._sxData[key] = val; } + } + function domGetData(el, key) { + return (el && el._sxData) ? (el._sxData[key] != null ? el._sxData[key] : nil) : nil; + } + function domInnerHtml(el) { + return (el && el.innerHTML != null) ? el.innerHTML : ""; + } + function jsonParse(s) { + try { return JSON.parse(s); } catch(e) { return {}; } + } + + // renderDomComponent and renderDomElement are transpiled from + // adapter-dom.sx — no imperative overrides needed. +""" + + +PLATFORM_ENGINE_PURE_JS = """ + // ========================================================================= + // Platform interface — Engine pure logic (browser + node compatible) + // ========================================================================= + + function browserLocationHref() { + return typeof location !== "undefined" ? location.href : ""; + } + + function browserSameOrigin(url) { + try { return new URL(url, location.href).origin === location.origin; } + catch (e) { return true; } + } + + function browserPushState(url) { + if (typeof history !== "undefined") { + try { history.pushState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); } + catch (e) {} + } + } + + function browserReplaceState(url) { + if (typeof history !== "undefined") { + try { history.replaceState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); } + catch (e) {} + } + } + + function nowMs() { return (typeof performance !== "undefined") ? performance.now() : Date.now(); } + + function parseHeaderValue(s) { + if (!s) return null; + try { + if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s); + return JSON.parse(s); + } catch (e) { return null; } + } +""" + + +PLATFORM_ORCHESTRATION_JS = """ + // ========================================================================= + // Platform interface — Orchestration (browser-only) + // ========================================================================= + + // --- Browser/Network --- + + function browserNavigate(url) { + if (typeof location !== "undefined") location.assign(url); + } + + function browserReload() { + if (typeof location !== "undefined") location.reload(); + } + + function browserScrollTo(x, y) { + if (typeof window !== "undefined") window.scrollTo(x, y); + } + + function browserMediaMatches(query) { + if (typeof window === "undefined") return false; + return window.matchMedia(query).matches; + } + + function browserConfirm(msg) { + if (typeof window === "undefined") return false; + return window.confirm(msg); + } + + function browserPrompt(msg) { + if (typeof window === "undefined") return NIL; + var r = window.prompt(msg); + return r === null ? NIL : r; + } + + function csrfToken() { + if (!_hasDom) return NIL; + var m = document.querySelector('meta[name="csrf-token"]'); + return m ? m.getAttribute("content") : NIL; + } + + function isCrossOrigin(url) { + try { + var h = new URL(url, location.href).hostname; + return h !== location.hostname && + (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0); + } catch (e) { return false; } + } + + // --- Promises --- + + function promiseResolve(val) { return Promise.resolve(val); } + + function promiseThen(p, onResolve, onReject) { + if (!p || !p.then) return p; + return onReject ? p.then(onResolve, onReject) : p.then(onResolve); + } + + function promiseCatch(p, fn) { return p && p.catch ? p.catch(fn) : p; } + + function promiseDelayed(ms, value) { + return new Promise(function(resolve) { + setTimeout(function() { resolve(value); }, ms); + }); + } + + // --- Abort controllers --- + + var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null; + + function abortPrevious(el) { + if (_controllers) { + var prev = _controllers.get(el); + if (prev) prev.abort(); + } + } + + function trackController(el, ctrl) { + if (_controllers) _controllers.set(el, ctrl); + } + + var _targetControllers = typeof WeakMap !== "undefined" ? new WeakMap() : null; + + function abortPreviousTarget(el) { + if (_targetControllers) { + var prev = _targetControllers.get(el); + if (prev) prev.abort(); + } + } + + function trackControllerTarget(el, ctrl) { + if (_targetControllers) _targetControllers.set(el, ctrl); + } + + function newAbortController() { + return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} }; + } + + function controllerSignal(ctrl) { return ctrl ? ctrl.signal : null; } + + function isAbortError(err) { return err && err.name === "AbortError"; } + + // --- Timers --- + + function _wrapSxFn(fn) { + if (fn && fn._lambda) { + return function() { return trampoline(callLambda(fn, [], lambdaClosure(fn))); }; + } + return fn; + } + function setTimeout_(fn, ms) { return setTimeout(_wrapSxFn(fn), ms || 0); } + function setInterval_(fn, ms) { return setInterval(_wrapSxFn(fn), ms || 1000); } + function clearTimeout_(id) { clearTimeout(id); } + function clearInterval_(id) { clearInterval(id); } + function requestAnimationFrame_(fn) { + var cb = _wrapSxFn(fn); + if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(cb); + else setTimeout(cb, 16); + } + + // --- Fetch --- + + function fetchRequest(config, successFn, errorFn) { + var opts = { method: config.method, headers: config.headers }; + if (config.signal) opts.signal = config.signal; + if (config.body && config.method !== "GET") opts.body = config.body; + if (config["cross-origin"]) opts.credentials = "include"; + + var p = (config.preloaded && config.preloaded !== NIL) + ? Promise.resolve({ + ok: true, status: 200, + headers: new Headers({ "Content-Type": config.preloaded["content-type"] || "" }), + text: function() { return Promise.resolve(config.preloaded.text); } + }) + : fetch(config.url, opts); + + return p.then(function(resp) { + return resp.text().then(function(text) { + var getHeader = function(name) { + var v = resp.headers.get(name); + return v === null ? NIL : v; + }; + return successFn(resp.ok, resp.status, getHeader, text); + }); + }).catch(function(err) { + return errorFn(err); + }); + } + + function fetchLocation(headerVal) { + if (!_hasDom) return; + var locUrl = headerVal; + try { var obj = JSON.parse(headerVal); locUrl = obj.path || obj; } catch (e) {} + fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function(r) { + return r.text().then(function(t) { + var main = document.getElementById("main-panel"); + if (main) { + main.innerHTML = t; + postSwap(main); + try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {} + } + }); + }); + } + + function fetchAndRestore(main, url, headers, scrollY) { + var opts = { headers: headers }; + try { + var h = new URL(url, location.href).hostname; + if (h !== location.hostname && + (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) { + opts.credentials = "include"; + } + } catch (e) {} + + fetch(url, opts).then(function(resp) { + return resp.text().then(function(text) { + text = stripComponentScripts(text); + text = extractResponseCss(text); + text = text.trim(); + if (text.charAt(0) === "(") { + try { + var dom = sxRender(text); + var container = document.createElement("div"); + container.appendChild(dom); + processOobSwaps(container, function(t, oob, s) { + swapDomNodes(t, oob, s); + sxHydrate(t); + processElements(t); + }); + var newMain = container.querySelector("#main-panel"); + morphChildren(main, newMain || container); + postSwap(main); + if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0); + } catch (err) { + console.error("sx-ref popstate error:", err); + location.reload(); + } + } else { + var parser = new DOMParser(); + var doc = parser.parseFromString(text, "text/html"); + var newMain = doc.getElementById("main-panel"); + if (newMain) { + morphChildren(main, newMain); + postSwap(main); + if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0); + } else { + location.reload(); + } + } + }); + }).catch(function() { location.reload(); }); + } + + function fetchStreaming(target, url, headers) { + // Streaming fetch for multi-stream pages. + // First chunk = OOB SX swap (shell with skeletons). + // Subsequent chunks = __sxResolve script tags filling suspense slots. + var opts = { headers: headers }; + try { + var h = new URL(url, location.href).hostname; + if (h !== location.hostname && + (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) { + opts.credentials = "include"; + } + } catch (e) {} + + fetch(url, opts).then(function(resp) { + if (!resp.ok || !resp.body) { + // Fallback: non-streaming + return resp.text().then(function(text) { + text = stripComponentScripts(text); + text = extractResponseCss(text); + text = text.trim(); + if (text.charAt(0) === "(") { + var dom = sxRender(text); + var container = document.createElement("div"); + container.appendChild(dom); + processOobSwaps(container, function(t, oob, s) { + swapDomNodes(t, oob, s); + sxHydrate(t); + processElements(t); + }); + var newMain = container.querySelector("#main-panel"); + morphChildren(target, newMain || container); + postSwap(target); + } + }); + } + + var reader = resp.body.getReader(); + var decoder = new TextDecoder(); + var buffer = ""; + var initialSwapDone = false; + // Regex to match __sxResolve script tags + var RESOLVE_START = ""; + + function processResolveScripts() { + // Strip and load any extra component defs before resolve scripts + buffer = stripSxScripts(buffer); + var idx; + while ((idx = buffer.indexOf(RESOLVE_START)) >= 0) { + var endIdx = buffer.indexOf(RESOLVE_END, idx); + if (endIdx < 0) break; // incomplete, wait for more data + var argsStr = buffer.substring(idx + RESOLVE_START.length, endIdx); + buffer = buffer.substring(endIdx + RESOLVE_END.length); + // argsStr is: "stream-id","sx source" + var commaIdx = argsStr.indexOf(","); + if (commaIdx >= 0) { + try { + var id = JSON.parse(argsStr.substring(0, commaIdx)); + var sx = JSON.parse(argsStr.substring(commaIdx + 1)); + if (typeof Sx !== "undefined" && Sx.resolveSuspense) { + Sx.resolveSuspense(id, sx); + } + } catch (e) { + console.error("[sx-ref] resolve parse error:", e); + } + } + } + } + + function pump() { + return reader.read().then(function(result) { + buffer += decoder.decode(result.value || new Uint8Array(), { stream: !result.done }); + + if (!initialSwapDone) { + // Look for the first resolve script — everything before it is OOB content + var scriptIdx = buffer.indexOf(" (without data-components or data-init). + // These contain extra component defs from streaming resolve chunks. + // data-init scripts are preserved for process-sx-scripts to evaluate as side effects. + var SxObj = typeof Sx !== "undefined" ? Sx : null; + return text.replace(/]*type="text\\/sx"[^>]*>[\\s\\S]*?<\\/script>/gi, + function(match) { + if (/data-init/.test(match)) return match; // preserve data-init scripts + var m = match.match(/]*>([\\s\\S]*?)<\\/script>/i); + if (m && SxObj && SxObj.loadComponents) SxObj.loadComponents(m[1]); + return ""; + }); + } + + function extractResponseCss(text) { + if (!_hasDom) return text; + var target = document.getElementById("sx-css"); + if (!target) return text; + return text.replace(/]*data-sx-css[^>]*>([\\s\\S]*?)<\\/style>/gi, + function(_, css) { target.textContent += css; return ""; }); + } + + function selectFromContainer(container, sel) { + var frag = document.createDocumentFragment(); + sel.split(",").forEach(function(s) { + container.querySelectorAll(s.trim()).forEach(function(m) { frag.appendChild(m); }); + }); + return frag; + } + + function childrenToFragment(container) { + var frag = document.createDocumentFragment(); + while (container.firstChild) frag.appendChild(container.firstChild); + return frag; + } + + function selectHtmlFromDoc(doc, sel) { + var parts = sel.split(",").map(function(s) { return s.trim(); }); + var frags = []; + parts.forEach(function(s) { + doc.querySelectorAll(s).forEach(function(m) { frags.push(m.outerHTML); }); + }); + return frags.join(""); + } + + // --- Parsing --- + + function tryParseJson(s) { + if (!s) return NIL; + try { return JSON.parse(s); } catch (e) { return NIL; } + } +""" + + +PLATFORM_BOOT_JS = """ + // ========================================================================= + // Platform interface — Boot (mount, hydrate, scripts, cookies) + // ========================================================================= + + function resolveMountTarget(target) { + if (typeof target === "string") return _hasDom ? document.querySelector(target) : null; + return target; + } + + function sxRenderWithEnv(source, extraEnv) { + var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + var exprs = parse(source); + if (!_hasDom) return null; + var frag = document.createDocumentFragment(); + for (var i = 0; i < exprs.length; i++) { + var node = renderToDom(exprs[i], env, null); + if (node) frag.appendChild(node); + } + return frag; + } + + function getRenderEnv(extraEnv) { + return extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + } + + function mergeEnvs(base, newEnv) { + return newEnv ? merge(componentEnv, base, newEnv) : merge(componentEnv, base); + } + + function sxLoadComponents(text) { + try { + var exprs = parse(text); + for (var i = 0; i < exprs.length; i++) trampoline(evalExpr(exprs[i], componentEnv)); + } catch (err) { + logParseError("loadComponents", text, err); + throw err; + } + } + + function setDocumentTitle(s) { + if (_hasDom) document.title = s || ""; + } + + function removeHeadElement(sel) { + if (!_hasDom) return; + var old = document.head.querySelector(sel); + if (old) old.parentNode.removeChild(old); + } + + function querySxScripts(root) { + if (!_hasDom) return []; + var r = (root && root !== NIL) ? root : document; + return Array.prototype.slice.call( + r.querySelectorAll('script[type="text/sx"]')); + } + + function queryPageScripts() { + if (!_hasDom) return []; + return Array.prototype.slice.call( + document.querySelectorAll('script[type="text/sx-pages"]')); + } + + // --- localStorage --- + + function localStorageGet(key) { + try { var v = localStorage.getItem(key); return v === null ? NIL : v; } + catch (e) { return NIL; } + } + + function localStorageSet(key, val) { + try { localStorage.setItem(key, val); } catch (e) {} + } + + function localStorageRemove(key) { + try { localStorage.removeItem(key); } catch (e) {} + } + + // --- Cookies --- + + function setSxCompCookie(hash) { + if (_hasDom) document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; + } + + function clearSxCompCookie() { + if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax"; + } + + // --- Env helpers --- + + function parseEnvAttr(el) { + var attr = el && el.getAttribute ? el.getAttribute("data-sx-env") : null; + if (!attr) return {}; + try { return JSON.parse(attr); } catch (e) { return {}; } + } + + function storeEnvAttr(el, base, newEnv) { + var merged = merge(base, newEnv); + if (el && el.setAttribute) el.setAttribute("data-sx-env", JSON.stringify(merged)); + } + + function toKebab(s) { return s.replace(/_/g, "-"); } + + // --- Logging --- + + function logInfo(msg) { + if (typeof console !== "undefined") console.log("[sx-ref] " + msg); + } + + function logWarn(msg) { + if (typeof console !== "undefined") console.warn("[sx-ref] " + msg); + } + + function logParseError(label, text, err) { + if (typeof console === "undefined") return; + var msg = err && err.message ? err.message : String(err); + var colMatch = msg.match(/col (\\d+)/); + var lineMatch = msg.match(/line (\\d+)/); + if (colMatch && text) { + var errLine = lineMatch ? parseInt(lineMatch[1]) : 1; + var errCol = parseInt(colMatch[1]); + var lines = text.split("\\n"); + var pos = 0; + for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1; + pos += errCol; + var ws = 80; + var start = Math.max(0, pos - ws); + var end = Math.min(text.length, pos + ws); + console.error("[sx-ref] " + label + ":", msg, + "\\n around error (pos ~" + pos + "):", + "\\n \\u00ab" + text.substring(start, pos) + "\\u26d4" + text.substring(pos, end) + "\\u00bb"); + } else { + console.error("[sx-ref] " + label + ":", msg); + } + } + +""" + + +def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_page_helpers=False): + lines = [''' + // ========================================================================= + // Post-transpilation fixups + // ========================================================================= + // The reference spec's call-lambda only handles Lambda objects, but HO forms + // (map, reduce, etc.) may receive native primitives. Wrap to handle both. + var _rawCallLambda = callLambda; + callLambda = function(f, args, callerEnv) { + if (typeof f === "function") return f.apply(null, args); + return _rawCallLambda(f, args, callerEnv); + }; + + // Expose render functions as primitives so SX code can call them'''] + if has_html: + lines.append(' if (typeof renderToHtml === "function") PRIMITIVES["render-to-html"] = renderToHtml;') + if has_sx: + lines.append(' if (typeof renderToSx === "function") PRIMITIVES["render-to-sx"] = renderToSx;') + lines.append(' if (typeof aser === "function") PRIMITIVES["aser"] = aser;') + if has_dom: + lines.append(' if (typeof renderToDom === "function") PRIMITIVES["render-to-dom"] = renderToDom;') + if has_signals: + lines.append(''' + // Expose signal functions as primitives so runtime-evaluated SX code + // (e.g. island bodies from .sx files) can call them + PRIMITIVES["signal"] = signal; + PRIMITIVES["signal?"] = isSignal; + PRIMITIVES["deref"] = deref; + PRIMITIVES["reset!"] = reset_b; + PRIMITIVES["swap!"] = swap_b; + PRIMITIVES["computed"] = computed; + PRIMITIVES["effect"] = effect; + PRIMITIVES["batch"] = batch; + // Timer primitives for island code + PRIMITIVES["set-interval"] = setInterval_; + PRIMITIVES["clear-interval"] = clearInterval_; + // Reactive DOM helpers for island code + PRIMITIVES["reactive-text"] = reactiveText; + PRIMITIVES["create-text-node"] = createTextNode; + PRIMITIVES["dom-set-text-content"] = domSetTextContent; + PRIMITIVES["dom-listen"] = domListen; + PRIMITIVES["dom-dispatch"] = domDispatch; + PRIMITIVES["event-detail"] = eventDetail; + PRIMITIVES["resource"] = resource; + PRIMITIVES["promise-delayed"] = promiseDelayed; + PRIMITIVES["promise-then"] = promiseThen; + PRIMITIVES["def-store"] = defStore; + PRIMITIVES["use-store"] = useStore; + PRIMITIVES["emit-event"] = emitEvent; + PRIMITIVES["on-event"] = onEvent; + PRIMITIVES["bridge-event"] = bridgeEvent; + // DOM primitives for island code + PRIMITIVES["dom-focus"] = domFocus; + PRIMITIVES["dom-tag-name"] = domTagName; + PRIMITIVES["dom-get-prop"] = domGetProp; + PRIMITIVES["stop-propagation"] = stopPropagation_; + PRIMITIVES["error-message"] = errorMessage; + PRIMITIVES["schedule-idle"] = scheduleIdle; + PRIMITIVES["invoke"] = invoke; + PRIMITIVES["error"] = function(msg) { throw new Error(msg); }; + PRIMITIVES["filter"] = filter; + // DOM primitives for sx-on:* handlers and data-init scripts + if (typeof domBody === "function") PRIMITIVES["dom-body"] = domBody; + if (typeof domQuery === "function") PRIMITIVES["dom-query"] = domQuery; + if (typeof domQueryAll === "function") PRIMITIVES["dom-query-all"] = domQueryAll; + if (typeof domQueryById === "function") PRIMITIVES["dom-query-by-id"] = domQueryById; + if (typeof domSetAttr === "function") PRIMITIVES["dom-set-attr"] = domSetAttr; + if (typeof domGetAttr === "function") PRIMITIVES["dom-get-attr"] = domGetAttr; + if (typeof domRemoveAttr === "function") PRIMITIVES["dom-remove-attr"] = domRemoveAttr; + if (typeof domHasAttr === "function") PRIMITIVES["dom-has-attr?"] = domHasAttr; + if (typeof domAddClass === "function") PRIMITIVES["dom-add-class"] = domAddClass; + if (typeof domRemoveClass === "function") PRIMITIVES["dom-remove-class"] = domRemoveClass; + if (typeof domHasClass === "function") PRIMITIVES["dom-has-class?"] = domHasClass; + if (typeof domClosest === "function") PRIMITIVES["dom-closest"] = domClosest; + if (typeof domMatches === "function") PRIMITIVES["dom-matches?"] = domMatches; + if (typeof preventDefault_ === "function") PRIMITIVES["prevent-default"] = preventDefault_; + if (typeof elementValue === "function") PRIMITIVES["element-value"] = elementValue; + if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml; + if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml; + if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent; + if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse; + if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs; + PRIMITIVES["sx-parse"] = sxParse;''') + if has_deps: + lines.append(''' + // Expose deps module functions as primitives so runtime-evaluated SX code + // (e.g. test-deps.sx in browser) can call them + // Platform functions (from PLATFORM_DEPS_JS) + PRIMITIVES["component-deps"] = componentDeps; + PRIMITIVES["component-set-deps!"] = componentSetDeps; + PRIMITIVES["component-css-classes"] = componentCssClasses; + PRIMITIVES["env-components"] = envComponents; + PRIMITIVES["regex-find-all"] = regexFindAll; + PRIMITIVES["scan-css-classes"] = scanCssClasses; + // Transpiled functions (from deps.sx) + PRIMITIVES["scan-refs"] = scanRefs; + PRIMITIVES["scan-refs-walk"] = scanRefsWalk; + PRIMITIVES["transitive-deps"] = transitiveDeps; + PRIMITIVES["transitive-deps-walk"] = transitiveDepsWalk; + PRIMITIVES["compute-all-deps"] = computeAllDeps; + PRIMITIVES["scan-components-from-source"] = scanComponentsFromSource; + PRIMITIVES["components-needed"] = componentsNeeded; + PRIMITIVES["page-component-bundle"] = pageComponentBundle; + PRIMITIVES["page-css-classes"] = pageCssClasses; + PRIMITIVES["scan-io-refs-walk"] = scanIoRefsWalk; + PRIMITIVES["scan-io-refs"] = scanIoRefs; + PRIMITIVES["transitive-io-refs-walk"] = transitiveIoRefsWalk; + PRIMITIVES["transitive-io-refs"] = transitiveIoRefs; + PRIMITIVES["compute-all-io-refs"] = computeAllIoRefs; + PRIMITIVES["component-io-refs-cached"] = componentIoRefsCached; + PRIMITIVES["component-pure?"] = componentPure_p; + PRIMITIVES["render-target"] = renderTarget; + PRIMITIVES["page-render-plan"] = pageRenderPlan;''') + if has_page_helpers: + lines.append(''' + // Expose page-helper functions as primitives + PRIMITIVES["categorize-special-forms"] = categorizeSpecialForms; + PRIMITIVES["extract-define-kwargs"] = extractDefineKwargs; + PRIMITIVES["build-reference-data"] = buildReferenceData; + PRIMITIVES["build-ref-items-with-href"] = buildRefItemsWithHref; + PRIMITIVES["build-attr-detail"] = buildAttrDetail; + PRIMITIVES["build-header-detail"] = buildHeaderDetail; + PRIMITIVES["build-event-detail"] = buildEventDetail; + PRIMITIVES["build-component-source"] = buildComponentSource; + PRIMITIVES["build-bundle-analysis"] = buildBundleAnalysis; + PRIMITIVES["build-routing-analysis"] = buildRoutingAnalysis; + PRIMITIVES["build-affinity-analysis"] = buildAffinityAnalysis;''') + return "\n".join(lines) + + +def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps=False, has_router=False, has_signals=False, has_page_helpers=False): + # Parser: use compiled sxParse from parser.sx, or inline a minimal fallback + if has_parser: + parser = ''' + // Parser — compiled from parser.sx (see PLATFORM_PARSER_JS for ident char classes) + var parse = sxParse;''' + else: + parser = r''' + // Minimal fallback parser (no parser adapter) + function parse(text) { + throw new Error("Parser adapter not included — cannot parse SX source at runtime"); + }''' + + # Public API — conditional on adapters + api_lines = [parser, ''' + // ========================================================================= + // Public API + // ========================================================================= + + var componentEnv = {}; + + function loadComponents(source) { + var exprs = parse(source); + for (var i = 0; i < exprs.length; i++) { + trampoline(evalExpr(exprs[i], componentEnv)); + } + }'''] + + # render() — auto-dispatches based on available adapters + if has_html and has_dom: + api_lines.append(''' + function render(source) { + if (!_hasDom) { + var exprs = parse(source); + var parts = []; + for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); + return parts.join(""); + } + var exprs = parse(source); + var frag = document.createDocumentFragment(); + for (var i = 0; i < exprs.length; i++) frag.appendChild(renderToDom(exprs[i], merge(componentEnv), null)); + return frag; + }''') + elif has_dom: + api_lines.append(''' + function render(source) { + var exprs = parse(source); + var frag = document.createDocumentFragment(); + for (var i = 0; i < exprs.length; i++) frag.appendChild(renderToDom(exprs[i], merge(componentEnv), null)); + return frag; + }''') + elif has_html: + api_lines.append(''' + function render(source) { + var exprs = parse(source); + var parts = []; + for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); + return parts.join(""); + }''') + else: + api_lines.append(''' + function render(source) { + var exprs = parse(source); + var results = []; + for (var i = 0; i < exprs.length; i++) results.push(trampoline(evalExpr(exprs[i], merge(componentEnv)))); + return results.length === 1 ? results[0] : results; + }''') + + # renderToString helper + if has_html: + api_lines.append(''' + function renderToString(source) { + var exprs = parse(source); + var parts = []; + for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); + return parts.join(""); + }''') + + # Build Sx object + version = f"ref-2.0 ({adapter_label}, bootstrap-compiled)" + api_lines.append(f''' + var Sx = {{ + VERSION: "ref-2.0", + parse: parse, + parseAll: parse, + eval: function(expr, env) {{ return trampoline(evalExpr(expr, env || merge(componentEnv))); }}, + loadComponents: loadComponents, + render: render,{"" if has_html else ""} + {"renderToString: renderToString," if has_html else ""} + serialize: serialize, + NIL: NIL, + Symbol: Symbol, + Keyword: Keyword, + isTruthy: isSxTruthy, + isNil: isNil, + componentEnv: componentEnv,''') + + if has_html: + api_lines.append(' renderToHtml: function(expr, env) { return renderToHtml(expr, env || merge(componentEnv)); },') + if has_sx: + api_lines.append(' renderToSx: function(expr, env) { return renderToSx(expr, env || merge(componentEnv)); },') + if has_dom: + api_lines.append(' renderToDom: _hasDom ? function(expr, env, ns) { return renderToDom(expr, env || merge(componentEnv), ns || null); } : null,') + if has_engine: + api_lines.append(' parseTriggerSpec: typeof parseTriggerSpec === "function" ? parseTriggerSpec : null,') + api_lines.append(' parseTime: typeof parseTime === "function" ? parseTime : null,') + api_lines.append(' defaultTrigger: typeof defaultTrigger === "function" ? defaultTrigger : null,') + api_lines.append(' parseSwapSpec: typeof parseSwapSpec === "function" ? parseSwapSpec : null,') + api_lines.append(' parseRetrySpec: typeof parseRetrySpec === "function" ? parseRetrySpec : null,') + api_lines.append(' nextRetryMs: typeof nextRetryMs === "function" ? nextRetryMs : null,') + api_lines.append(' filterParams: typeof filterParams === "function" ? filterParams : null,') + api_lines.append(' morphNode: typeof morphNode === "function" ? morphNode : null,') + api_lines.append(' morphChildren: typeof morphChildren === "function" ? morphChildren : null,') + api_lines.append(' swapDomNodes: typeof swapDomNodes === "function" ? swapDomNodes : null,') + if has_orch: + api_lines.append(' process: typeof processElements === "function" ? processElements : null,') + api_lines.append(' executeRequest: typeof executeRequest === "function" ? executeRequest : null,') + api_lines.append(' postSwap: typeof postSwap === "function" ? postSwap : null,') + if has_boot: + api_lines.append(' processScripts: typeof processSxScripts === "function" ? processSxScripts : null,') + api_lines.append(' mount: typeof sxMount === "function" ? sxMount : null,') + api_lines.append(' hydrate: typeof sxHydrateElements === "function" ? sxHydrateElements : null,') + api_lines.append(' update: typeof sxUpdateElement === "function" ? sxUpdateElement : null,') + api_lines.append(' renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null,') + api_lines.append(' getEnv: function() { return componentEnv; },') + api_lines.append(' resolveSuspense: typeof resolveSuspense === "function" ? resolveSuspense : null,') + api_lines.append(' hydrateIslands: typeof sxHydrateIslands === "function" ? sxHydrateIslands : null,') + api_lines.append(' disposeIsland: typeof disposeIsland === "function" ? disposeIsland : null,') + api_lines.append(' init: typeof bootInit === "function" ? bootInit : null,') + elif has_orch: + api_lines.append(' init: typeof engineInit === "function" ? engineInit : null,') + if has_deps: + api_lines.append(' scanRefs: scanRefs,') + api_lines.append(' scanComponentsFromSource: scanComponentsFromSource,') + api_lines.append(' transitiveDeps: transitiveDeps,') + api_lines.append(' computeAllDeps: computeAllDeps,') + api_lines.append(' componentsNeeded: componentsNeeded,') + api_lines.append(' pageComponentBundle: pageComponentBundle,') + api_lines.append(' pageCssClasses: pageCssClasses,') + api_lines.append(' scanIoRefs: scanIoRefs,') + api_lines.append(' transitiveIoRefs: transitiveIoRefs,') + api_lines.append(' computeAllIoRefs: computeAllIoRefs,') + api_lines.append(' componentPure_p: componentPure_p,') + if has_page_helpers: + api_lines.append(' categorizeSpecialForms: categorizeSpecialForms,') + api_lines.append(' buildReferenceData: buildReferenceData,') + api_lines.append(' buildAttrDetail: buildAttrDetail,') + api_lines.append(' buildHeaderDetail: buildHeaderDetail,') + api_lines.append(' buildEventDetail: buildEventDetail,') + api_lines.append(' buildComponentSource: buildComponentSource,') + api_lines.append(' buildBundleAnalysis: buildBundleAnalysis,') + api_lines.append(' buildRoutingAnalysis: buildRoutingAnalysis,') + api_lines.append(' buildAffinityAnalysis: buildAffinityAnalysis,') + if has_router: + api_lines.append(' splitPathSegments: splitPathSegments,') + api_lines.append(' parseRoutePattern: parseRoutePattern,') + api_lines.append(' matchRoute: matchRoute,') + api_lines.append(' findMatchingRoute: findMatchingRoute,') + + if has_dom: + api_lines.append(' registerIo: typeof registerIoPrimitive === "function" ? registerIoPrimitive : null,') + api_lines.append(' registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null,') + api_lines.append(' asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null,') + api_lines.append(' asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null,') + if has_signals: + api_lines.append(' signal: signal,') + api_lines.append(' deref: deref,') + api_lines.append(' reset: reset_b,') + api_lines.append(' swap: swap_b,') + api_lines.append(' computed: computed,') + api_lines.append(' effect: effect,') + api_lines.append(' batch: batch,') + api_lines.append(' isSignal: isSignal,') + api_lines.append(' makeSignal: makeSignal,') + api_lines.append(' defStore: defStore,') + api_lines.append(' useStore: useStore,') + api_lines.append(' clearStores: clearStores,') + api_lines.append(' emitEvent: emitEvent,') + api_lines.append(' onEvent: onEvent,') + api_lines.append(' bridgeEvent: bridgeEvent,') + api_lines.append(f' _version: "{version}"') + api_lines.append(' };') + api_lines.append('') + if has_orch: + api_lines.append(''' + // --- Popstate listener --- + if (typeof window !== "undefined") { + window.addEventListener("popstate", function(e) { + handlePopstate(e && e.state ? e.state.scrollY || 0 : 0); + }); + }''') + if has_boot: + api_lines.append(''' + // --- Auto-init --- + if (typeof document !== "undefined") { + var _sxInit = function() { + bootInit(); + // Process any suspense resolutions that arrived before init + if (global.__sxPending) { + for (var pi = 0; pi < global.__sxPending.length; pi++) { + resolveSuspense(global.__sxPending[pi].id, global.__sxPending[pi].sx); + } + global.__sxPending = null; + } + // Set up direct resolution for future chunks + global.__sxResolve = function(id, sx) { resolveSuspense(id, sx); }; + // Register service worker for offline data caching + if ("serviceWorker" in navigator) { + navigator.serviceWorker.register("/sx-sw.js", { scope: "/" }).then(function(reg) { + logInfo("sx:sw registered (scope: " + reg.scope + ")"); + }).catch(function(err) { + logWarn("sx:sw registration failed: " + (err && err.message ? err.message : err)); + }); + } + }; + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", _sxInit); + } else { + _sxInit(); + } + }''') + elif has_orch: + api_lines.append(''' + // --- Auto-init --- + if (typeof document !== "undefined") { + var _sxInit = function() { engineInit(); }; + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", _sxInit); + } else { + _sxInit(); + } + }''') + + api_lines.append(' if (typeof module !== "undefined" && module.exports) module.exports = Sx;') + api_lines.append(' else global.Sx = Sx;') + + return "\n".join(api_lines) + + +EPILOGUE = ''' +})(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : this);''' diff --git a/shared/sx/ref/platform_py.py b/shared/sx/ref/platform_py.py index 74fafaef..de9794fa 100644 --- a/shared/sx/ref/platform_py.py +++ b/shared/sx/ref/platform_py.py @@ -1428,6 +1428,7 @@ SPEC_MODULES = { "router": ("router.sx", "router (client-side route matching)"), "engine": ("engine.sx", "engine (fetch/swap/trigger pure logic)"), "signals": ("signals.sx", "signals (reactive signal runtime)"), + "page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"), } EXTENSION_NAMES = {"continuations"} diff --git a/shared/sx/ref/run_js_sx.py b/shared/sx/ref/run_js_sx.py index 9fd1e141..a5a92ed4 100644 --- a/shared/sx/ref/run_js_sx.py +++ b/shared/sx/ref/run_js_sx.py @@ -103,8 +103,11 @@ def compile_ref_to_js( if "boot" in adapter_set: spec_mod_set.add("router") spec_mod_set.add("deps") + if "page-helpers" in SPEC_MODULES: + spec_mod_set.add("page-helpers") has_deps = "deps" in spec_mod_set has_router = "router" in spec_mod_set + has_page_helpers = "page-helpers" in spec_mod_set # Resolve extensions ext_set = set() @@ -198,12 +201,12 @@ def compile_ref_to_js( if name in adapter_set and name in adapter_platform: parts.append(adapter_platform[name]) - parts.append(fixups_js(has_html, has_sx, has_dom, has_signals, has_deps)) + parts.append(fixups_js(has_html, has_sx, has_dom, has_signals, has_deps, has_page_helpers)) if has_continuations: parts.append(CONTINUATIONS_JS) if has_dom: parts.append(ASYNC_IO_JS) - parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps, has_router, has_signals)) + parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps, has_router, has_signals, has_page_helpers)) parts.append(EPILOGUE) build_ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py index 020daa3e..f5de87a2 100644 --- a/shared/sx/ref/sx_ref.py +++ b/shared/sx/ref/sx_ref.py @@ -2342,452 +2342,147 @@ 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 engine (fetch/swap/trigger pure logic) === +# === Transpiled from page-helpers (pure data transformation helpers) === -# ENGINE_VERBS -ENGINE_VERBS = ['get', 'post', 'put', 'delete', 'patch'] +# 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'} -# DEFAULT_SWAP -DEFAULT_SWAP = 'outerHTML' +# 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 -# parse-time -def parse_time(s): - if sx_truthy(is_nil(s)): - return 0 - elif sx_truthy(ends_with_p(s, 'ms')): - return parse_int(s, 0) - elif sx_truthy(ends_with_p(s, 's')): - return (parse_int(replace(s, 's', ''), 0) * 1000) +# 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'), '/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3)} + elif _match == 'headers': + return {'req-headers': build_ref_items_with_href(get(raw_data, 'req-headers'), '/hypermedia/reference/headers/', detail_keys, 3), 'resp-headers': build_ref_items_with_href(get(raw_data, 'resp-headers'), '/hypermedia/reference/headers/', detail_keys, 3)} + elif _match == 'events': + return {'events-list': build_ref_items_with_href(get(raw_data, 'events-list'), '/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 parse_int(s, 0) + return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3)} -# parse-trigger-spec -def parse_trigger_spec(spec): - if sx_truthy(is_nil(spec)): - return NIL +# build-attr-detail +def build_attr_detail(slug, detail): + if sx_truthy(is_nil(detail)): + return {'attr-not-found': True} else: - raw_parts = split(spec, ',') - return filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda part: (lambda tokens: (NIL if sx_truthy(empty_p(tokens)) else ({'event': 'every', 'modifiers': {'interval': parse_time(nth(tokens, 1))}} if sx_truthy(((first(tokens) == 'every') if not sx_truthy((first(tokens) == 'every')) else (len(tokens) >= 2))) else (lambda mods: _sx_begin(for_each(lambda tok: (_sx_dict_set(mods, 'once', True) if sx_truthy((tok == 'once')) else (_sx_dict_set(mods, 'changed', True) if sx_truthy((tok == 'changed')) else (_sx_dict_set(mods, 'delay', parse_time(slice(tok, 6))) if sx_truthy(starts_with_p(tok, 'delay:')) else (_sx_dict_set(mods, 'from', slice(tok, 5)) if sx_truthy(starts_with_p(tok, 'from:')) else NIL)))), rest(tokens)), {'event': first(tokens), 'modifiers': mods}))({}))))(split(trim(part), ' ')), raw_parts)) + 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)} -# default-trigger -def default_trigger(tag_name): - if sx_truthy((tag_name == 'FORM')): - return [{'event': 'submit', 'modifiers': {}}] - elif sx_truthy(((tag_name == 'INPUT') if sx_truthy((tag_name == 'INPUT')) else ((tag_name == 'SELECT') if sx_truthy((tag_name == 'SELECT')) else (tag_name == 'TEXTAREA')))): - return [{'event': 'change', 'modifiers': {}}] +# build-header-detail +def build_header_detail(slug, detail): + if sx_truthy(is_nil(detail)): + return {'header-not-found': True} else: - return [{'event': 'click', 'modifiers': {}}] + 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')} -# get-verb-info -def get_verb_info(el): - return some(lambda verb: (lambda url: ({'method': upper(verb), 'url': url} if sx_truthy(url) else NIL))(dom_get_attr(el, sx_str('sx-', verb))), ENGINE_VERBS) +# 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-request-headers -def build_request_headers(el, loaded_components, css_hash): - headers = {'SX-Request': 'true', 'SX-Current-URL': browser_location_href()} - target_sel = dom_get_attr(el, 'sx-target') - if sx_truthy(target_sel): - headers['SX-Target'] = target_sel - if sx_truthy((not sx_truthy(empty_p(loaded_components)))): - headers['SX-Components'] = join(',', loaded_components) - if sx_truthy(css_hash): - headers['SX-Css'] = css_hash - extra_h = dom_get_attr(el, 'sx-headers') - if sx_truthy(extra_h): - parsed = parse_header_value(extra_h) - if sx_truthy(parsed): - for key in keys(parsed): - headers[key] = sx_str(get(parsed, key)) - return headers +# 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, ')') -# process-response-headers -def process_response_headers(get_header): - return {'redirect': get_header('SX-Redirect'), 'refresh': get_header('SX-Refresh'), 'trigger': get_header('SX-Trigger'), 'retarget': get_header('SX-Retarget'), 'reswap': get_header('SX-Reswap'), 'location': get_header('SX-Location'), 'replace-url': get_header('SX-Replace-Url'), 'css-hash': get_header('SX-Css-Hash'), 'trigger-swap': get_header('SX-Trigger-After-Swap'), 'trigger-settle': get_header('SX-Trigger-After-Settle'), 'content-type': get_header('Content-Type'), 'cache-invalidate': get_header('SX-Cache-Invalidate'), 'cache-update': get_header('SX-Cache-Update')} - -# parse-swap-spec -def parse_swap_spec(raw_swap, global_transitions_p): +# build-bundle-analysis +def build_bundle_analysis(pages_raw, components_raw, total_components, total_macros, pure_count, io_count): _cells = {} - parts = split((raw_swap if sx_truthy(raw_swap) else DEFAULT_SWAP), ' ') - style = first(parts) - _cells['use_transition'] = global_transitions_p - for p in rest(parts): - if sx_truthy((p == 'transition:true')): - _cells['use_transition'] = True - elif sx_truthy((p == 'transition:false')): - _cells['use_transition'] = False - return {'style': style, 'transition': _cells['use_transition']} + 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} -# parse-retry-spec -def parse_retry_spec(retry_attr): - if sx_truthy(is_nil(retry_attr)): - return NIL - else: - parts = split(retry_attr, ':') - return {'strategy': first(parts), 'start-ms': parse_int(nth(parts, 1), 1000), 'cap-ms': parse_int(nth(parts, 2), 30000)} - -# next-retry-ms -def next_retry_ms(current_ms, cap_ms): - return min((current_ms * 2), cap_ms) - -# filter-params -def filter_params(params_spec, all_params): - if sx_truthy(is_nil(params_spec)): - return all_params - elif sx_truthy((params_spec == 'none')): - return [] - elif sx_truthy((params_spec == '*')): - return all_params - elif sx_truthy(starts_with_p(params_spec, 'not ')): - excluded = map(trim, split(slice(params_spec, 4), ',')) - return filter(lambda p: (not sx_truthy(contains_p(excluded, first(p)))), all_params) - else: - allowed = map(trim, split(params_spec, ',')) - return filter(lambda p: contains_p(allowed, first(p)), all_params) - -# resolve-target -def resolve_target(el): - sel = dom_get_attr(el, 'sx-target') - if sx_truthy((is_nil(sel) if sx_truthy(is_nil(sel)) else (sel == 'this'))): - return el - elif sx_truthy((sel == 'closest')): - return dom_parent(el) - else: - return dom_query(sel) - -# apply-optimistic -def apply_optimistic(el): - directive = dom_get_attr(el, 'sx-optimistic') - if sx_truthy(is_nil(directive)): - return NIL - else: - target = (resolve_target(el) if sx_truthy(resolve_target(el)) else el) - state = {'target': target, 'directive': directive} - (_sx_begin(_sx_dict_set(state, 'opacity', dom_get_style(target, 'opacity')), dom_set_style(target, 'opacity', '0'), dom_set_style(target, 'pointer-events', 'none')) if sx_truthy((directive == 'remove')) else (_sx_begin(_sx_dict_set(state, 'disabled', dom_get_prop(target, 'disabled')), dom_set_prop(target, 'disabled', True)) if sx_truthy((directive == 'disable')) else ((lambda cls: _sx_begin(_sx_dict_set(state, 'add-class', cls), dom_add_class(target, cls)))(slice(directive, 10)) if sx_truthy(starts_with_p(directive, 'add-class:')) else NIL))) - return state - -# revert-optimistic -def revert_optimistic(state): - if sx_truthy(state): - target = get(state, 'target') - directive = get(state, 'directive') - if sx_truthy((directive == 'remove')): - dom_set_style(target, 'opacity', (get(state, 'opacity') if sx_truthy(get(state, 'opacity')) else '')) - return dom_set_style(target, 'pointer-events', '') - elif sx_truthy((directive == 'disable')): - return dom_set_prop(target, 'disabled', (get(state, 'disabled') if sx_truthy(get(state, 'disabled')) else False)) - elif sx_truthy(get(state, 'add-class')): - return dom_remove_class(target, get(state, 'add-class')) - return NIL - return NIL - -# find-oob-swaps -def find_oob_swaps(container): - results = [] - for attr in ['sx-swap-oob', 'hx-swap-oob']: - oob_els = dom_query_all(container, sx_str('[', attr, ']')) - for oob in oob_els: - swap_type = (dom_get_attr(oob, attr) if sx_truthy(dom_get_attr(oob, attr)) else 'outerHTML') - target_id = dom_id(oob) - dom_remove_attr(oob, attr) - if sx_truthy(target_id): - results.append({'element': oob, 'swap-type': swap_type, 'target-id': target_id}) - return results - -# morph-node -def morph_node(old_node, new_node): - if sx_truthy((dom_has_attr_p(old_node, 'sx-preserve') if sx_truthy(dom_has_attr_p(old_node, 'sx-preserve')) else dom_has_attr_p(old_node, 'sx-ignore'))): - return NIL - elif sx_truthy((dom_has_attr_p(old_node, 'data-sx-island') if not sx_truthy(dom_has_attr_p(old_node, 'data-sx-island')) else (is_processed_p(old_node, 'island-hydrated') if not sx_truthy(is_processed_p(old_node, 'island-hydrated')) else (dom_has_attr_p(new_node, 'data-sx-island') if not sx_truthy(dom_has_attr_p(new_node, 'data-sx-island')) else (dom_get_attr(old_node, 'data-sx-island') == dom_get_attr(new_node, 'data-sx-island')))))): - return morph_island_children(old_node, new_node) - elif sx_truthy(((not sx_truthy((dom_node_type(old_node) == dom_node_type(new_node)))) if sx_truthy((not sx_truthy((dom_node_type(old_node) == dom_node_type(new_node))))) else (not sx_truthy((dom_node_name(old_node) == dom_node_name(new_node)))))): - return dom_replace_child(dom_parent(old_node), dom_clone(new_node), old_node) - elif sx_truthy(((dom_node_type(old_node) == 3) if sx_truthy((dom_node_type(old_node) == 3)) else (dom_node_type(old_node) == 8))): - if sx_truthy((not sx_truthy((dom_text_content(old_node) == dom_text_content(new_node))))): - return dom_set_text_content(old_node, dom_text_content(new_node)) - return NIL - elif sx_truthy((dom_node_type(old_node) == 1)): - sync_attrs(old_node, new_node) - if sx_truthy((not sx_truthy((dom_is_active_element_p(old_node) if not sx_truthy(dom_is_active_element_p(old_node)) else dom_is_input_element_p(old_node))))): - return morph_children(old_node, new_node) - return NIL - return NIL - -# sync-attrs -def sync_attrs(old_el, new_el): - ra_str = (dom_get_attr(old_el, 'data-sx-reactive-attrs') if sx_truthy(dom_get_attr(old_el, 'data-sx-reactive-attrs')) else '') - reactive_attrs = ([] if sx_truthy(empty_p(ra_str)) else split(ra_str, ',')) - for attr in dom_attr_list(new_el): - name = first(attr) - val = nth(attr, 1) - if sx_truthy(((not sx_truthy((dom_get_attr(old_el, name) == val))) if not sx_truthy((not sx_truthy((dom_get_attr(old_el, name) == val)))) else (not sx_truthy(contains_p(reactive_attrs, name))))): - dom_set_attr(old_el, name, val) - return for_each(lambda attr: (lambda aname: (dom_remove_attr(old_el, aname) if sx_truthy(((not sx_truthy(dom_has_attr_p(new_el, aname))) if not sx_truthy((not sx_truthy(dom_has_attr_p(new_el, aname)))) else ((not sx_truthy(contains_p(reactive_attrs, aname))) if not sx_truthy((not sx_truthy(contains_p(reactive_attrs, aname)))) else (not sx_truthy((aname == 'data-sx-reactive-attrs')))))) else NIL))(first(attr)), dom_attr_list(old_el)) - -# morph-children -def morph_children(old_parent, new_parent): +# build-routing-analysis +def build_routing_analysis(pages_raw): _cells = {} - old_kids = dom_child_list(old_parent) - new_kids = dom_child_list(new_parent) - old_by_id = reduce(lambda acc, kid: (lambda id_: (_sx_begin(_sx_dict_set(acc, id_, kid), acc) if sx_truthy(id_) else acc))(dom_id(kid)), {}, old_kids) - _cells['oi'] = 0 - for new_child in new_kids: - match_id = dom_id(new_child) - match_by_id = (dict_get(old_by_id, match_id) if sx_truthy(match_id) else NIL) - if sx_truthy((match_by_id if not sx_truthy(match_by_id) else (not sx_truthy(is_nil(match_by_id))))): - if sx_truthy(((_cells['oi'] < len(old_kids)) if not sx_truthy((_cells['oi'] < len(old_kids))) else (not sx_truthy((match_by_id == nth(old_kids, _cells['oi'])))))): - dom_insert_before(old_parent, match_by_id, (nth(old_kids, _cells['oi']) if sx_truthy((_cells['oi'] < len(old_kids))) else NIL)) - morph_node(match_by_id, new_child) - _cells['oi'] = (_cells['oi'] + 1) - elif sx_truthy((_cells['oi'] < len(old_kids))): - old_child = nth(old_kids, _cells['oi']) - if sx_truthy((dom_id(old_child) if not sx_truthy(dom_id(old_child)) else (not sx_truthy(match_id)))): - dom_insert_before(old_parent, dom_clone(new_child), old_child) - else: - morph_node(old_child, new_child) - _cells['oi'] = (_cells['oi'] + 1) + 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: - dom_append(old_parent, dom_clone(new_child)) - return for_each(lambda i: ((lambda leftover: (dom_remove_child(old_parent, leftover) if sx_truthy((dom_is_child_of_p(leftover, old_parent) if not sx_truthy(dom_is_child_of_p(leftover, old_parent)) else ((not sx_truthy(dom_has_attr_p(leftover, 'sx-preserve'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(leftover, 'sx-preserve')))) else (not sx_truthy(dom_has_attr_p(leftover, 'sx-ignore')))))) else NIL))(nth(old_kids, i)) if sx_truthy((i >= _cells['oi'])) else NIL), range(_cells['oi'], len(old_kids))) + _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']} -# morph-island-children -def morph_island_children(old_island, new_island): - old_lakes = dom_query_all(old_island, '[data-sx-lake]') - new_lakes = dom_query_all(new_island, '[data-sx-lake]') - old_marshes = dom_query_all(old_island, '[data-sx-marsh]') - new_marshes = dom_query_all(new_island, '[data-sx-marsh]') - new_lake_map = {} - new_marsh_map = {} - for lake in new_lakes: - id_ = dom_get_attr(lake, 'data-sx-lake') - if sx_truthy(id_): - new_lake_map[id_] = lake - for marsh in new_marshes: - id_ = dom_get_attr(marsh, 'data-sx-marsh') - if sx_truthy(id_): - new_marsh_map[id_] = marsh - for old_lake in old_lakes: - id_ = dom_get_attr(old_lake, 'data-sx-lake') - new_lake = dict_get(new_lake_map, id_) - if sx_truthy(new_lake): - sync_attrs(old_lake, new_lake) - morph_children(old_lake, new_lake) - for old_marsh in old_marshes: - id_ = dom_get_attr(old_marsh, 'data-sx-marsh') - new_marsh = dict_get(new_marsh_map, id_) - if sx_truthy(new_marsh): - morph_marsh(old_marsh, new_marsh, old_island) - return process_signal_updates(new_island) - -# morph-marsh -def morph_marsh(old_marsh, new_marsh, island_el): - transform = dom_get_data(old_marsh, 'sx-marsh-transform') - env = dom_get_data(old_marsh, 'sx-marsh-env') - new_html = dom_inner_html(new_marsh) - if sx_truthy((env if not sx_truthy(env) else (new_html if not sx_truthy(new_html) else (not sx_truthy(empty_p(new_html)))))): - parsed = parse(new_html) - sx_content = (invoke(transform, parsed) if sx_truthy(transform) else parsed) - dispose_marsh_scope(old_marsh) - return with_marsh_scope(old_marsh, lambda : (lambda new_dom: _sx_begin(dom_remove_children_after(old_marsh, NIL), dom_append(old_marsh, new_dom)))(render_to_dom(sx_content, env, NIL))) - else: - sync_attrs(old_marsh, new_marsh) - return morph_children(old_marsh, new_marsh) - -# process-signal-updates -def process_signal_updates(root): - signal_els = dom_query_all(root, '[data-sx-signal]') - return for_each(lambda el: (lambda spec: ((lambda colon_idx: ((lambda store_name: (lambda raw_value: _sx_begin((lambda parsed: reset_b(use_store(store_name), parsed))(json_parse(raw_value)), dom_remove_attr(el, 'data-sx-signal')))(slice(spec, (colon_idx + 1))))(slice(spec, 0, colon_idx)) if sx_truthy((colon_idx > 0)) else NIL))(index_of(spec, ':')) if sx_truthy(spec) else NIL))(dom_get_attr(el, 'data-sx-signal')), signal_els) - -# swap-dom-nodes -def swap_dom_nodes(target, new_nodes, strategy): - _match = strategy - if _match == 'innerHTML': - if sx_truthy(dom_is_fragment_p(new_nodes)): - return morph_children(target, new_nodes) - else: - wrapper = dom_create_element('div', NIL) - dom_append(wrapper, new_nodes) - return morph_children(target, wrapper) - elif _match == 'outerHTML': - parent = dom_parent(target) - ((lambda fc: (_sx_begin(morph_node(target, fc), (lambda sib: insert_remaining_siblings(parent, target, sib))(dom_next_sibling(fc))) if sx_truthy(fc) else dom_remove_child(parent, target)))(dom_first_child(new_nodes)) if sx_truthy(dom_is_fragment_p(new_nodes)) else morph_node(target, new_nodes)) - return parent - elif _match == 'afterend': - return dom_insert_after(target, new_nodes) - elif _match == 'beforeend': - return dom_append(target, new_nodes) - elif _match == 'afterbegin': - return dom_prepend(target, new_nodes) - elif _match == 'beforebegin': - return dom_insert_before(dom_parent(target), new_nodes, target) - elif _match == 'delete': - return dom_remove_child(dom_parent(target), target) - elif _match == 'none': - return NIL - else: - if sx_truthy(dom_is_fragment_p(new_nodes)): - return morph_children(target, new_nodes) - else: - wrapper = dom_create_element('div', NIL) - dom_append(wrapper, new_nodes) - return morph_children(target, wrapper) - -# insert-remaining-siblings -def insert_remaining_siblings(parent, ref_node, sib): - if sx_truthy(sib): - next = dom_next_sibling(sib) - dom_insert_after(ref_node, sib) - return insert_remaining_siblings(parent, sib, next) - return NIL - -# swap-html-string -def swap_html_string(target, html, strategy): - _match = strategy - if _match == 'innerHTML': - return dom_set_inner_html(target, html) - elif _match == 'outerHTML': - parent = dom_parent(target) - dom_insert_adjacent_html(target, 'afterend', html) - dom_remove_child(parent, target) - return parent - elif _match == 'afterend': - return dom_insert_adjacent_html(target, 'afterend', html) - elif _match == 'beforeend': - return dom_insert_adjacent_html(target, 'beforeend', html) - elif _match == 'afterbegin': - return dom_insert_adjacent_html(target, 'afterbegin', html) - elif _match == 'beforebegin': - return dom_insert_adjacent_html(target, 'beforebegin', html) - elif _match == 'delete': - return dom_remove_child(dom_parent(target), target) - elif _match == 'none': - return NIL - else: - return dom_set_inner_html(target, html) - -# handle-history -def handle_history(el, url, resp_headers): - push_url = dom_get_attr(el, 'sx-push-url') - replace_url = dom_get_attr(el, 'sx-replace-url') - hdr_replace = get(resp_headers, 'replace-url') - if sx_truthy(hdr_replace): - return browser_replace_state(hdr_replace) - elif sx_truthy((push_url if not sx_truthy(push_url) else (not sx_truthy((push_url == 'false'))))): - return browser_push_state((url if sx_truthy((push_url == 'true')) else push_url)) - elif sx_truthy((replace_url if not sx_truthy(replace_url) else (not sx_truthy((replace_url == 'false'))))): - return browser_replace_state((url if sx_truthy((replace_url == 'true')) else replace_url)) - return NIL - -# PRELOAD_TTL -PRELOAD_TTL = 30000 - -# preload-cache-get -def preload_cache_get(cache, url): - entry = dict_get(cache, url) - if sx_truthy(is_nil(entry)): - return NIL - else: - if sx_truthy(((now_ms() - get(entry, 'timestamp')) > PRELOAD_TTL)): - dict_delete(cache, url) - return NIL - else: - dict_delete(cache, url) - return entry - -# preload-cache-set -def preload_cache_set(cache, url, text, content_type): - return _sx_dict_set(cache, url, {'text': text, 'content-type': content_type, 'timestamp': now_ms()}) - -# classify-trigger -def classify_trigger(trigger): - event = get(trigger, 'event') - if sx_truthy((event == 'every')): - return 'poll' - elif sx_truthy((event == 'intersect')): - return 'intersect' - elif sx_truthy((event == 'load')): - return 'load' - elif sx_truthy((event == 'revealed')): - return 'revealed' - else: - return 'event' - -# should-boost-link? -def should_boost_link_p(link): - href = dom_get_attr(link, 'href') - return (href if not sx_truthy(href) else ((not sx_truthy(starts_with_p(href, '#'))) if not sx_truthy((not sx_truthy(starts_with_p(href, '#')))) else ((not sx_truthy(starts_with_p(href, 'javascript:'))) if not sx_truthy((not sx_truthy(starts_with_p(href, 'javascript:')))) else ((not sx_truthy(starts_with_p(href, 'mailto:'))) if not sx_truthy((not sx_truthy(starts_with_p(href, 'mailto:')))) else (browser_same_origin_p(href) if not sx_truthy(browser_same_origin_p(href)) else ((not sx_truthy(dom_has_attr_p(link, 'sx-get'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(link, 'sx-get')))) else ((not sx_truthy(dom_has_attr_p(link, 'sx-post'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(link, 'sx-post')))) else (not sx_truthy(dom_has_attr_p(link, 'sx-disable')))))))))) - -# should-boost-form? -def should_boost_form_p(form): - return ((not sx_truthy(dom_has_attr_p(form, 'sx-get'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(form, 'sx-get')))) else ((not sx_truthy(dom_has_attr_p(form, 'sx-post'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(form, 'sx-post')))) else (not sx_truthy(dom_has_attr_p(form, 'sx-disable'))))) - -# parse-sse-swap -def parse_sse_swap(el): - return (dom_get_attr(el, 'sx-sse-swap') if sx_truthy(dom_get_attr(el, 'sx-sse-swap')) else 'message') - - -# === 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 = {} - path_segs = split_path_segments(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'] +# build-affinity-analysis +def build_affinity_analysis(demo_components, page_plans): + return {'components': demo_components, 'page-plans': page_plans} # === Transpiled from signals (reactive signal runtime) === @@ -3058,4 +2753,4 @@ def render(expr, env=None): def make_env(**kwargs): """Create an environment with initial bindings.""" - return _Env(dict(kwargs)) + return _Env(dict(kwargs)) \ No newline at end of file diff --git a/sx/sx/boundary.sx b/sx/sx/boundary.sx index 0269f113..d39c940b 100644 --- a/sx/sx/boundary.sx +++ b/sx/sx/boundary.sx @@ -104,3 +104,8 @@ :params () :returns "dict" :service "sx") + +(define-page-helper "page-helpers-demo-data" + :params () + :returns "dict" + :service "sx") diff --git a/sx/sx/nav-data.sx b/sx/sx/nav-data.sx index bb2c30f4..1d2dfa71 100644 --- a/sx/sx/nav-data.sx +++ b/sx/sx/nav-data.sx @@ -227,7 +227,8 @@ (dict :label "JavaScript" :href "/bootstrappers/javascript") (dict :label "Python" :href "/bootstrappers/python") (dict :label "Self-Hosting (py.sx)" :href "/bootstrappers/self-hosting") - (dict :label "Self-Hosting JS (js.sx)" :href "/bootstrappers/self-hosting-js"))) + (dict :label "Self-Hosting JS (js.sx)" :href "/bootstrappers/self-hosting-js") + (dict :label "Page Helpers" :href "/bootstrappers/page-helpers"))) ;; Spec file registry — canonical metadata for spec viewer pages. ;; Python only handles file I/O (read-spec-file); all metadata lives here. diff --git a/sx/sx/page-helpers-demo.sx b/sx/sx/page-helpers-demo.sx new file mode 100644 index 00000000..f4adce1d --- /dev/null +++ b/sx/sx/page-helpers-demo.sx @@ -0,0 +1,265 @@ +;; page-helpers-demo.sx — Demo: same SX spec functions on server and client +;; +;; Shows page-helpers.sx functions running on Python (server-side, via sx_ref.py) +;; and JavaScript (client-side, via sx-browser.js) with identical results. +;; Server renders with render-to-html. Client runs as a defisland — pure SX, +;; no JavaScript file. The button click triggers spec functions via signals. + +;; --------------------------------------------------------------------------- +;; Shared card component — used by both server and client results +;; --------------------------------------------------------------------------- + +(defcomp ~demo-result-card (&key title ms desc theme &rest children) + (let ((border (if (= theme "blue") "border-blue-200 bg-blue-50/30" "border-stone-200")) + (title-c (if (= theme "blue") "text-blue-700" "text-stone-700")) + (badge-c (if (= theme "blue") "text-blue-400" "text-stone-400")) + (desc-c (if (= theme "blue") "text-blue-500" "text-stone-500")) + (body-c (if (= theme "blue") "text-blue-600" "text-stone-600"))) + (div :class (str "rounded-lg border p-4 " border) + (h4 :class (str "font-semibold text-sm mb-1 " title-c) + title " " + (span :class (str "text-xs " badge-c) (str ms "ms"))) + (p :class (str "text-xs mb-2 " desc-c) desc) + (div :class (str "text-xs space-y-0.5 " body-c) + children)))) + + +;; --------------------------------------------------------------------------- +;; Client-side island — runs spec functions in the browser on button click +;; --------------------------------------------------------------------------- + +(defisland ~demo-client-runner (&key sf-source attr-detail req-attrs attr-keys) + (let ((results (signal nil)) + (running (signal false)) + (run-demo (fn (e) + (reset! running true) + (let* ((t0 (now-ms)) + + ;; 1. categorize-special-forms + (t1 (now-ms)) + (sf-exprs (sx-parse sf-source)) + (sf-result (categorize-special-forms sf-exprs)) + (sf-ms (- (now-ms) t1)) + (sf-cats {}) + (sf-total 0) + + ;; 2. build-reference-data + (t2 (now-ms)) + (ref-result (build-reference-data "attributes" + {"req-attrs" req-attrs "beh-attrs" (list) "uniq-attrs" (list)} + attr-keys)) + (ref-ms (- (now-ms) t2)) + (ref-sample (slice (or (get ref-result "req-attrs") (list)) 0 3)) + + ;; 3. build-attr-detail + (t3 (now-ms)) + (attr-result (build-attr-detail "sx-get" attr-detail)) + (attr-ms (- (now-ms) t3)) + + ;; 4. build-component-source + (t4 (now-ms)) + (comp-result (build-component-source + {"type" "component" "name" "~demo-card" + "params" (list "title" "subtitle") + "has-children" true + "body-sx" "(div :class \"card\"\n (h2 title)\n (when subtitle (p subtitle))\n children)" + "affinity" "auto"})) + (comp-ms (- (now-ms) t4)) + + ;; 5. build-routing-analysis + (t5 (now-ms)) + (routing-result (build-routing-analysis (list + {"name" "home" "path" "/" "has-data" false "content-src" "(~home-content)"} + {"name" "dashboard" "path" "/dash" "has-data" true "content-src" "(~dashboard)"} + {"name" "about" "path" "/about" "has-data" false "content-src" "(~about-content)"} + {"name" "settings" "path" "/settings" "has-data" true "content-src" "(~settings)"}))) + (routing-ms (- (now-ms) t5)) + + (total-ms (- (now-ms) t0))) + + ;; Post-process sf-result: count forms per category + (for-each (fn (k) + (let ((count (len (get sf-result k)))) + (set! sf-cats (assoc sf-cats k count)) + (set! sf-total (+ sf-total count)))) + (keys sf-result)) + + (reset! results + {"sf-cats" sf-cats "sf-total" sf-total "sf-ms" sf-ms + "ref-sample" ref-sample "ref-ms" ref-ms + "attr-result" attr-result "attr-ms" attr-ms + "comp-result" comp-result "comp-ms" comp-ms + "routing-result" routing-result "routing-ms" routing-ms + "total-ms" total-ms}))))) + + (<> + (button + :class (if (deref running) + "px-4 py-2 rounded-md bg-blue-600 text-white font-medium text-sm cursor-default mb-4" + "px-4 py-2 rounded-md bg-violet-600 text-white font-medium text-sm hover:bg-violet-700 transition-colors mb-4") + :on-click run-demo + (if (deref running) + (str "Done (" (get (deref results) "total-ms") "ms total)") + "Run in Browser")) + + (when (deref results) + (let ((r (deref results))) + (div :class "grid grid-cols-1 md:grid-cols-2 gap-4" + + (~demo-result-card + :title "categorize-special-forms" + :ms (get r "sf-ms") :theme "blue" + :desc "Parses special-forms.sx and classifies each form by category (control flow, binding, quoting, etc)." + (p :class "text-sm mb-1" + (str (get r "sf-total") " forms in " + (len (keys (get r "sf-cats"))) " categories")) + (map (fn (k) + (div (str k ": " (get (get r "sf-cats") k)))) + (keys (get r "sf-cats")))) + + (~demo-result-card + :title "build-reference-data" + :ms (get r "ref-ms") :theme "blue" + :desc "Takes raw attribute tuples and generates reference table rows with documentation hrefs." + (p :class "text-sm mb-1" + (str (len (get r "ref-sample")) " attributes with detail page links")) + (map (fn (item) + (div (str (get item "name") " → " + (or (get item "href") "no detail page")))) + (get r "ref-sample"))) + + (~demo-result-card + :title "build-attr-detail" + :ms (get r "attr-ms") :theme "blue" + :desc "Builds a detail page data structure for a single attribute (sx-get): title, wire ID, handler status." + (div (str "title: " (get (get r "attr-result") "attr-title"))) + (div (str "wire-id: " (or (get (get r "attr-result") "attr-wire-id") "none"))) + (div (str "has handler: " (if (get (get r "attr-result") "attr-handler") "yes" "no")))) + + (~demo-result-card + :title "build-component-source" + :ms (get r "comp-ms") :theme "blue" + :desc "Reconstructs a defcomp source definition from a component metadata dict (name, params, body)." + (pre :class "bg-blue-50 p-2 rounded overflow-x-auto" + (get r "comp-result"))) + + (div :class "rounded-lg border border-blue-200 bg-blue-50/30 p-4 md:col-span-2" + (h4 :class "font-semibold text-blue-700 text-sm mb-1" + "build-routing-analysis " + (span :class "text-xs text-blue-400" (str (get r "routing-ms") "ms"))) + (p :class "text-xs text-blue-500 mb-2" + "Classifies pages as client-routable or server-only based on whether they have data dependencies.") + (div :class "text-xs text-blue-600" + (p :class "text-sm mb-1" + (str (get (get r "routing-result") "total-pages") " pages: " + (get (get r "routing-result") "client-count") " client-routable, " + (get (get r "routing-result") "server-count") " server-only")) + (div :class "space-y-0.5" + (map (fn (pg) + (div (str (get pg "name") " → " (get pg "mode") + (when (not (empty? (get pg "reason"))) + (str " (" (get pg "reason") ")"))))) + (get (get r "routing-result") "pages"))))))))))) + + +;; --------------------------------------------------------------------------- +;; Main page component — server-rendered content + client island +;; --------------------------------------------------------------------------- + +(defcomp ~page-helpers-demo-content (&key + sf-categories sf-total sf-ms + ref-sample ref-ms + attr-result attr-ms + comp-source comp-ms + routing-result routing-ms + server-total-ms + sf-source + attr-detail req-attrs attr-keys) + + (div :class "max-w-3xl mx-auto px-4" + (div :class "mb-8" + (h2 :class "text-2xl font-bold text-stone-800 mb-2" "Bootstrapped Page Helpers") + (p :class "text-stone-600 mb-4" + "These functions are defined once in " + (code :class "text-violet-700" "page-helpers.sx") + " and bootstrapped to both Python (" + (code :class "text-violet-700" "sx_ref.py") + ") and JavaScript (" + (code :class "text-violet-700" "sx-browser.js") + "). The server ran them in Python during this page load. Click the button below to run the identical functions client-side in the browser — same spec, same inputs, same results.")) + + ;; Server results + (div :class "mb-8" + (h3 :class "text-lg font-semibold text-stone-700 mb-3" + "Server Results " + (span :class "text-sm font-normal text-stone-500" + (str "(Python, " server-total-ms "ms total)"))) + + (div :class "grid grid-cols-1 md:grid-cols-2 gap-4" + + (~demo-result-card + :title "categorize-special-forms" + :ms sf-ms :theme "stone" + :desc "Parses special-forms.sx and classifies each form by category (control flow, binding, quoting, etc)." + (p :class "text-sm mb-1" + (str sf-total " forms in " + (len (keys sf-categories)) " categories")) + (map (fn (k) + (div (str k ": " (get sf-categories k)))) + (keys sf-categories))) + + (~demo-result-card + :title "build-reference-data" + :ms ref-ms :theme "stone" + :desc "Takes raw attribute tuples and generates reference table rows with documentation hrefs." + (p :class "text-sm mb-1" + (str (len ref-sample) " attributes with detail page links")) + (map (fn (item) + (div (str (get item "name") " → " + (or (get item "href") "no detail page")))) + ref-sample)) + + (~demo-result-card + :title "build-attr-detail" + :ms attr-ms :theme "stone" + :desc "Builds a detail page data structure for a single attribute (sx-get): title, wire ID, handler status." + (div (str "title: " (get attr-result "attr-title"))) + (div (str "wire-id: " (or (get attr-result "attr-wire-id") "none"))) + (div (str "has handler: " (if (get attr-result "attr-handler") "yes" "no")))) + + (~demo-result-card + :title "build-component-source" + :ms comp-ms :theme "stone" + :desc "Reconstructs a defcomp source definition from a component metadata dict (name, params, body)." + (pre :class "bg-stone-50 p-2 rounded overflow-x-auto" + comp-source)) + + (div :class "rounded-lg border border-stone-200 p-4 md:col-span-2" + (h4 :class "font-semibold text-stone-700 text-sm mb-1" + "build-routing-analysis " + (span :class "text-xs text-stone-400" (str routing-ms "ms"))) + (p :class "text-xs text-stone-500 mb-2" + "Classifies pages as client-routable or server-only based on whether they have data dependencies.") + (div :class "text-xs text-stone-600" + (p :class "text-sm mb-1" + (str (get routing-result "total-pages") " pages: " + (get routing-result "client-count") " client-routable, " + (get routing-result "server-count") " server-only")) + (div :class "space-y-0.5" + (map (fn (pg) + (div (str (get pg "name") " → " (get pg "mode") + (when (not (empty? (get pg "reason"))) + (str " (" (get pg "reason") ")"))))) + (get routing-result "pages"))))))) + + ;; Client execution area — pure SX island, no JavaScript file + (div :class "mb-8" + (h3 :class "text-lg font-semibold text-stone-700 mb-3" + "Client Results " + (span :class "text-sm font-normal text-stone-500" "(JavaScript, sx-browser.js)")) + + (~demo-client-runner + :sf-source sf-source + :attr-detail attr-detail + :req-attrs req-attrs + :attr-keys attr-keys)))) diff --git a/sx/sxc/pages/docs.sx b/sx/sxc/pages/docs.sx index c938f938..b6402e20 100644 --- a/sx/sxc/pages/docs.sx +++ b/sx/sxc/pages/docs.sx @@ -553,6 +553,28 @@ "phase2" (~reactive-islands-phase2-content) :else (~reactive-islands-index-content)))) +;; --------------------------------------------------------------------------- +;; Bootstrapped page helpers demo +;; --------------------------------------------------------------------------- + +(defpage page-helpers-demo + :path "/bootstrappers/page-helpers" + :auth :public + :layout :sx-docs + :data (page-helpers-demo-data) + :content (~sx-doc :path "/bootstrappers/page-helpers" + (~page-helpers-demo-content + :sf-categories sf-categories :sf-total sf-total :sf-ms sf-ms + :ref-sample ref-sample :ref-ms ref-ms + :attr-result attr-result :attr-ms attr-ms + :comp-source comp-source :comp-ms comp-ms + :routing-result routing-result :routing-ms routing-ms + :server-total-ms server-total-ms + :sf-source sf-source + :attr-detail attr-detail + :req-attrs req-attrs + :attr-keys attr-keys))) + ;; --------------------------------------------------------------------------- ;; Testing section ;; --------------------------------------------------------------------------- diff --git a/sx/sxc/pages/helpers.py b/sx/sxc/pages/helpers.py index f8c6deea..a9c8cbb2 100644 --- a/sx/sxc/pages/helpers.py +++ b/sx/sxc/pages/helpers.py @@ -33,6 +33,7 @@ def _register_sx_helpers() -> None: "action:add-demo-item": _add_demo_item, "offline-demo-data": _offline_demo_data, "prove-data": _prove_data, + "page-helpers-demo-data": _page_helpers_demo_data, }) @@ -41,26 +42,29 @@ def _component_source(name: str) -> str: from shared.sx.jinja_bridge import get_component_env from shared.sx.parser import serialize from shared.sx.types import Component, Island + from shared.sx.ref.sx_ref import build_component_source comp = get_component_env().get(name) if isinstance(comp, Island): - param_strs = (["&key"] + list(comp.params)) if comp.params else [] - if comp.has_children: - param_strs.extend(["&rest", "children"]) - params_sx = "(" + " ".join(param_strs) + ")" - body_sx = serialize(comp.body, pretty=True) - return f"(defisland {name} {params_sx}\n {body_sx})" + return build_component_source({ + "type": "island", "name": name, + "params": list(comp.params) if comp.params else [], + "has-children": comp.has_children, + "body-sx": serialize(comp.body, pretty=True), + "affinity": None, + }) if not isinstance(comp, Component): - return f";; component {name} not found" - param_strs = ["&key"] + list(comp.params) - if comp.has_children: - param_strs.extend(["&rest", "children"]) - params_sx = "(" + " ".join(param_strs) + ")" - body_sx = serialize(comp.body, pretty=True) - affinity = "" - if comp.render_target == "server": - affinity = " :affinity :server" - return f"(defcomp {name} {params_sx}{affinity}\n {body_sx})" + return build_component_source({ + "type": "not-found", "name": name, + "params": [], "has-children": False, "body-sx": "", "affinity": None, + }) + return build_component_source({ + "type": "component", "name": name, + "params": list(comp.params), + "has-children": comp.has_children, + "body-sx": serialize(comp.body, pretty=True), + "affinity": comp.affinity, + }) def _primitives_data() -> dict: @@ -70,168 +74,57 @@ def _primitives_data() -> dict: def _special_forms_data() -> dict: - """Parse special-forms.sx and return categorized form data. - - Returns a dict of category → list of form dicts, each with: - name, syntax, doc, tail_position, example - """ + """Parse special-forms.sx and return categorized form data.""" import os - from shared.sx.parser import parse_all, serialize - from shared.sx.types import Symbol, Keyword + from shared.sx.parser import parse_all + from shared.sx.ref.sx_ref import categorize_special_forms - ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref") - if not os.path.isdir(ref_dir): - ref_dir = "/app/shared/sx/ref" + ref_dir = _ref_dir() spec_path = os.path.join(ref_dir, "special-forms.sx") with open(spec_path) as f: exprs = parse_all(f.read()) - - # Categories inferred from comment sections in the file. - # We assign forms to categories based on their order in the spec. - categories: dict[str, list[dict]] = {} - current_category = "Other" - - # Map form names to categories - 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", - } - - for expr in exprs: - if not isinstance(expr, list) or len(expr) < 2: - continue - head = expr[0] - if not isinstance(head, Symbol) or head.name != "define-special-form": - continue - - name = expr[1] - # Extract keyword args - kwargs: dict[str, str] = {} - i = 2 - while i < len(expr) - 1: - if isinstance(expr[i], Keyword): - key = expr[i].name - val = expr[i + 1] - if isinstance(val, list): - # For :syntax, avoid quote sugar (quasiquote → `x) - items = [serialize(item) for item in val] - kwargs[key] = "(" + " ".join(items) + ")" - else: - kwargs[key] = str(val) - i += 2 - else: - i += 1 - - category = category_map.get(name, "Other") - if category not in categories: - categories[category] = [] - categories[category].append({ - "name": name, - "syntax": kwargs.get("syntax", ""), - "doc": kwargs.get("doc", ""), - "tail-position": kwargs.get("tail-position", ""), - "example": kwargs.get("example", ""), - }) - - return categories + return categorize_special_forms(exprs) def _reference_data(slug: str) -> dict: - """Return reference table data for a given slug. - - Returns a dict whose keys become SX env bindings: - - attributes: req-attrs, beh-attrs, uniq-attrs - - headers: req-headers, resp-headers - - events: events-list - - js-api: js-api-list - """ + """Return reference table data for a given slug.""" from content.pages import ( REQUEST_ATTRS, BEHAVIOR_ATTRS, SX_UNIQUE_ATTRS, REQUEST_HEADERS, RESPONSE_HEADERS, EVENTS, JS_API, ATTR_DETAILS, HEADER_DETAILS, ) + from shared.sx.ref.sx_ref import build_reference_data + # Build raw data dict and detail keys based on slug if slug == "attributes": - return { - "req-attrs": [ - {"name": a, "desc": d, "exists": e, - "href": f"/hypermedia/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} - for a, d, e in REQUEST_ATTRS - ], - "beh-attrs": [ - {"name": a, "desc": d, "exists": e, - "href": f"/hypermedia/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} - for a, d, e in BEHAVIOR_ATTRS - ], - "uniq-attrs": [ - {"name": a, "desc": d, "exists": e, - "href": f"/hypermedia/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} - for a, d, e in SX_UNIQUE_ATTRS - ], + raw = { + "req-attrs": [list(t) for t in REQUEST_ATTRS], + "beh-attrs": [list(t) for t in BEHAVIOR_ATTRS], + "uniq-attrs": [list(t) for t in SX_UNIQUE_ATTRS], } + detail_keys = list(ATTR_DETAILS.keys()) elif slug == "headers": - return { - "req-headers": [ - {"name": n, "value": v, "desc": d, - "href": f"/hypermedia/reference/headers/{n}" if n in HEADER_DETAILS else None} - for n, v, d in REQUEST_HEADERS - ], - "resp-headers": [ - {"name": n, "value": v, "desc": d, - "href": f"/hypermedia/reference/headers/{n}" if n in HEADER_DETAILS else None} - for n, v, d in RESPONSE_HEADERS - ], + raw = { + "req-headers": [list(t) for t in REQUEST_HEADERS], + "resp-headers": [list(t) for t in RESPONSE_HEADERS], } + detail_keys = list(HEADER_DETAILS.keys()) elif slug == "events": from content.pages import EVENT_DETAILS - return { - "events-list": [ - {"name": n, "desc": d, - "href": f"/hypermedia/reference/events/{n}" if n in EVENT_DETAILS else None} - for n, d in EVENTS - ], - } + raw = {"events-list": [list(t) for t in EVENTS]} + detail_keys = list(EVENT_DETAILS.keys()) elif slug == "js-api": - return { - "js-api-list": [ - {"name": n, "desc": d} - for n, d in JS_API - ], + raw = {"js-api-list": [list(t) for t in JS_API]} + detail_keys = [] + else: + raw = { + "req-attrs": [list(t) for t in REQUEST_ATTRS], + "beh-attrs": [list(t) for t in BEHAVIOR_ATTRS], + "uniq-attrs": [list(t) for t in SX_UNIQUE_ATTRS], } - # Default — return attrs data for fallback - return { - "req-attrs": [ - {"name": a, "desc": d, "exists": e, - "href": f"/hypermedia/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} - for a, d, e in REQUEST_ATTRS - ], - "beh-attrs": [ - {"name": a, "desc": d, "exists": e, - "href": f"/hypermedia/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} - for a, d, e in BEHAVIOR_ATTRS - ], - "uniq-attrs": [ - {"name": a, "desc": d, "exists": e, - "href": f"/hypermedia/reference/attributes/{a}" if e and a in ATTR_DETAILS else None} - for a, d, e in SX_UNIQUE_ATTRS - ], - } + detail_keys = list(ATTR_DETAILS.keys()) + + return build_reference_data(slug, raw, detail_keys) def _read_spec_file(filename: str) -> str: @@ -425,6 +318,7 @@ def _js_self_hosting_data(ref_dir: str) -> dict: return { "bootstrapper-not-found": None, "js-sx-source": js_sx_source, + "defines-matched": str(total), "defines-total": str(total), "js-sx-lines": str(len(js_sx_source.splitlines())), "verification-status": status, @@ -438,6 +332,7 @@ def _bundle_analyzer_data() -> dict: from shared.sx.deps import components_needed, scan_components_from_sx from shared.sx.parser import serialize from shared.sx.types import Component, Macro + from shared.sx.ref.sx_ref import build_bundle_analysis env = get_component_env() total_components = sum(1 for v in env.values() if isinstance(v, Component)) @@ -445,68 +340,47 @@ def _bundle_analyzer_data() -> dict: pure_count = sum(1 for v in env.values() if isinstance(v, Component) and v.is_pure) io_count = total_components - pure_count - pages_data = [] + # Extract raw data at I/O edge — Python accesses Component objects, serializes bodies + pages_raw = [] + components_raw: dict[str, dict] = {} for name, page_def in sorted(get_all_pages("sx").items()): content_sx = serialize(page_def.content_expr) direct = scan_components_from_sx(content_sx) - needed = components_needed(content_sx, env) - n = len(needed) - pct = round(n / total_components * 100) if total_components else 0 - savings = 100 - pct + needed = sorted(components_needed(content_sx, env)) - # IO classification + component details for this page - pure_in_page = 0 - io_in_page = 0 - page_io_refs: set[str] = set() - comp_details = [] - for comp_name in sorted(needed): - val = env.get(comp_name) - if isinstance(val, Component): - is_pure = val.is_pure - if is_pure: - pure_in_page += 1 - else: - io_in_page += 1 - page_io_refs.update(val.io_refs) - # Reconstruct defcomp source - param_strs = ["&key"] + list(val.params) - if val.has_children: - param_strs.extend(["&rest", "children"]) - params_sx = "(" + " ".join(param_strs) + ")" - body_sx = serialize(val.body, pretty=True) - source = f"(defcomp ~{val.name} {params_sx}\n {body_sx})" - comp_details.append({ - "name": comp_name, - "is-pure": is_pure, - "affinity": val.affinity, - "render-target": val.render_target, - "io-refs": sorted(val.io_refs), - "deps": sorted(val.deps), - "source": source, - }) + for comp_name in needed: + if comp_name not in components_raw: + val = env.get(comp_name) + if isinstance(val, Component): + param_strs = ["&key"] + list(val.params) + if val.has_children: + param_strs.extend(["&rest", "children"]) + params_sx = "(" + " ".join(param_strs) + ")" + body_sx = serialize(val.body, pretty=True) + components_raw[comp_name] = { + "is-pure": val.is_pure, + "affinity": val.affinity, + "render-target": val.render_target, + "io-refs": sorted(val.io_refs), + "deps": sorted(val.deps), + "source": f"(defcomp ~{val.name} {params_sx}\n {body_sx})", + } - pages_data.append({ + pages_raw.append({ "name": name, "path": page_def.path, "direct": len(direct), - "needed": n, - "pct": pct, - "savings": savings, - "io-refs": len(page_io_refs), - "pure-in-page": pure_in_page, - "io-in-page": io_in_page, - "components": comp_details, + "needed-names": needed, }) - pages_data.sort(key=lambda p: p["needed"], reverse=True) - - return { - "pages": pages_data, - "total-components": total_components, - "total-macros": total_macros, - "pure-count": pure_count, - "io-count": io_count, - } + # Pure data transformation in SX spec + result = build_bundle_analysis( + pages_raw, components_raw, + total_components, total_macros, pure_count, io_count, + ) + # Sort pages by needed count (descending) — SX has no sort primitive + result["pages"] = sorted(result["pages"], key=lambda p: p["needed"], reverse=True) + return result def _routing_analyzer_data() -> dict: @@ -514,12 +388,11 @@ def _routing_analyzer_data() -> dict: from shared.sx.pages import get_all_pages from shared.sx.parser import serialize as sx_serialize from shared.sx.helpers import _sx_literal + from shared.sx.ref.sx_ref import build_routing_analysis - pages_data = [] - full_content: list[tuple[str, str, bool]] = [] # (name, full_content, has_data) - client_count = 0 - server_count = 0 - + # I/O edge: extract page data from page registry + pages_raw = [] + full_content: list[tuple[str, str, bool]] = [] for name, page_def in sorted(get_all_pages("sx").items()): has_data = page_def.data_expr is not None content_src = "" @@ -528,37 +401,21 @@ def _routing_analyzer_data() -> dict: content_src = sx_serialize(page_def.content_expr) except Exception: pass - + pages_raw.append({ + "name": name, "path": page_def.path, + "has-data": has_data, "content-src": content_src, + }) full_content.append((name, content_src, has_data)) - # Determine routing mode and reason - if has_data: - mode = "server" - reason = "Has :data expression — needs server IO" - server_count += 1 - elif not content_src: - mode = "server" - reason = "No content expression" - server_count += 1 - else: - mode = "client" - reason = "" - client_count += 1 + # Pure classification in SX spec + result = build_routing_analysis(pages_raw) + # Sort: client pages first, then server (SX has no sort primitive) + result["pages"] = sorted( + result["pages"], + key=lambda p: (0 if p["mode"] == "client" else 1, p["name"]), + ) - pages_data.append({ - "name": name, - "path": page_def.path, - "mode": mode, - "has-data": has_data, - "content-expr": content_src[:80] + ("..." if len(content_src) > 80 else ""), - "reason": reason, - }) - - # Sort: client pages first, then server - pages_data.sort(key=lambda p: (0 if p["mode"] == "client" else 1, p["name"])) - - # Build a sample of the SX page registry format (use full content, first 3) - total = client_count + server_count + # Build registry sample (uses _sx_literal which is Python string escaping) sample_entries = [] sorted_full = sorted(full_content, key=lambda x: x[0]) for name, csrc, hd in sorted_full[:3]: @@ -574,86 +431,50 @@ def _routing_analyzer_data() -> dict: + "\n :closure {}}" ) sample_entries.append(entry) - registry_sample = "\n\n".join(sample_entries) + result["registry-sample"] = "\n\n".join(sample_entries) - return { - "pages": pages_data, - "total-pages": total, - "client-count": client_count, - "server-count": server_count, - "registry-sample": registry_sample, - } + return result def _attr_detail_data(slug: str) -> dict: - """Return attribute detail data for a specific attribute slug. - - Returns a dict whose keys become SX env bindings: - - attr-title, attr-description, attr-example, attr-handler - - attr-demo (component call or None) - - attr-wire-id (wire placeholder id or None) - - attr-not-found (truthy if not found) - """ + """Return attribute detail data for a specific attribute slug.""" from content.pages import ATTR_DETAILS from shared.sx.helpers import sx_call + from shared.sx.ref.sx_ref import build_attr_detail detail = ATTR_DETAILS.get(slug) - if not detail: - return {"attr-not-found": True} - - demo_name = detail.get("demo") - wire_id = None - if "handler" in detail: - wire_id = f"ref-wire-{slug.replace(':', '-').replace('*', 'star')}" - - return { - "attr-not-found": None, - "attr-title": slug, - "attr-description": detail["description"], - "attr-example": detail["example"], - "attr-handler": detail.get("handler"), - "attr-demo": sx_call(demo_name) if demo_name else None, - "attr-wire-id": wire_id, - } + result = build_attr_detail(slug, detail) + # Convert demo name to sx_call if present + demo_name = result.get("attr-demo") + if demo_name: + result["attr-demo"] = sx_call(demo_name) + return result def _header_detail_data(slug: str) -> dict: """Return header detail data for a specific header slug.""" from content.pages import HEADER_DETAILS from shared.sx.helpers import sx_call + from shared.sx.ref.sx_ref import build_header_detail - detail = HEADER_DETAILS.get(slug) - if not detail: - return {"header-not-found": True} - - demo_name = detail.get("demo") - return { - "header-not-found": None, - "header-title": slug, - "header-direction": detail["direction"], - "header-description": detail["description"], - "header-example": detail.get("example"), - "header-demo": sx_call(demo_name) if demo_name else None, - } + result = build_header_detail(slug, HEADER_DETAILS.get(slug)) + demo_name = result.get("header-demo") + if demo_name: + result["header-demo"] = sx_call(demo_name) + return result def _event_detail_data(slug: str) -> dict: """Return event detail data for a specific event slug.""" from content.pages import EVENT_DETAILS from shared.sx.helpers import sx_call + from shared.sx.ref.sx_ref import build_event_detail - detail = EVENT_DETAILS.get(slug) - if not detail: - return {"event-not-found": True} - - demo_name = detail.get("demo") - return { - "event-not-found": None, - "event-title": slug, - "event-description": detail["description"], - "event-example": detail.get("example"), - "event-demo": sx_call(demo_name) if demo_name else None, - } + result = build_event_detail(slug, EVENT_DETAILS.get(slug)) + demo_name = result.get("event-demo") + if demo_name: + result["event-demo"] = sx_call(demo_name) + return result def _run_spec_tests() -> dict: @@ -1089,35 +910,30 @@ def _affinity_demo_data() -> dict: from shared.sx.jinja_bridge import get_component_env from shared.sx.types import Component from shared.sx.pages import get_all_pages + from shared.sx.ref.sx_ref import build_affinity_analysis + # I/O edge: extract component data and page render plans env = get_component_env() demo_names = [ - "~aff-demo-auto", - "~aff-demo-client", - "~aff-demo-server", - "~aff-demo-io-auto", - "~aff-demo-io-client", + "~aff-demo-auto", "~aff-demo-client", "~aff-demo-server", + "~aff-demo-io-auto", "~aff-demo-io-client", ] components = [] for name in demo_names: val = env.get(name) if isinstance(val, Component): components.append({ - "name": name, - "affinity": val.affinity, + "name": name, "affinity": val.affinity, "render-target": val.render_target, - "io-refs": sorted(val.io_refs), - "is-pure": val.is_pure, + "io-refs": sorted(val.io_refs), "is-pure": val.is_pure, }) - # Collect render plans from all sx service pages page_plans = [] for page_def in get_all_pages("sx").values(): plan = page_def.render_plan if plan: page_plans.append({ - "name": page_def.name, - "path": page_def.path, + "name": page_def.name, "path": page_def.path, "server-count": len(plan.get("server", [])), "client-count": len(plan.get("client", [])), "server": plan.get("server", []), @@ -1125,7 +941,7 @@ def _affinity_demo_data() -> dict: "io-deps": plan.get("io-deps", []), }) - return {"components": components, "page-plans": page_plans} + return build_affinity_analysis(components, page_plans) def _optimistic_demo_data() -> dict: @@ -1271,3 +1087,84 @@ def _offline_demo_data() -> dict: ], "server-time": datetime.now(timezone.utc).isoformat(timespec="seconds"), } + + +def _page_helpers_demo_data() -> dict: + """Run page-helpers.sx functions server-side, return results for comparison with client.""" + import os + import time + from shared.sx.parser import parse_all + from shared.sx.ref.sx_ref import ( + categorize_special_forms, build_reference_data, + build_attr_detail, build_component_source, + build_routing_analysis, + ) + + ref_dir = _ref_dir() + results = {} + + # 1. categorize-special-forms + t0 = time.monotonic() + with open(os.path.join(ref_dir, "special-forms.sx")) as f: + sf_exprs = parse_all(f.read()) + sf_result = categorize_special_forms(sf_exprs) + sf_ms = round((time.monotonic() - t0) * 1000, 1) + sf_summary = {cat: len(forms) for cat, forms in sf_result.items()} + results["sf-categories"] = sf_summary + results["sf-total"] = sum(sf_summary.values()) + results["sf-ms"] = sf_ms + + # 2. build-reference-data + from content.pages import REQUEST_ATTRS, ATTR_DETAILS + t1 = time.monotonic() + ref_result = build_reference_data("attributes", { + "req-attrs": [list(t) for t in REQUEST_ATTRS[:5]], + "beh-attrs": [], "uniq-attrs": [], + }, list(ATTR_DETAILS.keys())) + ref_ms = round((time.monotonic() - t1) * 1000, 1) + results["ref-sample"] = ref_result.get("req-attrs", [])[:3] + results["ref-ms"] = ref_ms + + # 3. build-attr-detail + t2 = time.monotonic() + detail = ATTR_DETAILS.get("sx-get") + attr_result = build_attr_detail("sx-get", detail) + attr_ms = round((time.monotonic() - t2) * 1000, 1) + results["attr-result"] = attr_result + results["attr-ms"] = attr_ms + + # 4. build-component-source + t3 = time.monotonic() + comp_result = build_component_source({ + "type": "component", "name": "~demo-card", + "params": ["title", "subtitle"], + "has-children": True, + "body-sx": "(div :class \"card\"\n (h2 title)\n (when subtitle (p subtitle))\n children)", + "affinity": "auto", + }) + comp_ms = round((time.monotonic() - t3) * 1000, 1) + results["comp-source"] = comp_result + results["comp-ms"] = comp_ms + + # 5. build-routing-analysis + t4 = time.monotonic() + routing_result = build_routing_analysis([ + {"name": "home", "path": "/", "has-data": False, "content-src": "(~home-content)"}, + {"name": "dashboard", "path": "/dash", "has-data": True, "content-src": "(~dashboard)"}, + {"name": "about", "path": "/about", "has-data": False, "content-src": "(~about-content)"}, + {"name": "settings", "path": "/settings", "has-data": True, "content-src": "(~settings)"}, + ]) + routing_ms = round((time.monotonic() - t4) * 1000, 1) + results["routing-result"] = routing_result + results["routing-ms"] = routing_ms + + # Total + results["server-total-ms"] = round(sf_ms + ref_ms + attr_ms + comp_ms + routing_ms, 1) + + # Pass raw inputs for client-side island (serialized as data-sx-state) + results["sf-source"] = open(os.path.join(ref_dir, "special-forms.sx")).read() + results["attr-detail"] = detail + results["req-attrs"] = [list(t) for t in REQUEST_ATTRS[:5]] + results["attr-keys"] = list(ATTR_DETAILS.keys()) + + return results From e843602ac920b7ada0ddccb931e31b56371ffc1f Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 11 Mar 2026 14:59:31 +0000 Subject: [PATCH 3/6] Fix aser list flattening bug, add wire format test suite (41 tests) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sync aser-call in adapter-sx.sx didn't flatten list results from map/filter in positional children — serialize(list) wrapped in parens creating ((div ...) ...) which re-parses as an invalid call. Rewrote aser-call from reduce to for-each (bootstrapper can't nest for-each inside reduce lambdas) and added list flattening in both aser-call and aser-fragment. Also adds test-aser.sx (41 tests), render-sx platform function, expanded test-render.sx (+7 map/filter children tests), and specs async-eval-slot-inner in adapter-async.sx. Co-Authored-By: Claude Opus 4.6 --- shared/static/scripts/sx-browser.js | 28 ++-- shared/sx/ref/adapter-async.sx | 58 +++++++ shared/sx/ref/adapter-sx.sx | 65 +++++--- shared/sx/ref/sx_ref.py | 41 ++++- shared/sx/ref/test-aser.sx | 241 ++++++++++++++++++++++++++++ shared/sx/ref/test-render.sx | 43 +++++ shared/sx/tests/run.py | 30 +++- 7 files changed, 465 insertions(+), 41 deletions(-) create mode 100644 shared/sx/ref/test-aser.sx diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 69090b80..4f4a886f 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-11T13:57:48Z"; + var SX_VERSION = "2026-03-11T14:54:55Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -1505,30 +1505,34 @@ return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if // aser-fragment var aserFragment = function(children, env) { return (function() { - var parts = filter(function(x) { return !isSxTruthy(isNil(x)); }, map(function(c) { return aser(c, env); }, children)); - return (isSxTruthy(isEmpty(parts)) ? "" : (String("(<> ") + String(join(" ", map(serialize, parts))) + String(")"))); + var parts = []; + { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() { + var result = aser(c, env); + return (isSxTruthy((typeOf(result) == "list")) ? forEach(function(item) { return (isSxTruthy(!isSxTruthy(isNil(item))) ? append_b(parts, serialize(item)) : NIL); }, result) : (isSxTruthy(!isSxTruthy(isNil(result))) ? append_b(parts, serialize(result)) : NIL)); +})(); } } + return (isSxTruthy(isEmpty(parts)) ? "" : (String("(<> ") + String(join(" ", parts)) + String(")"))); })(); }; // aser-call var aserCall = function(name, args, env) { return (function() { var parts = [name]; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var val = aser(nth(args, (get(state, "i") + 1)), env); + var skip = false; + var i = 0; + { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (isSxTruthy(skip) ? ((skip = false), (i = (i + 1))) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((i + 1) < len(args)))) ? (function() { + var val = aser(nth(args, (i + 1)), env); if (isSxTruthy(!isSxTruthy(isNil(val)))) { parts.push((String(":") + String(keywordName(arg)))); parts.push(serialize(val)); } - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); + skip = true; + return (i = (i + 1)); })() : (function() { var val = aser(arg, env); if (isSxTruthy(!isSxTruthy(isNil(val)))) { - parts.push(serialize(val)); + (isSxTruthy((typeOf(val) == "list")) ? forEach(function(item) { return (isSxTruthy(!isSxTruthy(isNil(item))) ? append_b(parts, serialize(item)) : NIL); }, val) : append_b(parts, serialize(val))); } - return assoc(state, "i", (get(state, "i") + 1)); -})())); -})(); }, {["i"]: 0, ["skip"]: false}, args); + return (i = (i + 1)); +})())); } } return (String("(") + String(join(" ", parts)) + String(")")); })(); }; diff --git a/shared/sx/ref/adapter-async.sx b/shared/sx/ref/adapter-async.sx index f57dfe35..b6200907 100644 --- a/shared/sx/ref/adapter-async.sx +++ b/shared/sx/ref/adapter-async.sx @@ -1149,6 +1149,64 @@ results))) +;; -------------------------------------------------------------------------- +;; async-eval-slot-inner — server-side slot expansion for aser mode +;; -------------------------------------------------------------------------- +;; +;; Coordinates component expansion for server-rendered pages: +;; 1. If expression is a direct component call (~name ...), expand it +;; 2. Otherwise aser the expression, then check if result is a (~...) +;; call that should be re-expanded +;; +;; Platform primitives required: +;; (sx-parse src) — parse SX source string +;; (make-sx-expr s) — wrap as SxExpr +;; (sx-expr? x) — check if SxExpr +;; (set-expand-components!) — enable component expansion context var + +(define-async async-eval-slot-inner + (fn (expr env ctx) + (let ((result + (if (and (list? expr) (not (empty? expr))) + (let ((head (first expr))) + (if (and (= (type-of head) "symbol") + (starts-with? (symbol-name head) "~")) + (let ((name (symbol-name head)) + (val (if (env-has? env name) (env-get env name) nil))) + (if (component? val) + (async-aser-component val (rest expr) env ctx) + ;; Islands and unknown components — fall through to aser + (async-maybe-expand-result (async-aser expr env ctx) env ctx))) + (async-maybe-expand-result (async-aser expr env ctx) env ctx))) + (async-maybe-expand-result (async-aser expr env ctx) env ctx)))) + ;; Normalize result to SxExpr + (if (sx-expr? result) + result + (if (nil? result) + (make-sx-expr "") + (if (string? result) + (make-sx-expr result) + (make-sx-expr (serialize result)))))))) + + +(define-async async-maybe-expand-result + (fn (result env ctx) + ;; If the aser result is a component call string like "(~foo ...)", + ;; re-parse and expand it. This handles indirect component references + ;; (e.g. a let binding that evaluates to a component call). + (let ((raw (if (sx-expr? result) + (trim (str result)) + (if (string? result) + (trim result) + nil)))) + (if (and raw (starts-with? raw "(~")) + (let ((parsed (sx-parse raw))) + (if (and parsed (not (empty? parsed))) + (async-eval-slot-inner (first parsed) env ctx) + result)) + result)))) + + ;; -------------------------------------------------------------------------- ;; Platform interface — async adapter ;; -------------------------------------------------------------------------- diff --git a/shared/sx/ref/adapter-sx.sx b/shared/sx/ref/adapter-sx.sx index b045faa3..5bc388f0 100644 --- a/shared/sx/ref/adapter-sx.sx +++ b/shared/sx/ref/adapter-sx.sx @@ -106,35 +106,56 @@ (define aser-fragment (fn (children env) ;; Serialize (<> child1 child2 ...) to sx source string - (let ((parts (filter - (fn (x) (not (nil? x))) - (map (fn (c) (aser c env)) children)))) + ;; Must flatten list results (e.g. from map/filter) to avoid nested parens + (let ((parts (list))) + (for-each + (fn (c) + (let ((result (aser c env))) + (if (= (type-of result) "list") + (for-each + (fn (item) + (when (not (nil? item)) + (append! parts (serialize item)))) + result) + (when (not (nil? result)) + (append! parts (serialize result)))))) + children) (if (empty? parts) "" - (str "(<> " (join " " (map serialize parts)) ")"))))) + (str "(<> " (join " " parts) ")"))))) (define aser-call (fn (name args env) ;; Serialize (name :key val child ...) — evaluate args but keep as sx - (let ((parts (list name))) - (reduce - (fn (state arg) - (let ((skip (get state "skip"))) - (if skip - (assoc state "skip" false "i" (inc (get state "i"))) - (if (and (= (type-of arg) "keyword") - (< (inc (get state "i")) (len args))) - (let ((val (aser (nth args (inc (get state "i"))) env))) - (when (not (nil? val)) - (append! parts (str ":" (keyword-name arg))) - (append! parts (serialize val))) - (assoc state "skip" true "i" (inc (get state "i")))) - (let ((val (aser arg env))) - (when (not (nil? val)) - (append! parts (serialize val))) - (assoc state "i" (inc (get state "i")))))))) - (dict "i" 0 "skip" false) + ;; Uses for-each + mutable state (not reduce) so bootstrapper emits for-loops + ;; that can contain nested for-each for list flattening. + (let ((parts (list name)) + (skip false) + (i 0)) + (for-each + (fn (arg) + (if skip + (do (set! skip false) + (set! i (inc i))) + (if (and (= (type-of arg) "keyword") + (< (inc i) (len args))) + (let ((val (aser (nth args (inc i)) env))) + (when (not (nil? val)) + (append! parts (str ":" (keyword-name arg))) + (append! parts (serialize val))) + (set! skip true) + (set! i (inc i))) + (let ((val (aser arg env))) + (when (not (nil? val)) + (if (= (type-of val) "list") + (for-each + (fn (item) + (when (not (nil? item)) + (append! parts (serialize item)))) + val) + (append! parts (serialize val)))) + (set! i (inc i)))))) args) (str "(" (join " " parts) ")")))) diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py index f5de87a2..75120910 100644 --- a/shared/sx/ref/sx_ref.py +++ b/shared/sx/ref/sx_ref.py @@ -2015,16 +2015,49 @@ def aser_list(expr, env): # aser-fragment def aser_fragment(children, env): - parts = filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda c: aser(c, env), children)) + 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(' ', map(serialize, parts)), ')') + return sx_str('(<> ', join(' ', parts), ')') # aser-call def aser_call(name, args, env): + _cells = {} parts = [name] - 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_begin(_sx_append(parts, sx_str(':', keyword_name(arg))), _sx_append(parts, serialize(val))) if sx_truthy((not sx_truthy(is_nil(val)))) else NIL), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(aser(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 (lambda val: _sx_begin((_sx_append(parts, serialize(val)) if sx_truthy((not sx_truthy(is_nil(val)))) else NIL), assoc(state, 'i', (get(state, 'i') + 1))))(aser(arg, env)))))(get(state, 'skip')), {'i': 0, 'skip': False}, args) + _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 = aser(nth(args, (_cells['i'] + 1)), env) + if sx_truthy((not sx_truthy(is_nil(val)))): + parts.append(sx_str(':', keyword_name(arg))) + 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)))): + parts.append(serialize(item)) + else: + parts.append(serialize(val)) + _cells['i'] = (_cells['i'] + 1) return sx_str('(', join(' ', parts), ')') # SPECIAL_FORM_NAMES @@ -2753,4 +2786,4 @@ def render(expr, env=None): def make_env(**kwargs): """Create an environment with initial bindings.""" - return _Env(dict(kwargs)) \ No newline at end of file + return _Env(dict(kwargs)) diff --git a/shared/sx/ref/test-aser.sx b/shared/sx/ref/test-aser.sx new file mode 100644 index 00000000..053386a0 --- /dev/null +++ b/shared/sx/ref/test-aser.sx @@ -0,0 +1,241 @@ +;; ========================================================================== +;; test-aser.sx — Tests for the SX wire format (aser) adapter +;; +;; Requires: test-framework.sx loaded first. +;; Modules tested: adapter-sx.sx (aser, aser-call, aser-fragment, aser-special) +;; +;; Platform functions required (beyond test framework): +;; render-sx (sx-source) -> SX wire format string +;; Parses the sx-source string, evaluates via aser in a +;; fresh env, and returns the resulting SX wire format string. +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; Basic serialization +;; -------------------------------------------------------------------------- + +(defsuite "aser-basics" + (deftest "number literal passes through" + (assert-equal "42" + (render-sx "42"))) + + (deftest "string literal passes through" + ;; aser returns the raw string value; render-sx concatenates it directly + (assert-equal "hello" + (render-sx "\"hello\""))) + + (deftest "boolean true passes through" + (assert-equal "true" + (render-sx "true"))) + + (deftest "boolean false passes through" + (assert-equal "false" + (render-sx "false"))) + + (deftest "nil produces empty" + (assert-equal "" + (render-sx "nil")))) + + +;; -------------------------------------------------------------------------- +;; HTML tag serialization +;; -------------------------------------------------------------------------- + +(defsuite "aser-tags" + (deftest "simple div" + (assert-equal "(div \"hello\")" + (render-sx "(div \"hello\")"))) + + (deftest "nested tags" + (assert-equal "(div (span \"hi\"))" + (render-sx "(div (span \"hi\"))"))) + + (deftest "multiple children" + (assert-equal "(div (p \"a\") (p \"b\"))" + (render-sx "(div (p \"a\") (p \"b\"))"))) + + (deftest "attributes serialize" + (assert-equal "(div :class \"foo\" \"bar\")" + (render-sx "(div :class \"foo\" \"bar\")"))) + + (deftest "multiple attributes" + (assert-equal "(a :href \"/home\" :class \"link\" \"Home\")" + (render-sx "(a :href \"/home\" :class \"link\" \"Home\")"))) + + (deftest "void elements" + (assert-equal "(br)" + (render-sx "(br)"))) + + (deftest "void element with attrs" + (assert-equal "(img :src \"pic.jpg\")" + (render-sx "(img :src \"pic.jpg\")")))) + + +;; -------------------------------------------------------------------------- +;; Fragment serialization +;; -------------------------------------------------------------------------- + +(defsuite "aser-fragments" + (deftest "simple fragment" + (assert-equal "(<> (p \"a\") (p \"b\"))" + (render-sx "(<> (p \"a\") (p \"b\"))"))) + + (deftest "empty fragment" + (assert-equal "" + (render-sx "(<>)"))) + + (deftest "single-child fragment" + (assert-equal "(<> (div \"x\"))" + (render-sx "(<> (div \"x\"))")))) + + +;; -------------------------------------------------------------------------- +;; Control flow in aser mode +;; -------------------------------------------------------------------------- + +(defsuite "aser-control-flow" + (deftest "if true branch" + (assert-equal "(p \"yes\")" + (render-sx "(if true (p \"yes\") (p \"no\"))"))) + + (deftest "if false branch" + (assert-equal "(p \"no\")" + (render-sx "(if false (p \"yes\") (p \"no\"))"))) + + (deftest "when true" + (assert-equal "(p \"ok\")" + (render-sx "(when true (p \"ok\"))"))) + + (deftest "when false" + (assert-equal "" + (render-sx "(when false (p \"ok\"))"))) + + (deftest "cond serializes matching branch" + (assert-equal "(p \"two\")" + (render-sx "(cond false (p \"one\") true (p \"two\") :else (p \"three\"))"))) + + (deftest "let binds then serializes" + (assert-equal "(p \"hello\")" + (render-sx "(let ((x \"hello\")) (p x))"))) + + (deftest "begin serializes last" + (assert-equal "(p \"last\")" + (render-sx "(begin (p \"first\") (p \"last\"))")))) + + +;; -------------------------------------------------------------------------- +;; THE BUG — map/filter list flattening in children (critical regression) +;; -------------------------------------------------------------------------- + +(defsuite "aser-list-flattening" + (deftest "map inside tag flattens children" + (assert-equal "(div (span \"a\") (span \"b\") (span \"c\"))" + (render-sx "(do (define items (list \"a\" \"b\" \"c\")) + (div (map (fn (x) (span x)) items)))"))) + + (deftest "map inside tag with other children" + (assert-equal "(ul (li \"first\") (li \"a\") (li \"b\"))" + (render-sx "(do (define items (list \"a\" \"b\")) + (ul (li \"first\") (map (fn (x) (li x)) items)))"))) + + (deftest "filter result via let binding as children" + ;; Note: (filter ...) is treated as an SVG tag in aser dispatch (SVG has ), + ;; so we evaluate filter via let binding + map to serialize children + (assert-equal "(ul (li \"a\") (li \"b\"))" + (render-sx "(do (define items (list \"a\" nil \"b\")) + (define kept (filter (fn (x) (not (nil? x))) items)) + (ul (map (fn (x) (li x)) kept)))"))) + + (deftest "map inside fragment flattens" + (assert-equal "(<> (p \"a\") (p \"b\"))" + (render-sx "(do (define items (list \"a\" \"b\")) + (<> (map (fn (x) (p x)) items)))"))) + + (deftest "nested map does not double-wrap" + (assert-equal "(div (span \"1\") (span \"2\"))" + (render-sx "(do (define nums (list 1 2)) + (div (map (fn (n) (span (str n))) nums)))"))) + + (deftest "map with component-like output flattens" + (assert-equal "(div (li \"x\") (li \"y\"))" + (render-sx "(do (define items (list \"x\" \"y\")) + (div (map (fn (x) (li x)) items)))")))) + + +;; -------------------------------------------------------------------------- +;; Component serialization (NOT expanded in basic aser mode) +;; -------------------------------------------------------------------------- + +(defsuite "aser-components" + (deftest "unknown component serializes as-is" + (assert-equal "(~foo :title \"bar\")" + (render-sx "(~foo :title \"bar\")"))) + + (deftest "defcomp then unexpanded component call" + (assert-equal "(~card :title \"Hi\")" + (render-sx "(do (defcomp ~card (&key title) (h1 title)) (~card :title \"Hi\"))"))) + + (deftest "component with children serializes unexpanded" + (assert-equal "(~box (p \"inside\"))" + (render-sx "(do (defcomp ~box (&key &rest children) (div children)) + (~box (p \"inside\")))")))) + + +;; -------------------------------------------------------------------------- +;; Definition forms in aser mode +;; -------------------------------------------------------------------------- + +(defsuite "aser-definitions" + (deftest "define evaluates for side effects, returns nil" + (assert-equal "(p 42)" + (render-sx "(do (define x 42) (p x))"))) + + (deftest "defcomp evaluates and returns nil" + (assert-equal "(~tag :x 1)" + (render-sx "(do (defcomp ~tag (&key x) (span x)) (~tag :x 1))"))) + + (deftest "defisland evaluates AND serializes" + (let ((result (render-sx "(defisland ~counter (&key count) (span count))"))) + (assert-true (string-contains? result "defisland"))))) + + +;; -------------------------------------------------------------------------- +;; Function calls in aser mode +;; -------------------------------------------------------------------------- + +(defsuite "aser-function-calls" + (deftest "named function call evaluates fully" + (assert-equal "3" + (render-sx "(do (define inc1 (fn (x) (+ x 1))) (inc1 2))"))) + + (deftest "define + call" + (assert-equal "10" + (render-sx "(do (define double (fn (x) (* x 2))) (double 5))"))) + + (deftest "higher-order: map returns list" + (let ((result (render-sx "(map (fn (x) (+ x 1)) (list 1 2 3))"))) + ;; map at top level returns a list, not serialized tags + (assert-true (not (nil? result)))))) + + +;; -------------------------------------------------------------------------- +;; and/or short-circuit in aser mode +;; -------------------------------------------------------------------------- + +(defsuite "aser-logic" + (deftest "and short-circuits on false" + (assert-equal "false" + (render-sx "(and true false true)"))) + + (deftest "and returns last truthy" + (assert-equal "3" + (render-sx "(and 1 2 3)"))) + + (deftest "or short-circuits on true" + (assert-equal "1" + (render-sx "(or 1 2 3)"))) + + (deftest "or returns last falsy" + (assert-equal "false" + (render-sx "(or false false)")))) diff --git a/shared/sx/ref/test-render.sx b/shared/sx/ref/test-render.sx index c714fc7d..08f1d7f4 100644 --- a/shared/sx/ref/test-render.sx +++ b/shared/sx/ref/test-render.sx @@ -165,3 +165,46 @@ (let ((html (render-html "(do (defcomp ~box (&key &rest children) (div :class \"box\" children)) (~box (p \"inside\")))"))) (assert-true (string-contains? html "class=\"box\"")) (assert-true (string-contains? html "

inside

"))))) + + +;; -------------------------------------------------------------------------- +;; Map/filter producing multiple children (aser-adjacent regression tests) +;; -------------------------------------------------------------------------- + +(defsuite "render-map-children" + (deftest "map producing multiple children inside tag" + (assert-equal "
  • a
  • b
  • c
" + (render-html "(do (define items (list \"a\" \"b\" \"c\")) + (ul (map (fn (x) (li x)) items)))"))) + + (deftest "map with other siblings" + (assert-equal "
  • first
  • a
  • b
" + (render-html "(do (define items (list \"a\" \"b\")) + (ul (li \"first\") (map (fn (x) (li x)) items)))"))) + + (deftest "filter with nil results inside tag" + (assert-equal "
  • a
  • c
" + (render-html "(do (define items (list \"a\" nil \"c\")) + (ul (map (fn (x) (li x)) + (filter (fn (x) (not (nil? x))) items))))"))) + + (deftest "nested map inside let" + (assert-equal "
12
" + (render-html "(let ((nums (list 1 2))) + (div (map (fn (n) (span n)) nums)))"))) + + (deftest "component with &rest receiving mapped results" + (let ((html (render-html "(do (defcomp ~list-box (&key &rest children) (div :class \"lb\" children)) + (define items (list \"x\" \"y\")) + (~list-box (map (fn (x) (p x)) items)))"))) + (assert-true (string-contains? html "class=\"lb\"")) + (assert-true (string-contains? html "

x

")) + (assert-true (string-contains? html "

y

")))) + + (deftest "map-indexed renders with index" + (assert-equal "
  • 0: a
  • 1: b
  • " + (render-html "(map-indexed (fn (i x) (li (str i \": \" x))) (list \"a\" \"b\"))"))) + + (deftest "for-each renders each item" + (assert-equal "

    1

    2

    " + (render-html "(for-each (fn (x) (p x)) (list 1 2))")))) diff --git a/shared/sx/tests/run.py b/shared/sx/tests/run.py index abdc791a..297ebacb 100644 --- a/shared/sx/tests/run.py +++ b/shared/sx/tests/run.py @@ -134,6 +134,28 @@ def render_html(sx_source): return result +# --- Render SX (aser) platform function --- + +def render_sx(sx_source): + """Parse SX source and serialize to SX wire format via the bootstrapped evaluator.""" + try: + from shared.sx.ref.sx_ref import aser as _aser, serialize as _serialize + except ImportError: + raise RuntimeError("aser not available — sx_ref.py not built") + exprs = parse_all(sx_source) + render_env = dict(env) + result = "" + for expr in exprs: + val = _aser(expr, render_env) + if isinstance(val, str): + result += val + elif val is None or val is NIL: + pass + else: + result += _serialize(val) + return result + + # --- Signal platform primitives --- # Implements the signal runtime platform interface for testing signals.sx @@ -258,6 +280,7 @@ SPECS = { "parser": {"file": "test-parser.sx", "needs": ["sx-parse"]}, "router": {"file": "test-router.sx", "needs": []}, "render": {"file": "test-render.sx", "needs": ["render-html"]}, + "aser": {"file": "test-aser.sx", "needs": ["render-sx"]}, "deps": {"file": "test-deps.sx", "needs": []}, "engine": {"file": "test-engine.sx", "needs": []}, "orchestration": {"file": "test-orchestration.sx", "needs": []}, @@ -296,8 +319,9 @@ env = _Env({ "make-keyword": make_keyword, "symbol-name": symbol_name, "keyword-name": keyword_name, - # Render platform function + # Render platform functions "render-html": render_html, + "render-sx": render_sx, # Extra primitives needed by spec modules (router.sx, deps.sx) "for-each-indexed": "_deferred", # replaced below "dict-set!": "_deferred", @@ -773,9 +797,9 @@ def main(): print(f"# --- {spec_name} ---") eval_file(spec["file"], env) - # Reset render state after render tests to avoid leaking + # Reset render state after render/aser tests to avoid leaking # into subsequent specs (bootstrapped evaluator checks render_active) - if spec_name == "render": + if spec_name in ("render", "aser"): try: from shared.sx.ref.sx_ref import set_render_active_b set_render_active_b(False) From ff6c1fab71d3f48f9ad9bfd333027b18f5aae49d Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 11 Mar 2026 16:38:47 +0000 Subject: [PATCH 4/6] Fix process-bindings scope loss and async-invoke arity, bootstrap async adapter Two bugs fixed: 1. process-bindings used merge(env) which returns {} for Env objects (Env is not a dict subclass). Changed to env-extend in render.sx and adapter-async.sx. This caused "Undefined symbol: theme" etc. 2. async-aser-eval-call passed evaled-args list to async-invoke(&rest), double-wrapping it. Changed to inline apply + coroutine check. Also: bootstrap define-async into sx_ref.py (Phase 6), replace ~1000 LOC hand-written async_eval_ref.py with 24-line thin re-export shim. Test runner now uses Env (not flat dict) for render envs to catch scope bugs. 8 new regression tests (4 scope chain, 2 native callable arity, 2 render). Co-Authored-By: Claude Opus 4.6 --- shared/static/scripts/sx-browser.js | 102 ++- shared/sx/html.py | 27 +- shared/sx/ref/adapter-async.sx | 36 +- shared/sx/ref/adapter-dom.sx | 7 +- shared/sx/ref/adapter-html.sx | 18 +- shared/sx/ref/async_eval_ref.py | 1028 +------------------------ shared/sx/ref/boot.sx | 10 +- shared/sx/ref/bootstrap_py.py | 87 ++- shared/sx/ref/js.sx | 5 +- shared/sx/ref/platform_js.py | 34 +- shared/sx/ref/platform_py.py | 97 ++- shared/sx/ref/render.sx | 4 +- shared/sx/ref/sx_ref.py | 1103 ++++++++++++++++++++++++++- shared/sx/ref/test-aser.sx | 24 + shared/sx/ref/test-render.sx | 15 +- shared/sx/tests/run.py | 7 +- sx/sx/page-helpers-demo.sx | 1 - 17 files changed, 1402 insertions(+), 1203 deletions(-) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 4f4a886f..617df9ad 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-11T14:54:55Z"; + var SX_VERSION = "2026-03-11T16:35:21Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -204,7 +204,7 @@ // JSON / dict helpers for island state serialization function jsonSerialize(obj) { - try { return JSON.stringify(obj); } catch(e) { return "{}"; } + return JSON.stringify(obj); } function isEmptyDict(d) { if (!d || typeof d !== "object") return true; @@ -214,11 +214,34 @@ function envHas(env, name) { return name in env; } function envGet(env, name) { return env[name]; } - function envSet(env, name, val) { env[name] = val; } + function envSet(env, name, val) { + // Walk prototype chain to find where the variable is defined (for set!) + var obj = env; + while (obj !== null && obj !== Object.prototype) { + if (obj.hasOwnProperty(name)) { obj[name] = val; return; } + obj = Object.getPrototypeOf(obj); + } + // Not found in any parent scope — set on the immediate env + env[name] = val; + } function envExtend(env) { return Object.create(env); } function envMerge(base, overlay) { + // Same env or overlay is descendant of base — just extend, no copy. + // This prevents set! inside lambdas from modifying shadow copies. + if (base === overlay) return Object.create(base); + var p = overlay; + for (var d = 0; p && p !== Object.prototype && d < 100; d++) { + if (p === base) return Object.create(base); + p = Object.getPrototypeOf(p); + } + // General case: extend base, copy ONLY overlay properties that don't + // exist in the base chain (avoids shadowing closure bindings). var child = Object.create(base); - if (overlay) for (var k in overlay) if (overlay.hasOwnProperty(k)) child[k] = overlay[k]; + if (overlay) { + for (var k in overlay) { + if (overlay.hasOwnProperty(k) && !(k in base)) child[k] = overlay[k]; + } + } return child; } @@ -732,9 +755,9 @@ var kwargs = first(parsed); var children = nth(parsed, 1); var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = sxOr(dictGet(kwargs, p), NIL); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, sxOr(dictGet(kwargs, p), NIL)); } } if (isSxTruthy(componentHasChildren(comp))) { - local["children"] = children; + envSet(local, "children", children); } return makeThunk(componentBody(comp), local); })(); }; @@ -841,7 +864,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var loopBody = (isSxTruthy((len(body) == 1)) ? first(body) : cons(makeSymbol("begin"), body)); var loopFn = makeLambda(params, loopBody, env); loopFn.name = loopName; - lambdaClosure(loopFn)[loopName] = loopFn; + envSet(lambdaClosure(loopFn), loopName, loopFn); return (function() { var initVals = map(function(e) { return trampoline(evalExpr(e, env)); }, inits); return callLambda(loopFn, initVals, env); @@ -865,7 +888,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai if (isSxTruthy((isSxTruthy(isLambda(value)) && isNil(lambdaName(value))))) { value.name = symbolName(nameSym); } - env[symbolName(nameSym)] = value; + envSet(env, symbolName(nameSym), value); return value; })(); }; @@ -881,7 +904,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var affinity = defcompKwarg(args, "affinity", "auto"); return (function() { var comp = makeComponent(compName, params, hasChildren, body, env, affinity); - env[symbolName(nameSym)] = comp; + envSet(env, symbolName(nameSym), comp); return comp; })(); })(); }; @@ -924,7 +947,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var hasChildren = nth(parsed, 1); return (function() { var island = makeIsland(compName, params, hasChildren, body, env); - env[symbolName(nameSym)] = island; + envSet(env, symbolName(nameSym), island); return island; })(); })(); }; @@ -939,7 +962,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var restParam = nth(parsed, 1); return (function() { var mac = makeMacro(params, restParam, body, env, symbolName(nameSym)); - env[symbolName(nameSym)] = mac; + envSet(env, symbolName(nameSym), mac); return mac; })(); })(); }; @@ -956,7 +979,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var sfDefstyle = function(args, env) { return (function() { var nameSym = first(args); var value = trampoline(evalExpr(nth(args, 1), env)); - env[symbolName(nameSym)] = value; + envSet(env, symbolName(nameSym), value); return value; })(); }; @@ -996,7 +1019,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var sfSetBang = function(args, env) { return (function() { var name = symbolName(first(args)); var value = trampoline(evalExpr(nth(args, 1), env)); - env[name] = value; + envSet(env, name, value); return value; })(); }; @@ -1021,7 +1044,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai })(); }, NIL, range(0, (len(bindings) / 2)))); (function() { var values = map(function(e) { return trampoline(evalExpr(e, local)); }, valExprs); - { var _c = zip(names, values); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; local[first(pair)] = nth(pair, 1); } } + { var _c = zip(names, values); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envSet(local, first(pair), nth(pair, 1)); } } return forEach(function(val) { return (isSxTruthy(isLambda(val)) ? forEach(function(n) { return envSet(lambdaClosure(val), n, envGet(local, n)); }, names) : NIL); }, values); })(); { var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } } @@ -1046,9 +1069,9 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai // expand-macro var expandMacro = function(mac, rawArgs, env) { return (function() { var local = envMerge(macroClosure(mac), env); - { var _c = mapIndexed(function(i, p) { return [p, i]; }, macroParams(mac)); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; local[first(pair)] = (isSxTruthy((nth(pair, 1) < len(rawArgs))) ? nth(rawArgs, nth(pair, 1)) : NIL); } } + { var _c = mapIndexed(function(i, p) { return [p, i]; }, macroParams(mac)); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envSet(local, first(pair), (isSxTruthy((nth(pair, 1) < len(rawArgs))) ? nth(rawArgs, nth(pair, 1)) : NIL)); } } if (isSxTruthy(macroRestParam(mac))) { - local[macroRestParam(mac)] = slice(rawArgs, len(macroParams(mac))); + envSet(local, macroRestParam(mac), slice(rawArgs, len(macroParams(mac)))); } return trampoline(evalExpr(macroBody(mac), local)); })(); }; @@ -1162,7 +1185,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai // process-bindings var processBindings = function(bindings, env) { return (function() { - var local = merge(env); + var local = envExtend(env); { var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) { (function() { var name = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair)))); @@ -1392,9 +1415,9 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m = })(); }, {["i"]: 0, ["skip"]: false}, args); return (function() { var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } if (isSxTruthy(componentHasChildren(comp))) { - local["children"] = makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children))); + envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); } return renderToHtml(componentBody(comp), local); })(); @@ -1458,20 +1481,20 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m = return (function() { var local = envMerge(componentClosure(island), env); var islandName = componentName(island); - { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } } + { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } if (isSxTruthy(componentHasChildren(island))) { - local["children"] = makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children))); + envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); } return (function() { var bodyHtml = renderToHtml(componentBody(island), local); - var stateJson = serializeIslandState(kwargs); - return (String("") + String(bodyHtml) + String("")); + var stateSx = serializeIslandState(kwargs); + return (String("") + String(bodyHtml) + String("")); })(); })(); })(); }; // serialize-island-state - var serializeIslandState = function(kwargs) { return (isSxTruthy(isEmptyDict(kwargs)) ? NIL : jsonSerialize(kwargs)); }; + var serializeIslandState = function(kwargs) { return (isSxTruthy(isEmptyDict(kwargs)) ? NIL : sxSerialize(kwargs)); }; // === Transpiled from adapter-sx === @@ -1586,7 +1609,7 @@ return result; }, args); var coll = trampoline(evalExpr(nth(args, 1), env)); return map(function(item) { return (isSxTruthy(isLambda(f)) ? (function() { var local = envMerge(lambdaClosure(f), env); - local[first(lambdaParams(f))] = item; + envSet(local, first(lambdaParams(f)), item); return aser(lambdaBody(f), local); })() : invoke(f, item)); }, coll); })() : (isSxTruthy((name == "map-indexed")) ? (function() { @@ -1594,8 +1617,8 @@ return result; }, args); var coll = trampoline(evalExpr(nth(args, 1), env)); return mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? (function() { var local = envMerge(lambdaClosure(f), env); - local[first(lambdaParams(f))] = i; - local[nth(lambdaParams(f), 1)] = item; + envSet(local, first(lambdaParams(f)), i); + envSet(local, nth(lambdaParams(f), 1), item); return aser(lambdaBody(f), local); })() : invoke(f, i, item)); }, coll); })() : (isSxTruthy((name == "for-each")) ? (function() { @@ -1604,7 +1627,7 @@ return result; }, args); var results = []; { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (isSxTruthy(isLambda(f)) ? (function() { var local = envMerge(lambdaClosure(f), env); - local[first(lambdaParams(f))] = item; + envSet(local, first(lambdaParams(f)), item); return append_b(results, aser(lambdaBody(f), local)); })() : invoke(f, item)); } } return (isSxTruthy(isEmpty(results)) ? NIL : results); @@ -1662,7 +1685,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme var attrExpr = nth(args, (get(state, "i") + 1)); (isSxTruthy(startsWith(attrName, "on-")) ? (function() { var attrVal = trampoline(evalExpr(attrExpr, env)); - return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), (isSxTruthy((isSxTruthy(isLambda(attrVal)) && (len(lambdaParams(attrVal)) == 0))) ? function(e) { return callLambda(attrVal, [], lambdaClosure(attrVal)); } : attrVal)) : NIL); + return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), attrVal) : NIL); })() : (isSxTruthy((attrName == "bind")) ? (function() { var attrVal = trampoline(evalExpr(attrExpr, env)); return (isSxTruthy(isSignal(attrVal)) ? bindInput(el, attrVal) : NIL); @@ -1696,7 +1719,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme })(); }, {["i"]: 0, ["skip"]: false}, args); return (function() { var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } if (isSxTruthy(componentHasChildren(comp))) { (function() { var childFrag = createFragment(); @@ -1887,7 +1910,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme return (function() { var local = envMerge(componentClosure(island), env); var islandName = componentName(island); - { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } } + { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } if (isSxTruthy(componentHasChildren(island))) { (function() { var childFrag = createFragment(); @@ -3002,9 +3025,9 @@ return postSwap(target); }))) : NIL); var exprs = sxParse(body); return domListen(el, eventName, function(e) { return (function() { var handlerEnv = envExtend({}); - handlerEnv["event"] = e; - handlerEnv["this"] = el; - handlerEnv["detail"] = eventDetail(e); + envSet(handlerEnv, "event", e); + envSet(handlerEnv, "this", el); + envSet(handlerEnv, "detail", eventDetail(e)); return forEach(function(expr) { return evalExpr(expr, handlerEnv); }, exprs); })(); }); })()) : NIL); @@ -3233,17 +3256,17 @@ callExpr.push(dictGet(kwargs, k)); } } // hydrate-island var hydrateIsland = function(el) { return (function() { var name = domGetAttr(el, "data-sx-island"); - var stateJson = sxOr(domGetAttr(el, "data-sx-state"), "{}"); + var stateSx = sxOr(domGetAttr(el, "data-sx-state"), "{}"); return (function() { var compName = (String("~") + String(name)); var env = getRenderEnv(NIL); return (function() { var comp = envGet(env, compName); return (isSxTruthy(!isSxTruthy(sxOr(isComponent(comp), isIsland(comp)))) ? logWarn((String("hydrate-island: unknown island ") + String(compName))) : (function() { - var kwargs = jsonParse(stateJson); + var kwargs = sxOr(first(sxParse(stateSx)), {}); var disposers = []; var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } return (function() { var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); }); domSetTextContent(el, ""); @@ -3976,8 +3999,11 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { function domListen(el, name, handler) { if (!_hasDom || !el) return function() {}; // Wrap SX lambdas from runtime-evaluated island code into native fns + // If lambda takes 0 params, call without event arg (convenience for on-click handlers) var wrapped = isLambda(handler) - ? function(e) { try { invoke(handler, e); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } + ? (lambdaParams(handler).length === 0 + ? function(e) { try { invoke(handler); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } + : function(e) { try { invoke(handler, e); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } }) : handler; if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler)); el.addEventListener(name, wrapped); diff --git a/shared/sx/html.py b/shared/sx/html.py index f7fc9d50..694a03fc 100644 --- a/shared/sx/html.py +++ b/shared/sx/html.py @@ -414,10 +414,10 @@ def _render_component(comp: Component, args: list, env: dict[str, Any]) -> str: def _render_island(island: Island, args: list, env: dict[str, Any]) -> str: """Render an island as static HTML with hydration attributes. - Produces: body HTML - The client hydrates this into a reactive island. + Produces: body HTML + The client hydrates this into a reactive island via sx-parse (not JSON). """ - import json as _json + from .parser import serialize as _sx_serialize kwargs: dict[str, Any] = {} children: list[Any] = [] @@ -443,26 +443,13 @@ def _render_island(island: Island, args: list, env: dict[str, Any]) -> str: body_html = _render(island.body, local) - # Serialize state for hydration — only keyword args - state = {} - for k, v in kwargs.items(): - if isinstance(v, (str, int, float, bool)): - state[k] = v - elif v is NIL or v is None: - state[k] = None - elif isinstance(v, list): - state[k] = v - elif isinstance(v, dict): - state[k] = v - else: - state[k] = str(v) - - state_json = _escape_attr(_json.dumps(state, separators=(",", ":"))) if state else "" + # Serialize state for hydration — SX format (not JSON) + state_sx = _escape_attr(_sx_serialize(kwargs)) if kwargs else "" island_name = _escape_attr(island.name) parts = [f'") parts.append(body_html) parts.append("") diff --git a/shared/sx/ref/adapter-async.sx b/shared/sx/ref/adapter-async.sx index b6200907..c2f093db 100644 --- a/shared/sx/ref/adapter-async.sx +++ b/shared/sx/ref/adapter-async.sx @@ -450,7 +450,9 @@ (define-async async-process-bindings (fn (bindings env ctx) - (let ((local (merge env))) + ;; env-extend (not merge) — Env is not a dict subclass, so merge() + ;; returns an empty dict, losing all parent scope bindings. + (let ((local (env-extend env))) (if (and (= (type-of bindings) "list") (not (empty? bindings))) (if (= (type-of (first bindings)) "list") ;; Scheme-style: ((name val) ...) @@ -669,7 +671,10 @@ (evaled-args (async-eval-args args env ctx))) (cond (and (callable? f) (not (lambda? f)) (not (component? f))) - (async-invoke f evaled-args) + ;; apply directly — async-invoke takes &rest so passing a list + ;; would wrap it in another list + (let ((r (apply f evaled-args))) + (if (async-coroutine? r) (async-await! r) r)) (lambda? f) (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed @@ -1166,19 +1171,20 @@ (define-async async-eval-slot-inner (fn (expr env ctx) - (let ((result - (if (and (list? expr) (not (empty? expr))) - (let ((head (first expr))) - (if (and (= (type-of head) "symbol") - (starts-with? (symbol-name head) "~")) - (let ((name (symbol-name head)) - (val (if (env-has? env name) (env-get env name) nil))) - (if (component? val) - (async-aser-component val (rest expr) env ctx) - ;; Islands and unknown components — fall through to aser - (async-maybe-expand-result (async-aser expr env ctx) env ctx))) - (async-maybe-expand-result (async-aser expr env ctx) env ctx))) - (async-maybe-expand-result (async-aser expr env ctx) env ctx)))) + ;; NOTE: Uses statement-form let + set! to avoid expression-context + ;; let (IIFE lambdas) which can't contain await in Python. + (let ((result nil)) + (if (and (list? expr) (not (empty? expr))) + (let ((head (first expr))) + (if (and (= (type-of head) "symbol") + (starts-with? (symbol-name head) "~")) + (let ((name (symbol-name head)) + (val (if (env-has? env name) (env-get env name) nil))) + (if (component? val) + (set! result (async-aser-component val (rest expr) env ctx)) + (set! result (async-maybe-expand-result (async-aser expr env ctx) env ctx)))) + (set! result (async-maybe-expand-result (async-aser expr env ctx) env ctx)))) + (set! result (async-maybe-expand-result (async-aser expr env ctx) env ctx))) ;; Normalize result to SxExpr (if (sx-expr? result) result diff --git a/shared/sx/ref/adapter-dom.sx b/shared/sx/ref/adapter-dom.sx index 3e84f4f2..e659b994 100644 --- a/shared/sx/ref/adapter-dom.sx +++ b/shared/sx/ref/adapter-dom.sx @@ -186,15 +186,10 @@ (attr-expr (nth args (inc (get state "i"))))) (cond ;; Event handler: evaluate eagerly, bind listener - ;; If handler is a 0-arity lambda, wrap to ignore the event arg (starts-with? attr-name "on-") (let ((attr-val (trampoline (eval-expr attr-expr env)))) (when (callable? attr-val) - (dom-listen el (slice attr-name 3) - (if (and (lambda? attr-val) - (= (len (lambda-params attr-val)) 0)) - (fn (e) (call-lambda attr-val (list) (lambda-closure attr-val))) - attr-val)))) + (dom-listen el (slice attr-name 3) attr-val))) ;; Two-way input binding: :bind signal (= attr-name "bind") (let ((attr-val (trampoline (eval-expr attr-expr env)))) diff --git a/shared/sx/ref/adapter-html.sx b/shared/sx/ref/adapter-html.sx index 039090e7..f4719e22 100644 --- a/shared/sx/ref/adapter-html.sx +++ b/shared/sx/ref/adapter-html.sx @@ -433,11 +433,11 @@ ;; Render the island body as HTML (let ((body-html (render-to-html (component-body island) local)) - (state-json (serialize-island-state kwargs))) + (state-sx (serialize-island-state kwargs))) ;; Wrap in container with hydration attributes (str "" body-html @@ -445,17 +445,17 @@ ;; -------------------------------------------------------------------------- -;; serialize-island-state — serialize kwargs to JSON for hydration +;; serialize-island-state — serialize kwargs to SX for hydration ;; -------------------------------------------------------------------------- ;; -;; Only serializes simple values (numbers, strings, booleans, nil, lists, dicts). -;; Functions, components, and other non-serializable values are skipped. +;; Uses the SX serializer (not JSON) so the client can parse with sx-parse. +;; Handles all SX types natively: numbers, strings, booleans, nil, lists, dicts. (define serialize-island-state (fn (kwargs) (if (empty-dict? kwargs) nil - (json-serialize kwargs)))) + (sx-serialize kwargs)))) ;; -------------------------------------------------------------------------- @@ -476,8 +476,8 @@ ;; Raw HTML construction: ;; (make-raw-html s) → wrap string as raw HTML (not double-escaped) ;; -;; JSON serialization (for island state): -;; (json-serialize dict) → JSON string +;; Island state serialization: +;; (sx-serialize val) → SX source string (from parser.sx) ;; (empty-dict? d) → boolean ;; (escape-attr s) → HTML attribute escape ;; diff --git a/shared/sx/ref/async_eval_ref.py b/shared/sx/ref/async_eval_ref.py index eebe2b8b..96a79d56 100644 --- a/shared/sx/ref/async_eval_ref.py +++ b/shared/sx/ref/async_eval_ref.py @@ -1,1022 +1,22 @@ -"""Async evaluation wrapper for the transpiled reference evaluator. +"""Async evaluation — thin re-export from bootstrapped sx_ref.py. -Wraps the sync sx_ref.py evaluator with async I/O support, mirroring -the hand-written async_eval.py. Provides the same public API: +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. - async_eval() — evaluate with I/O primitives - async_render() — render to HTML with I/O - async_eval_to_sx() — evaluate to SX wire format with I/O - async_eval_slot_to_sx() — expand components server-side, then serialize +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) -The sync transpiled evaluator handles all control flow, special forms, -and lambda/component dispatch. This wrapper adds: - - - RequestContext threading - - I/O primitive interception (query, service, request-arg, etc.) - - Async trampoline for thunks - - SxExpr wrapping for wire format output - -DO NOT EDIT by hand — this is a thin wrapper; the actual eval logic -lives in sx_ref.py (generated) and the I/O primitives in primitives_io.py. +Platform async primitives (I/O dispatch, context vars, RequestContext) +are in shared/sx/ref/platform_py.py → PLATFORM_ASYNC_PY. """ -from __future__ import annotations - -import contextvars -import inspect -from typing import Any - -from ..types import Component, Island, Keyword, Lambda, Macro, NIL, Symbol -from ..parser import SxExpr, serialize -from ..primitives_io import IO_PRIMITIVES, RequestContext, execute_io -from ..html import ( - HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS, - escape_text, escape_attr, _RawHTML, css_class_collector, _svg_context, -) - from . import sx_ref -# Re-export EvalError from sx_ref +# Re-export the public API used by handlers.py, helpers.py, pages.py, etc. EvalError = sx_ref.EvalError - -# When True, _aser expands known components server-side -_expand_components: contextvars.ContextVar[bool] = contextvars.ContextVar( - "_expand_components_ref", default=False -) - - -# --------------------------------------------------------------------------- -# Async TCO -# --------------------------------------------------------------------------- - -class _AsyncThunk: - __slots__ = ("expr", "env", "ctx") - def __init__(self, expr, env, ctx): - self.expr = expr - self.env = env - self.ctx = ctx - - -async def _async_trampoline(val): - while isinstance(val, _AsyncThunk): - val = await _async_eval(val.expr, val.env, val.ctx) - return val - - -# --------------------------------------------------------------------------- -# Async evaluate — wraps transpiled sync eval with I/O support -# --------------------------------------------------------------------------- - -async def async_eval(expr, env, ctx=None): - """Public entry point: evaluate with I/O primitives.""" - if ctx is None: - ctx = RequestContext() - result = await _async_eval(expr, env, ctx) - while isinstance(result, _AsyncThunk): - result = await _async_eval(result.expr, result.env, result.ctx) - return result - - -async def _async_eval(expr, env, ctx): - """Internal async evaluator. Intercepts I/O primitives, - delegates everything else to the sync transpiled evaluator.""" - # Intercept I/O primitive calls - if isinstance(expr, list) and expr: - head = expr[0] - if isinstance(head, Symbol) and head.name in IO_PRIMITIVES: - args, kwargs = await _parse_io_args(expr[1:], env, ctx) - return await execute_io(head.name, args, kwargs, ctx) - - # Check if this is a render expression (HTML tag, component, fragment) - # so we can wrap the result in _RawHTML to prevent double-escaping. - # The sync evaluator returns plain strings from render_list_to_html; - # the async renderer would HTML-escape those without this wrapper. - is_render = isinstance(expr, list) and sx_ref.is_render_expr(expr) - - # For everything else, use the sync transpiled evaluator - result = sx_ref.eval_expr(expr, env) - result = sx_ref.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).""" - args = [] - kwargs = {} - i = 0 - while i < len(exprs): - item = exprs[i] - if isinstance(item, Keyword) and i + 1 < len(exprs): - kwargs[item.name] = await async_eval(exprs[i + 1], env, ctx) - i += 2 - else: - args.append(await async_eval(item, env, ctx)) - i += 1 - return args, kwargs - - -# --------------------------------------------------------------------------- -# Async HTML renderer -# --------------------------------------------------------------------------- - -async def async_render(expr, env, ctx=None): - """Render to HTML, awaiting I/O primitives inline.""" - if ctx is None: - ctx = RequestContext() - return await _arender(expr, env, ctx) - - -async def _arender(expr, env, ctx): - if expr is None or expr is NIL or expr is False or expr is True: - return "" - if isinstance(expr, _RawHTML): - return expr.html - # Also handle sx_ref._RawHTML from the sync evaluator - if isinstance(expr, sx_ref._RawHTML): - return expr.html - if isinstance(expr, str): - return escape_text(expr) - if isinstance(expr, (int, float)): - return escape_text(str(expr)) - if isinstance(expr, Symbol): - val = await async_eval(expr, env, ctx) - return await _arender(val, env, ctx) - if isinstance(expr, Keyword): - return escape_text(expr.name) - if isinstance(expr, list): - if not expr: - return "" - return await _arender_list(expr, env, ctx) - if isinstance(expr, dict): - return "" - return escape_text(str(expr)) - - -async def _arender_list(expr, env, ctx): - head = expr[0] - if isinstance(head, Symbol): - name = head.name - - # I/O primitive - if name in IO_PRIMITIVES: - result = await async_eval(expr, env, ctx) - return await _arender(result, env, ctx) - - # raw! - if name == "raw!": - parts = [] - for arg in expr[1:]: - val = await async_eval(arg, env, ctx) - if isinstance(val, _RawHTML): - parts.append(val.html) - elif isinstance(val, str): - parts.append(val) - elif val is not None and val is not NIL: - parts.append(str(val)) - return "".join(parts) - - # Fragment - if name == "<>": - parts = [await _arender(c, env, ctx) for c in expr[1:]] - return "".join(parts) - - # html: prefix - if name.startswith("html:"): - return await _arender_element(name[5:], expr[1:], env, ctx) - - # Render-aware special forms - arsf = _ASYNC_RENDER_FORMS.get(name) - if arsf is not None: - if name in HTML_TAGS and ( - (len(expr) > 1 and isinstance(expr[1], Keyword)) - or _svg_context.get(False) - ): - return await _arender_element(name, expr[1:], env, ctx) - return await arsf(expr, env, ctx) - - # Macro expansion - if name in env: - val = env[name] - if isinstance(val, Macro): - expanded = sx_ref.trampoline( - sx_ref.expand_macro(val, expr[1:], env) - ) - return await _arender(expanded, env, ctx) - - # HTML tag - if name in HTML_TAGS: - return await _arender_element(name, expr[1:], env, ctx) - - # Component / Island - if name.startswith("~"): - val = env.get(name) - if isinstance(val, Island): - return sx_ref.render_html_island(val, expr[1:], env) - if isinstance(val, Component): - return await _arender_component(val, expr[1:], env, ctx) - - # Custom element - if "-" in name and len(expr) > 1 and isinstance(expr[1], Keyword): - return await _arender_element(name, expr[1:], env, ctx) - - # SVG context - if _svg_context.get(False): - return await _arender_element(name, expr[1:], env, ctx) - - # Fallback — evaluate then render - result = await async_eval(expr, env, ctx) - return await _arender(result, env, ctx) - - if isinstance(head, (Lambda, list)): - result = await async_eval(expr, env, ctx) - return await _arender(result, env, ctx) - - # Data list - parts = [await _arender(item, env, ctx) for item in expr] - return "".join(parts) - - -async def _arender_element(tag, args, env, ctx): - attrs = {} - children = [] - i = 0 - while i < len(args): - arg = args[i] - if isinstance(arg, Keyword) and i + 1 < len(args): - attrs[arg.name] = await async_eval(args[i + 1], env, ctx) - i += 2 - else: - children.append(arg) - i += 1 - - class_val = attrs.get("class") - if class_val is not None and class_val is not NIL and class_val is not False: - collector = css_class_collector.get(None) - if collector is not None: - collector.update(str(class_val).split()) - - parts = [f"<{tag}"] - for attr_name, attr_val in attrs.items(): - if attr_val is None or attr_val is NIL or attr_val is False: - continue - if attr_name in BOOLEAN_ATTRS: - if attr_val: - parts.append(f" {attr_name}") - elif attr_val is True: - parts.append(f" {attr_name}") - else: - parts.append(f' {attr_name}="{escape_attr(str(attr_val))}"') - parts.append(">") - opening = "".join(parts) - - if tag in VOID_ELEMENTS: - return opening - - token = None - if tag in ("svg", "math"): - token = _svg_context.set(True) - try: - child_parts = [await _arender(c, env, ctx) for c in children] - finally: - if token is not None: - _svg_context.reset(token) - - return f"{opening}{''.join(child_parts)}" - - -async def _arender_component(comp, args, env, ctx): - kwargs = {} - children = [] - i = 0 - while i < len(args): - arg = args[i] - if isinstance(arg, Keyword) and i + 1 < len(args): - kwargs[arg.name] = await async_eval(args[i + 1], env, ctx) - i += 2 - else: - children.append(arg) - i += 1 - local = dict(comp.closure) - local.update(env) - for p in comp.params: - local[p] = kwargs.get(p, NIL) - if comp.has_children: - child_html = [await _arender(c, env, ctx) for c in children] - local["children"] = _RawHTML("".join(child_html)) - return await _arender(comp.body, local, ctx) - - -async def _arender_lambda(fn, args, env, ctx): - local = dict(fn.closure) - local.update(env) - for p, v in zip(fn.params, args): - local[p] = v - return await _arender(fn.body, local, ctx) - - -# --------------------------------------------------------------------------- -# Render-aware special forms -# --------------------------------------------------------------------------- - -async def _arsf_if(expr, env, ctx): - cond = await async_eval(expr[1], env, ctx) - if cond and cond is not NIL: - return await _arender(expr[2], env, ctx) - return await _arender(expr[3], env, ctx) if len(expr) > 3 else "" - - -async def _arsf_when(expr, env, ctx): - cond = await async_eval(expr[1], env, ctx) - if cond and cond is not NIL: - return "".join([await _arender(b, env, ctx) for b in expr[2:]]) - return "" - - -async def _arsf_cond(expr, env, ctx): - clauses = expr[1:] - if not clauses: - return "" - if isinstance(clauses[0], list) and len(clauses[0]) == 2: - for clause in clauses: - test = clause[0] - if isinstance(test, Symbol) and test.name in ("else", ":else"): - return await _arender(clause[1], env, ctx) - if isinstance(test, Keyword) and test.name == "else": - return await _arender(clause[1], env, ctx) - if await async_eval(test, env, ctx): - return await _arender(clause[1], env, ctx) - else: - i = 0 - while i < len(clauses) - 1: - test, result = clauses[i], clauses[i + 1] - if isinstance(test, Keyword) and test.name == "else": - return await _arender(result, env, ctx) - if isinstance(test, Symbol) and test.name in (":else", "else"): - return await _arender(result, env, ctx) - if await async_eval(test, env, ctx): - return await _arender(result, env, ctx) - i += 2 - return "" - - -async def _arsf_let(expr, env, ctx): - bindings = expr[1] - local = dict(env) - if isinstance(bindings, list): - if bindings and isinstance(bindings[0], list): - for b in bindings: - var = b[0] - vname = var.name if isinstance(var, Symbol) else var - local[vname] = await async_eval(b[1], local, ctx) - elif len(bindings) % 2 == 0: - for i in range(0, len(bindings), 2): - var = bindings[i] - vname = var.name if isinstance(var, Symbol) else var - local[vname] = await async_eval(bindings[i + 1], local, ctx) - return "".join([await _arender(b, local, ctx) for b in expr[2:]]) - - -async def _arsf_begin(expr, env, ctx): - return "".join([await _arender(sub, env, ctx) for sub in expr[1:]]) - - -async def _arsf_define(expr, env, ctx): - await async_eval(expr, env, ctx) - return "" - - -async def _arsf_map(expr, env, ctx): - fn = await async_eval(expr[1], env, ctx) - coll = await async_eval(expr[2], env, ctx) - parts = [] - for item in coll: - if isinstance(fn, Lambda): - parts.append(await _arender_lambda(fn, (item,), env, ctx)) - elif callable(fn): - r = fn(item) - if inspect.iscoroutine(r): - r = await r - parts.append(await _arender(r, env, ctx)) - else: - parts.append(await _arender(item, env, ctx)) - return "".join(parts) - - -async def _arsf_map_indexed(expr, env, ctx): - fn = await async_eval(expr[1], env, ctx) - coll = await async_eval(expr[2], env, ctx) - parts = [] - for i, item in enumerate(coll): - if isinstance(fn, Lambda): - parts.append(await _arender_lambda(fn, (i, item), env, ctx)) - elif callable(fn): - r = fn(i, item) - if inspect.iscoroutine(r): - r = await r - parts.append(await _arender(r, env, ctx)) - else: - parts.append(await _arender(item, env, ctx)) - return "".join(parts) - - -async def _arsf_filter(expr, env, ctx): - result = await async_eval(expr, env, ctx) - return await _arender(result, env, ctx) - - -async def _arsf_for_each(expr, env, ctx): - fn = await async_eval(expr[1], env, ctx) - coll = await async_eval(expr[2], env, ctx) - parts = [] - for item in coll: - if isinstance(fn, Lambda): - parts.append(await _arender_lambda(fn, (item,), env, ctx)) - elif callable(fn): - r = fn(item) - if inspect.iscoroutine(r): - r = await r - parts.append(await _arender(r, env, ctx)) - else: - parts.append(await _arender(item, env, ctx)) - return "".join(parts) - - -_ASYNC_RENDER_FORMS = { - "if": _arsf_if, - "when": _arsf_when, - "cond": _arsf_cond, - "let": _arsf_let, - "let*": _arsf_let, - "begin": _arsf_begin, - "do": _arsf_begin, - "define": _arsf_define, - "defstyle": _arsf_define, - "defcomp": _arsf_define, - "defmacro": _arsf_define, - "defhandler": _arsf_define, - "defisland": _arsf_define, - "map": _arsf_map, - "map-indexed": _arsf_map_indexed, - "filter": _arsf_filter, - "for-each": _arsf_for_each, -} - - -# --------------------------------------------------------------------------- -# Async SX wire format (aser) -# --------------------------------------------------------------------------- - -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 _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(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.set(True) - try: - return await _eval_slot_inner(expr, env, ctx) - finally: - _expand_components.reset(token) - - -async def _eval_slot_inner(expr, env, ctx): - if isinstance(expr, list) and expr: - head = expr[0] - if isinstance(head, Symbol) and head.name.startswith("~"): - comp = env.get(head.name) - if isinstance(comp, Component): - result = await _aser_component(comp, expr[1:], 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(serialize(result)) - elif isinstance(comp, Island): - pass # Islands serialize as SX for client hydration - result = await _aser(expr, env, ctx) - result = await _maybe_expand_component_result(result, 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(serialize(result)) - - -async def _maybe_expand_component_result(result, env, ctx): - raw = None - if isinstance(result, SxExpr): - raw = str(result).strip() - elif isinstance(result, str): - raw = result.strip() - if raw and raw.startswith("(~"): - from ..parser import parse_all - parsed = parse_all(raw) - if parsed: - return await async_eval_slot_to_sx(parsed[0], env, ctx) - return result - - -_aser_stack: list[str] = [] # diagnostic: track expression context - - -async def _aser(expr, env, ctx): - """Evaluate for SX wire format — serialize rendering forms, evaluate control flow.""" - if isinstance(expr, (int, float, bool)): - return expr - if isinstance(expr, SxExpr): - return expr - if isinstance(expr, str): - return expr - if expr is None or expr is NIL: - return NIL - - if isinstance(expr, Symbol): - name = expr.name - if name in env: - return env[name] - if sx_ref.is_primitive(name): - return sx_ref.get_primitive(name) - if name == "true": - return True - if name == "false": - return False - if name == "nil": - return NIL - ctx_info = " → ".join(_aser_stack[-5:]) if _aser_stack else "(top)" - raise EvalError(f"Undefined symbol: {name} [aser context: {ctx_info}]") - - if isinstance(expr, Keyword): - return expr.name - - if isinstance(expr, dict): - return {k: await _aser(v, env, ctx) for k, v in expr.items()} - - if not isinstance(expr, list): - return expr - if not expr: - return [] - - head = expr[0] - if not isinstance(head, (Symbol, Lambda, list)): - return [await _aser(x, env, ctx) for x in expr] - - if isinstance(head, Symbol): - name = head.name - - # I/O primitives - if name in IO_PRIMITIVES: - args, kwargs = await _parse_io_args(expr[1:], env, ctx) - return await execute_io(name, args, kwargs, ctx) - - # Fragment - if name == "<>": - return await _aser_fragment(expr[1:], env, ctx) - - # raw! - if name == "raw!": - return await _aser_call("raw!", expr[1:], env, ctx) - - # html: prefix - if name.startswith("html:"): - return await _aser_call(name[5:], expr[1:], env, ctx) - - # Component / Island call - if name.startswith("~"): - val = env.get(name) - if isinstance(val, Macro): - expanded = sx_ref.trampoline( - sx_ref.expand_macro(val, expr[1:], env) - ) - return await _aser(expanded, env, ctx) - if isinstance(val, Component) and ( - _expand_components.get() - or getattr(val, "render_target", None) == "server" - ): - return await _aser_component(val, expr[1:], env, ctx) - return await _aser_call(name, expr[1:], env, ctx) - - # Serialize-mode special/HO forms - sf = _ASER_FORMS.get(name) - if sf is not None: - if name in HTML_TAGS and ( - (len(expr) > 1 and isinstance(expr[1], Keyword)) - or _svg_context.get(False) - ): - return await _aser_call(name, expr[1:], env, ctx) - return await sf(expr, env, ctx) - - # HTML tag - if name in HTML_TAGS: - return await _aser_call(name, expr[1:], env, ctx) - - # Macro - if name in env: - val = env[name] - if isinstance(val, Macro): - expanded = sx_ref.trampoline( - sx_ref.expand_macro(val, expr[1:], env) - ) - return await _aser(expanded, env, ctx) - - # Custom element - if "-" in name and len(expr) > 1 and isinstance(expr[1], Keyword): - return await _aser_call(name, expr[1:], env, ctx) - - # SVG context - if _svg_context.get(False): - return await _aser_call(name, expr[1:], env, ctx) - - # Function/lambda call — fallback: evaluate head as callable - fn = await async_eval(head, env, ctx) - args = [await async_eval(a, env, ctx) for a in expr[1:]] - - if callable(fn) and not isinstance(fn, (Lambda, Component, Island)): - result = fn(*args) - if inspect.iscoroutine(result): - return await result - return result - if isinstance(fn, Lambda): - local = dict(fn.closure) - local.update(env) - for p, v in zip(fn.params, args): - local[p] = v - return await _aser(fn.body, local, ctx) - if isinstance(fn, Component): - return await _aser_call(f"~{fn.name}", expr[1:], env, ctx) - if isinstance(fn, Island): - return await _aser_call(f"~{fn.name}", expr[1:], env, ctx) - raise EvalError(f"Not callable in aser: {fn!r} (expr head: {head!r})") - - -async def _aser_fragment(children, env, ctx): - parts = [] - for child in children: - result = await _aser(child, env, ctx) - if isinstance(result, list): - for item in result: - if item is not NIL and item is not None: - parts.append(serialize(item)) - elif result is not NIL and result is not None: - parts.append(serialize(result)) - if not parts: - return SxExpr("") - return SxExpr("(<> " + " ".join(parts) + ")") - - -async def _aser_component(comp, args, env, ctx): - _aser_stack.append(f"~{comp.name}") - try: - kwargs = {} - children = [] - i = 0 - while i < len(args): - arg = args[i] - if isinstance(arg, Keyword) and i + 1 < len(args): - kwargs[arg.name] = await _aser(args[i + 1], env, ctx) - i += 2 - else: - children.append(arg) - i += 1 - local = dict(comp.closure) - local.update(env) - for p in comp.params: - local[p] = kwargs.get(p, NIL) - if comp.has_children: - child_parts = [] - for c in children: - result = await _aser(c, env, ctx) - if isinstance(result, list): - for item in result: - if item is not NIL and item is not None: - child_parts.append(serialize(item)) - elif result is not NIL and result is not None: - child_parts.append(serialize(result)) - local["children"] = SxExpr("(<> " + " ".join(child_parts) + ")") - return await _aser(comp.body, local, ctx) - finally: - _aser_stack.pop() - - -async def _aser_call(name, args, env, ctx): - _aser_stack.append(name) - token = None - if name in ("svg", "math"): - token = _svg_context.set(True) - try: - parts = [name] - extra_class = None - i = 0 - while i < len(args): - arg = args[i] - if isinstance(arg, Keyword) and i + 1 < len(args): - val = await _aser(args[i + 1], env, ctx) - if val is not NIL and val is not None: - parts.append(f":{arg.name}") - if isinstance(val, list): - live = [v for v in val if v is not NIL and v is not None] - items = [serialize(v) for v in live] - if not items: - parts.append("nil") - elif any(isinstance(v, SxExpr) for v in live): - parts.append("(<> " + " ".join(items) + ")") - else: - parts.append("(list " + " ".join(items) + ")") - else: - parts.append(serialize(val)) - i += 2 - else: - result = await _aser(arg, env, ctx) - if result is not NIL and result is not None: - if isinstance(result, list): - for item in result: - if item is not NIL and item is not None: - parts.append(serialize(item)) - else: - parts.append(serialize(result)) - i += 1 - if extra_class: - _merge_class_into_parts(parts, extra_class) - return SxExpr("(" + " ".join(parts) + ")") - finally: - _aser_stack.pop() - if token is not None: - _svg_context.reset(token) - - -def _merge_class_into_parts(parts, class_name): - for i, p in enumerate(parts): - if p == ":class" and i + 1 < len(parts): - existing = parts[i + 1] - if existing.startswith('"') and existing.endswith('"'): - parts[i + 1] = existing[:-1] + " " + class_name + '"' - else: - parts[i + 1] = f'(str {existing} " {class_name}")' - return - parts.insert(1, f'"{class_name}"') - parts.insert(1, ":class") - - -# --------------------------------------------------------------------------- -# Aser-mode special forms -# --------------------------------------------------------------------------- - -async def _assf_if(expr, env, ctx): - cond = await async_eval(expr[1], env, ctx) - if cond and cond is not NIL: - return await _aser(expr[2], env, ctx) - return await _aser(expr[3], env, ctx) if len(expr) > 3 else NIL - - -async def _assf_when(expr, env, ctx): - cond = await async_eval(expr[1], env, ctx) - if cond and cond is not NIL: - result = NIL - for body_expr in expr[2:]: - result = await _aser(body_expr, env, ctx) - return result - return NIL - - -async def _assf_let(expr, env, ctx): - bindings = expr[1] - local = dict(env) - if isinstance(bindings, list): - if bindings and isinstance(bindings[0], list): - for b in bindings: - var = b[0] - vname = var.name if isinstance(var, Symbol) else var - local[vname] = await _aser(b[1], local, ctx) - elif len(bindings) % 2 == 0: - for i in range(0, len(bindings), 2): - var = bindings[i] - vname = var.name if isinstance(var, Symbol) else var - local[vname] = await _aser(bindings[i + 1], local, ctx) - result = NIL - for body_expr in expr[2:]: - result = await _aser(body_expr, local, ctx) - return result - - -async def _assf_cond(expr, env, ctx): - clauses = expr[1:] - if not clauses: - return NIL - if isinstance(clauses[0], list) and len(clauses[0]) == 2: - for clause in clauses: - test = clause[0] - if isinstance(test, Symbol) and test.name in ("else", ":else"): - return await _aser(clause[1], env, ctx) - if isinstance(test, Keyword) and test.name == "else": - return await _aser(clause[1], env, ctx) - if await async_eval(test, env, ctx): - return await _aser(clause[1], env, ctx) - else: - i = 0 - while i < len(clauses) - 1: - test, result = clauses[i], clauses[i + 1] - if isinstance(test, Keyword) and test.name == "else": - return await _aser(result, env, ctx) - if isinstance(test, Symbol) and test.name in (":else", "else"): - return await _aser(result, env, ctx) - if await async_eval(test, env, ctx): - return await _aser(result, env, ctx) - i += 2 - return NIL - - -async def _assf_case(expr, env, ctx): - match_val = await async_eval(expr[1], env, ctx) - clauses = expr[2:] - i = 0 - while i < len(clauses) - 1: - test, result = clauses[i], clauses[i + 1] - if isinstance(test, Keyword) and test.name == "else": - return await _aser(result, env, ctx) - if isinstance(test, Symbol) and test.name in (":else", "else"): - return await _aser(result, env, ctx) - if match_val == await async_eval(test, env, ctx): - return await _aser(result, env, ctx) - i += 2 - return NIL - - -async def _assf_begin(expr, env, ctx): - result = NIL - for sub in expr[1:]: - result = await _aser(sub, env, ctx) - return result - - -async def _assf_define(expr, env, ctx): - await async_eval(expr, env, ctx) - return NIL - - -async def _assf_and(expr, env, ctx): - result = True - for arg in expr[1:]: - result = await async_eval(arg, env, ctx) - if not result: - return result - return result - - -async def _assf_or(expr, env, ctx): - result = False - for arg in expr[1:]: - result = await async_eval(arg, env, ctx) - if result: - return result - return result - - -async def _assf_lambda(expr, env, ctx): - params_expr = expr[1] - param_names = [] - for p in params_expr: - if isinstance(p, Symbol): - param_names.append(p.name) - elif isinstance(p, str): - param_names.append(p) - return Lambda(param_names, expr[2], dict(env)) - - -async def _assf_quote(expr, env, ctx): - return expr[1] if len(expr) > 1 else NIL - - -async def _assf_thread_first(expr, env, ctx): - result = await async_eval(expr[1], env, ctx) - for form in expr[2:]: - if isinstance(form, list): - fn = await async_eval(form[0], env, ctx) - fn_args = [result] + [await async_eval(a, env, ctx) for a in form[1:]] - else: - fn = await async_eval(form, env, ctx) - fn_args = [result] - if callable(fn) and not isinstance(fn, (Lambda, Component, Island)): - result = fn(*fn_args) - if inspect.iscoroutine(result): - result = await result - elif isinstance(fn, Lambda): - local = dict(fn.closure) - local.update(env) - for p, v in zip(fn.params, fn_args): - local[p] = v - result = await async_eval(fn.body, local, ctx) - else: - raise EvalError(f"-> form not callable: {fn!r}") - return result - - -async def _assf_set_bang(expr, env, ctx): - value = await async_eval(expr[2], env, ctx) - env[expr[1].name] = value - return value - - -# Aser-mode HO forms - -async def _asho_map(expr, env, ctx): - fn = await async_eval(expr[1], env, ctx) - coll = await async_eval(expr[2], env, ctx) - results = [] - for item in coll: - if isinstance(fn, Lambda): - local = dict(fn.closure) - local.update(env) - local[fn.params[0]] = item - results.append(await _aser(fn.body, local, ctx)) - elif callable(fn): - r = fn(item) - results.append(await r if inspect.iscoroutine(r) else r) - else: - raise EvalError(f"map requires callable, got {type(fn).__name__}") - return results - - -async def _asho_map_indexed(expr, env, ctx): - fn = await async_eval(expr[1], env, ctx) - coll = await async_eval(expr[2], env, ctx) - results = [] - for i, item in enumerate(coll): - if isinstance(fn, Lambda): - local = dict(fn.closure) - local.update(env) - local[fn.params[0]] = i - local[fn.params[1]] = item - results.append(await _aser(fn.body, local, ctx)) - elif callable(fn): - r = fn(i, item) - results.append(await r if inspect.iscoroutine(r) else r) - else: - raise EvalError(f"map-indexed requires callable, got {type(fn).__name__}") - return results - - -async def _asho_filter(expr, env, ctx): - return await async_eval(expr, env, ctx) - - -async def _asho_for_each(expr, env, ctx): - fn = await async_eval(expr[1], env, ctx) - coll = await async_eval(expr[2], env, ctx) - results = [] - for item in coll: - if isinstance(fn, Lambda): - local = dict(fn.closure) - local.update(env) - local[fn.params[0]] = item - results.append(await _aser(fn.body, local, ctx)) - elif callable(fn): - r = fn(item) - results.append(await r if inspect.iscoroutine(r) else r) - return results - - -_ASER_FORMS = { - "if": _assf_if, - "when": _assf_when, - "cond": _assf_cond, - "case": _assf_case, - "and": _assf_and, - "or": _assf_or, - "let": _assf_let, - "let*": _assf_let, - "lambda": _assf_lambda, - "fn": _assf_lambda, - "define": _assf_define, - "defstyle": _assf_define, - "defcomp": _assf_define, - "defmacro": _assf_define, - "defhandler": _assf_define, - "defisland": _assf_define, - "begin": _assf_begin, - "do": _assf_begin, - "quote": _assf_quote, - "->": _assf_thread_first, - "set!": _assf_set_bang, - "map": _asho_map, - "map-indexed": _asho_map_indexed, - "filter": _asho_filter, - "for-each": _asho_for_each, -} +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/boot.sx b/shared/sx/ref/boot.sx index afb9d55d..30a2e634 100644 --- a/shared/sx/ref/boot.sx +++ b/shared/sx/ref/boot.sx @@ -344,15 +344,15 @@ (define hydrate-island (fn (el) (let ((name (dom-get-attr el "data-sx-island")) - (state-json (or (dom-get-attr el "data-sx-state") "{}"))) + (state-sx (or (dom-get-attr el "data-sx-state") "{}"))) (let ((comp-name (str "~" name)) (env (get-render-env nil))) (let ((comp (env-get env comp-name))) (if (not (or (component? comp) (island? comp))) (log-warn (str "hydrate-island: unknown island " comp-name)) - ;; Parse state and build keyword args - (let ((kwargs (json-parse state-json)) + ;; Parse state and build keyword args — SX format, not JSON + (let ((kwargs (or (first (sx-parse state-sx)) {})) (disposers (list)) (local (env-merge (component-closure comp) env))) @@ -494,8 +494,8 @@ ;; (log-info msg) → void (console.log with prefix) ;; (log-parse-error label text err) → void (diagnostic parse error) ;; -;; === JSON === -;; (json-parse str) → dict/list/value (JSON.parse) +;; === Parsing (island state) === +;; (sx-parse str) → list of AST expressions (from parser.sx) ;; ;; === Processing markers === ;; (mark-processed! el key) → void diff --git a/shared/sx/ref/bootstrap_py.py b/shared/sx/ref/bootstrap_py.py index 875b381c..2a168063 100644 --- a/shared/sx/ref/bootstrap_py.py +++ b/shared/sx/ref/bootstrap_py.py @@ -49,6 +49,8 @@ class PyEmitter: def __init__(self): self.indent = 0 + self._async_names: set[str] = set() # SX names of define-async functions + self._in_async: bool = False # Currently emitting async def body? def emit(self, expr) -> str: """Emit a Python expression from an SX AST node.""" @@ -80,6 +82,8 @@ class PyEmitter: name = head.name if name == "define": return self._emit_define(expr, indent) + if name == "define-async": + return self._emit_define_async(expr, indent) if name == "set!": return f"{pad}{self._mangle(expr[1].name)} = {self.emit(expr[2])}" if name == "when": @@ -275,6 +279,19 @@ class PyEmitter: "sf-defisland": "sf_defisland", # adapter-sx.sx "render-to-sx": "render_to_sx", + # adapter-async.sx platform primitives + "svg-context-set!": "svg_context_set", + "svg-context-reset!": "svg_context_reset", + "css-class-collect!": "css_class_collect", + "is-raw-html?": "is_raw_html", + "async-coroutine?": "is_async_coroutine", + "async-await!": "async_await", + "is-sx-expr?": "is_sx_expr", + "sx-expr?": "is_sx_expr", + "io-primitive?": "io_primitive_p", + "expand-components?": "expand_components_p", + "svg-context?": "svg_context_p", + "make-sx-expr": "make_sx_expr", "aser": "aser", "eval-case-aser": "eval_case_aser", "sx-serialize": "sx_serialize", @@ -417,6 +434,8 @@ class PyEmitter: # Regular function call fn_name = self._mangle(name) args = ", ".join(self.emit(x) for x in expr[1:]) + if self._in_async and name in self._async_names: + return f"(await {fn_name}({args}))" return f"{fn_name}({args})" # --- Special form emitters --- @@ -513,7 +532,7 @@ class PyEmitter: body_parts = expr[2:] lines = [f"{pad}if sx_truthy({cond}):"] for b in body_parts: - lines.append(self.emit_statement(b, indent + 1)) + self._emit_stmt_recursive(b, lines, indent + 1) return "\n".join(lines) def _emit_cond(self, expr) -> str: @@ -642,6 +661,16 @@ class PyEmitter: val = self.emit(val_expr) return f"{pad}{self._mangle(name)} = {val}" + def _emit_define_async(self, expr, indent: int = 0) -> str: + """Emit a define-async form as an async def statement.""" + name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1]) + val_expr = expr[2] + if (isinstance(val_expr, list) and val_expr and + isinstance(val_expr[0], Symbol) and val_expr[0].name in ("fn", "lambda")): + return self._emit_define_as_def(name, val_expr, indent, is_async=True) + # Shouldn't happen — define-async should always wrap fn/lambda + return self._emit_define(expr, indent) + def _body_uses_set(self, fn_expr) -> bool: """Check if a fn expression's body (recursively) uses set!.""" def _has_set(node): @@ -654,12 +683,16 @@ class PyEmitter: body = fn_expr[2:] return any(_has_set(b) for b in body) - def _emit_define_as_def(self, name: str, fn_expr, indent: int = 0) -> str: + def _emit_define_as_def(self, name: str, fn_expr, indent: int = 0, + is_async: bool = False) -> str: """Emit a define with fn value as a proper def statement. This is used for functions that contain set! — Python closures can't rebind outer lambda params, so we need proper def + local variables. Variables mutated by set! from nested lambdas use a _cells dict. + + When is_async=True, emits 'async def' and sets _in_async so that + calls to other async functions receive 'await'. """ pad = " " * indent params = fn_expr[1] @@ -686,14 +719,19 @@ class PyEmitter: py_name = self._mangle(name) # Find set! target variables that are used from nested lambda scopes nested_set_vars = self._find_nested_set_vars(body) - lines = [f"{pad}def {py_name}({params_str}):"] + def_kw = "async def" if is_async else "def" + lines = [f"{pad}{def_kw} {py_name}({params_str}):"] if nested_set_vars: lines.append(f"{pad} _cells = {{}}") - # Emit body with cell var tracking + # Emit body with cell var tracking (and async context if needed) old_cells = getattr(self, '_current_cell_vars', set()) + old_async = self._in_async self._current_cell_vars = nested_set_vars + if is_async: + self._in_async = True self._emit_body_stmts(body, lines, indent + 1) self._current_cell_vars = old_cells + self._in_async = old_async return "\n".join(lines) def _find_nested_set_vars(self, body) -> set[str]: @@ -750,7 +788,7 @@ class PyEmitter: if is_last: self._emit_return_expr(expr, lines, indent) else: - lines.append(self.emit_statement(expr, indent)) + self._emit_stmt_recursive(expr, lines, indent) def _emit_return_expr(self, expr, lines: list, indent: int) -> None: """Emit an expression in return position, flattening control flow.""" @@ -775,6 +813,11 @@ class PyEmitter: if name in ("do", "begin"): self._emit_body_stmts(expr[1:], lines, indent) return + if name == "for-each": + # for-each in return position: emit as statement, return NIL + lines.append(self._emit_for_each_stmt(expr, indent)) + lines.append(f"{pad}return NIL") + return lines.append(f"{pad}return {self.emit(expr)}") def _emit_if_return(self, expr, lines: list, indent: int) -> None: @@ -1034,12 +1077,15 @@ class PyEmitter: # --------------------------------------------------------------------------- def extract_defines(source: str) -> list[tuple[str, list]]: - """Parse .sx source, return list of (name, define-expr) for top-level defines.""" + """Parse .sx source, return list of (name, define-expr) for top-level defines. + + Extracts both (define ...) and (define-async ...) forms. + """ 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": + if expr[0].name in ("define", "define-async"): name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1]) defines.append((name, expr)) return defines @@ -1212,6 +1258,28 @@ def compile_ref_to_py( for name in sorted(spec_mod_set): sx_files.append(SPEC_MODULES[name]) + # Pre-scan define-async names (needed before transpilation so emitter + # knows which calls require 'await') + has_async = "async" in adapter_set + if has_async: + async_filename = ADAPTER_FILES["async"][0] + async_filepath = os.path.join(ref_dir, async_filename) + if os.path.exists(async_filepath): + with open(async_filepath) as f: + async_src = f.read() + for aexpr in parse_all(async_src): + if (isinstance(aexpr, list) and aexpr + and isinstance(aexpr[0], Symbol) + and aexpr[0].name == "define-async"): + aname = aexpr[1].name if isinstance(aexpr[1], Symbol) else str(aexpr[1]) + emitter._async_names.add(aname) + # Platform async primitives (provided by host, also need await) + emitter._async_names.update({ + "async-eval", "execute-io", "async-await!", + }) + # Async adapter is transpiled last (after sync adapters) + sx_files.append(ADAPTER_FILES["async"]) + all_sections = [] for filename, label in sx_files: filepath = os.path.join(ref_dir, filename) @@ -1248,6 +1316,9 @@ def compile_ref_to_py( if has_deps: parts.append(PLATFORM_DEPS_PY) + if has_async: + parts.append(PLATFORM_ASYNC_PY) + for label, defines in all_sections: parts.append(f"\n# === Transpiled from {label} ===\n") for name, expr in defines: @@ -1258,7 +1329,7 @@ def compile_ref_to_py( parts.append(FIXUPS_PY) if has_continuations: parts.append(CONTINUATIONS_PY) - parts.append(public_api_py(has_html, has_sx, has_deps)) + parts.append(public_api_py(has_html, has_sx, has_deps, has_async)) return "\n".join(parts) diff --git a/shared/sx/ref/js.sx b/shared/sx/ref/js.sx index cea6379b..3c35b73b 100644 --- a/shared/sx/ref/js.sx +++ b/shared/sx/ref/js.sx @@ -1290,8 +1290,9 @@ (= name "append!") (str (js-expr (nth expr 1)) ".push(" (js-expr (nth expr 2)) ");") (= name "env-set!") - (str (js-expr (nth expr 1)) "[" (js-expr (nth expr 2)) - "] = " (js-expr (nth expr 3)) ";") + (str "envSet(" (js-expr (nth expr 1)) + ", " (js-expr (nth expr 2)) + ", " (js-expr (nth expr 3)) ");") (= name "set-lambda-name!") (str (js-expr (nth expr 1)) ".name = " (js-expr (nth expr 2)) ";") :else diff --git a/shared/sx/ref/platform_js.py b/shared/sx/ref/platform_js.py index 9dd28817..ad5e759e 100644 --- a/shared/sx/ref/platform_js.py +++ b/shared/sx/ref/platform_js.py @@ -1194,7 +1194,7 @@ PLATFORM_JS_PRE = ''' // JSON / dict helpers for island state serialization function jsonSerialize(obj) { - try { return JSON.stringify(obj); } catch(e) { return "{}"; } + return JSON.stringify(obj); } function isEmptyDict(d) { if (!d || typeof d !== "object") return true; @@ -1204,11 +1204,34 @@ PLATFORM_JS_PRE = ''' function envHas(env, name) { return name in env; } function envGet(env, name) { return env[name]; } - function envSet(env, name, val) { env[name] = val; } + function envSet(env, name, val) { + // Walk prototype chain to find where the variable is defined (for set!) + var obj = env; + while (obj !== null && obj !== Object.prototype) { + if (obj.hasOwnProperty(name)) { obj[name] = val; return; } + obj = Object.getPrototypeOf(obj); + } + // Not found in any parent scope — set on the immediate env + env[name] = val; + } function envExtend(env) { return Object.create(env); } function envMerge(base, overlay) { + // Same env or overlay is descendant of base — just extend, no copy. + // This prevents set! inside lambdas from modifying shadow copies. + if (base === overlay) return Object.create(base); + var p = overlay; + for (var d = 0; p && p !== Object.prototype && d < 100; d++) { + if (p === base) return Object.create(base); + p = Object.getPrototypeOf(p); + } + // General case: extend base, copy ONLY overlay properties that don't + // exist in the base chain (avoids shadowing closure bindings). var child = Object.create(base); - if (overlay) for (var k in overlay) if (overlay.hasOwnProperty(k)) child[k] = overlay[k]; + if (overlay) { + for (var k in overlay) { + if (overlay.hasOwnProperty(k) && !(k in base)) child[k] = overlay[k]; + } + } return child; } @@ -1649,8 +1672,11 @@ PLATFORM_DOM_JS = """ function domListen(el, name, handler) { if (!_hasDom || !el) return function() {}; // Wrap SX lambdas from runtime-evaluated island code into native fns + // If lambda takes 0 params, call without event arg (convenience for on-click handlers) var wrapped = isLambda(handler) - ? function(e) { try { invoke(handler, e); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } + ? (lambdaParams(handler).length === 0 + ? function(e) { try { invoke(handler); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } + : function(e) { try { invoke(handler, e); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } }) : handler; if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler)); el.addEventListener(name, wrapped); diff --git a/shared/sx/ref/platform_py.py b/shared/sx/ref/platform_py.py index de9794fa..345bb674 100644 --- a/shared/sx/ref/platform_py.py +++ b/shared/sx/ref/platform_py.py @@ -462,10 +462,7 @@ def invoke(f, *args): def json_serialize(obj): import json - try: - return json.dumps(obj) - except (TypeError, ValueError): - return "{}" + return json.dumps(obj) def is_empty_dict(d): @@ -1067,10 +1064,19 @@ import inspect from shared.sx.primitives_io import ( IO_PRIMITIVES, RequestContext, execute_io, - css_class_collector as _css_class_collector_cv, - _svg_context as _svg_context_cv, ) +# 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 @@ -1094,18 +1100,22 @@ def expand_components_p(): 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()) @@ -1123,6 +1133,25 @@ 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 sx_parse(src): + from shared.sx.parser import parse_all + return parse_all(src) + + def is_async_coroutine(x): return inspect.iscoroutine(x) @@ -1199,48 +1228,16 @@ async def async_eval_slot_to_sx(expr, env, ctx=None): ctx = RequestContext() token = _expand_components_cv.set(True) try: - return await _eval_slot_inner(expr, env, ctx) + 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) - - -async def _eval_slot_inner(expr, env, ctx): - if isinstance(expr, list) and expr: - head = expr[0] - if isinstance(head, Symbol) and head.name.startswith("~"): - comp = env.get(head.name) - if isinstance(comp, Component): - result = await async_aser_component(comp, expr[1:], 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)) - result = await async_aser(expr, env, ctx) - result = await _maybe_expand_component_result(result, 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 _maybe_expand_component_result(result, env, ctx): - raw = None - if isinstance(result, SxExpr): - raw = str(result).strip() - elif isinstance(result, str): - raw = result.strip() - if raw and raw.startswith("(~"): - from shared.sx.parser import parse_all as _pa - parsed = _pa(raw) - if parsed: - return await async_eval_slot_to_sx(parsed[0], env, ctx) - return result ''' # --------------------------------------------------------------------------- @@ -1366,7 +1363,8 @@ aser_special = _aser_special_with_continuations # Public API generator # --------------------------------------------------------------------------- -def public_api_py(has_html: bool, has_sx: bool, has_deps: bool = False) -> str: +def public_api_py(has_html: bool, has_sx: bool, has_deps: bool = False, + has_async: bool = False) -> str: lines = [ '', '# =========================================================================', @@ -1419,8 +1417,9 @@ def public_api_py(has_html: bool, has_sx: bool, has_deps: bool = False) -> str: # --------------------------------------------------------------------------- ADAPTER_FILES = { - "html": ("adapter-html.sx", "adapter-html"), - "sx": ("adapter-sx.sx", "adapter-sx"), + "html": ("adapter-html.sx", "adapter-html"), + "sx": ("adapter-sx.sx", "adapter-sx"), + "async": ("adapter-async.sx", "adapter-async"), } SPEC_MODULES = { diff --git a/shared/sx/ref/render.sx b/shared/sx/ref/render.sx index 66384c5b..e6793ca7 100644 --- a/shared/sx/ref/render.sx +++ b/shared/sx/ref/render.sx @@ -178,7 +178,9 @@ ;; bindings = ((name1 expr1) (name2 expr2) ...) (define process-bindings (fn (bindings env) - (let ((local (merge env))) + ;; env-extend (not merge) — Env is not a dict subclass, so merge() + ;; returns an empty dict, losing all parent scope bindings. + (let ((local (env-extend env))) (for-each (fn (pair) (when (and (= (type-of pair) "list") (>= (len pair) 2)) diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py index 75120910..f7bc12a5 100644 --- a/shared/sx/ref/sx_ref.py +++ b/shared/sx/ref/sx_ref.py @@ -421,10 +421,7 @@ def invoke(f, *args): def json_serialize(obj): import json - try: - return json.dumps(obj) - except (TypeError, ValueError): - return "{}" + return json.dumps(obj) def is_empty_dict(d): @@ -968,6 +965,191 @@ def component_set_io_refs(c, refs): c.io_refs = set(refs) if not isinstance(refs, set) else refs +# ========================================================================= +# Platform interface -- Async adapter +# ========================================================================= + +import contextvars +import 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 sx_parse(src): + from shared.sx.parser import parse_all + return parse_all(src) + + +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 @@ -1267,7 +1449,13 @@ def sf_let(args, env): bindings = first(args) body = rest(args) local = env_extend(env) - (for_each(lambda binding: (lambda vname: _sx_dict_set(local, vname, trampoline(eval_expr(nth(binding, 1), local))))((symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))), 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 i: 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))))(0)) + 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) @@ -1279,10 +1467,12 @@ def sf_named_let(args, env): body = slice(args, 2) params = [] inits = [] - (for_each(_sx_fn(lambda binding: ( - _sx_append(params, (symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))), - _sx_append(inits, nth(binding, 1)) -)[-1]), bindings) if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))) 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)))) + 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 @@ -1448,7 +1638,14 @@ def sf_letrec(args, env): local = env_extend(env) names = [] val_exprs = [] - (for_each(lambda binding: (lambda vname: _sx_begin(_sx_append(names, vname), _sx_append(val_exprs, nth(binding, 1)), _sx_dict_set(local, vname, NIL)))((symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))), bindings) if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))) 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)))) + 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) @@ -1531,7 +1728,9 @@ def ho_every(args, env): def ho_for_each(args, env): f = trampoline(eval_expr(first(args), env)) coll = trampoline(eval_expr(nth(args, 1), env)) - return for_each(lambda item: call_fn(f, [item], env), coll) + for item in coll: + call_fn(f, [item], env) + return NIL # === Transpiled from forms (server definition forms) === @@ -1698,7 +1897,7 @@ def eval_cond_clojure(clauses, env): # process-bindings def process_bindings(bindings, env): - local = merge(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))) @@ -1920,15 +2119,15 @@ def render_html_island(island, args, env): 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_json = serialize_island_state(kwargs) - return sx_str('', body_html, '') + 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 json_serialize(kwargs) + return sx_serialize(kwargs) # === Transpiled from adapter-sx === @@ -2195,9 +2394,13 @@ def scan_refs_walk(node, refs): return NIL return NIL elif sx_truthy((type_of(node) == 'list')): - return for_each(lambda item: scan_refs_walk(item, refs), node) + for item in node: + scan_refs_walk(item, refs) + return NIL elif sx_truthy((type_of(node) == 'dict')): - return for_each(lambda key: scan_refs_walk(dict_get(node, key), refs), keys(node)) + for key in keys(node): + scan_refs_walk(dict_get(node, key), refs) + return NIL else: return NIL @@ -2207,9 +2410,13 @@ def transitive_deps_walk(n, seen, env): seen.append(n) val = env_get(env, n) if sx_truthy((type_of(val) == 'component')): - return for_each(lambda ref: transitive_deps_walk(ref, seen, env), scan_refs(component_body(val))) + for ref in scan_refs(component_body(val)): + transitive_deps_walk(ref, seen, env) + return NIL elif sx_truthy((type_of(val) == 'macro')): - return for_each(lambda ref: transitive_deps_walk(ref, seen, env), scan_refs(macro_body(val))) + for ref in scan_refs(macro_body(val)): + transitive_deps_walk(ref, seen, env) + return NIL else: return NIL return NIL @@ -2223,7 +2430,11 @@ def transitive_deps(name, env): # compute-all-deps def compute_all_deps(env): - return for_each(lambda name: (lambda val: (component_set_deps(val, transitive_deps(name, env)) if sx_truthy((type_of(val) == 'component')) else NIL))(env_get(env, name)), env_components(env)) + for name in env_components(env): + val = env_get(env, name) + if sx_truthy((type_of(val) == 'component')): + component_set_deps(val, transitive_deps(name, env)) + return NIL # scan-components-from-source def scan_components_from_source(source): @@ -2273,9 +2484,13 @@ def scan_io_refs_walk(node, io_names, refs): return NIL return NIL elif sx_truthy((type_of(node) == 'list')): - return for_each(lambda item: scan_io_refs_walk(item, io_names, refs), node) + for item in node: + scan_io_refs_walk(item, io_names, refs) + return NIL elif sx_truthy((type_of(node) == 'dict')): - return for_each(lambda key: scan_io_refs_walk(dict_get(node, key), io_names, refs), keys(node)) + for key in keys(node): + scan_io_refs_walk(dict_get(node, key), io_names, refs) + return NIL else: return NIL @@ -2294,12 +2509,16 @@ def transitive_io_refs_walk(n, seen, all_refs, env, io_names): 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) - return for_each(lambda dep: transitive_io_refs_walk(dep, seen, all_refs, env, io_names), scan_refs(component_body(val))) + 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) - return for_each(lambda dep: transitive_io_refs_walk(dep, seen, all_refs, env, io_names), scan_refs(macro_body(val))) + 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 @@ -2314,7 +2533,11 @@ def transitive_io_refs(name, env, io_names): # compute-all-io-refs def compute_all_io_refs(env, io_names): - return for_each(lambda name: (lambda val: (component_set_io_refs(val, transitive_io_refs(name, env, io_names)) if sx_truthy((type_of(val) == 'component')) else NIL))(env_get(env, name)), env_components(env)) + 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): @@ -2608,7 +2831,9 @@ def batch(thunk): if sx_truthy((not sx_truthy(contains_p(seen, sub)))): seen.append(sub) pending.append(sub) - return for_each(lambda sub: sub(), pending) + for sub in pending: + sub() + return NIL return NIL # notify-subscribers @@ -2622,7 +2847,9 @@ def notify_subscribers(s): # flush-subscribers def flush_subscribers(s): - return for_each(lambda sub: sub(), signal_subscribers(s)) + for sub in signal_subscribers(s): + sub() + return NIL # dispose-computed def dispose_computed(s): @@ -2704,6 +2931,826 @@ def resource(fetch_fn): 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 == '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)) + opening = sx_str('<', tag, render_attrs(attrs), '>') + if sx_truthy(contains_p(VOID_ELEMENTS, tag)): + return opening + else: + token = (svg_context_set(True) if sx_truthy(((tag == 'svg') if sx_truthy((tag == 'svg')) else (tag == 'math'))) else NIL) + child_html = join('', (await async_map_render(children, env, ctx))) + if sx_truthy(token): + svg_context_reset(token) + return sx_str(opening, child_html, '') + +# 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)): + local['children'] = make_raw_html(join('', (await async_map_render(children, env, ctx)))) + 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)): + local['children'] = make_raw_html(join('', (await async_map_render(children, env, ctx)))) + 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', 'map', 'map-indexed', 'filter', 'for-each'] + +# 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: + 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)) + return join('', (await async_map_render(slice(expr, 2), local, ctx))) + elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))): + 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))) + 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): + _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: + return error(sx_str('Undefined symbol: ', name)) + elif _match == 'keyword': + return keyword_name(expr) + elif _match == 'dict': + return (await async_aser_dict(expr, env, ctx)) + elif _match == 'list': + if sx_truthy(empty_p(expr)): + return [] + else: + return (await async_aser_list(expr, env, ctx)) + else: + return expr + +# 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) + parts = [name] + _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)) + if sx_truthy((not sx_truthy(is_nil(val)))): + 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)): + parts.append('nil') + else: + items = map(serialize, live) + if sx_truthy(some(lambda v: is_sx_expr(v), live)): + parts.append(sx_str('(<> ', join(' ', items), ')')) + else: + parts.append(sx_str('(list ', join(' ', items), ')')) + else: + 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)))): + parts.append(serialize(item)) + else: + parts.append(serialize(result)) + _cells['i'] = (_cells['i'] + 1) + if sx_truthy(token): + svg_context_reset(token) + 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'] + +# 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'))))))))): + (await async_eval(expr, env, ctx)) + return NIL + 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 # ========================================================================= @@ -2786,4 +3833,4 @@ def render(expr, env=None): def make_env(**kwargs): """Create an environment with initial bindings.""" - return _Env(dict(kwargs)) + return _Env(dict(kwargs)) \ No newline at end of file diff --git a/shared/sx/ref/test-aser.sx b/shared/sx/ref/test-aser.sx index 053386a0..217b0a44 100644 --- a/shared/sx/ref/test-aser.sx +++ b/shared/sx/ref/test-aser.sx @@ -119,6 +119,19 @@ (assert-equal "(p \"hello\")" (render-sx "(let ((x \"hello\")) (p x))"))) + (deftest "let preserves outer scope bindings" + ;; Regression: process-bindings must preserve parent env scope chain. + ;; Using merge() instead of env-extend loses parent scope items. + (assert-equal "(p \"outer\")" + (render-sx "(do (define theme \"outer\") (let ((x 1)) (p theme)))"))) + + (deftest "nested let preserves outer scope" + (assert-equal "(div (span \"hello\") (span \"world\"))" + (render-sx "(do (define a \"hello\") + (define b \"world\") + (div (let ((x 1)) (span a)) + (let ((y 2)) (span b))))"))) + (deftest "begin serializes last" (assert-equal "(p \"last\")" (render-sx "(begin (p \"first\") (p \"last\"))")))) @@ -213,6 +226,17 @@ (assert-equal "10" (render-sx "(do (define double (fn (x) (* x 2))) (double 5))"))) + (deftest "native callable with multiple args" + ;; Regression: async-aser-eval-call passed evaled-args list to + ;; async-invoke (&rest), wrapping it in another list. apply(f, [list]) + ;; calls f(list) instead of f(*list). + (assert-equal "3" + (render-sx "(do (define my-add +) (my-add 1 2))"))) + + (deftest "native callable with two args via alias" + (assert-equal "hello world" + (render-sx "(do (define my-join str) (my-join \"hello\" \" world\"))"))) + (deftest "higher-order: map returns list" (let ((result (render-sx "(map (fn (x) (+ x 1)) (list 1 2 3))"))) ;; map at top level returns a list, not serialized tags diff --git a/shared/sx/ref/test-render.sx b/shared/sx/ref/test-render.sx index 08f1d7f4..ccca649b 100644 --- a/shared/sx/ref/test-render.sx +++ b/shared/sx/ref/test-render.sx @@ -149,7 +149,20 @@ (deftest "let in render context" (assert-equal "

    hello

    " - (render-html "(let ((x \"hello\")) (p x))")))) + (render-html "(let ((x \"hello\")) (p x))"))) + + (deftest "let preserves outer scope bindings" + ;; Regression: process-bindings must preserve parent env scope chain. + ;; Using merge() on Env objects returns empty dict (Env is not dict subclass). + (assert-equal "

    outer

    " + (render-html "(do (define theme \"outer\") (let ((x 1)) (p theme)))"))) + + (deftest "nested let preserves outer scope" + (assert-equal "
    helloworld
    " + (render-html "(do (define a \"hello\") + (define b \"world\") + (div (let ((x 1)) (span a)) + (let ((y 2)) (span b))))")))) ;; -------------------------------------------------------------------------- diff --git a/shared/sx/tests/run.py b/shared/sx/tests/run.py index 297ebacb..80ae4324 100644 --- a/shared/sx/tests/run.py +++ b/shared/sx/tests/run.py @@ -127,7 +127,8 @@ def render_html(sx_source): except ImportError: raise RuntimeError("render-to-html not available — sx_ref.py not built") exprs = parse_all(sx_source) - render_env = dict(env) + # Use Env (not flat dict) so tests exercise the real scope chain path. + render_env = _Env(dict(env)) result = "" for expr in exprs: result += _render_to_html(expr, render_env) @@ -143,7 +144,9 @@ def render_sx(sx_source): except ImportError: raise RuntimeError("aser not available — sx_ref.py not built") exprs = parse_all(sx_source) - render_env = dict(env) + # Use Env (not flat dict) so tests exercise the real scope chain path. + # Using dict(env) hides bugs where merge() drops Env parent scopes. + render_env = _Env(dict(env)) result = "" for expr in exprs: val = _aser(expr, render_env) diff --git a/sx/sx/page-helpers-demo.sx b/sx/sx/page-helpers-demo.sx index f4adce1d..2bf8e8c7 100644 --- a/sx/sx/page-helpers-demo.sx +++ b/sx/sx/page-helpers-demo.sx @@ -42,7 +42,6 @@ (sf-ms (- (now-ms) t1)) (sf-cats {}) (sf-total 0) - ;; 2. build-reference-data (t2 (now-ms)) (ref-result (build-reference-data "attributes" From cd7653d8c38a2ba525f95dd063322ada4bacc230 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 11 Mar 2026 16:51:41 +0000 Subject: [PATCH 5/6] Fix cond ambiguity: check ALL clauses with cond-scheme?, not just first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cond special form misclassified Clojure-style as scheme-style when the first test was a 2-element list like (nil? x) — treating it as a scheme clause ((test body)) instead of a function call. Define cond-scheme? using every? to check ALL clauses, fix eval.sx sf-cond and render.sx eval-cond, rewrite engine.sx parse-time/filter-params as nested if to avoid the ambiguity, add regression tests across eval/ render/aser specs. 378/378 tests pass. Co-Authored-By: Claude Opus 4.6 --- shared/static/scripts/sx-browser.js | 6066 --------------------------- shared/sx/ref/bootstrap_py.py | 3 + shared/sx/ref/engine.sx | 38 +- shared/sx/ref/eval.sx | 14 +- shared/sx/ref/render.sx | 6 +- shared/sx/ref/sx_ref.py | 10 +- shared/sx/ref/test-aser.sx | 7 + shared/sx/ref/test-eval.sx | 23 + shared/sx/ref/test-render.sx | 7 + 9 files changed, 76 insertions(+), 6098 deletions(-) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 617df9ad..e69de29b 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -1,6066 +0,0 @@ -/** - * sx-ref.js — Generated from reference SX evaluator specification. - * - * Bootstrap-compiled from shared/sx/ref/{eval,render,primitives}.sx - * Compare against hand-written sx.js for correctness verification. - * - * DO NOT EDIT — regenerate with: python bootstrap_js.py - */ -;(function(global) { - "use strict"; - - // ========================================================================= - // Types - // ========================================================================= - - var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-11T16:35:21Z"; - - function isNil(x) { return x === NIL || x === null || x === undefined; } - function isSxTruthy(x) { return x !== false && !isNil(x); } - - function Symbol(name) { this.name = name; } - Symbol.prototype.toString = function() { return this.name; }; - Symbol.prototype._sym = true; - - function Keyword(name) { this.name = name; } - Keyword.prototype.toString = function() { return ":" + this.name; }; - Keyword.prototype._kw = true; - - function Lambda(params, body, closure, name) { - this.params = params; - this.body = body; - this.closure = closure || {}; - this.name = name || null; - } - Lambda.prototype._lambda = true; - - function Component(name, params, hasChildren, body, closure, affinity) { - this.name = name; - this.params = params; - this.hasChildren = hasChildren; - this.body = body; - this.closure = closure || {}; - this.affinity = affinity || "auto"; - } - Component.prototype._component = true; - - function Island(name, params, hasChildren, body, closure) { - this.name = name; - this.params = params; - this.hasChildren = hasChildren; - this.body = body; - this.closure = closure || {}; - } - Island.prototype._island = true; - - function SxSignal(value) { - this.value = value; - this.subscribers = []; - this.deps = []; - } - SxSignal.prototype._signal = true; - - function TrackingCtx(notifyFn) { - this.notifyFn = notifyFn; - this.deps = []; - } - - var _trackingContext = null; - - function Macro(params, restParam, body, closure, name) { - this.params = params; - this.restParam = restParam; - this.body = body; - this.closure = closure || {}; - this.name = name || null; - } - Macro.prototype._macro = true; - - function Thunk(expr, env) { this.expr = expr; this.env = env; } - Thunk.prototype._thunk = true; - - function RawHTML(html) { this.html = html; } - RawHTML.prototype._raw = true; - - function isSym(x) { return x != null && x._sym === true; } - function isKw(x) { return x != null && x._kw === true; } - - function merge() { - var out = {}; - for (var i = 0; i < arguments.length; i++) { - var d = arguments[i]; - if (d) for (var k in d) out[k] = d[k]; - } - return out; - } - - function sxOr() { - for (var i = 0; i < arguments.length; i++) { - if (isSxTruthy(arguments[i])) return arguments[i]; - } - return arguments.length ? arguments[arguments.length - 1] : false; - } - - // ========================================================================= - // Platform interface — JS implementation - // ========================================================================= - - function typeOf(x) { - if (isNil(x)) return "nil"; - if (typeof x === "number") return "number"; - if (typeof x === "string") return "string"; - if (typeof x === "boolean") return "boolean"; - if (x._sym) return "symbol"; - if (x._kw) return "keyword"; - if (x._thunk) return "thunk"; - if (x._lambda) return "lambda"; - if (x._component) return "component"; - if (x._island) return "island"; - if (x._signal) return "signal"; - if (x._macro) return "macro"; - if (x._raw) return "raw-html"; - if (typeof Node !== "undefined" && x instanceof Node) return "dom-node"; - if (Array.isArray(x)) return "list"; - if (typeof x === "object") return "dict"; - return "unknown"; - } - - function symbolName(s) { return s.name; } - function keywordName(k) { return k.name; } - function makeSymbol(n) { return new Symbol(n); } - function makeKeyword(n) { return new Keyword(n); } - - function makeLambda(params, body, env) { return new Lambda(params, body, merge(env)); } - function makeComponent(name, params, hasChildren, body, env, affinity) { - return new Component(name, params, hasChildren, body, merge(env), affinity); - } - function makeMacro(params, restParam, body, env, name) { - return new Macro(params, restParam, body, merge(env), name); - } - function makeThunk(expr, env) { return new Thunk(expr, env); } - - function lambdaParams(f) { return f.params; } - function lambdaBody(f) { return f.body; } - function lambdaClosure(f) { return f.closure; } - function lambdaName(f) { return f.name; } - function setLambdaName(f, n) { f.name = n; } - - function componentParams(c) { return c.params; } - function componentBody(c) { return c.body; } - function componentClosure(c) { return c.closure; } - function componentHasChildren(c) { return c.hasChildren; } - function componentName(c) { return c.name; } - function componentAffinity(c) { return c.affinity || "auto"; } - - function macroParams(m) { return m.params; } - function macroRestParam(m) { return m.restParam; } - function macroBody(m) { return m.body; } - function macroClosure(m) { return m.closure; } - - function isThunk(x) { return x != null && x._thunk === true; } - function thunkExpr(t) { return t.expr; } - function thunkEnv(t) { return t.env; } - - function isCallable(x) { return typeof x === "function" || (x != null && x._lambda === true); } - function isLambda(x) { return x != null && x._lambda === true; } - function isComponent(x) { return x != null && x._component === true; } - function isIsland(x) { return x != null && x._island === true; } - function isMacro(x) { return x != null && x._macro === true; } - function isIdentical(a, b) { return a === b; } - - // Island platform - function makeIsland(name, params, hasChildren, body, env) { - return new Island(name, params, hasChildren, body, merge(env)); - } - - // Signal platform - function makeSignal(value) { return new SxSignal(value); } - function isSignal(x) { return x != null && x._signal === true; } - function signalValue(s) { return s.value; } - function signalSetValue(s, v) { s.value = v; } - function signalSubscribers(s) { return s.subscribers.slice(); } - function signalAddSub(s, fn) { if (s.subscribers.indexOf(fn) < 0) s.subscribers.push(fn); } - function signalRemoveSub(s, fn) { var i = s.subscribers.indexOf(fn); if (i >= 0) s.subscribers.splice(i, 1); } - function signalDeps(s) { return s.deps.slice(); } - function signalSetDeps(s, deps) { s.deps = Array.isArray(deps) ? deps.slice() : []; } - function setTrackingContext(ctx) { _trackingContext = ctx; } - function getTrackingContext() { return _trackingContext || NIL; } - function makeTrackingContext(notifyFn) { return new TrackingCtx(notifyFn); } - function trackingContextDeps(ctx) { return ctx ? ctx.deps : []; } - function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); } - function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; } - - // invoke — call any callable (native fn or SX lambda) with args. - // Transpiled code emits direct calls f(args) which fail on SX lambdas - // from runtime-evaluated island bodies. invoke dispatches correctly. - function invoke() { - var f = arguments[0]; - var args = Array.prototype.slice.call(arguments, 1); - if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f))); - if (typeof f === 'function') return f.apply(null, args); - return NIL; - } - - // JSON / dict helpers for island state serialization - function jsonSerialize(obj) { - return JSON.stringify(obj); - } - function isEmptyDict(d) { - if (!d || typeof d !== "object") return true; - for (var k in d) if (d.hasOwnProperty(k)) return false; - return true; - } - - function envHas(env, name) { return name in env; } - function envGet(env, name) { return env[name]; } - function envSet(env, name, val) { - // Walk prototype chain to find where the variable is defined (for set!) - var obj = env; - while (obj !== null && obj !== Object.prototype) { - if (obj.hasOwnProperty(name)) { obj[name] = val; return; } - obj = Object.getPrototypeOf(obj); - } - // Not found in any parent scope — set on the immediate env - env[name] = val; - } - function envExtend(env) { return Object.create(env); } - function envMerge(base, overlay) { - // Same env or overlay is descendant of base — just extend, no copy. - // This prevents set! inside lambdas from modifying shadow copies. - if (base === overlay) return Object.create(base); - var p = overlay; - for (var d = 0; p && p !== Object.prototype && d < 100; d++) { - if (p === base) return Object.create(base); - p = Object.getPrototypeOf(p); - } - // General case: extend base, copy ONLY overlay properties that don't - // exist in the base chain (avoids shadowing closure bindings). - var child = Object.create(base); - if (overlay) { - for (var k in overlay) { - if (overlay.hasOwnProperty(k) && !(k in base)) child[k] = overlay[k]; - } - } - return child; - } - - function dictSet(d, k, v) { d[k] = v; return v; } - function dictGet(d, k) { var v = d[k]; return v !== undefined ? v : NIL; } - - // Render-expression detection — lets the evaluator delegate to the active adapter. - // Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements. - // Placeholder — overridden by transpiled version from render.sx - function isRenderExpr(expr) { return false; } - - // Render dispatch — call the active adapter's render function. - // Set by each adapter when loaded; defaults to identity (no rendering). - var _renderExprFn = null; - - // Render mode flag — set by render-to-html/aser, checked by eval-list. - // When false, render expressions fall through to evalCall. - var _renderMode = false; - function renderActiveP() { return _renderMode; } - function setRenderActiveB(val) { _renderMode = !!val; } - - function renderExpr(expr, env) { - if (_renderExprFn) return _renderExprFn(expr, env); - // No adapter loaded — fall through to evalCall - return evalCall(first(expr), rest(expr), env); - } - - function stripPrefix(s, prefix) { - return s.indexOf(prefix) === 0 ? s.slice(prefix.length) : s; - } - - function error(msg) { throw new Error(msg); } - function inspect(x) { return JSON.stringify(x); } - - - - // ========================================================================= - // Primitives - // ========================================================================= - - var PRIMITIVES = {}; - - // core.arithmetic - PRIMITIVES["+"] = function() { var s = 0; for (var i = 0; i < arguments.length; i++) s += arguments[i]; return s; }; - PRIMITIVES["-"] = function(a, b) { return arguments.length === 1 ? -a : a - b; }; - PRIMITIVES["*"] = function() { var s = 1; for (var i = 0; i < arguments.length; i++) s *= arguments[i]; return s; }; - PRIMITIVES["/"] = function(a, b) { return a / b; }; - PRIMITIVES["mod"] = function(a, b) { return a % b; }; - PRIMITIVES["inc"] = function(n) { return n + 1; }; - PRIMITIVES["dec"] = function(n) { return n - 1; }; - PRIMITIVES["abs"] = Math.abs; - PRIMITIVES["floor"] = Math.floor; - PRIMITIVES["ceil"] = Math.ceil; - PRIMITIVES["round"] = function(x, n) { - if (n === undefined || n === 0) return Math.round(x); - var f = Math.pow(10, n); return Math.round(x * f) / f; - }; - PRIMITIVES["min"] = Math.min; - PRIMITIVES["max"] = Math.max; - PRIMITIVES["sqrt"] = Math.sqrt; - PRIMITIVES["pow"] = Math.pow; - PRIMITIVES["clamp"] = function(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); }; - - - // core.comparison - PRIMITIVES["="] = function(a, b) { return a === b; }; - PRIMITIVES["!="] = function(a, b) { return a !== b; }; - PRIMITIVES["<"] = function(a, b) { return a < b; }; - PRIMITIVES[">"] = function(a, b) { return a > b; }; - PRIMITIVES["<="] = function(a, b) { return a <= b; }; - PRIMITIVES[">="] = function(a, b) { return a >= b; }; - - - // core.logic - PRIMITIVES["not"] = function(x) { return !isSxTruthy(x); }; - - - // core.predicates - PRIMITIVES["nil?"] = isNil; - PRIMITIVES["number?"] = function(x) { return typeof x === "number"; }; - PRIMITIVES["string?"] = function(x) { return typeof x === "string"; }; - PRIMITIVES["list?"] = Array.isArray; - PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; }; - PRIMITIVES["empty?"] = function(c) { return isNil(c) || (Array.isArray(c) ? c.length === 0 : typeof c === "string" ? c.length === 0 : Object.keys(c).length === 0); }; - PRIMITIVES["contains?"] = function(c, k) { - if (typeof c === "string") return c.indexOf(String(k)) !== -1; - if (Array.isArray(c)) return c.indexOf(k) !== -1; - return k in c; - }; - PRIMITIVES["odd?"] = function(n) { return n % 2 !== 0; }; - PRIMITIVES["even?"] = function(n) { return n % 2 === 0; }; - PRIMITIVES["zero?"] = function(n) { return n === 0; }; - PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; }; - PRIMITIVES["component-affinity"] = componentAffinity; - - - // core.strings - PRIMITIVES["str"] = function() { - var p = []; - for (var i = 0; i < arguments.length; i++) { - var v = arguments[i]; if (isNil(v)) continue; p.push(String(v)); - } - return p.join(""); - }; - PRIMITIVES["upper"] = function(s) { return String(s).toUpperCase(); }; - PRIMITIVES["lower"] = function(s) { return String(s).toLowerCase(); }; - PRIMITIVES["trim"] = function(s) { return String(s).trim(); }; - PRIMITIVES["split"] = function(s, sep) { return String(s).split(sep || " "); }; - PRIMITIVES["join"] = function(sep, coll) { return coll.join(sep); }; - PRIMITIVES["replace"] = function(s, old, nw) { return s.split(old).join(nw); }; - PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); }; - PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; }; - PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; }; - PRIMITIVES["slice"] = function(c, a, b) { return b !== undefined ? c.slice(a, b) : c.slice(a); }; - PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); }; - PRIMITIVES["string-length"] = function(s) { return String(s).length; }; - PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; }; - PRIMITIVES["concat"] = function() { - var out = []; - for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]); - return out; - }; - - - // core.collections - PRIMITIVES["list"] = function() { return Array.prototype.slice.call(arguments); }; - PRIMITIVES["dict"] = function() { - var d = {}; - for (var i = 0; i < arguments.length - 1; i += 2) d[arguments[i]] = arguments[i + 1]; - return d; - }; - PRIMITIVES["range"] = function(a, b, step) { - var r = []; step = step || 1; - for (var i = a; step > 0 ? i < b : i > b; i += step) r.push(i); - return r; - }; - PRIMITIVES["get"] = function(c, k, def) { var v = (c && c[k]); return v !== undefined ? v : (def !== undefined ? def : NIL); }; - PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; }; - PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; }; - PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; }; - PRIMITIVES["rest"] = function(c) { return c ? c.slice(1) : []; }; - PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; }; - PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); }; - PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); }; - PRIMITIVES["append!"] = function(arr, x) { arr.push(x); return arr; }; - PRIMITIVES["chunk-every"] = function(c, n) { - var r = []; for (var i = 0; i < c.length; i += n) r.push(c.slice(i, i + n)); return r; - }; - PRIMITIVES["zip-pairs"] = function(c) { - var r = []; for (var i = 0; i < c.length - 1; i++) r.push([c[i], c[i + 1]]); return r; - }; - PRIMITIVES["reverse"] = function(c) { return Array.isArray(c) ? c.slice().reverse() : String(c).split("").reverse().join(""); }; - PRIMITIVES["flatten"] = function(c) { - var out = []; - function walk(a) { for (var i = 0; i < a.length; i++) Array.isArray(a[i]) ? walk(a[i]) : out.push(a[i]); } - walk(c || []); return out; - }; - - - // core.dict - PRIMITIVES["keys"] = function(d) { return Object.keys(d || {}); }; - PRIMITIVES["vals"] = function(d) { var r = []; for (var k in d) r.push(d[k]); return r; }; - PRIMITIVES["merge"] = function() { - var out = {}; - for (var i = 0; i < arguments.length; i++) { var d = arguments[i]; if (d && !isNil(d)) for (var k in d) out[k] = d[k]; } - return out; - }; - PRIMITIVES["assoc"] = function(d) { - var out = {}; if (d && !isNil(d)) for (var k in d) out[k] = d[k]; - for (var i = 1; i < arguments.length - 1; i += 2) out[arguments[i]] = arguments[i + 1]; - return out; - }; - PRIMITIVES["dissoc"] = function(d) { - var out = {}; for (var k in d) out[k] = d[k]; - for (var i = 1; i < arguments.length; i++) delete out[arguments[i]]; - return out; - }; - PRIMITIVES["dict-set!"] = function(d, k, v) { d[k] = v; return v; }; - PRIMITIVES["has-key?"] = function(d, k) { return d !== null && d !== undefined && k in d; }; - PRIMITIVES["into"] = function(target, coll) { - if (Array.isArray(target)) return Array.isArray(coll) ? coll.slice() : Object.entries(coll); - var r = {}; for (var i = 0; i < coll.length; i++) { var p = coll[i]; if (Array.isArray(p) && p.length >= 2) r[p[0]] = p[1]; } - return r; - }; - - - // stdlib.format - PRIMITIVES["format-decimal"] = function(v, p) { return Number(v).toFixed(p || 2); }; - PRIMITIVES["parse-int"] = function(v, d) { var n = parseInt(v, 10); return isNaN(n) ? (d || 0) : n; }; - PRIMITIVES["format-date"] = function(s, fmt) { - if (!s) return ""; - try { - var d = new Date(s); - if (isNaN(d.getTime())) return String(s); - var months = ["January","February","March","April","May","June","July","August","September","October","November","December"]; - var short_months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; - return fmt.replace(/%-d/g, d.getDate()).replace(/%d/g, ("0"+d.getDate()).slice(-2)) - .replace(/%B/g, months[d.getMonth()]).replace(/%b/g, short_months[d.getMonth()]) - .replace(/%Y/g, d.getFullYear()).replace(/%m/g, ("0"+(d.getMonth()+1)).slice(-2)) - .replace(/%H/g, ("0"+d.getHours()).slice(-2)).replace(/%M/g, ("0"+d.getMinutes()).slice(-2)); - } catch (e) { return String(s); } - }; - PRIMITIVES["parse-datetime"] = function(s) { return s ? String(s) : NIL; }; - - - // stdlib.text - PRIMITIVES["pluralize"] = function(n, s, p) { - if (s || (p && p !== "s")) return n == 1 ? (s || "") : (p || "s"); - return n == 1 ? "" : "s"; - }; - PRIMITIVES["escape"] = function(s) { - return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"); - }; - PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); }; - - - // stdlib.debug - PRIMITIVES["assert"] = function(cond, msg) { - if (!isSxTruthy(cond)) throw new Error("Assertion error: " + (msg || "Assertion failed")); - return true; - }; - - - function isPrimitive(name) { return name in PRIMITIVES; } - function getPrimitive(name) { return PRIMITIVES[name]; } - - // Higher-order helpers used by the transpiled code - function map(fn, coll) { return coll.map(fn); } - function mapIndexed(fn, coll) { return coll.map(function(item, i) { return fn(i, item); }); } - function filter(fn, coll) { return coll.filter(function(x) { return isSxTruthy(fn(x)); }); } - function reduce(fn, init, coll) { - var acc = init; - for (var i = 0; i < coll.length; i++) acc = fn(acc, coll[i]); - return acc; - } - function some(fn, coll) { - for (var i = 0; i < coll.length; i++) { var r = fn(coll[i]); if (isSxTruthy(r)) return r; } - return NIL; - } - function forEach(fn, coll) { for (var i = 0; i < coll.length; i++) fn(coll[i]); return NIL; } - function isEvery(fn, coll) { - for (var i = 0; i < coll.length; i++) { if (!isSxTruthy(fn(coll[i]))) return false; } - return true; - } - function mapDict(fn, d) { var r = {}; for (var k in d) r[k] = fn(k, d[k]); return r; } - - // List primitives used directly by transpiled code - var len = PRIMITIVES["len"]; - var first = PRIMITIVES["first"]; - var last = PRIMITIVES["last"]; - var rest = PRIMITIVES["rest"]; - var nth = PRIMITIVES["nth"]; - var cons = PRIMITIVES["cons"]; - var append = PRIMITIVES["append"]; - var isEmpty = PRIMITIVES["empty?"]; - var contains = PRIMITIVES["contains?"]; - var startsWith = PRIMITIVES["starts-with?"]; - var slice = PRIMITIVES["slice"]; - var concat = PRIMITIVES["concat"]; - var str = PRIMITIVES["str"]; - var join = PRIMITIVES["join"]; - var keys = PRIMITIVES["keys"]; - var get = PRIMITIVES["get"]; - var assoc = PRIMITIVES["assoc"]; - var range = PRIMITIVES["range"]; - function zip(a, b) { var r = []; for (var i = 0; i < Math.min(a.length, b.length); i++) r.push([a[i], b[i]]); return r; } - function append_b(arr, x) { arr.push(x); return arr; } - var apply = function(f, args) { - if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f))); - return f.apply(null, args); - }; - - // Additional primitive aliases used by adapter/engine transpiled code - var split = PRIMITIVES["split"]; - var trim = PRIMITIVES["trim"]; - var upper = PRIMITIVES["upper"]; - var lower = PRIMITIVES["lower"]; - var replace_ = function(s, old, nw) { return s.split(old).join(nw); }; - var endsWith = PRIMITIVES["ends-with?"]; - var parseInt_ = PRIMITIVES["parse-int"]; - var dict_fn = PRIMITIVES["dict"]; - - // HTML rendering helpers - function escapeHtml(s) { - return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,"""); - } - function escapeAttr(s) { return escapeHtml(s); } - function rawHtmlContent(r) { return r.html; } - function makeRawHtml(s) { return { _raw: true, html: s }; } - function sxExprSource(x) { return x && x.source ? x.source : String(x); } - - // Placeholders — overridden by transpiled spec from parser.sx / adapter-sx.sx - function serialize(val) { return String(val); } - function isSpecialForm(n) { return false; } - function isHoForm(n) { return false; } - - // processBindings and evalCond — now specced in render.sx, bootstrapped above - - function isDefinitionForm(name) { - return name === "define" || name === "defcomp" || name === "defmacro" || - name === "defstyle" || name === "defhandler"; - } - - function indexOf_(s, ch) { - return typeof s === "string" ? s.indexOf(ch) : -1; - } - - function dictHas(d, k) { return d != null && k in d; } - function dictDelete(d, k) { delete d[k]; } - - function forEachIndexed(fn, coll) { - for (var i = 0; i < coll.length; i++) fn(i, coll[i]); - return NIL; - } - - // ========================================================================= - // Performance overrides — evaluator hot path - // ========================================================================= - - // Override parseKeywordArgs: imperative loop instead of reduce+assoc - parseKeywordArgs = function(rawArgs, env) { - var kwargs = {}; - var children = []; - for (var i = 0; i < rawArgs.length; i++) { - var arg = rawArgs[i]; - if (arg && arg._kw && (i + 1) < rawArgs.length) { - kwargs[arg.name] = trampoline(evalExpr(rawArgs[i + 1], env)); - i++; - } else { - children.push(trampoline(evalExpr(arg, env))); - } - } - return [kwargs, children]; - }; - - // Override callComponent: use prototype chain env, imperative kwarg binding - callComponent = function(comp, rawArgs, env) { - var kwargs = {}; - var children = []; - for (var i = 0; i < rawArgs.length; i++) { - var arg = rawArgs[i]; - if (arg && arg._kw && (i + 1) < rawArgs.length) { - kwargs[arg.name] = trampoline(evalExpr(rawArgs[i + 1], env)); - i++; - } else { - children.push(trampoline(evalExpr(arg, env))); - } - } - var local = Object.create(componentClosure(comp)); - for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; - var params = componentParams(comp); - for (var j = 0; j < params.length; j++) { - var p = params[j]; - local[p] = p in kwargs ? kwargs[p] : NIL; - } - if (componentHasChildren(comp)) { - local["children"] = children; - } - return makeThunk(componentBody(comp), local); - }; - - // ========================================================================= - // Platform: deps module — component dependency analysis - // ========================================================================= - - function componentDeps(c) { - return c.deps ? c.deps.slice() : []; - } - - function componentSetDeps(c, deps) { - c.deps = deps; - } - - function componentCssClasses(c) { - return c.cssClasses ? c.cssClasses.slice() : []; - } - - function envComponents(env) { - var names = []; - for (var k in env) { - var v = env[k]; - if (v && (v._component || v._macro)) names.push(k); - } - return names; - } - - function regexFindAll(pattern, source) { - var re = new RegExp(pattern, "g"); - var results = []; - var m; - while ((m = re.exec(source)) !== null) { - if (m[1] !== undefined) results.push(m[1]); - else results.push(m[0]); - } - return results; - } - - function scanCssClasses(source) { - var classes = {}; - var result = []; - var m; - var re1 = /:class\s+"([^"]*)"/g; - while ((m = re1.exec(source)) !== null) { - var parts = m[1].split(/\s+/); - for (var i = 0; i < parts.length; i++) { - if (parts[i] && !classes[parts[i]]) { - classes[parts[i]] = true; - result.push(parts[i]); - } - } - } - var re2 = /:class\s+\(str\s+((?:"[^"]*"\s*)+)\)/g; - while ((m = re2.exec(source)) !== null) { - var re3 = /"([^"]*)"/g; - var m2; - while ((m2 = re3.exec(m[1])) !== null) { - var parts2 = m2[1].split(/\s+/); - for (var j = 0; j < parts2.length; j++) { - if (parts2[j] && !classes[parts2[j]]) { - classes[parts2[j]] = true; - result.push(parts2[j]); - } - } - } - } - var re4 = /;;\s*@css\s+(.+)/g; - while ((m = re4.exec(source)) !== null) { - var parts3 = m[1].split(/\s+/); - for (var k = 0; k < parts3.length; k++) { - if (parts3[k] && !classes[parts3[k]]) { - classes[parts3[k]] = true; - result.push(parts3[k]); - } - } - } - return result; - } - - function componentIoRefs(c) { - return c.ioRefs ? c.ioRefs.slice() : []; - } - - function componentSetIoRefs(c, refs) { - c.ioRefs = refs; - } - - - // ========================================================================= - // Platform interface — Parser - // ========================================================================= - // Character classification derived from the grammar: - // ident-start → [a-zA-Z_~*+\-><=/!?&] - // ident-char → ident-start + [0-9.:\/\[\]#,] - - var _identStartRe = /[a-zA-Z_~*+\-><=/!?&]/; - var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]/; - - function isIdentStart(ch) { return _identStartRe.test(ch); } - function isIdentChar(ch) { return _identCharRe.test(ch); } - function parseNumber(s) { return Number(s); } - function escapeString(s) { - return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t"); - } - function sxExprSource(e) { return typeof e === "string" ? e : String(e); } - - - // === Transpiled from eval === - - // trampoline - var trampoline = function(val) { return (function() { - var result = val; - return (isSxTruthy(isThunk(result)) ? trampoline(evalExpr(thunkExpr(result), thunkEnv(result))) : result); -})(); }; - - // eval-expr - var evalExpr = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() { - var name = symbolName(expr); - return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name)))))))); -})(); if (_m == "keyword") return keywordName(expr); if (_m == "dict") return mapDict(function(k, v) { return trampoline(evalExpr(v, env)); }, expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : evalList(expr, env)); return expr; })(); }; - - // eval-list - var evalList = function(expr, env) { return (function() { - var head = first(expr); - var args = rest(expr); - return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() { - var name = symbolName(head); - return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { - var mac = envGet(env, name); - return makeThunk(expandMacro(mac, args, env), env); -})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))))); -})() : evalCall(head, args, env))); -})(); }; - - // eval-call - var evalCall = function(head, args, env) { return (function() { - var f = trampoline(evalExpr(head, env)); - var evaluatedArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args); - return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaluatedArgs) : (isSxTruthy(isLambda(f)) ? callLambda(f, evaluatedArgs, env) : (isSxTruthy(isComponent(f)) ? callComponent(f, args, env) : (isSxTruthy(isIsland(f)) ? callComponent(f, args, env) : error((String("Not callable: ") + String(inspect(f)))))))); -})(); }; - - // call-lambda - var callLambda = function(f, args, callerEnv) { return (function() { - var params = lambdaParams(f); - var local = envMerge(lambdaClosure(f), callerEnv); - return (isSxTruthy((len(args) > len(params))) ? error((String(sxOr(lambdaName(f), "lambda")) + String(" expects ") + String(len(params)) + String(" args, got ") + String(len(args)))) : (forEach(function(pair) { return envSet(local, first(pair), nth(pair, 1)); }, zip(params, args)), forEach(function(p) { return envSet(local, p, NIL); }, slice(params, len(args))), makeThunk(lambdaBody(f), local))); -})(); }; - - // call-component - var callComponent = function(comp, rawArgs, env) { return (function() { - var parsed = parseKeywordArgs(rawArgs, env); - var kwargs = first(parsed); - var children = nth(parsed, 1); - var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, sxOr(dictGet(kwargs, p), NIL)); } } - if (isSxTruthy(componentHasChildren(comp))) { - envSet(local, "children", children); -} - return makeThunk(componentBody(comp), local); -})(); }; - - // parse-keyword-args - var parseKeywordArgs = function(rawArgs, env) { return (function() { - var kwargs = {}; - var children = []; - var i = 0; - reduce(function(state, arg) { return (function() { - var idx = get(state, "i"); - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (idx + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((idx + 1) < len(rawArgs)))) ? (dictSet(kwargs, keywordName(arg), trampoline(evalExpr(nth(rawArgs, (idx + 1)), env))), assoc(state, "skip", true, "i", (idx + 1))) : (append_b(children, trampoline(evalExpr(arg, env))), assoc(state, "i", (idx + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, rawArgs); - return [kwargs, children]; -})(); }; - - // sf-if - var sfIf = function(args, env) { return (function() { - var condition = trampoline(evalExpr(first(args), env)); - return (isSxTruthy((isSxTruthy(condition) && !isSxTruthy(isNil(condition)))) ? makeThunk(nth(args, 1), env) : (isSxTruthy((len(args) > 2)) ? makeThunk(nth(args, 2), env) : NIL)); -})(); }; - - // sf-when - var sfWhen = function(args, env) { return (function() { - var condition = trampoline(evalExpr(first(args), env)); - return (isSxTruthy((isSxTruthy(condition) && !isSxTruthy(isNil(condition)))) ? (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 1, (len(args) - 1))), makeThunk(last(args), env)) : NIL); -})(); }; - - // sf-cond - var sfCond = function(args, env) { return (isSxTruthy((isSxTruthy((typeOf(first(args)) == "list")) && (len(first(args)) == 2))) ? sfCondScheme(args, env) : sfCondClojure(args, env)); }; - - // sf-cond-scheme - var sfCondScheme = function(clauses, env) { return (isSxTruthy(isEmpty(clauses)) ? NIL : (function() { - var clause = first(clauses); - var test = first(clause); - var body = nth(clause, 1); - return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))), (isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")))) ? makeThunk(body, env) : (isSxTruthy(trampoline(evalExpr(test, env))) ? makeThunk(body, env) : sfCondScheme(rest(clauses), env))); -})()); }; - - // sf-cond-clojure - var sfCondClojure = function(clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() { - var test = first(clauses); - var body = nth(clauses, 1); - return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? makeThunk(body, env) : (isSxTruthy(trampoline(evalExpr(test, env))) ? makeThunk(body, env) : sfCondClojure(slice(clauses, 2), env))); -})()); }; - - // sf-case - var sfCase = function(args, env) { return (function() { - var matchVal = trampoline(evalExpr(first(args), env)); - var clauses = rest(args); - return sfCaseLoop(matchVal, clauses, env); -})(); }; - - // sf-case-loop - var sfCaseLoop = function(matchVal, clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() { - var test = first(clauses); - var body = nth(clauses, 1); - return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? makeThunk(body, env) : (isSxTruthy((matchVal == trampoline(evalExpr(test, env)))) ? makeThunk(body, env) : sfCaseLoop(matchVal, slice(clauses, 2), env))); -})()); }; - - // sf-and - var sfAnd = function(args, env) { return (isSxTruthy(isEmpty(args)) ? true : (function() { - var val = trampoline(evalExpr(first(args), env)); - return (isSxTruthy(!isSxTruthy(val)) ? val : (isSxTruthy((len(args) == 1)) ? val : sfAnd(rest(args), env))); -})()); }; - - // sf-or - var sfOr = function(args, env) { return (isSxTruthy(isEmpty(args)) ? false : (function() { - var val = trampoline(evalExpr(first(args), env)); - return (isSxTruthy(val) ? val : sfOr(rest(args), env)); -})()); }; - - // sf-let - var sfLet = function(args, env) { return (isSxTruthy((typeOf(first(args)) == "symbol")) ? sfNamedLet(args, env) : (function() { - var bindings = first(args); - var body = rest(args); - var local = envExtend(env); - (isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { return (function() { - var vname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding)); - return envSet(local, vname, trampoline(evalExpr(nth(binding, 1), local))); -})(); }, bindings) : (function() { - var i = 0; - return reduce(function(acc, pairIdx) { return (function() { - var vname = (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2))); - var valExpr = nth(bindings, ((pairIdx * 2) + 1)); - return envSet(local, vname, trampoline(evalExpr(valExpr, local))); -})(); }, NIL, range(0, (len(bindings) / 2))); -})()); - { var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } } - return makeThunk(last(body), local); -})()); }; - - // sf-named-let - var sfNamedLet = function(args, env) { return (function() { - var loopName = symbolName(first(args)); - var bindings = nth(args, 1); - var body = slice(args, 2); - var params = []; - var inits = []; - (isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { params.push((isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding))); -return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pairIdx) { return (append_b(params, (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2)))), append_b(inits, nth(bindings, ((pairIdx * 2) + 1)))); }, NIL, range(0, (len(bindings) / 2)))); - return (function() { - var loopBody = (isSxTruthy((len(body) == 1)) ? first(body) : cons(makeSymbol("begin"), body)); - var loopFn = makeLambda(params, loopBody, env); - loopFn.name = loopName; - envSet(lambdaClosure(loopFn), loopName, loopFn); - return (function() { - var initVals = map(function(e) { return trampoline(evalExpr(e, env)); }, inits); - return callLambda(loopFn, initVals, env); -})(); -})(); -})(); }; - - // sf-lambda - var sfLambda = function(args, env) { return (function() { - var paramsExpr = first(args); - var bodyExprs = rest(args); - var body = (isSxTruthy((len(bodyExprs) == 1)) ? first(bodyExprs) : cons(makeSymbol("begin"), bodyExprs)); - var paramNames = map(function(p) { return (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p); }, paramsExpr); - return makeLambda(paramNames, body, env); -})(); }; - - // sf-define - var sfDefine = function(args, env) { return (function() { - var nameSym = first(args); - var value = trampoline(evalExpr(nth(args, 1), env)); - if (isSxTruthy((isSxTruthy(isLambda(value)) && isNil(lambdaName(value))))) { - value.name = symbolName(nameSym); -} - envSet(env, symbolName(nameSym), value); - return value; -})(); }; - - // sf-defcomp - var sfDefcomp = function(args, env) { return (function() { - var nameSym = first(args); - var paramsRaw = nth(args, 1); - var body = last(args); - var compName = stripPrefix(symbolName(nameSym), "~"); - var parsed = parseCompParams(paramsRaw); - var params = first(parsed); - var hasChildren = nth(parsed, 1); - var affinity = defcompKwarg(args, "affinity", "auto"); - return (function() { - var comp = makeComponent(compName, params, hasChildren, body, env, affinity); - envSet(env, symbolName(nameSym), comp); - return comp; -})(); -})(); }; - - // defcomp-kwarg - var defcompKwarg = function(args, key, default_) { return (function() { - var end = (len(args) - 1); - var result = default_; - { var _c = range(2, end, 1); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(nth(args, i)) == "keyword")) && isSxTruthy((keywordName(nth(args, i)) == key)) && ((i + 1) < end)))) { - (function() { - var val = nth(args, (i + 1)); - return (result = (isSxTruthy((typeOf(val) == "keyword")) ? keywordName(val) : val)); -})(); -} } } - return result; -})(); }; - - // parse-comp-params - var parseCompParams = function(paramsExpr) { return (function() { - var params = []; - var hasChildren = false; - var inKey = false; - { var _c = paramsExpr; for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; if (isSxTruthy((typeOf(p) == "symbol"))) { - (function() { - var name = symbolName(p); - return (isSxTruthy((name == "&key")) ? (inKey = true) : (isSxTruthy((name == "&rest")) ? (hasChildren = true) : (isSxTruthy((name == "&children")) ? (hasChildren = true) : (isSxTruthy(hasChildren) ? NIL : (isSxTruthy(inKey) ? append_b(params, name) : append_b(params, name)))))); -})(); -} } } - return [params, hasChildren]; -})(); }; - - // sf-defisland - var sfDefisland = function(args, env) { return (function() { - var nameSym = first(args); - var paramsRaw = nth(args, 1); - var body = last(args); - var compName = stripPrefix(symbolName(nameSym), "~"); - var parsed = parseCompParams(paramsRaw); - var params = first(parsed); - var hasChildren = nth(parsed, 1); - return (function() { - var island = makeIsland(compName, params, hasChildren, body, env); - envSet(env, symbolName(nameSym), island); - return island; -})(); -})(); }; - - // sf-defmacro - var sfDefmacro = function(args, env) { return (function() { - var nameSym = first(args); - var paramsRaw = nth(args, 1); - var body = nth(args, 2); - var parsed = parseMacroParams(paramsRaw); - var params = first(parsed); - var restParam = nth(parsed, 1); - return (function() { - var mac = makeMacro(params, restParam, body, env, symbolName(nameSym)); - envSet(env, symbolName(nameSym), mac); - return mac; -})(); -})(); }; - - // parse-macro-params - var parseMacroParams = function(paramsExpr) { return (function() { - var params = []; - var restParam = NIL; - reduce(function(state, p) { return (isSxTruthy((isSxTruthy((typeOf(p) == "symbol")) && (symbolName(p) == "&rest"))) ? assoc(state, "in-rest", true) : (isSxTruthy(get(state, "in-rest")) ? ((restParam = (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p)), state) : (append_b(params, (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p)), state))); }, {["in-rest"]: false}, paramsExpr); - return [params, restParam]; -})(); }; - - // sf-defstyle - var sfDefstyle = function(args, env) { return (function() { - var nameSym = first(args); - var value = trampoline(evalExpr(nth(args, 1), env)); - envSet(env, symbolName(nameSym), value); - return value; -})(); }; - - // sf-begin - var sfBegin = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 0, (len(args) - 1))), makeThunk(last(args), env))); }; - - // sf-quote - var sfQuote = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : first(args)); }; - - // sf-quasiquote - var sfQuasiquote = function(args, env) { return qqExpand(first(args), env); }; - - // qq-expand - var qqExpand = function(template, env) { return (isSxTruthy(!isSxTruthy((typeOf(template) == "list"))) ? template : (isSxTruthy(isEmpty(template)) ? [] : (function() { - var head = first(template); - return (isSxTruthy((isSxTruthy((typeOf(head) == "symbol")) && (symbolName(head) == "unquote"))) ? trampoline(evalExpr(nth(template, 1), env)) : reduce(function(result, item) { return (isSxTruthy((isSxTruthy((typeOf(item) == "list")) && isSxTruthy((len(item) == 2)) && isSxTruthy((typeOf(first(item)) == "symbol")) && (symbolName(first(item)) == "splice-unquote"))) ? (function() { - var spliced = trampoline(evalExpr(nth(item, 1), env)); - return (isSxTruthy((typeOf(spliced) == "list")) ? concat(result, spliced) : (isSxTruthy(isNil(spliced)) ? result : concat(result, [spliced]))); -})() : concat(result, [qqExpand(item, env)])); }, [], template)); -})())); }; - - // sf-thread-first - var sfThreadFirst = function(args, env) { return (function() { - var val = trampoline(evalExpr(first(args), env)); - return reduce(function(result, form) { return (isSxTruthy((typeOf(form) == "list")) ? (function() { - var f = trampoline(evalExpr(first(form), env)); - var restArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, rest(form)); - var allArgs = cons(result, restArgs); - return (isSxTruthy((isSxTruthy(isCallable(f)) && !isSxTruthy(isLambda(f)))) ? apply(f, allArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, allArgs, env)) : error((String("-> form not callable: ") + String(inspect(f)))))); -})() : (function() { - var f = trampoline(evalExpr(form, env)); - return (isSxTruthy((isSxTruthy(isCallable(f)) && !isSxTruthy(isLambda(f)))) ? f(result) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, [result], env)) : error((String("-> form not callable: ") + String(inspect(f)))))); -})()); }, val, rest(args)); -})(); }; - - // sf-set! - var sfSetBang = function(args, env) { return (function() { - var name = symbolName(first(args)); - var value = trampoline(evalExpr(nth(args, 1), env)); - envSet(env, name, value); - return value; -})(); }; - - // sf-letrec - var sfLetrec = function(args, env) { return (function() { - var bindings = first(args); - var body = rest(args); - var local = envExtend(env); - var names = []; - var valExprs = []; - (isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { return (function() { - var vname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding)); - names.push(vname); - valExprs.push(nth(binding, 1)); - return envSet(local, vname, NIL); -})(); }, bindings) : reduce(function(acc, pairIdx) { return (function() { - var vname = (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2))); - var valExpr = nth(bindings, ((pairIdx * 2) + 1)); - names.push(vname); - valExprs.push(valExpr); - return envSet(local, vname, NIL); -})(); }, NIL, range(0, (len(bindings) / 2)))); - (function() { - var values = map(function(e) { return trampoline(evalExpr(e, local)); }, valExprs); - { var _c = zip(names, values); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envSet(local, first(pair), nth(pair, 1)); } } - return forEach(function(val) { return (isSxTruthy(isLambda(val)) ? forEach(function(n) { return envSet(lambdaClosure(val), n, envGet(local, n)); }, names) : NIL); }, values); -})(); - { var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } } - return makeThunk(last(body), local); -})(); }; - - // sf-dynamic-wind - var sfDynamicWind = function(args, env) { return (function() { - var before = trampoline(evalExpr(first(args), env)); - var body = trampoline(evalExpr(nth(args, 1), env)); - var after = trampoline(evalExpr(nth(args, 2), env)); - callThunk(before, env); - pushWind(before, after); - return (function() { - var result = callThunk(body, env); - popWind(); - callThunk(after, env); - return result; -})(); -})(); }; - - // expand-macro - var expandMacro = function(mac, rawArgs, env) { return (function() { - var local = envMerge(macroClosure(mac), env); - { var _c = mapIndexed(function(i, p) { return [p, i]; }, macroParams(mac)); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envSet(local, first(pair), (isSxTruthy((nth(pair, 1) < len(rawArgs))) ? nth(rawArgs, nth(pair, 1)) : NIL)); } } - if (isSxTruthy(macroRestParam(mac))) { - envSet(local, macroRestParam(mac), slice(rawArgs, len(macroParams(mac)))); -} - return trampoline(evalExpr(macroBody(mac), local)); -})(); }; - - // call-fn - var callFn = function(f, args, env) { return (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, args, env)) : (isSxTruthy(isCallable(f)) ? apply(f, args) : error((String("Not callable in HO form: ") + String(inspect(f)))))); }; - - // ho-map - var hoMap = function(args, env) { return (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - return map(function(item) { return callFn(f, [item], env); }, coll); -})(); }; - - // ho-map-indexed - var hoMapIndexed = function(args, env) { return (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - return mapIndexed(function(i, item) { return callFn(f, [i, item], env); }, coll); -})(); }; - - // ho-filter - var hoFilter = function(args, env) { return (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - return filter(function(item) { return callFn(f, [item], env); }, coll); -})(); }; - - // ho-reduce - var hoReduce = function(args, env) { return (function() { - var f = trampoline(evalExpr(first(args), env)); - var init = trampoline(evalExpr(nth(args, 1), env)); - var coll = trampoline(evalExpr(nth(args, 2), env)); - return reduce(function(acc, item) { return callFn(f, [acc, item], env); }, init, coll); -})(); }; - - // ho-some - var hoSome = function(args, env) { return (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - return some(function(item) { return callFn(f, [item], env); }, coll); -})(); }; - - // ho-every - var hoEvery = function(args, env) { return (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - return isEvery(function(item) { return callFn(f, [item], env); }, coll); -})(); }; - - // ho-for-each - var hoForEach = function(args, env) { return (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - return forEach(function(item) { return callFn(f, [item], env); }, coll); -})(); }; - - - // === Transpiled from render (core) === - - // HTML_TAGS - var 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 - var VOID_ELEMENTS = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]; - - // BOOLEAN_ATTRS - var 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? - var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler")); }; - - // parse-element-args - var parseElementArgs = function(args, env) { return (function() { - var attrs = {}; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - attrs[keywordName(arg)] = val; - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return [attrs, children]; -})(); }; - - // render-attrs - var renderAttrs = function(attrs) { return join("", map(function(key) { return (function() { - var val = dictGet(attrs, key); - return (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && val)) ? (String(" ") + String(key)) : (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && !isSxTruthy(val))) ? "" : (isSxTruthy(isNil(val)) ? "" : (String(" ") + String(key) + String("=\"") + String(escapeAttr((String(val)))) + String("\""))))); -})(); }, keys(attrs))); }; - - // eval-cond - var evalCond = function(clauses, env) { return (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(clauses))) && isSxTruthy((typeOf(first(clauses)) == "list")) && (len(first(clauses)) == 2))) ? evalCondScheme(clauses, env) : evalCondClojure(clauses, env)); }; - - // eval-cond-scheme - var evalCondScheme = function(clauses, env) { return (isSxTruthy(isEmpty(clauses)) ? NIL : (function() { - var clause = first(clauses); - var test = first(clause); - var body = nth(clause, 1); - return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))), (isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")))) ? body : (isSxTruthy(trampoline(evalExpr(test, env))) ? body : evalCondScheme(rest(clauses), env))); -})()); }; - - // eval-cond-clojure - var evalCondClojure = function(clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() { - var test = first(clauses); - var body = nth(clauses, 1); - return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? body : (isSxTruthy(trampoline(evalExpr(test, env))) ? body : evalCondClojure(slice(clauses, 2), env))); -})()); }; - - // process-bindings - var processBindings = function(bindings, env) { return (function() { - var local = envExtend(env); - { var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) { - (function() { - var name = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair)))); - return envSet(local, name, trampoline(evalExpr(nth(pair, 1), local))); -})(); -} } } - return local; -})(); }; - - // is-render-expr? - var isRenderExpr = function(expr) { return (isSxTruthy(sxOr(!isSxTruthy((typeOf(expr) == "list")), isEmpty(expr))) ? false : (function() { - var h = first(expr); - return (isSxTruthy(!isSxTruthy((typeOf(h) == "symbol"))) ? false : (function() { - var n = symbolName(h); - return sxOr((n == "<>"), (n == "raw!"), startsWith(n, "~"), startsWith(n, "html:"), contains(HTML_TAGS, n), (isSxTruthy((indexOf_(n, "-") > 0)) && isSxTruthy((len(expr) > 1)) && (typeOf(nth(expr, 1)) == "keyword"))); -})()); -})()); }; - - - // === Transpiled from parser === - - // sx-parse - 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) == "\n"))))) { pos = (pos + 1); -continue; } else { return NIL; } } }; - var skipWs = function() { while(true) { if (isSxTruthy((pos < lenSrc))) { { var ch = nth(source, pos); -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; } } }; - var readString = function() { pos = (pos + 1); -return (function() { - var buf = ""; - var readStrLoop = function() { while(true) { if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated string"); } else { { var ch = nth(source, pos); -if (isSxTruthy((ch == "\""))) { pos = (pos + 1); -return NIL; } else if (isSxTruthy((ch == "\\"))) { pos = (pos + 1); -{ var esc = nth(source, pos); -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); -continue; } } } } }; - readStrLoop(); - return buf; -})(); }; - var readIdent = function() { return (function() { - var start = pos; - var readIdentLoop = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && isIdentChar(nth(source, pos))))) { pos = (pos + 1); -continue; } else { return NIL; } } }; - readIdentLoop(); - return slice(source, start, pos); -})(); }; - var readKeyword = function() { pos = (pos + 1); -return makeKeyword(readIdent()); }; - var readNumber = function() { return (function() { - var start = pos; - if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "-")))) { - pos = (pos + 1); -} - var readDigits = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (function() { - var c = nth(source, pos); - return (isSxTruthy((c >= "0")) && (c <= "9")); -})()))) { pos = (pos + 1); -continue; } else { return NIL; } } }; - readDigits(); - if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == ".")))) { - pos = (pos + 1); - readDigits(); -} - if (isSxTruthy((isSxTruthy((pos < lenSrc)) && sxOr((nth(source, pos) == "e"), (nth(source, pos) == "E"))))) { - pos = (pos + 1); - if (isSxTruthy((isSxTruthy((pos < lenSrc)) && sxOr((nth(source, pos) == "+"), (nth(source, pos) == "-"))))) { - pos = (pos + 1); -} - readDigits(); -} - return parseNumber(slice(source, start, pos)); -})(); }; - var readSymbol = function() { return (function() { - var name = readIdent(); - return (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : makeSymbol(name)))); -})(); }; - var readList = function(closeCh) { return (function() { - var items = []; - var readListLoop = function() { while(true) { skipWs(); -if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated list"); } else { if (isSxTruthy((nth(source, pos) == closeCh))) { pos = (pos + 1); -return NIL; } else { items.push(readExpr()); -continue; } } } }; - readListLoop(); - return items; -})(); }; - var readMap = function() { return (function() { - var result = {}; - var readMapLoop = function() { while(true) { skipWs(); -if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated map"); } else { if (isSxTruthy((nth(source, pos) == "}"))) { pos = (pos + 1); -return NIL; } else { { var keyExpr = readExpr(); -var keyStr = (isSxTruthy((typeOf(keyExpr) == "keyword")) ? keywordName(keyExpr) : (String(keyExpr))); -var valExpr = readExpr(); -result[keyStr] = valExpr; -continue; } } } } }; - readMapLoop(); - return result; -})(); }; - var readRawString = function() { return (function() { - var buf = ""; - var rawLoop = function() { while(true) { if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated raw string"); } else { { var ch = nth(source, pos); -if (isSxTruthy((ch == "|"))) { pos = (pos + 1); -return NIL; } else { buf = (String(buf) + String(ch)); -pos = (pos + 1); -continue; } } } } }; - rawLoop(); - return buf; -})(); }; - var readExpr = function() { while(true) { skipWs(); -if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input"); } else { { var ch = nth(source, pos); -if (isSxTruthy((ch == "("))) { pos = (pos + 1); -return readList(")"); } else if (isSxTruthy((ch == "["))) { pos = (pos + 1); -return readList("]"); } else if (isSxTruthy((ch == "{"))) { pos = (pos + 1); -return readMap(); } else if (isSxTruthy((ch == "\""))) { return readString(); } else if (isSxTruthy((ch == ":"))) { return readKeyword(); } else if (isSxTruthy((ch == "`"))) { pos = (pos + 1); -return [makeSymbol("quasiquote"), readExpr()]; } else if (isSxTruthy((ch == ","))) { pos = (pos + 1); -if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "@")))) { pos = (pos + 1); -return [makeSymbol("splice-unquote"), readExpr()]; } else { return [makeSymbol("unquote"), readExpr()]; } } else if (isSxTruthy((ch == "#"))) { pos = (pos + 1); -if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input after #"); } else { { var dispatchCh = nth(source, pos); -if (isSxTruthy((dispatchCh == ";"))) { pos = (pos + 1); -readExpr(); -continue; } else if (isSxTruthy((dispatchCh == "|"))) { pos = (pos + 1); -return readRawString(); } else if (isSxTruthy((dispatchCh == "'"))) { pos = (pos + 1); -return [makeSymbol("quote"), readExpr()]; } else if (isSxTruthy(isIdentStart(dispatchCh))) { { var macroName = readIdent(); -{ var handler = readerMacroGet(macroName); -if (isSxTruthy(handler)) { return handler(readExpr()); } else { return error((String("Unknown reader macro: #") + String(macroName))); } } } } else { return error((String("Unknown reader macro: #") + String(dispatchCh))); } } } } else if (isSxTruthy(sxOr((isSxTruthy((ch >= "0")) && (ch <= "9")), (isSxTruthy((ch == "-")) && isSxTruthy(((pos + 1) < lenSrc)) && (function() { - var nextCh = nth(source, (pos + 1)); - return (isSxTruthy((nextCh >= "0")) && (nextCh <= "9")); -})())))) { return readNumber(); } else if (isSxTruthy((isSxTruthy((ch == ".")) && isSxTruthy(((pos + 2) < lenSrc)) && isSxTruthy((nth(source, (pos + 1)) == ".")) && (nth(source, (pos + 2)) == ".")))) { pos = (pos + 3); -return makeSymbol("..."); } else if (isSxTruthy(isIdentStart(ch))) { return readSymbol(); } else { return error((String("Unexpected character: ") + String(ch))); } } } } }; - return (function() { - var exprs = []; - var parseLoop = function() { while(true) { skipWs(); -if (isSxTruthy((pos < lenSrc))) { exprs.push(readExpr()); -continue; } else { return NIL; } } }; - parseLoop(); - return exprs; -})(); -})(); }; - - // sx-serialize - var sxSerialize = function(val) { return (function() { var _m = typeOf(val); if (_m == "nil") return "nil"; if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "number") return (String(val)); if (_m == "string") return (String("\"") + String(escapeString(val)) + String("\"")); if (_m == "symbol") return symbolName(val); if (_m == "keyword") return (String(":") + String(keywordName(val))); if (_m == "list") return (String("(") + String(join(" ", map(sxSerialize, val))) + String(")")); if (_m == "dict") return sxSerializeDict(val); if (_m == "sx-expr") return sxExprSource(val); return (String(val)); })(); }; - - // sx-serialize-dict - var sxSerializeDict = function(d) { return (String("{") + String(join(" ", reduce(function(acc, key) { return concat(acc, [(String(":") + String(key)), sxSerialize(dictGet(d, key))]); }, [], keys(d)))) + String("}")); }; - - // serialize - var serialize = sxSerialize; - - - // === Transpiled from adapter-html === - - // render-to-html - var renderToHtml = function(expr, env) { setRenderActiveB(true); -return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); }; - - // render-value-to-html - var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); return escapeHtml((String(val))); })(); }; - - // RENDER_HTML_FORMS - var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each"]; - - // render-html-form? - var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); }; - - // render-list-to-html - var renderListToHtml = function(expr, env) { return (isSxTruthy(isEmpty(expr)) ? "" : (function() { - var head = first(expr); - return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", map(function(x) { return renderValueToHtml(x, env); }, expr)) : (function() { - var name = symbolName(head); - var args = rest(expr); - return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy((name == "marsh")) ? renderHtmlMarsh(args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() { - var val = envGet(env, name); - return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name))))); -})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))); -})()); -})()); }; - - // dispatch-html-form - var dispatchHtmlForm = function(name, expr, env) { return (isSxTruthy((name == "if")) ? (function() { - var condVal = trampoline(evalExpr(nth(expr, 1), env)); - return (isSxTruthy(condVal) ? renderToHtml(nth(expr, 2), env) : (isSxTruthy((len(expr) > 3)) ? renderToHtml(nth(expr, 3), env) : "")); -})() : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? "" : join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(2, len(expr))))) : (isSxTruthy((name == "cond")) ? (function() { - var branch = evalCond(rest(expr), env); - return (isSxTruthy(branch) ? renderToHtml(branch, env) : ""); -})() : (isSxTruthy((name == "case")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() { - var local = processBindings(nth(expr, 1), env); - return join("", map(function(i) { return renderToHtml(nth(expr, i), local); }, range(2, len(expr)))); -})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(1, len(expr)))) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), "") : (isSxTruthy((name == "map")) ? (function() { - var f = trampoline(evalExpr(nth(expr, 1), env)); - var coll = trampoline(evalExpr(nth(expr, 2), env)); - return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll)); -})() : (isSxTruthy((name == "map-indexed")) ? (function() { - var f = trampoline(evalExpr(nth(expr, 1), env)); - var coll = trampoline(evalExpr(nth(expr, 2), env)); - return join("", mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [i, item], env) : renderToHtml(apply(f, [i, item]), env)); }, coll)); -})() : (isSxTruthy((name == "filter")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy((name == "for-each")) ? (function() { - var f = trampoline(evalExpr(nth(expr, 1), env)); - var coll = trampoline(evalExpr(nth(expr, 2), env)); - return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll)); -})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))))); }; - - // render-lambda-html - var renderLambdaHtml = function(f, args, env) { return (function() { - var local = envMerge(lambdaClosure(f), env); - forEachIndexed(function(i, p) { return envSet(local, p, nth(args, i)); }, lambdaParams(f)); - return renderToHtml(lambdaBody(f), local); -})(); }; - - // render-html-component - var renderHtmlComponent = function(comp, args, env) { return (function() { - var kwargs = {}; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - kwargs[keywordName(arg)] = val; - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return (function() { - var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } - if (isSxTruthy(componentHasChildren(comp))) { - envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); -} - return renderToHtml(componentBody(comp), local); -})(); -})(); }; - - // render-html-element - var renderHtmlElement = function(tag, args, env) { return (function() { - var parsed = parseElementArgs(args, env); - var attrs = first(parsed); - var children = nth(parsed, 1); - var isVoid = contains(VOID_ELEMENTS, tag); - return (String("<") + String(tag) + String(renderAttrs(attrs)) + String((isSxTruthy(isVoid) ? " />" : (String(">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String(""))))); -})(); }; - - // render-html-lake - var renderHtmlLake = function(args, env) { return (function() { - var lakeId = NIL; - var lakeTag = "div"; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var kname = keywordName(arg); - var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - (isSxTruthy((kname == "id")) ? (lakeId = kval) : (isSxTruthy((kname == "tag")) ? (lakeTag = kval) : NIL)); - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return (String("<") + String(lakeTag) + String(" data-sx-lake=\"") + String(escapeAttr(sxOr(lakeId, ""))) + String("\">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("")); -})(); }; - - // render-html-marsh - var renderHtmlMarsh = function(args, env) { return (function() { - var marshId = NIL; - var marshTag = "div"; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var kname = keywordName(arg); - var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - (isSxTruthy((kname == "id")) ? (marshId = kval) : (isSxTruthy((kname == "tag")) ? (marshTag = kval) : (isSxTruthy((kname == "transform")) ? NIL : NIL))); - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return (String("<") + String(marshTag) + String(" data-sx-marsh=\"") + String(escapeAttr(sxOr(marshId, ""))) + String("\">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("")); -})(); }; - - // render-html-island - var renderHtmlIsland = function(island, args, env) { return (function() { - var kwargs = {}; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - kwargs[keywordName(arg)] = val; - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return (function() { - var local = envMerge(componentClosure(island), env); - var islandName = componentName(island); - { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } - if (isSxTruthy(componentHasChildren(island))) { - envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); -} - return (function() { - var bodyHtml = renderToHtml(componentBody(island), local); - var stateSx = serializeIslandState(kwargs); - return (String("") + String(bodyHtml) + String("")); -})(); -})(); -})(); }; - - // serialize-island-state - var serializeIslandState = function(kwargs) { return (isSxTruthy(isEmptyDict(kwargs)) ? NIL : sxSerialize(kwargs)); }; - - - // === Transpiled from adapter-sx === - - // render-to-sx - var renderToSx = function(expr, env) { return (function() { - var result = aser(expr, env); - return (isSxTruthy((typeOf(result) == "string")) ? result : serialize(result)); -})(); }; - - // aser - var aser = function(expr, env) { setRenderActiveB(true); -return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() { - var name = symbolName(expr); - return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name)))))))); -})(); if (_m == "keyword") return keywordName(expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : aserList(expr, env)); return expr; })(); }; - - // aser-list - var aserList = function(expr, env) { return (function() { - var head = first(expr); - var args = rest(expr); - return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? map(function(x) { return aser(x, env); }, expr) : (function() { - var name = symbolName(head); - return (isSxTruthy((name == "<>")) ? aserFragment(args, env) : (isSxTruthy(startsWith(name, "~")) ? aserCall(name, args, env) : (isSxTruthy((name == "lake")) ? aserCall(name, args, env) : (isSxTruthy((name == "marsh")) ? aserCall(name, args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? aserCall(name, args, env) : (isSxTruthy(sxOr(isSpecialForm(name), isHoForm(name))) ? aserSpecial(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? aser(expandMacro(envGet(env, name), args, env), env) : (function() { - var f = trampoline(evalExpr(head, env)); - var evaledArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args); - return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaledArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, evaledArgs, env)) : (isSxTruthy(isComponent(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : (isSxTruthy(isIsland(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : error((String("Not callable: ") + String(inspect(f)))))))); -})()))))))); -})()); -})(); }; - - // aser-fragment - var aserFragment = function(children, env) { return (function() { - var parts = []; - { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() { - var result = aser(c, env); - return (isSxTruthy((typeOf(result) == "list")) ? forEach(function(item) { return (isSxTruthy(!isSxTruthy(isNil(item))) ? append_b(parts, serialize(item)) : NIL); }, result) : (isSxTruthy(!isSxTruthy(isNil(result))) ? append_b(parts, serialize(result)) : NIL)); -})(); } } - return (isSxTruthy(isEmpty(parts)) ? "" : (String("(<> ") + String(join(" ", parts)) + String(")"))); -})(); }; - - // aser-call - var aserCall = function(name, args, env) { return (function() { - var parts = [name]; - var skip = false; - var i = 0; - { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (isSxTruthy(skip) ? ((skip = false), (i = (i + 1))) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((i + 1) < len(args)))) ? (function() { - var val = aser(nth(args, (i + 1)), env); - if (isSxTruthy(!isSxTruthy(isNil(val)))) { - parts.push((String(":") + String(keywordName(arg)))); - parts.push(serialize(val)); -} - skip = true; - return (i = (i + 1)); -})() : (function() { - var val = aser(arg, env); - if (isSxTruthy(!isSxTruthy(isNil(val)))) { - (isSxTruthy((typeOf(val) == "list")) ? forEach(function(item) { return (isSxTruthy(!isSxTruthy(isNil(item))) ? append_b(parts, serialize(item)) : NIL); }, val) : append_b(parts, serialize(val))); -} - return (i = (i + 1)); -})())); } } - return (String("(") + String(join(" ", parts)) + String(")")); -})(); }; - - // SPECIAL_FORM_NAMES - var 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"]; - - // HO_FORM_NAMES - var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"]; - - // special-form? - var isSpecialForm = function(name) { return contains(SPECIAL_FORM_NAMES, name); }; - - // ho-form? - var isHoForm = function(name) { return contains(HO_FORM_NAMES, name); }; - - // aser-special - var aserSpecial = function(name, expr, env) { return (function() { - var args = rest(expr); - return (isSxTruthy((name == "if")) ? (isSxTruthy(trampoline(evalExpr(first(args), env))) ? aser(nth(args, 1), env) : (isSxTruthy((len(args) > 2)) ? aser(nth(args, 2), env) : NIL)) : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(first(args), env)))) ? NIL : (function() { - var result = NIL; - { var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } } - return result; -})()) : (isSxTruthy((name == "cond")) ? (function() { - var branch = evalCond(args, env); - return (isSxTruthy(branch) ? aser(branch, env) : NIL); -})() : (isSxTruthy((name == "case")) ? (function() { - var matchVal = trampoline(evalExpr(first(args), env)); - var clauses = rest(args); - return evalCaseAser(matchVal, clauses, env); -})() : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() { - var local = processBindings(first(args), env); - var result = NIL; - { var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, local); } } - return result; -})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (function() { - var result = NIL; - { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } } - return result; -})() : (isSxTruthy((name == "and")) ? (function() { - var result = true; - some(function(arg) { result = trampoline(evalExpr(arg, env)); -return !isSxTruthy(result); }, args); - return result; -})() : (isSxTruthy((name == "or")) ? (function() { - var result = false; - some(function(arg) { result = trampoline(evalExpr(arg, env)); -return result; }, args); - return result; -})() : (isSxTruthy((name == "map")) ? (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - return map(function(item) { return (isSxTruthy(isLambda(f)) ? (function() { - var local = envMerge(lambdaClosure(f), env); - envSet(local, first(lambdaParams(f)), item); - return aser(lambdaBody(f), local); -})() : invoke(f, item)); }, coll); -})() : (isSxTruthy((name == "map-indexed")) ? (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - return mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? (function() { - var local = envMerge(lambdaClosure(f), env); - envSet(local, first(lambdaParams(f)), i); - envSet(local, nth(lambdaParams(f), 1), item); - return aser(lambdaBody(f), local); -})() : invoke(f, i, item)); }, coll); -})() : (isSxTruthy((name == "for-each")) ? (function() { - var f = trampoline(evalExpr(first(args), env)); - var coll = trampoline(evalExpr(nth(args, 1), env)); - var results = []; - { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (isSxTruthy(isLambda(f)) ? (function() { - var local = envMerge(lambdaClosure(f), env); - envSet(local, first(lambdaParams(f)), item); - return append_b(results, aser(lambdaBody(f), local)); -})() : invoke(f, item)); } } - return (isSxTruthy(isEmpty(results)) ? NIL : results); -})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"))) ? (trampoline(evalExpr(expr, env)), NIL) : trampoline(evalExpr(expr, env))))))))))))))); -})(); }; - - // eval-case-aser - var evalCaseAser = function(matchVal, clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() { - var test = first(clauses); - var body = nth(clauses, 1); - return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == ":else"), (symbolName(test) == "else"))))) ? aser(body, env) : (isSxTruthy((matchVal == trampoline(evalExpr(test, env)))) ? aser(body, env) : evalCaseAser(matchVal, slice(clauses, 2), env))); -})()); }; - - - // === Transpiled from adapter-dom === - - // SVG_NS - var SVG_NS = "http://www.w3.org/2000/svg"; - - // MATH_NS - var MATH_NS = "http://www.w3.org/1998/Math/MathML"; - - // render-to-dom - var renderToDom = function(expr, env, ns) { setRenderActiveB(true); -return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(_islandScope) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); }; - - // render-dom-list - var renderDomList = function(expr, env, ns) { return (function() { - var head = first(expr); - return (isSxTruthy((typeOf(head) == "symbol")) ? (function() { - var name = symbolName(head); - var args = rest(expr); - return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy((name == "lake")) ? renderDomLake(args, env, ns) : (isSxTruthy((name == "marsh")) ? renderDomMarsh(args, env, ns) : (isSxTruthy(startsWith(name, "html:")) ? renderDomElement(slice(name, 5), args, env, ns) : (isSxTruthy(isRenderDomForm(name)) ? (isSxTruthy((isSxTruthy(contains(HTML_TAGS, name)) && sxOr((isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword")), ns))) ? renderDomElement(name, args, env, ns) : dispatchRenderForm(name, expr, env, ns)) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToDom(expandMacro(envGet(env, name), args, env), env, ns) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderDomIsland(envGet(env, name), args, env, ns) : (isSxTruthy(startsWith(name, "~")) ? (function() { - var comp = envGet(env, name); - return (isSxTruthy(isComponent(comp)) ? renderDomComponent(comp, args, env, ns) : renderDomUnknownComponent(name)); -})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && _islandScope)) ? (function() { - var sigOrVal = trampoline(evalExpr(first(args), env)); - return (isSxTruthy(isSignal(sigOrVal)) ? reactiveText(sigOrVal) : createTextNode((String(deref(sigOrVal))))); -})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); -})() : (isSxTruthy(sxOr(isLambda(head), (typeOf(head) == "list"))) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (function() { - var frag = createFragment(); - { var _c = expr; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; domAppend(frag, renderToDom(x, env, ns)); } } - return frag; -})())); -})(); }; - - // render-dom-element - var renderDomElement = function(tag, args, env, ns) { return (function() { - var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns)); - var el = domCreateElement(tag, newNs); - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var attrName = keywordName(arg); - var attrExpr = nth(args, (get(state, "i") + 1)); - (isSxTruthy(startsWith(attrName, "on-")) ? (function() { - var attrVal = trampoline(evalExpr(attrExpr, env)); - return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), attrVal) : NIL); -})() : (isSxTruthy((attrName == "bind")) ? (function() { - var attrVal = trampoline(evalExpr(attrExpr, env)); - return (isSxTruthy(isSignal(attrVal)) ? bindInput(el, attrVal) : NIL); -})() : (isSxTruthy((attrName == "ref")) ? (function() { - var attrVal = trampoline(evalExpr(attrExpr, env)); - return dictSet(attrVal, "current", el); -})() : (isSxTruthy((attrName == "key")) ? (function() { - var attrVal = trampoline(evalExpr(attrExpr, env)); - return domSetAttr(el, "key", (String(attrVal))); -})() : (isSxTruthy(_islandScope) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() { - var attrVal = trampoline(evalExpr(attrExpr, env)); - return (isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal)))))); -})()))))); - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return el; -})(); }; - - // render-dom-component - var renderDomComponent = function(comp, args, env, ns) { return (function() { - var kwargs = {}; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - kwargs[keywordName(arg)] = val; - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return (function() { - var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } - if (isSxTruthy(componentHasChildren(comp))) { - (function() { - var childFrag = createFragment(); - { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(childFrag, renderToDom(c, env, ns)); } } - return envSet(local, "children", childFrag); -})(); -} - return renderToDom(componentBody(comp), local, ns); -})(); -})(); }; - - // render-dom-fragment - var renderDomFragment = function(args, env, ns) { return (function() { - var frag = createFragment(); - { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; domAppend(frag, renderToDom(x, env, ns)); } } - return frag; -})(); }; - - // render-dom-raw - var renderDomRaw = function(args, env) { return (function() { - var frag = createFragment(); - { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (function() { - var val = trampoline(evalExpr(arg, env)); - return (isSxTruthy((typeOf(val) == "string")) ? domAppend(frag, domParseHtml(val)) : (isSxTruthy((typeOf(val) == "dom-node")) ? domAppend(frag, domClone(val)) : (isSxTruthy(!isSxTruthy(isNil(val))) ? domAppend(frag, createTextNode((String(val)))) : NIL))); -})(); } } - return frag; -})(); }; - - // render-dom-unknown-component - var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); }; - - // RENDER_DOM_FORMS - var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary"]; - - // render-dom-form? - var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); }; - - // dispatch-render-form - var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(_islandScope) ? (function() { - var marker = createComment("r-if"); - var currentNodes = []; - var initialResult = NIL; - effect(function() { return (function() { - var result = (function() { - var condVal = trampoline(evalExpr(nth(expr, 1), env)); - return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment())); -})(); - return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result])), domInsertAfter(marker, result)) : (initialResult = result)); -})(); }); - return (function() { - var frag = createFragment(); - domAppend(frag, marker); - if (isSxTruthy(initialResult)) { - currentNodes = (isSxTruthy(domIsFragment(initialResult)) ? domChildNodes(initialResult) : [initialResult]); - domAppend(frag, initialResult); -} - return frag; -})(); -})() : (function() { - var condVal = trampoline(evalExpr(nth(expr, 1), env)); - return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment())); -})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(_islandScope) ? (function() { - var marker = createComment("r-when"); - var currentNodes = []; - var initialResult = NIL; - effect(function() { return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = []), (isSxTruthy(trampoline(evalExpr(nth(expr, 1), env))) ? (function() { - var frag = createFragment(); - { var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } - currentNodes = domChildNodes(frag); - return domInsertAfter(marker, frag); -})() : NIL)) : (isSxTruthy(trampoline(evalExpr(nth(expr, 1), env))) ? (function() { - var frag = createFragment(); - { var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } - currentNodes = domChildNodes(frag); - return (initialResult = frag); -})() : NIL)); }); - return (function() { - var frag = createFragment(); - domAppend(frag, marker); - if (isSxTruthy(initialResult)) { - domAppend(frag, initialResult); -} - return frag; -})(); -})() : (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? createFragment() : (function() { - var frag = createFragment(); - { var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } - return frag; -})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(_islandScope) ? (function() { - var marker = createComment("r-cond"); - var currentNodes = []; - var initialResult = NIL; - effect(function() { return (function() { - var branch = evalCond(rest(expr), env); - return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = []), (isSxTruthy(branch) ? (function() { - var result = renderToDom(branch, env, ns); - currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result]); - return domInsertAfter(marker, result); -})() : NIL)) : (isSxTruthy(branch) ? (function() { - var result = renderToDom(branch, env, ns); - currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result]); - return (initialResult = result); -})() : NIL)); -})(); }); - return (function() { - var frag = createFragment(); - domAppend(frag, marker); - if (isSxTruthy(initialResult)) { - domAppend(frag, initialResult); -} - return frag; -})(); -})() : (function() { - var branch = evalCond(rest(expr), env); - return (isSxTruthy(branch) ? renderToDom(branch, env, ns) : createFragment()); -})()) : (isSxTruthy((name == "case")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() { - var local = processBindings(nth(expr, 1), env); - var frag = createFragment(); - { var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), local, ns)); } } - return frag; -})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (function() { - var frag = createFragment(); - { var _c = range(1, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } - return frag; -})() : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), createFragment()) : (isSxTruthy((name == "map")) ? (function() { - var collExpr = nth(expr, 2); - return (isSxTruthy((isSxTruthy(_islandScope) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && isSxTruthy((typeOf(first(collExpr)) == "symbol")) && (symbolName(first(collExpr)) == "deref"))) ? (function() { - var f = trampoline(evalExpr(nth(expr, 1), env)); - var sig = trampoline(evalExpr(nth(collExpr, 1), env)); - return (isSxTruthy(isSignal(sig)) ? reactiveList(f, sig, env, ns) : (function() { - var coll = deref(sig); - var frag = createFragment(); - { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() { - var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns)); - return domAppend(frag, val); -})(); } } - return frag; -})()); -})() : (function() { - var f = trampoline(evalExpr(nth(expr, 1), env)); - var coll = trampoline(evalExpr(nth(expr, 2), env)); - var frag = createFragment(); - { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() { - var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns)); - return domAppend(frag, val); -})(); } } - return frag; -})()); -})() : (isSxTruthy((name == "map-indexed")) ? (function() { - var f = trampoline(evalExpr(nth(expr, 1), env)); - var coll = trampoline(evalExpr(nth(expr, 2), env)); - var frag = createFragment(); - forEachIndexed(function(i, item) { return (function() { - var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [i, item], env, ns) : renderToDom(apply(f, [i, item]), env, ns)); - return domAppend(frag, val); -})(); }, coll); - return frag; -})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(rest(expr), env, ns) : (isSxTruthy((name == "error-boundary")) ? renderDomErrorBoundary(rest(expr), env, ns) : (isSxTruthy((name == "for-each")) ? (function() { - var f = trampoline(evalExpr(nth(expr, 1), env)); - var coll = trampoline(evalExpr(nth(expr, 2), env)); - var frag = createFragment(); - { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() { - var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns)); - return domAppend(frag, val); -})(); } } - return frag; -})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); }; - - // render-lambda-dom - var renderLambdaDom = function(f, args, env, ns) { return (function() { - var local = envMerge(lambdaClosure(f), env); - forEachIndexed(function(i, p) { return envSet(local, p, nth(args, i)); }, lambdaParams(f)); - return renderToDom(lambdaBody(f), local, ns); -})(); }; - - // render-dom-island - var renderDomIsland = function(island, args, env, ns) { return (function() { - var kwargs = {}; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - kwargs[keywordName(arg)] = val; - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return (function() { - var local = envMerge(componentClosure(island), env); - var islandName = componentName(island); - { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } - if (isSxTruthy(componentHasChildren(island))) { - (function() { - var childFrag = createFragment(); - { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(childFrag, renderToDom(c, env, ns)); } } - return envSet(local, "children", childFrag); -})(); -} - return (function() { - var container = domCreateElement("span", NIL); - var disposers = []; - domSetAttr(container, "data-sx-island", islandName); - markProcessed(container, "island-hydrated"); - return (function() { - var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(island), local, ns); }); - domAppend(container, bodyDom); - domSetData(container, "sx-disposers", disposers); - return container; -})(); -})(); -})(); -})(); }; - - // render-dom-lake - var renderDomLake = function(args, env, ns) { return (function() { - var lakeId = NIL; - var lakeTag = "div"; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var kname = keywordName(arg); - var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - (isSxTruthy((kname == "id")) ? (lakeId = kval) : (isSxTruthy((kname == "tag")) ? (lakeTag = kval) : NIL)); - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return (function() { - var el = domCreateElement(lakeTag, NIL); - domSetAttr(el, "data-sx-lake", sxOr(lakeId, "")); - { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(el, renderToDom(c, env, ns)); } } - return el; -})(); -})(); }; - - // render-dom-marsh - var renderDomMarsh = function(args, env, ns) { return (function() { - var marshId = NIL; - var marshTag = "div"; - var marshTransform = NIL; - var children = []; - reduce(function(state, arg) { return (function() { - var skip = get(state, "skip"); - return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { - var kname = keywordName(arg); - var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); - (isSxTruthy((kname == "id")) ? (marshId = kval) : (isSxTruthy((kname == "tag")) ? (marshTag = kval) : (isSxTruthy((kname == "transform")) ? (marshTransform = kval) : NIL))); - return assoc(state, "skip", true, "i", (get(state, "i") + 1)); -})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); -})(); }, {["i"]: 0, ["skip"]: false}, args); - return (function() { - var el = domCreateElement(marshTag, NIL); - domSetAttr(el, "data-sx-marsh", sxOr(marshId, "")); - if (isSxTruthy(marshTransform)) { - domSetData(el, "sx-marsh-transform", marshTransform); -} - domSetData(el, "sx-marsh-env", env); - { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(el, renderToDom(c, env, ns)); } } - return el; -})(); -})(); }; - - // reactive-text - var reactiveText = function(sig) { return (function() { - var node = createTextNode((String(deref(sig)))); - effect(function() { return domSetTextContent(node, (String(deref(sig)))); }); - return node; -})(); }; - - // reactive-attr - var reactiveAttr = function(el, attrName, computeFn) { (function() { - var existing = sxOr(domGetAttr(el, "data-sx-reactive-attrs"), ""); - var updated = (isSxTruthy(isEmpty(existing)) ? attrName : (String(existing) + String(",") + String(attrName))); - return domSetAttr(el, "data-sx-reactive-attrs", updated); -})(); -return effect(function() { return (function() { - var raw = computeFn(); - return (function() { - var val = (isSxTruthy(isSignal(raw)) ? deref(raw) : raw); - return (isSxTruthy(sxOr(isNil(val), (val == false))) ? domRemoveAttr(el, attrName) : (isSxTruthy((val == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(val))))); -})(); -})(); }); }; - - // reactive-fragment - var reactiveFragment = function(testFn, renderFn, env, ns) { return (function() { - var marker = createComment("island-fragment"); - var currentNodes = []; - effect(function() { { var _c = currentNodes; for (var _i = 0; _i < _c.length; _i++) { var n = _c[_i]; domRemove(n); } } -currentNodes = []; -return (isSxTruthy(testFn()) ? (function() { - var frag = renderFn(); - currentNodes = domChildNodes(frag); - return domInsertAfter(marker, frag); -})() : NIL); }); - return marker; -})(); }; - - // render-list-item - var renderListItem = function(mapFn, item, env, ns) { return (isSxTruthy(isLambda(mapFn)) ? renderLambdaDom(mapFn, [item], env, ns) : renderToDom(apply(mapFn, [item]), env, ns)); }; - - // extract-key - var extractKey = function(node, index) { return (function() { - var k = domGetAttr(node, "key"); - return (isSxTruthy(k) ? (domRemoveAttr(node, "key"), k) : (function() { - var dk = domGetData(node, "key"); - return (isSxTruthy(dk) ? (String(dk)) : (String("__idx_") + String(index))); -})()); -})(); }; - - // reactive-list - var reactiveList = function(mapFn, itemsSig, env, ns) { return (function() { - var container = createFragment(); - var marker = createComment("island-list"); - var keyMap = {}; - var keyOrder = []; - domAppend(container, marker); - effect(function() { return (function() { - var items = deref(itemsSig); - return (isSxTruthy(domParent(marker)) ? (function() { - var newMap = {}; - var newKeys = []; - var hasKeys = false; - forEachIndexed(function(idx, item) { return (function() { - var rendered = renderListItem(mapFn, item, env, ns); - var key = extractKey(rendered, idx); - if (isSxTruthy((isSxTruthy(!isSxTruthy(hasKeys)) && !isSxTruthy(startsWith(key, "__idx_"))))) { - hasKeys = true; -} - (isSxTruthy(dictHas(keyMap, key)) ? dictSet(newMap, key, dictGet(keyMap, key)) : dictSet(newMap, key, rendered)); - return append_b(newKeys, key); -})(); }, items); - (isSxTruthy(!isSxTruthy(hasKeys)) ? (domRemoveChildrenAfter(marker), (function() { - var frag = createFragment(); - { var _c = newKeys; for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; domAppend(frag, dictGet(newMap, k)); } } - return domInsertAfter(marker, frag); -})()) : (forEach(function(oldKey) { return (isSxTruthy(!isSxTruthy(dictHas(newMap, oldKey))) ? domRemove(dictGet(keyMap, oldKey)) : NIL); }, keyOrder), (function() { - var cursor = marker; - return forEach(function(k) { return (function() { - var node = dictGet(newMap, k); - var next = domNextSibling(cursor); - if (isSxTruthy(!isSxTruthy(isIdentical(node, next)))) { - domInsertAfter(cursor, node); -} - return (cursor = node); -})(); }, newKeys); -})())); - keyMap = newMap; - return (keyOrder = newKeys); -})() : forEachIndexed(function(idx, item) { return (function() { - var rendered = renderListItem(mapFn, item, env, ns); - var key = extractKey(rendered, idx); - keyMap[key] = rendered; - keyOrder.push(key); - return domAppend(container, rendered); -})(); }, items)); -})(); }); - return container; -})(); }; - - // bind-input - var bindInput = function(el, sig) { return (function() { - var inputType = lower(sxOr(domGetAttr(el, "type"), "")); - var isCheckbox = sxOr((inputType == "checkbox"), (inputType == "radio")); - (isSxTruthy(isCheckbox) ? domSetProp(el, "checked", deref(sig)) : domSetProp(el, "value", (String(deref(sig))))); - effect(function() { return (isSxTruthy(isCheckbox) ? domSetProp(el, "checked", deref(sig)) : (function() { - var v = (String(deref(sig))); - return (isSxTruthy((domGetProp(el, "value") != v)) ? domSetProp(el, "value", v) : NIL); -})()); }); - return domListen(el, (isSxTruthy(isCheckbox) ? "change" : "input"), function(e) { return (isSxTruthy(isCheckbox) ? reset_b(sig, domGetProp(el, "checked")) : reset_b(sig, domGetProp(el, "value"))); }); -})(); }; - - // render-dom-portal - var renderDomPortal = function(args, env, ns) { return (function() { - var selector = trampoline(evalExpr(first(args), env)); - var target = sxOr(domQuery(selector), domEnsureElement(selector)); - return (isSxTruthy(!isSxTruthy(target)) ? createComment((String("portal: ") + String(selector) + String(" (not found)"))) : (function() { - var marker = createComment((String("portal: ") + String(selector))); - var frag = createFragment(); - { var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } } - (function() { - var portalNodes = domChildNodes(frag); - domAppend(target, frag); - return registerInScope(function() { return forEach(function(n) { return domRemove(n); }, portalNodes); }); -})(); - return marker; -})()); -})(); }; - - // render-dom-error-boundary - var renderDomErrorBoundary = function(args, env, ns) { return (function() { - var fallbackExpr = first(args); - var bodyExprs = rest(args); - var container = domCreateElement("div", NIL); - var retryVersion = signal(0); - domSetAttr(container, "data-sx-boundary", "true"); - effect(function() { deref(retryVersion); -domSetProp(container, "innerHTML", ""); -return (function() { - var savedScope = _islandScope; - _islandScope = NIL; - return tryCatch(function() { (function() { - var frag = createFragment(); - { var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } } - return domAppend(container, frag); -})(); -return (_islandScope = savedScope); }, function(err) { _islandScope = savedScope; -return (function() { - var fallbackFn = trampoline(evalExpr(fallbackExpr, env)); - var retryFn = function() { return swap_b(retryVersion, function(n) { return (n + 1); }); }; - return (function() { - var fallbackDom = (isSxTruthy(isLambda(fallbackFn)) ? renderLambdaDom(fallbackFn, [err, retryFn], env, ns) : renderToDom(apply(fallbackFn, [err, retryFn]), env, ns)); - return domAppend(container, fallbackDom); -})(); -})(); }); -})(); }); - return container; -})(); }; - - - // === Transpiled from engine === - - // ENGINE_VERBS - var ENGINE_VERBS = ["get", "post", "put", "delete", "patch"]; - - // DEFAULT_SWAP - var DEFAULT_SWAP = "outerHTML"; - - // parse-time - var parseTime = function(s) { return (isSxTruthy(isNil(s)) ? 0 : (isSxTruthy(endsWith(s, "ms")) ? parseInt_(s, 0) : (isSxTruthy(endsWith(s, "s")) ? (parseInt_(replace_(s, "s", ""), 0) * 1000) : parseInt_(s, 0)))); }; - - // parse-trigger-spec - var parseTriggerSpec = function(spec) { return (isSxTruthy(isNil(spec)) ? NIL : (function() { - var rawParts = split(spec, ","); - return filter(function(x) { return !isSxTruthy(isNil(x)); }, map(function(part) { return (function() { - var tokens = split(trim(part), " "); - return (isSxTruthy(isEmpty(tokens)) ? NIL : (isSxTruthy((isSxTruthy((first(tokens) == "every")) && (len(tokens) >= 2))) ? {["event"]: "every", ["modifiers"]: {["interval"]: parseTime(nth(tokens, 1))}} : (function() { - var mods = {}; - { var _c = rest(tokens); for (var _i = 0; _i < _c.length; _i++) { var tok = _c[_i]; (isSxTruthy((tok == "once")) ? dictSet(mods, "once", true) : (isSxTruthy((tok == "changed")) ? dictSet(mods, "changed", true) : (isSxTruthy(startsWith(tok, "delay:")) ? dictSet(mods, "delay", parseTime(slice(tok, 6))) : (isSxTruthy(startsWith(tok, "from:")) ? dictSet(mods, "from", slice(tok, 5)) : NIL)))); } } - return {["event"]: first(tokens), ["modifiers"]: mods}; -})())); -})(); }, rawParts)); -})()); }; - - // default-trigger - var defaultTrigger = function(tagName) { return (isSxTruthy((tagName == "FORM")) ? [{["event"]: "submit", ["modifiers"]: {}}] : (isSxTruthy(sxOr((tagName == "INPUT"), (tagName == "SELECT"), (tagName == "TEXTAREA"))) ? [{["event"]: "change", ["modifiers"]: {}}] : [{["event"]: "click", ["modifiers"]: {}}])); }; - - // get-verb-info - var getVerbInfo = function(el) { return some(function(verb) { return (function() { - var url = domGetAttr(el, (String("sx-") + String(verb))); - return (isSxTruthy(url) ? {["method"]: upper(verb), ["url"]: url} : NIL); -})(); }, ENGINE_VERBS); }; - - // build-request-headers - var buildRequestHeaders = function(el, loadedComponents, cssHash) { return (function() { - var headers = {["SX-Request"]: "true", ["SX-Current-URL"]: browserLocationHref()}; - (function() { - var targetSel = domGetAttr(el, "sx-target"); - return (isSxTruthy(targetSel) ? dictSet(headers, "SX-Target", targetSel) : NIL); -})(); - if (isSxTruthy(!isSxTruthy(isEmpty(loadedComponents)))) { - headers["SX-Components"] = join(",", loadedComponents); -} - if (isSxTruthy(cssHash)) { - headers["SX-Css"] = cssHash; -} - (function() { - var extraH = domGetAttr(el, "sx-headers"); - return (isSxTruthy(extraH) ? (function() { - var parsed = parseHeaderValue(extraH); - return (isSxTruthy(parsed) ? forEach(function(key) { return dictSet(headers, key, (String(get(parsed, key)))); }, keys(parsed)) : NIL); -})() : NIL); -})(); - return headers; -})(); }; - - // process-response-headers - var processResponseHeaders = function(getHeader) { return {["redirect"]: getHeader("SX-Redirect"), ["refresh"]: getHeader("SX-Refresh"), ["trigger"]: getHeader("SX-Trigger"), ["retarget"]: getHeader("SX-Retarget"), ["reswap"]: getHeader("SX-Reswap"), ["location"]: getHeader("SX-Location"), ["replace-url"]: getHeader("SX-Replace-Url"), ["css-hash"]: getHeader("SX-Css-Hash"), ["trigger-swap"]: getHeader("SX-Trigger-After-Swap"), ["trigger-settle"]: getHeader("SX-Trigger-After-Settle"), ["content-type"]: getHeader("Content-Type"), ["cache-invalidate"]: getHeader("SX-Cache-Invalidate"), ["cache-update"]: getHeader("SX-Cache-Update")}; }; - - // parse-swap-spec - var parseSwapSpec = function(rawSwap, globalTransitions_p) { return (function() { - var parts = split(sxOr(rawSwap, DEFAULT_SWAP), " "); - var style = first(parts); - var useTransition = globalTransitions_p; - { var _c = rest(parts); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; (isSxTruthy((p == "transition:true")) ? (useTransition = true) : (isSxTruthy((p == "transition:false")) ? (useTransition = false) : NIL)); } } - return {["style"]: style, ["transition"]: useTransition}; -})(); }; - - // parse-retry-spec - var parseRetrySpec = function(retryAttr) { return (isSxTruthy(isNil(retryAttr)) ? NIL : (function() { - var parts = split(retryAttr, ":"); - return {["strategy"]: first(parts), ["start-ms"]: parseInt_(nth(parts, 1), 1000), ["cap-ms"]: parseInt_(nth(parts, 2), 30000)}; -})()); }; - - // next-retry-ms - var nextRetryMs = function(currentMs, capMs) { return min((currentMs * 2), capMs); }; - - // filter-params - var filterParams = function(paramsSpec, allParams) { return (isSxTruthy(isNil(paramsSpec)) ? allParams : (isSxTruthy((paramsSpec == "none")) ? [] : (isSxTruthy((paramsSpec == "*")) ? allParams : (isSxTruthy(startsWith(paramsSpec, "not ")) ? (function() { - var excluded = map(trim, split(slice(paramsSpec, 4), ",")); - return filter(function(p) { return !isSxTruthy(contains(excluded, first(p))); }, allParams); -})() : (function() { - var allowed = map(trim, split(paramsSpec, ",")); - return filter(function(p) { return contains(allowed, first(p)); }, allParams); -})())))); }; - - // resolve-target - var resolveTarget = function(el) { return (function() { - var sel = domGetAttr(el, "sx-target"); - return (isSxTruthy(sxOr(isNil(sel), (sel == "this"))) ? el : (isSxTruthy((sel == "closest")) ? domParent(el) : domQuery(sel))); -})(); }; - - // apply-optimistic - var applyOptimistic = function(el) { return (function() { - var directive = domGetAttr(el, "sx-optimistic"); - return (isSxTruthy(isNil(directive)) ? NIL : (function() { - var target = sxOr(resolveTarget(el), el); - var state = {["target"]: target, ["directive"]: directive}; - (isSxTruthy((directive == "remove")) ? (dictSet(state, "opacity", domGetStyle(target, "opacity")), domSetStyle(target, "opacity", "0"), domSetStyle(target, "pointer-events", "none")) : (isSxTruthy((directive == "disable")) ? (dictSet(state, "disabled", domGetProp(target, "disabled")), domSetProp(target, "disabled", true)) : (isSxTruthy(startsWith(directive, "add-class:")) ? (function() { - var cls = slice(directive, 10); - state["add-class"] = cls; - return domAddClass(target, cls); -})() : NIL))); - return state; -})()); -})(); }; - - // revert-optimistic - var revertOptimistic = function(state) { return (isSxTruthy(state) ? (function() { - var target = get(state, "target"); - var directive = get(state, "directive"); - return (isSxTruthy((directive == "remove")) ? (domSetStyle(target, "opacity", sxOr(get(state, "opacity"), "")), domSetStyle(target, "pointer-events", "")) : (isSxTruthy((directive == "disable")) ? domSetProp(target, "disabled", sxOr(get(state, "disabled"), false)) : (isSxTruthy(get(state, "add-class")) ? domRemoveClass(target, get(state, "add-class")) : NIL))); -})() : NIL); }; - - // find-oob-swaps - var findOobSwaps = function(container) { return (function() { - var results = []; - { var _c = ["sx-swap-oob", "hx-swap-oob"]; for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() { - var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]"))); - return forEach(function(oob) { return (function() { - var swapType = sxOr(domGetAttr(oob, attr), "outerHTML"); - var targetId = domId(oob); - domRemoveAttr(oob, attr); - return (isSxTruthy(targetId) ? append_b(results, {["element"]: oob, ["swap-type"]: swapType, ["target-id"]: targetId}) : NIL); -})(); }, oobEls); -})(); } } - return results; -})(); }; - - // morph-node - var morphNode = function(oldNode, newNode) { return (isSxTruthy(sxOr(domHasAttr(oldNode, "sx-preserve"), domHasAttr(oldNode, "sx-ignore"))) ? NIL : (isSxTruthy((isSxTruthy(domHasAttr(oldNode, "data-sx-island")) && isSxTruthy(isProcessed(oldNode, "island-hydrated")) && isSxTruthy(domHasAttr(newNode, "data-sx-island")) && (domGetAttr(oldNode, "data-sx-island") == domGetAttr(newNode, "data-sx-island")))) ? morphIslandChildren(oldNode, newNode) : (isSxTruthy(sxOr(!isSxTruthy((domNodeType(oldNode) == domNodeType(newNode))), !isSxTruthy((domNodeName(oldNode) == domNodeName(newNode))))) ? domReplaceChild(domParent(oldNode), domClone(newNode), oldNode) : (isSxTruthy(sxOr((domNodeType(oldNode) == 3), (domNodeType(oldNode) == 8))) ? (isSxTruthy(!isSxTruthy((domTextContent(oldNode) == domTextContent(newNode)))) ? domSetTextContent(oldNode, domTextContent(newNode)) : NIL) : (isSxTruthy((domNodeType(oldNode) == 1)) ? (syncAttrs(oldNode, newNode), (isSxTruthy(!isSxTruthy((isSxTruthy(domIsActiveElement(oldNode)) && domIsInputElement(oldNode)))) ? morphChildren(oldNode, newNode) : NIL)) : NIL))))); }; - - // sync-attrs - var syncAttrs = function(oldEl, newEl) { return (function() { - var raStr = sxOr(domGetAttr(oldEl, "data-sx-reactive-attrs"), ""); - var reactiveAttrs = (isSxTruthy(isEmpty(raStr)) ? [] : split(raStr, ",")); - { var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() { - var name = first(attr); - var val = nth(attr, 1); - return (isSxTruthy((isSxTruthy(!isSxTruthy((domGetAttr(oldEl, name) == val))) && !isSxTruthy(contains(reactiveAttrs, name)))) ? domSetAttr(oldEl, name, val) : NIL); -})(); } } - return forEach(function(attr) { return (function() { - var aname = first(attr); - return (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(newEl, aname))) && isSxTruthy(!isSxTruthy(contains(reactiveAttrs, aname))) && !isSxTruthy((aname == "data-sx-reactive-attrs")))) ? domRemoveAttr(oldEl, aname) : NIL); -})(); }, domAttrList(oldEl)); -})(); }; - - // morph-children - var morphChildren = function(oldParent, newParent) { return (function() { - var oldKids = domChildList(oldParent); - var newKids = domChildList(newParent); - var oldById = reduce(function(acc, kid) { return (function() { - var id = domId(kid); - return (isSxTruthy(id) ? (dictSet(acc, id, kid), acc) : acc); -})(); }, {}, oldKids); - var oi = 0; - { var _c = newKids; for (var _i = 0; _i < _c.length; _i++) { var newChild = _c[_i]; (function() { - var matchId = domId(newChild); - var matchById = (isSxTruthy(matchId) ? dictGet(oldById, matchId) : NIL); - return (isSxTruthy((isSxTruthy(matchById) && !isSxTruthy(isNil(matchById)))) ? ((isSxTruthy((isSxTruthy((oi < len(oldKids))) && !isSxTruthy((matchById == nth(oldKids, oi))))) ? domInsertBefore(oldParent, matchById, (isSxTruthy((oi < len(oldKids))) ? nth(oldKids, oi) : NIL)) : NIL), morphNode(matchById, newChild), (oi = (oi + 1))) : (isSxTruthy((oi < len(oldKids))) ? (function() { - var oldChild = nth(oldKids, oi); - return (isSxTruthy((isSxTruthy(domId(oldChild)) && !isSxTruthy(matchId))) ? domInsertBefore(oldParent, domClone(newChild), oldChild) : (morphNode(oldChild, newChild), (oi = (oi + 1)))); -})() : domAppend(oldParent, domClone(newChild)))); -})(); } } - return forEach(function(i) { return (isSxTruthy((i >= oi)) ? (function() { - var leftover = nth(oldKids, i); - return (isSxTruthy((isSxTruthy(domIsChildOf(leftover, oldParent)) && isSxTruthy(!isSxTruthy(domHasAttr(leftover, "sx-preserve"))) && !isSxTruthy(domHasAttr(leftover, "sx-ignore")))) ? domRemoveChild(oldParent, leftover) : NIL); -})() : NIL); }, range(oi, len(oldKids))); -})(); }; - - // morph-island-children - var morphIslandChildren = function(oldIsland, newIsland) { return (function() { - var oldLakes = domQueryAll(oldIsland, "[data-sx-lake]"); - var newLakes = domQueryAll(newIsland, "[data-sx-lake]"); - var oldMarshes = domQueryAll(oldIsland, "[data-sx-marsh]"); - var newMarshes = domQueryAll(newIsland, "[data-sx-marsh]"); - return (function() { - var newLakeMap = {}; - var newMarshMap = {}; - { var _c = newLakes; for (var _i = 0; _i < _c.length; _i++) { var lake = _c[_i]; (function() { - var id = domGetAttr(lake, "data-sx-lake"); - return (isSxTruthy(id) ? dictSet(newLakeMap, id, lake) : NIL); -})(); } } - { var _c = newMarshes; for (var _i = 0; _i < _c.length; _i++) { var marsh = _c[_i]; (function() { - var id = domGetAttr(marsh, "data-sx-marsh"); - return (isSxTruthy(id) ? dictSet(newMarshMap, id, marsh) : NIL); -})(); } } - { var _c = oldLakes; for (var _i = 0; _i < _c.length; _i++) { var oldLake = _c[_i]; (function() { - var id = domGetAttr(oldLake, "data-sx-lake"); - return (function() { - var newLake = dictGet(newLakeMap, id); - return (isSxTruthy(newLake) ? (syncAttrs(oldLake, newLake), morphChildren(oldLake, newLake)) : NIL); -})(); -})(); } } - { var _c = oldMarshes; for (var _i = 0; _i < _c.length; _i++) { var oldMarsh = _c[_i]; (function() { - var id = domGetAttr(oldMarsh, "data-sx-marsh"); - return (function() { - var newMarsh = dictGet(newMarshMap, id); - return (isSxTruthy(newMarsh) ? morphMarsh(oldMarsh, newMarsh, oldIsland) : NIL); -})(); -})(); } } - return processSignalUpdates(newIsland); -})(); -})(); }; - - // morph-marsh - var morphMarsh = function(oldMarsh, newMarsh, islandEl) { return (function() { - var transform = domGetData(oldMarsh, "sx-marsh-transform"); - var env = domGetData(oldMarsh, "sx-marsh-env"); - var newHtml = domInnerHtml(newMarsh); - return (isSxTruthy((isSxTruthy(env) && isSxTruthy(newHtml) && !isSxTruthy(isEmpty(newHtml)))) ? (function() { - var parsed = parse(newHtml); - return (function() { - var sxContent = (isSxTruthy(transform) ? invoke(transform, parsed) : parsed); - disposeMarshScope(oldMarsh); - return withMarshScope(oldMarsh, function() { return (function() { - var newDom = renderToDom(sxContent, env, NIL); - domRemoveChildrenAfter(oldMarsh, NIL); - return domAppend(oldMarsh, newDom); -})(); }); -})(); -})() : (syncAttrs(oldMarsh, newMarsh), morphChildren(oldMarsh, newMarsh))); -})(); }; - - // process-signal-updates - var processSignalUpdates = function(root) { return (function() { - var signalEls = domQueryAll(root, "[data-sx-signal]"); - return forEach(function(el) { return (function() { - var spec = domGetAttr(el, "data-sx-signal"); - return (isSxTruthy(spec) ? (function() { - var colonIdx = indexOf_(spec, ":"); - return (isSxTruthy((colonIdx > 0)) ? (function() { - var storeName = slice(spec, 0, colonIdx); - var rawValue = slice(spec, (colonIdx + 1)); - (function() { - var parsed = jsonParse(rawValue); - return reset_b(useStore(storeName), parsed); -})(); - return domRemoveAttr(el, "data-sx-signal"); -})() : NIL); -})() : NIL); -})(); }, signalEls); -})(); }; - - // swap-dom-nodes - var swapDomNodes = function(target, newNodes, strategy) { return (function() { var _m = strategy; if (_m == "innerHTML") return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() { - var wrapper = domCreateElement("div", NIL); - domAppend(wrapper, newNodes); - return morphChildren(target, wrapper); -})()); if (_m == "outerHTML") return (function() { - var parent = domParent(target); - (isSxTruthy(domIsFragment(newNodes)) ? (function() { - var fc = domFirstChild(newNodes); - return (isSxTruthy(fc) ? (morphNode(target, fc), (function() { - var sib = domNextSibling(fc); - return insertRemainingSiblings(parent, target, sib); -})()) : domRemoveChild(parent, target)); -})() : morphNode(target, newNodes)); - return parent; -})(); if (_m == "afterend") return domInsertAfter(target, newNodes); if (_m == "beforeend") return domAppend(target, newNodes); if (_m == "afterbegin") return domPrepend(target, newNodes); if (_m == "beforebegin") return domInsertBefore(domParent(target), newNodes, target); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() { - var wrapper = domCreateElement("div", NIL); - domAppend(wrapper, newNodes); - return morphChildren(target, wrapper); -})()); })(); }; - - // insert-remaining-siblings - var insertRemainingSiblings = function(parent, refNode, sib) { return (isSxTruthy(sib) ? (function() { - var next = domNextSibling(sib); - domInsertAfter(refNode, sib); - return insertRemainingSiblings(parent, sib, next); -})() : NIL); }; - - // swap-html-string - var swapHtmlString = function(target, html, strategy) { return (function() { var _m = strategy; if (_m == "innerHTML") return domSetInnerHtml(target, html); if (_m == "outerHTML") return (function() { - var parent = domParent(target); - domInsertAdjacentHtml(target, "afterend", html); - domRemoveChild(parent, target); - return parent; -})(); if (_m == "afterend") return domInsertAdjacentHtml(target, "afterend", html); if (_m == "beforeend") return domInsertAdjacentHtml(target, "beforeend", html); if (_m == "afterbegin") return domInsertAdjacentHtml(target, "afterbegin", html); if (_m == "beforebegin") return domInsertAdjacentHtml(target, "beforebegin", html); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return domSetInnerHtml(target, html); })(); }; - - // handle-history - var handleHistory = function(el, url, respHeaders) { return (function() { - var pushUrl = domGetAttr(el, "sx-push-url"); - var replaceUrl = domGetAttr(el, "sx-replace-url"); - var hdrReplace = get(respHeaders, "replace-url"); - return (isSxTruthy(hdrReplace) ? browserReplaceState(hdrReplace) : (isSxTruthy((isSxTruthy(pushUrl) && !isSxTruthy((pushUrl == "false")))) ? browserPushState((isSxTruthy((pushUrl == "true")) ? url : pushUrl)) : (isSxTruthy((isSxTruthy(replaceUrl) && !isSxTruthy((replaceUrl == "false")))) ? browserReplaceState((isSxTruthy((replaceUrl == "true")) ? url : replaceUrl)) : NIL))); -})(); }; - - // PRELOAD_TTL - var PRELOAD_TTL = 30000; - - // preload-cache-get - var preloadCacheGet = function(cache, url) { return (function() { - var entry = dictGet(cache, url); - return (isSxTruthy(isNil(entry)) ? NIL : (isSxTruthy(((nowMs() - get(entry, "timestamp")) > PRELOAD_TTL)) ? (dictDelete(cache, url), NIL) : (dictDelete(cache, url), entry))); -})(); }; - - // preload-cache-set - var preloadCacheSet = function(cache, url, text, contentType) { return dictSet(cache, url, {["text"]: text, ["content-type"]: contentType, ["timestamp"]: nowMs()}); }; - - // classify-trigger - var classifyTrigger = function(trigger) { return (function() { - var event = get(trigger, "event"); - return (isSxTruthy((event == "every")) ? "poll" : (isSxTruthy((event == "intersect")) ? "intersect" : (isSxTruthy((event == "load")) ? "load" : (isSxTruthy((event == "revealed")) ? "revealed" : "event")))); -})(); }; - - // should-boost-link? - var shouldBoostLink = function(link) { return (function() { - var href = domGetAttr(link, "href"); - return (isSxTruthy(href) && isSxTruthy(!isSxTruthy(startsWith(href, "#"))) && isSxTruthy(!isSxTruthy(startsWith(href, "javascript:"))) && isSxTruthy(!isSxTruthy(startsWith(href, "mailto:"))) && isSxTruthy(browserSameOrigin(href)) && isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-get"))) && isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-post"))) && !isSxTruthy(domHasAttr(link, "sx-disable"))); -})(); }; - - // should-boost-form? - var shouldBoostForm = function(form) { return (isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-get"))) && isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-post"))) && !isSxTruthy(domHasAttr(form, "sx-disable"))); }; - - // parse-sse-swap - var parseSseSwap = function(el) { return sxOr(domGetAttr(el, "sx-sse-swap"), "message"); }; - - - // === Transpiled from orchestration === - - // _preload-cache - var _preloadCache = {}; - - // _css-hash - var _cssHash = ""; - - // dispatch-trigger-events - var dispatchTriggerEvents = function(el, headerVal) { return (isSxTruthy(headerVal) ? (function() { - var parsed = tryParseJson(headerVal); - return (isSxTruthy(parsed) ? forEach(function(key) { return domDispatch(el, key, get(parsed, key)); }, keys(parsed)) : forEach(function(name) { return (function() { - var trimmed = trim(name); - return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? domDispatch(el, trimmed, {}) : NIL); -})(); }, split(headerVal, ","))); -})() : NIL); }; - - // init-css-tracking - var initCssTracking = function() { return (function() { - var meta = domQuery("meta[name=\"sx-css-classes\"]"); - return (isSxTruthy(meta) ? (function() { - var content = domGetAttr(meta, "content"); - return (isSxTruthy(content) ? (_cssHash = content) : NIL); -})() : NIL); -})(); }; - - // execute-request - var executeRequest = function(el, verbInfo, extraParams) { return (function() { - var info = sxOr(getVerbInfo(el), verbInfo); - return (isSxTruthy(isNil(info)) ? promiseResolve(NIL) : (function() { - var verb = get(info, "method"); - var url = get(info, "url"); - return (isSxTruthy((function() { - var media = domGetAttr(el, "sx-media"); - return (isSxTruthy(media) && !isSxTruthy(browserMediaMatches(media))); -})()) ? promiseResolve(NIL) : (isSxTruthy((function() { - var confirmMsg = domGetAttr(el, "sx-confirm"); - return (isSxTruthy(confirmMsg) && !isSxTruthy(browserConfirm(confirmMsg))); -})()) ? promiseResolve(NIL) : (function() { - var promptMsg = domGetAttr(el, "sx-prompt"); - var promptVal = (isSxTruthy(promptMsg) ? browserPrompt(promptMsg) : NIL); - return (isSxTruthy((isSxTruthy(promptMsg) && isNil(promptVal))) ? promiseResolve(NIL) : (isSxTruthy(!isSxTruthy(validateForRequest(el))) ? promiseResolve(NIL) : doFetch(el, verb, verb, url, (isSxTruthy(promptVal) ? assoc(sxOr(extraParams, {}), "SX-Prompt", promptVal) : extraParams)))); -})())); -})()); -})(); }; - - // do-fetch - var doFetch = function(el, verb, method, url, extraParams) { return (function() { - var sync = domGetAttr(el, "sx-sync"); - if (isSxTruthy((sync == "replace"))) { - abortPrevious(el); -} - (function() { - var targetEl = resolveTarget(el); - return (isSxTruthy((isSxTruthy(targetEl) && !isSxTruthy(isIdentical(el, targetEl)))) ? abortPreviousTarget(targetEl) : NIL); -})(); - return (function() { - var ctrl = newAbortController(); - trackController(el, ctrl); - (function() { - var targetEl = resolveTarget(el); - return (isSxTruthy(targetEl) ? trackControllerTarget(targetEl, ctrl) : NIL); -})(); - return (function() { - var bodyInfo = buildRequestBody(el, method, url); - var finalUrl = get(bodyInfo, "url"); - var body = get(bodyInfo, "body"); - var ct = get(bodyInfo, "content-type"); - var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash); - var csrf = csrfToken(); - if (isSxTruthy(extraParams)) { - { var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; headers[k] = get(extraParams, k); } } -} - if (isSxTruthy(ct)) { - headers["Content-Type"] = ct; -} - if (isSxTruthy(csrf)) { - headers["X-CSRFToken"] = csrf; -} - return (function() { - var cached = preloadCacheGet(_preloadCache, finalUrl); - var optimisticState = applyOptimistic(el); - var indicator = showIndicator(el); - var disabledElts = disableElements(el); - domAddClass(el, "sx-request"); - domSetAttr(el, "aria-busy", "true"); - domDispatch(el, "sx:beforeRequest", {["url"]: finalUrl, ["method"]: method}); - return fetchRequest({["url"]: finalUrl, ["method"]: method, ["headers"]: headers, ["body"]: body, ["signal"]: controllerSignal(ctrl), ["cross-origin"]: isCrossOrigin(finalUrl), ["preloaded"]: cached}, function(respOk, status, getHeader, text) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isSxTruthy(respOk)) ? (domDispatch(el, "sx:responseError", {["status"]: status, ["text"]: text}), (isSxTruthy((isSxTruthy(text) && (len(text) > 0))) ? handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text) : handleRetry(el, verb, method, finalUrl, extraParams))) : (domDispatch(el, "sx:afterRequest", {["status"]: status}), handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text)))); }, function(err) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isSxTruthy(isAbortError(err))) ? domDispatch(el, "sx:requestError", {["error"]: err}) : NIL)); }); -})(); -})(); -})(); -})(); }; - - // handle-fetch-success - var handleFetchSuccess = function(el, url, verb, extraParams, getHeader, text) { return (function() { - var respHeaders = processResponseHeaders(getHeader); - (function() { - var newHash = get(respHeaders, "css-hash"); - return (isSxTruthy(newHash) ? (_cssHash = newHash) : NIL); -})(); - dispatchTriggerEvents(el, get(respHeaders, "trigger")); - processCacheDirectives(el, respHeaders, text); - return (isSxTruthy(get(respHeaders, "redirect")) ? browserNavigate(get(respHeaders, "redirect")) : (isSxTruthy(get(respHeaders, "refresh")) ? browserReload() : (isSxTruthy(get(respHeaders, "location")) ? fetchLocation(get(respHeaders, "location")) : (function() { - var targetEl = (isSxTruthy(get(respHeaders, "retarget")) ? domQuery(get(respHeaders, "retarget")) : resolveTarget(el)); - var swapSpec = parseSwapSpec(sxOr(get(respHeaders, "reswap"), domGetAttr(el, "sx-swap")), domHasClass(domBody(), "sx-transitions")); - var swapStyle = get(swapSpec, "style"); - var useTransition = get(swapSpec, "transition"); - var ct = sxOr(get(respHeaders, "content-type"), ""); - (isSxTruthy(contains(ct, "text/sx")) ? handleSxResponse(el, targetEl, text, swapStyle, useTransition) : handleHtmlResponse(el, targetEl, text, swapStyle, useTransition)); - dispatchTriggerEvents(el, get(respHeaders, "trigger-swap")); - handleHistory(el, url, respHeaders); - setTimeout_(function() { if (isSxTruthy(get(respHeaders, "trigger-settle"))) { - dispatchTriggerEvents(el, get(respHeaders, "trigger-settle")); -} -return processSettleHooks(el); }, 20); - return domDispatch(el, "sx:afterSwap", {["target"]: targetEl, ["swap"]: swapStyle}); -})()))); -})(); }; - - // handle-sx-response - var handleSxResponse = function(el, target, text, swapStyle, useTransition) { return (function() { - var cleaned = stripComponentScripts(text); - return (function() { - var final_ = extractResponseCss(cleaned); - return (function() { - var trimmed = trim(final_); - return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? (function() { - var rendered = sxRender(trimmed); - var container = domCreateElement("div", NIL); - domAppend(container, rendered); - processOobSwaps(container, function(t, oob, s) { disposeIslandsIn(t); -swapDomNodes(t, oob, s); -sxHydrate(t); -return processElements(t); }); - return (function() { - var selectSel = domGetAttr(el, "sx-select"); - var content = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container)); - disposeIslandsIn(target); - return withTransition(useTransition, function() { swapDomNodes(target, content, swapStyle); -return postSwap(target); }); -})(); -})() : NIL); -})(); -})(); -})(); }; - - // handle-html-response - var handleHtmlResponse = function(el, target, text, swapStyle, useTransition) { return (function() { - var doc = domParseHtmlDocument(text); - return (isSxTruthy(doc) ? (function() { - var selectSel = domGetAttr(el, "sx-select"); - disposeIslandsIn(target); - return (isSxTruthy(selectSel) ? (function() { - var html = selectHtmlFromDoc(doc, selectSel); - return withTransition(useTransition, function() { swapHtmlString(target, html, swapStyle); -return postSwap(target); }); -})() : (function() { - var container = domCreateElement("div", NIL); - domSetInnerHtml(container, domBodyInnerHtml(doc)); - processOobSwaps(container, function(t, oob, s) { disposeIslandsIn(t); -swapDomNodes(t, oob, s); -return postSwap(t); }); - hoistHeadElements(container); - return withTransition(useTransition, function() { swapDomNodes(target, childrenToFragment(container), swapStyle); -return postSwap(target); }); -})()); -})() : NIL); -})(); }; - - // handle-retry - var handleRetry = function(el, verb, method, url, extraParams) { return (function() { - var retryAttr = domGetAttr(el, "sx-retry"); - var spec = parseRetrySpec(retryAttr); - return (isSxTruthy(spec) ? (function() { - var currentMs = sxOr(domGetAttr(el, "data-sx-retry-ms"), get(spec, "start-ms")); - return (function() { - var ms = parseInt_(currentMs, get(spec, "start-ms")); - domSetAttr(el, "data-sx-retry-ms", (String(nextRetryMs(ms, get(spec, "cap-ms"))))); - return setTimeout_(function() { return doFetch(el, verb, method, url, extraParams); }, ms); -})(); -})() : NIL); -})(); }; - - // bind-triggers - var bindTriggers = function(el, verbInfo) { return (function() { - var triggers = sxOr(parseTriggerSpec(domGetAttr(el, "sx-trigger")), defaultTrigger(domTagName(el))); - return forEach(function(trigger) { return (function() { - var kind = classifyTrigger(trigger); - var mods = get(trigger, "modifiers"); - return (isSxTruthy((kind == "poll")) ? setInterval_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "interval")) : (isSxTruthy((kind == "intersect")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, false, get(mods, "delay")) : (isSxTruthy((kind == "load")) ? setTimeout_(function() { return executeRequest(el, NIL, NIL); }, sxOr(get(mods, "delay"), 0)) : (isSxTruthy((kind == "revealed")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, true, get(mods, "delay")) : (isSxTruthy((kind == "event")) ? bindEvent(el, get(trigger, "event"), mods, verbInfo) : NIL))))); -})(); }, triggers); -})(); }; - - // bind-event - var bindEvent = function(el, eventName, mods, verbInfo) { return (function() { - var timer = NIL; - var lastVal = NIL; - var listenTarget = (isSxTruthy(get(mods, "from")) ? domQuery(get(mods, "from")) : el); - return (isSxTruthy(listenTarget) ? domAddListener(listenTarget, eventName, function(e) { return (function() { - var shouldFire = true; - if (isSxTruthy(get(mods, "changed"))) { - (function() { - var val = elementValue(el); - return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val)); -})(); -} - return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() { - var liveInfo = sxOr(getVerbInfo(el), verbInfo); - var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(liveInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay"))); - var clientRouted = false; - if (isSxTruthy(isGetLink)) { - clientRouted = tryClientRoute(urlPathname(get(liveInfo, "url")), domGetAttr(el, "sx-target")); -} - return (isSxTruthy(clientRouted) ? (browserPushState(get(liveInfo, "url")), browserScrollTo(0, 0)) : ((isSxTruthy(isGetLink) ? logInfo((String("sx:route server fetch ") + String(get(liveInfo, "url")))) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "delay")))) : executeRequest(el, NIL, NIL)))); -})()) : NIL); -})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL); -})(); }; - - // post-swap - var postSwap = function(root) { activateScripts(root); -sxProcessScripts(root); -sxHydrate(root); -sxHydrateIslands(root); -return processElements(root); }; - - // process-settle-hooks - var processSettleHooks = function(el) { return (function() { - var settleExpr = domGetAttr(el, "sx-on-settle"); - return (isSxTruthy((isSxTruthy(settleExpr) && !isSxTruthy(isEmpty(settleExpr)))) ? (function() { - var exprs = sxParse(settleExpr); - return forEach(function(expr) { return evalExpr(expr, envExtend({})); }, exprs); -})() : NIL); -})(); }; - - // activate-scripts - var activateScripts = function(root) { return (isSxTruthy(root) ? (function() { - var scripts = domQueryAll(root, "script"); - return forEach(function(dead) { return (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(dead, "data-components"))) && !isSxTruthy(domHasAttr(dead, "data-sx-activated")))) ? (function() { - var live = createScriptClone(dead); - domSetAttr(live, "data-sx-activated", "true"); - return domReplaceChild(domParent(dead), live, dead); -})() : NIL); }, scripts); -})() : NIL); }; - - // process-oob-swaps - var processOobSwaps = function(container, swapFn) { return (function() { - var oobs = findOobSwaps(container); - return forEach(function(oob) { return (function() { - var targetId = get(oob, "target-id"); - var target = domQueryById(targetId); - var oobEl = get(oob, "element"); - var swapType = get(oob, "swap-type"); - if (isSxTruthy(domParent(oobEl))) { - domRemoveChild(domParent(oobEl), oobEl); -} - return (isSxTruthy(target) ? swapFn(target, oobEl, swapType) : NIL); -})(); }, oobs); -})(); }; - - // hoist-head-elements - var hoistHeadElements = function(container) { { var _c = domQueryAll(container, "style[data-sx-css]"); for (var _i = 0; _i < _c.length; _i++) { var style = _c[_i]; if (isSxTruthy(domParent(style))) { - domRemoveChild(domParent(style), style); -} -domAppendToHead(style); } } -return forEach(function(link) { if (isSxTruthy(domParent(link))) { - domRemoveChild(domParent(link), link); -} -return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"]")); }; - - // process-boosted - var processBoosted = function(root) { return forEach(function(container) { return boostDescendants(container); }, domQueryAll(sxOr(root, domBody()), "[sx-boost]")); }; - - // boost-descendants - var boostDescendants = function(container) { return (function() { - var boostTarget = domGetAttr(container, "sx-boost"); - { var _c = domQueryAll(container, "a[href]"); for (var _i = 0; _i < _c.length; _i++) { var link = _c[_i]; if (isSxTruthy((isSxTruthy(!isSxTruthy(isProcessed(link, "boost"))) && shouldBoostLink(link)))) { - markProcessed(link, "boost"); - if (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-target"))) && isSxTruthy(boostTarget) && !isSxTruthy((boostTarget == "true"))))) { - domSetAttr(link, "sx-target", boostTarget); -} - if (isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-swap")))) { - domSetAttr(link, "sx-swap", "innerHTML"); -} - if (isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-push-url")))) { - domSetAttr(link, "sx-push-url", "true"); -} - bindClientRouteLink(link, domGetAttr(link, "href")); -} } } - return forEach(function(form) { return (isSxTruthy((isSxTruthy(!isSxTruthy(isProcessed(form, "boost"))) && shouldBoostForm(form))) ? (markProcessed(form, "boost"), (function() { - var method = upper(sxOr(domGetAttr(form, "method"), "GET")); - var action = sxOr(domGetAttr(form, "action"), browserLocationHref()); - if (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-target"))) && isSxTruthy(boostTarget) && !isSxTruthy((boostTarget == "true"))))) { - domSetAttr(form, "sx-target", boostTarget); -} - if (isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-swap")))) { - domSetAttr(form, "sx-swap", "innerHTML"); -} - return bindBoostForm(form, method, action); -})()) : NIL); }, domQueryAll(container, "form")); -})(); }; - - // _page-data-cache - var _pageDataCache = {}; - - // _page-data-cache-ttl - var _pageDataCacheTtl = 30000; - - // page-data-cache-key - var pageDataCacheKey = function(pageName, params) { return (function() { - var base = pageName; - return (isSxTruthy(sxOr(isNil(params), isEmpty(keys(params)))) ? base : (function() { - var parts = []; - { var _c = keys(params); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; parts.push((String(k) + String("=") + String(get(params, k)))); } } - return (String(base) + String(":") + String(join("&", parts))); -})()); -})(); }; - - // page-data-cache-get - var pageDataCacheGet = function(cacheKey) { return (function() { - var entry = get(_pageDataCache, cacheKey); - return (isSxTruthy(isNil(entry)) ? NIL : (isSxTruthy(((nowMs() - get(entry, "ts")) > _pageDataCacheTtl)) ? (dictSet(_pageDataCache, cacheKey, NIL), NIL) : get(entry, "data"))); -})(); }; - - // page-data-cache-set - var pageDataCacheSet = function(cacheKey, data) { return dictSet(_pageDataCache, cacheKey, {"data": data, "ts": nowMs()}); }; - - // invalidate-page-cache - var invalidatePageCache = function(pageName) { { var _c = keys(_pageDataCache); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; if (isSxTruthy(sxOr((k == pageName), startsWith(k, (String(pageName) + String(":")))))) { - _pageDataCache[k] = NIL; -} } } -swPostMessage({"type": "invalidate", "page": pageName}); -return logInfo((String("sx:cache invalidate ") + String(pageName))); }; - - // invalidate-all-page-cache - var invalidateAllPageCache = function() { _pageDataCache = {}; -swPostMessage({"type": "invalidate", "page": "*"}); -return logInfo("sx:cache invalidate *"); }; - - // update-page-cache - var updatePageCache = function(pageName, data) { return (function() { - var cacheKey = pageDataCacheKey(pageName, {}); - pageDataCacheSet(cacheKey, data); - return logInfo((String("sx:cache update ") + String(pageName))); -})(); }; - - // process-cache-directives - var processCacheDirectives = function(el, respHeaders, responseText) { (function() { - var elInvalidate = domGetAttr(el, "sx-cache-invalidate"); - return (isSxTruthy(elInvalidate) ? (isSxTruthy((elInvalidate == "*")) ? invalidateAllPageCache() : invalidatePageCache(elInvalidate)) : NIL); -})(); -(function() { - var hdrInvalidate = get(respHeaders, "cache-invalidate"); - return (isSxTruthy(hdrInvalidate) ? (isSxTruthy((hdrInvalidate == "*")) ? invalidateAllPageCache() : invalidatePageCache(hdrInvalidate)) : NIL); -})(); -return (function() { - var hdrUpdate = get(respHeaders, "cache-update"); - return (isSxTruthy(hdrUpdate) ? (function() { - var data = parseSxData(responseText); - return (isSxTruthy(data) ? updatePageCache(hdrUpdate, data) : NIL); -})() : NIL); -})(); }; - - // _optimistic-snapshots - var _optimisticSnapshots = {}; - - // optimistic-cache-update - var optimisticCacheUpdate = function(cacheKey, mutator) { return (function() { - var cached = pageDataCacheGet(cacheKey); - return (isSxTruthy(cached) ? (function() { - var predicted = mutator(cached); - _optimisticSnapshots[cacheKey] = cached; - pageDataCacheSet(cacheKey, predicted); - return predicted; -})() : NIL); -})(); }; - - // optimistic-cache-revert - var optimisticCacheRevert = function(cacheKey) { return (function() { - var snapshot = get(_optimisticSnapshots, cacheKey); - return (isSxTruthy(snapshot) ? (pageDataCacheSet(cacheKey, snapshot), dictDelete(_optimisticSnapshots, cacheKey), snapshot) : NIL); -})(); }; - - // optimistic-cache-confirm - var optimisticCacheConfirm = function(cacheKey) { return dictDelete(_optimisticSnapshots, cacheKey); }; - - // submit-mutation - var submitMutation = function(pageName, params, actionName, payload, mutatorFn, onComplete) { return (function() { - var cacheKey = pageDataCacheKey(pageName, params); - var predicted = optimisticCacheUpdate(cacheKey, mutatorFn); - if (isSxTruthy(predicted)) { - tryRerenderPage(pageName, params, predicted); -} - return executeAction(actionName, payload, function(result) { if (isSxTruthy(result)) { - pageDataCacheSet(cacheKey, result); -} -optimisticCacheConfirm(cacheKey); -if (isSxTruthy(result)) { - tryRerenderPage(pageName, params, result); -} -logInfo((String("sx:optimistic confirmed ") + String(pageName))); -return (isSxTruthy(onComplete) ? onComplete("confirmed") : NIL); }, function(error) { return (function() { - var reverted = optimisticCacheRevert(cacheKey); - if (isSxTruthy(reverted)) { - tryRerenderPage(pageName, params, reverted); -} - logWarn((String("sx:optimistic reverted ") + String(pageName) + String(": ") + String(error))); - return (isSxTruthy(onComplete) ? onComplete("reverted") : NIL); -})(); }); -})(); }; - - // _is-online - var _isOnline = true; - - // _offline-queue - var _offlineQueue = []; - - // offline-is-online? - var offlineIsOnline_p = function() { return _isOnline; }; - - // offline-set-online! - var offlineSetOnline_b = function(val) { return (_isOnline = val); }; - - // offline-queue-mutation - var offlineQueueMutation = function(actionName, payload, pageName, params, mutatorFn) { return (function() { - var cacheKey = pageDataCacheKey(pageName, params); - var entry = {["action"]: actionName, ["payload"]: payload, ["page"]: pageName, ["params"]: params, ["timestamp"]: nowMs(), ["status"]: "pending"}; - _offlineQueue.push(entry); - (function() { - var predicted = optimisticCacheUpdate(cacheKey, mutatorFn); - return (isSxTruthy(predicted) ? tryRerenderPage(pageName, params, predicted) : NIL); -})(); - logInfo((String("sx:offline queued ") + String(actionName) + String(" (") + String(len(_offlineQueue)) + String(" pending)"))); - return entry; -})(); }; - - // offline-sync - var offlineSync = function() { return (function() { - var pending = filter(function(e) { return (get(e, "status") == "pending"); }, _offlineQueue); - return (isSxTruthy(!isSxTruthy(isEmpty(pending))) ? (logInfo((String("sx:offline syncing ") + String(len(pending)) + String(" mutations"))), forEach(function(entry) { return executeAction(get(entry, "action"), get(entry, "payload"), function(result) { entry["status"] = "synced"; -return logInfo((String("sx:offline synced ") + String(get(entry, "action")))); }, function(error) { entry["status"] = "failed"; -return logWarn((String("sx:offline sync failed ") + String(get(entry, "action")) + String(": ") + String(error))); }); }, pending)) : NIL); -})(); }; - - // offline-pending-count - var offlinePendingCount = function() { return len(filter(function(e) { return (get(e, "status") == "pending"); }, _offlineQueue)); }; - - // offline-aware-mutation - var offlineAwareMutation = function(pageName, params, actionName, payload, mutatorFn, onComplete) { return (isSxTruthy(_isOnline) ? submitMutation(pageName, params, actionName, payload, mutatorFn, onComplete) : (offlineQueueMutation(actionName, payload, pageName, params, mutatorFn), (isSxTruthy(onComplete) ? onComplete("queued") : NIL))); }; - - // current-page-layout - var currentPageLayout = function() { return (function() { - var pathname = urlPathname(browserLocationHref()); - var match = findMatchingRoute(pathname, _pageRoutes); - return (isSxTruthy(isNil(match)) ? "" : sxOr(get(match, "layout"), "")); -})(); }; - - // swap-rendered-content - var swapRenderedContent = function(target, rendered, pathname) { return (disposeIslandsIn(target), domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); }; - - // resolve-route-target - var resolveRouteTarget = function(targetSel) { return (isSxTruthy((isSxTruthy(targetSel) && !isSxTruthy((targetSel == "true")))) ? domQuery(targetSel) : NIL); }; - - // deps-satisfied? - var depsSatisfied_p = function(match) { return (function() { - var deps = get(match, "deps"); - var loaded = loadedComponentNames(); - return (isSxTruthy(sxOr(isNil(deps), isEmpty(deps))) ? true : isEvery(function(dep) { return contains(loaded, dep); }, deps)); -})(); }; - - // try-client-route - var tryClientRoute = function(pathname, targetSel) { return (function() { - var match = findMatchingRoute(pathname, _pageRoutes); - return (isSxTruthy(isNil(match)) ? (logInfo((String("sx:route no match (") + String(len(_pageRoutes)) + String(" routes) ") + String(pathname))), false) : (function() { - var targetLayout = sxOr(get(match, "layout"), ""); - var curLayout = currentPageLayout(); - return (isSxTruthy(!isSxTruthy((targetLayout == curLayout))) ? (logInfo((String("sx:route server (layout: ") + String(curLayout) + String(" -> ") + String(targetLayout) + String(") ") + String(pathname))), false) : (function() { - var contentSrc = get(match, "content"); - var closure = sxOr(get(match, "closure"), {}); - var params = get(match, "params"); - var pageName = get(match, "name"); - return (isSxTruthy(sxOr(isNil(contentSrc), isEmpty(contentSrc))) ? (logWarn((String("sx:route no content for ") + String(pathname))), false) : (function() { - var target = resolveRouteTarget(targetSel); - return (isSxTruthy(isNil(target)) ? (logWarn((String("sx:route target not found: ") + String(targetSel))), false) : (isSxTruthy(!isSxTruthy(depsSatisfied_p(match))) ? (logInfo((String("sx:route deps miss for ") + String(pageName))), false) : (function() { - var ioDeps = get(match, "io-deps"); - var hasIo = (isSxTruthy(ioDeps) && !isSxTruthy(isEmpty(ioDeps))); - var renderPlan = get(match, "render-plan"); - if (isSxTruthy(renderPlan)) { - (function() { - var srv = sxOr(get(renderPlan, "server"), []); - var cli = sxOr(get(renderPlan, "client"), []); - return logInfo((String("sx:route plan ") + String(pageName) + String(" — ") + String(len(srv)) + String(" server, ") + String(len(cli)) + String(" client"))); -})(); -} - if (isSxTruthy(hasIo)) { - registerIoDeps(ioDeps); -} - return (isSxTruthy(get(match, "stream")) ? (logInfo((String("sx:route streaming ") + String(pathname))), fetchStreaming(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash)), true) : (isSxTruthy(get(match, "has-data")) ? (function() { - var cacheKey = pageDataCacheKey(pageName, params); - var cached = pageDataCacheGet(cacheKey); - return (isSxTruthy(cached) ? (function() { - var env = merge(closure, params, cached); - return (isSxTruthy(hasIo) ? (logInfo((String("sx:route client+cache+async ") + String(pathname))), tryAsyncEvalContent(contentSrc, env, function(rendered) { return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route cache+async eval failed for ") + String(pathname) + String(" — server fallback"))), fetchAndRestore(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash), 0)) : swapRenderedContent(target, rendered, pathname)); }), true) : (function() { - var rendered = tryEvalContent(contentSrc, env); - return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route cached eval failed for ") + String(pathname))), false) : (logInfo((String("sx:route client+cache ") + String(pathname))), swapRenderedContent(target, rendered, pathname), true)); -})()); -})() : (logInfo((String("sx:route client+data ") + String(pathname))), resolvePageData(pageName, params, function(data) { pageDataCacheSet(cacheKey, data); -return (function() { - var env = merge(closure, params, data); - return (isSxTruthy(hasIo) ? tryAsyncEvalContent(contentSrc, env, function(rendered) { return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route data+async eval failed for ") + String(pathname) + String(" — server fallback"))), fetchAndRestore(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash), 0)) : swapRenderedContent(target, rendered, pathname)); }) : (function() { - var rendered = tryEvalContent(contentSrc, env); - return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route data eval failed for ") + String(pathname) + String(" — server fallback"))), fetchAndRestore(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash), 0)) : swapRenderedContent(target, rendered, pathname)); -})()); -})(); }), true)); -})() : (isSxTruthy(hasIo) ? (logInfo((String("sx:route client+async ") + String(pathname))), tryAsyncEvalContent(contentSrc, merge(closure, params), function(rendered) { return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route async eval failed for ") + String(pathname) + String(" — server fallback"))), fetchAndRestore(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash), 0)) : swapRenderedContent(target, rendered, pathname)); }), true) : (function() { - var env = merge(closure, params); - var rendered = tryEvalContent(contentSrc, env); - return (isSxTruthy(isNil(rendered)) ? (logInfo((String("sx:route server (eval failed) ") + String(pathname))), false) : (swapRenderedContent(target, rendered, pathname), true)); -})()))); -})())); -})()); -})()); -})()); -})(); }; - - // bind-client-route-link - var bindClientRouteLink = function(link, href) { return bindClientRouteClick(link, href, function() { return bindBoostLink(link, href); }); }; - - // process-sse - var processSse = function(root) { return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "sse"))) ? (markProcessed(el, "sse"), bindSse(el)) : NIL); }, domQueryAll(sxOr(root, domBody()), "[sx-sse]")); }; - - // bind-sse - var bindSse = function(el) { return (function() { - var url = domGetAttr(el, "sx-sse"); - return (isSxTruthy(url) ? (function() { - var source = eventSourceConnect(url, el); - var eventName = parseSseSwap(el); - return eventSourceListen(source, eventName, function(data) { return bindSseSwap(el, data); }); -})() : NIL); -})(); }; - - // bind-sse-swap - var bindSseSwap = function(el, data) { return (function() { - var target = resolveTarget(el); - var swapSpec = parseSwapSpec(domGetAttr(el, "sx-swap"), domHasClass(domBody(), "sx-transitions")); - var swapStyle = get(swapSpec, "style"); - var useTransition = get(swapSpec, "transition"); - var trimmed = trim(data); - return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? (disposeIslandsIn(target), (isSxTruthy(startsWith(trimmed, "(")) ? (function() { - var rendered = sxRender(trimmed); - var container = domCreateElement("div", NIL); - domAppend(container, rendered); - return withTransition(useTransition, function() { swapDomNodes(target, childrenToFragment(container), swapStyle); -return postSwap(target); }); -})() : withTransition(useTransition, function() { swapHtmlString(target, trimmed, swapStyle); -return postSwap(target); }))) : NIL); -})(); }; - - // bind-inline-handlers - var bindInlineHandlers = function(root) { return forEach(function(el) { return forEach(function(attr) { return (function() { - var name = first(attr); - var body = nth(attr, 1); - return (isSxTruthy(startsWith(name, "sx-on:")) ? (function() { - var eventName = slice(name, 6); - return (isSxTruthy(!isSxTruthy(isProcessed(el, (String("on:") + String(eventName))))) ? (markProcessed(el, (String("on:") + String(eventName))), (function() { - var exprs = sxParse(body); - return domListen(el, eventName, function(e) { return (function() { - var handlerEnv = envExtend({}); - envSet(handlerEnv, "event", e); - envSet(handlerEnv, "this", el); - envSet(handlerEnv, "detail", eventDetail(e)); - return forEach(function(expr) { return evalExpr(expr, handlerEnv); }, exprs); -})(); }); -})()) : NIL); -})() : NIL); -})(); }, domAttrList(el)); }, domQueryAll(sxOr(root, domBody()), "[sx-on\\:]")); }; - - // bind-preload-for - var bindPreloadFor = function(el) { return (function() { - var preloadAttr = domGetAttr(el, "sx-preload"); - return (isSxTruthy(preloadAttr) ? (function() { - var events = (isSxTruthy((preloadAttr == "mousedown")) ? ["mousedown", "touchstart"] : ["mouseover"]); - var debounceMs = (isSxTruthy((preloadAttr == "mousedown")) ? 0 : 100); - return bindPreload(el, events, debounceMs, function() { return (function() { - var info = getVerbInfo(el); - return (isSxTruthy(info) ? doPreload(get(info, "url"), buildRequestHeaders(el, loadedComponentNames(), _cssHash)) : NIL); -})(); }); -})() : NIL); -})(); }; - - // do-preload - var doPreload = function(url, headers) { return (isSxTruthy(isNil(preloadCacheGet(_preloadCache, url))) ? fetchPreload(url, headers, _preloadCache) : NIL); }; - - // VERB_SELECTOR - var VERB_SELECTOR = (String("[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]")); - - // process-elements - var processElements = function(root) { (function() { - var els = domQueryAll(sxOr(root, domBody()), VERB_SELECTOR); - return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "verb"))) ? (markProcessed(el, "verb"), processOne(el)) : NIL); }, els); -})(); -processBoosted(root); -processSse(root); -bindInlineHandlers(root); -return processEmitElements(root); }; - - // process-one - var processOne = function(el) { return (function() { - var verbInfo = getVerbInfo(el); - return (isSxTruthy(verbInfo) ? (isSxTruthy(!isSxTruthy(domHasAttr(el, "sx-disable"))) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : NIL) : NIL); -})(); }; - - // process-emit-elements - var processEmitElements = function(root) { return (function() { - var els = domQueryAll(sxOr(root, domBody()), "[data-sx-emit]"); - return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "emit"))) ? (markProcessed(el, "emit"), (function() { - var eventName = domGetAttr(el, "data-sx-emit"); - return (isSxTruthy(eventName) ? domListen(el, "click", function(e) { return (function() { - var detailJson = domGetAttr(el, "data-sx-emit-detail"); - var detail = (isSxTruthy(detailJson) ? jsonParse(detailJson) : {}); - return domDispatch(el, eventName, detail); -})(); }) : NIL); -})()) : NIL); }, els); -})(); }; - - // handle-popstate - var handlePopstate = function(scrollY) { return (function() { - var url = browserLocationHref(); - var boostEl = domQuery("[sx-boost]"); - var targetSel = (isSxTruthy(boostEl) ? (function() { - var attr = domGetAttr(boostEl, "sx-boost"); - return (isSxTruthy((isSxTruthy(attr) && !isSxTruthy((attr == "true")))) ? attr : NIL); -})() : NIL); - var targetSel = sxOr(targetSel, "#main-panel"); - var target = domQuery(targetSel); - var pathname = urlPathname(url); - return (isSxTruthy(target) ? (isSxTruthy(tryClientRoute(pathname, targetSel)) ? browserScrollTo(0, scrollY) : (function() { - var headers = buildRequestHeaders(target, loadedComponentNames(), _cssHash); - return fetchAndRestore(target, url, headers, scrollY); -})()) : NIL); -})(); }; - - // engine-init - var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); }; - - - // === Transpiled from boot === - - // HEAD_HOIST_SELECTOR - var HEAD_HOIST_SELECTOR = "meta, title, link[rel='canonical'], script[type='application/ld+json']"; - - // hoist-head-elements-full - var hoistHeadElementsFull = function(root) { return (function() { - var els = domQueryAll(root, HEAD_HOIST_SELECTOR); - return forEach(function(el) { return (function() { - var tag = lower(domTagName(el)); - return (isSxTruthy((tag == "title")) ? (setDocumentTitle(domTextContent(el)), domRemoveChild(domParent(el), el)) : (isSxTruthy((tag == "meta")) ? ((function() { - var name = domGetAttr(el, "name"); - var prop = domGetAttr(el, "property"); - if (isSxTruthy(name)) { - removeHeadElement((String("meta[name=\"") + String(name) + String("\"]"))); -} - return (isSxTruthy(prop) ? removeHeadElement((String("meta[property=\"") + String(prop) + String("\"]"))) : NIL); -})(), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (isSxTruthy((isSxTruthy((tag == "link")) && (domGetAttr(el, "rel") == "canonical"))) ? (removeHeadElement("link[rel=\"canonical\"]"), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (domRemoveChild(domParent(el), el), domAppendToHead(el))))); -})(); }, els); -})(); }; - - // sx-mount - var sxMount = function(target, source, extraEnv) { return (function() { - var el = resolveMountTarget(target); - return (isSxTruthy(el) ? (function() { - var node = sxRenderWithEnv(source, extraEnv); - domSetTextContent(el, ""); - domAppend(el, node); - hoistHeadElementsFull(el); - processElements(el); - sxHydrateElements(el); - return sxHydrateIslands(el); -})() : NIL); -})(); }; - - // resolve-suspense - var resolveSuspense = function(id, sx) { processSxScripts(NIL); -return (function() { - var el = domQuery((String("[data-suspense=\"") + String(id) + String("\"]"))); - return (isSxTruthy(el) ? (function() { - var exprs = parse(sx); - var env = getRenderEnv(NIL); - domSetTextContent(el, ""); - { var _c = exprs; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; domAppend(el, renderToDom(expr, env, NIL)); } } - processElements(el); - sxHydrateElements(el); - sxHydrateIslands(el); - return domDispatch(el, "sx:resolved", {"id": id}); -})() : logWarn((String("resolveSuspense: no element for id=") + String(id)))); -})(); }; - - // sx-hydrate-elements - var sxHydrateElements = function(root) { return (function() { - var els = domQueryAll(sxOr(root, domBody()), "[data-sx]"); - return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "hydrated"))) ? (markProcessed(el, "hydrated"), sxUpdateElement(el, NIL)) : NIL); }, els); -})(); }; - - // sx-update-element - var sxUpdateElement = function(el, newEnv) { return (function() { - var target = resolveMountTarget(el); - return (isSxTruthy(target) ? (function() { - var source = domGetAttr(target, "data-sx"); - return (isSxTruthy(source) ? (function() { - var baseEnv = parseEnvAttr(target); - var env = mergeEnvs(baseEnv, newEnv); - return (function() { - var node = sxRenderWithEnv(source, env); - domSetTextContent(target, ""); - domAppend(target, node); - return (isSxTruthy(newEnv) ? storeEnvAttr(target, baseEnv, newEnv) : NIL); -})(); -})() : NIL); -})() : NIL); -})(); }; - - // sx-render-component - var sxRenderComponent = function(name, kwargs, extraEnv) { return (function() { - var fullName = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); - return (function() { - var env = getRenderEnv(extraEnv); - var comp = envGet(env, fullName); - return (isSxTruthy(!isSxTruthy(isComponent(comp))) ? error((String("Unknown component: ") + String(fullName))) : (function() { - var callExpr = [makeSymbol(fullName)]; - { var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; callExpr.push(makeKeyword(toKebab(k))); -callExpr.push(dictGet(kwargs, k)); } } - return renderToDom(callExpr, env, NIL); -})()); -})(); -})(); }; - - // process-sx-scripts - var processSxScripts = function(root) { return (function() { - var scripts = querySxScripts(root); - return forEach(function(s) { return (isSxTruthy(!isSxTruthy(isProcessed(s, "script"))) ? (markProcessed(s, "script"), (function() { - var text = domTextContent(s); - return (isSxTruthy(domHasAttr(s, "data-components")) ? processComponentScript(s, text) : (isSxTruthy(sxOr(isNil(text), isEmpty(trim(text)))) ? NIL : (isSxTruthy(domHasAttr(s, "data-init")) ? (function() { - var exprs = sxParse(text); - return forEach(function(expr) { return evalExpr(expr, envExtend({})); }, exprs); -})() : (isSxTruthy(domHasAttr(s, "data-mount")) ? (function() { - var mountSel = domGetAttr(s, "data-mount"); - var target = domQuery(mountSel); - return (isSxTruthy(target) ? sxMount(target, text, NIL) : NIL); -})() : sxLoadComponents(text))))); -})()) : NIL); }, scripts); -})(); }; - - // process-component-script - var processComponentScript = function(script, text) { return (function() { - var hash = domGetAttr(script, "data-hash"); - return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))))) ? sxLoadComponents(text) : NIL) : (function() { - var hasInline = (isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text)))); - (function() { - var cachedHash = localStorageGet("sx-components-hash"); - return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo("components: downloaded (cookie stale)")) : (function() { - var cached = localStorageGet("sx-components-src"); - return (isSxTruthy(cached) ? (sxLoadComponents(cached), logInfo((String("components: cached (") + String(hash) + String(")")))) : (clearSxCompCookie(), browserReload())); -})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo((String("components: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-components-hash"), localStorageRemove("sx-components-src"), clearSxCompCookie(), browserReload()))); -})(); - return setSxCompCookie(hash); -})()); -})(); }; - - // _page-routes - var _pageRoutes = []; - - // process-page-scripts - var processPageScripts = function() { return (function() { - var scripts = queryPageScripts(); - logInfo((String("pages: found ") + String(len(scripts)) + String(" script tags"))); - { var _c = scripts; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; if (isSxTruthy(!isSxTruthy(isProcessed(s, "pages")))) { - markProcessed(s, "pages"); - (function() { - var text = domTextContent(s); - logInfo((String("pages: script text length=") + String((isSxTruthy(text) ? len(text) : 0)))); - return (isSxTruthy((isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))))) ? (function() { - var pages = parse(text); - logInfo((String("pages: parsed ") + String(len(pages)) + String(" entries"))); - return forEach(function(page) { return append_b(_pageRoutes, merge(page, {"parsed": parseRoutePattern(get(page, "path"))})); }, pages); -})() : logWarn("pages: script tag is empty")); -})(); -} } } - return logInfo((String("pages: ") + String(len(_pageRoutes)) + String(" routes loaded"))); -})(); }; - - // sx-hydrate-islands - var sxHydrateIslands = function(root) { return (function() { - var els = domQueryAll(sxOr(root, domBody()), "[data-sx-island]"); - return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "island-hydrated"))) ? (markProcessed(el, "island-hydrated"), hydrateIsland(el)) : NIL); }, els); -})(); }; - - // hydrate-island - var hydrateIsland = function(el) { return (function() { - var name = domGetAttr(el, "data-sx-island"); - var stateSx = sxOr(domGetAttr(el, "data-sx-state"), "{}"); - return (function() { - var compName = (String("~") + String(name)); - var env = getRenderEnv(NIL); - return (function() { - var comp = envGet(env, compName); - return (isSxTruthy(!isSxTruthy(sxOr(isComponent(comp), isIsland(comp)))) ? logWarn((String("hydrate-island: unknown island ") + String(compName))) : (function() { - var kwargs = sxOr(first(sxParse(stateSx)), {}); - var disposers = []; - var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } - return (function() { - var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); }); - domSetTextContent(el, ""); - domAppend(el, bodyDom); - domSetData(el, "sx-disposers", disposers); - processElements(el); - return logInfo((String("hydrated island: ") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)"))); -})(); -})()); -})(); -})(); -})(); }; - - // dispose-island - var disposeIsland = function(el) { return (function() { - var disposers = domGetData(el, "sx-disposers"); - return (isSxTruthy(disposers) ? (forEach(function(d) { return (isSxTruthy(isCallable(d)) ? d() : NIL); }, disposers), domSetData(el, "sx-disposers", NIL)) : NIL); -})(); }; - - // dispose-islands-in - var disposeIslandsIn = function(root) { return (isSxTruthy(root) ? (function() { - var islands = domQueryAll(root, "[data-sx-island]"); - return (isSxTruthy((isSxTruthy(islands) && !isSxTruthy(isEmpty(islands)))) ? (function() { - var toDispose = filter(function(el) { return !isSxTruthy(isProcessed(el, "island-hydrated")); }, islands); - return (isSxTruthy(!isSxTruthy(isEmpty(toDispose))) ? (logInfo((String("disposing ") + String(len(toDispose)) + String(" island(s)"))), forEach(disposeIsland, toDispose)) : NIL); -})() : NIL); -})() : NIL); }; - - // boot-init - var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), processElements(NIL)); }; - - - // === Transpiled from deps (component dependency analysis) === - - // scan-refs - var scanRefs = function(node) { return (function() { - var refs = []; - scanRefsWalk(node, refs); - return refs; -})(); }; - - // scan-refs-walk - var scanRefsWalk = function(node, refs) { return (isSxTruthy((typeOf(node) == "symbol")) ? (function() { - var name = symbolName(node); - return (isSxTruthy(startsWith(name, "~")) ? (isSxTruthy(!isSxTruthy(contains(refs, name))) ? append_b(refs, name) : NIL) : NIL); -})() : (isSxTruthy((typeOf(node) == "list")) ? forEach(function(item) { return scanRefsWalk(item, refs); }, node) : (isSxTruthy((typeOf(node) == "dict")) ? forEach(function(key) { return scanRefsWalk(dictGet(node, key), refs); }, keys(node)) : NIL))); }; - - // transitive-deps-walk - var transitiveDepsWalk = function(n, seen, env) { return (isSxTruthy(!isSxTruthy(contains(seen, n))) ? (append_b(seen, n), (function() { - var val = envGet(env, n); - return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(componentBody(val))) : (isSxTruthy((typeOf(val) == "macro")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(macroBody(val))) : NIL)); -})()) : NIL); }; - - // transitive-deps - var transitiveDeps = function(name, env) { return (function() { - var seen = []; - var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); - transitiveDepsWalk(key, seen, env); - return filter(function(x) { return !isSxTruthy((x == key)); }, seen); -})(); }; - - // compute-all-deps - var computeAllDeps = function(env) { return forEach(function(name) { return (function() { - var val = envGet(env, name); - return (isSxTruthy((typeOf(val) == "component")) ? componentSetDeps(val, transitiveDeps(name, env)) : NIL); -})(); }, envComponents(env)); }; - - // scan-components-from-source - var scanComponentsFromSource = function(source) { return (function() { - var matches = regexFindAll("\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)", source); - return map(function(m) { return (String("~") + String(m)); }, matches); -})(); }; - - // components-needed - var componentsNeeded = function(pageSource, env) { return (function() { - var direct = scanComponentsFromSource(pageSource); - var allNeeded = []; - { var _c = direct; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(allNeeded, name)))) { - allNeeded.push(name); -} -(function() { - var val = envGet(env, name); - return (function() { - var deps = (isSxTruthy((isSxTruthy((typeOf(val) == "component")) && !isSxTruthy(isEmpty(componentDeps(val))))) ? componentDeps(val) : transitiveDeps(name, env)); - return forEach(function(dep) { return (isSxTruthy(!isSxTruthy(contains(allNeeded, dep))) ? append_b(allNeeded, dep) : NIL); }, deps); -})(); -})(); } } - return allNeeded; -})(); }; - - // page-component-bundle - var pageComponentBundle = function(pageSource, env) { return componentsNeeded(pageSource, env); }; - - // page-css-classes - var pageCssClasses = function(pageSource, env) { return (function() { - var needed = componentsNeeded(pageSource, env); - var classes = []; - { var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() { - var val = envGet(env, name); - return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(cls) { return (isSxTruthy(!isSxTruthy(contains(classes, cls))) ? append_b(classes, cls) : NIL); }, componentCssClasses(val)) : NIL); -})(); } } - { var _c = scanCssClasses(pageSource); for (var _i = 0; _i < _c.length; _i++) { var cls = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(classes, cls)))) { - classes.push(cls); -} } } - return classes; -})(); }; - - // scan-io-refs-walk - var scanIoRefsWalk = function(node, ioNames, refs) { return (isSxTruthy((typeOf(node) == "symbol")) ? (function() { - var name = symbolName(node); - return (isSxTruthy(contains(ioNames, name)) ? (isSxTruthy(!isSxTruthy(contains(refs, name))) ? append_b(refs, name) : NIL) : NIL); -})() : (isSxTruthy((typeOf(node) == "list")) ? forEach(function(item) { return scanIoRefsWalk(item, ioNames, refs); }, node) : (isSxTruthy((typeOf(node) == "dict")) ? forEach(function(key) { return scanIoRefsWalk(dictGet(node, key), ioNames, refs); }, keys(node)) : NIL))); }; - - // scan-io-refs - var scanIoRefs = function(node, ioNames) { return (function() { - var refs = []; - scanIoRefsWalk(node, ioNames, refs); - return refs; -})(); }; - - // transitive-io-refs-walk - var transitiveIoRefsWalk = function(n, seen, allRefs, env, ioNames) { return (isSxTruthy(!isSxTruthy(contains(seen, n))) ? (append_b(seen, n), (function() { - var val = envGet(env, n); - return (isSxTruthy((typeOf(val) == "component")) ? (forEach(function(ref) { return (isSxTruthy(!isSxTruthy(contains(allRefs, ref))) ? append_b(allRefs, ref) : NIL); }, scanIoRefs(componentBody(val), ioNames)), forEach(function(dep) { return transitiveIoRefsWalk(dep, seen, allRefs, env, ioNames); }, scanRefs(componentBody(val)))) : (isSxTruthy((typeOf(val) == "macro")) ? (forEach(function(ref) { return (isSxTruthy(!isSxTruthy(contains(allRefs, ref))) ? append_b(allRefs, ref) : NIL); }, scanIoRefs(macroBody(val), ioNames)), forEach(function(dep) { return transitiveIoRefsWalk(dep, seen, allRefs, env, ioNames); }, scanRefs(macroBody(val)))) : NIL)); -})()) : NIL); }; - - // transitive-io-refs - var transitiveIoRefs = function(name, env, ioNames) { return (function() { - var allRefs = []; - var seen = []; - var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); - transitiveIoRefsWalk(key, seen, allRefs, env, ioNames); - return allRefs; -})(); }; - - // compute-all-io-refs - var computeAllIoRefs = function(env, ioNames) { return forEach(function(name) { return (function() { - var val = envGet(env, name); - return (isSxTruthy((typeOf(val) == "component")) ? componentSetIoRefs(val, transitiveIoRefs(name, env, ioNames)) : NIL); -})(); }, envComponents(env)); }; - - // component-io-refs-cached - var componentIoRefsCached = function(name, env, ioNames) { return (function() { - var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); - return (function() { - var val = envGet(env, key); - return (isSxTruthy((isSxTruthy((typeOf(val) == "component")) && isSxTruthy(!isSxTruthy(isNil(componentIoRefs(val)))) && !isSxTruthy(isEmpty(componentIoRefs(val))))) ? componentIoRefs(val) : transitiveIoRefs(name, env, ioNames)); -})(); -})(); }; - - // component-pure? - var componentPure_p = function(name, env, ioNames) { return (function() { - var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); - return (function() { - var val = envGet(env, key); - return (isSxTruthy((isSxTruthy((typeOf(val) == "component")) && !isSxTruthy(isNil(componentIoRefs(val))))) ? isEmpty(componentIoRefs(val)) : isEmpty(transitiveIoRefs(name, env, ioNames))); -})(); -})(); }; - - // render-target - var renderTarget = function(name, env, ioNames) { return (function() { - var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); - return (function() { - var val = envGet(env, key); - return (isSxTruthy(!isSxTruthy((typeOf(val) == "component"))) ? "server" : (function() { - var affinity = componentAffinity(val); - return (isSxTruthy((affinity == "server")) ? "server" : (isSxTruthy((affinity == "client")) ? "client" : (isSxTruthy(!isSxTruthy(componentPure_p(name, env, ioNames))) ? "server" : "client"))); -})()); -})(); -})(); }; - - // page-render-plan - var pageRenderPlan = function(pageSource, env, ioNames) { return (function() { - var needed = componentsNeeded(pageSource, env); - var compTargets = {}; - var serverList = []; - var clientList = []; - var ioDeps = []; - { var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() { - var target = renderTarget(name, env, ioNames); - compTargets[name] = target; - return (isSxTruthy((target == "server")) ? (append_b(serverList, name), forEach(function(ioRef) { return (isSxTruthy(!isSxTruthy(contains(ioDeps, ioRef))) ? append_b(ioDeps, ioRef) : NIL); }, componentIoRefsCached(name, env, ioNames))) : append_b(clientList, name)); -})(); } } - return {"components": compTargets, "server": serverList, "client": clientList, "io-deps": ioDeps}; -})(); }; - - // env-components - var envComponents = function(env) { return filter(function(k) { return (function() { - var v = envGet(env, k); - return sxOr(isComponent(v), isMacro(v)); -})(); }, keys(env)); }; - - - // === Transpiled from page-helpers (pure data transformation helpers) === - - // special-form-category-map - var specialFormCategoryMap = {"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 - var extractDefineKwargs = function(expr) { return (function() { - var result = {}; - var items = slice(expr, 2); - var n = len(items); - { var _c = range(0, n); for (var _i = 0; _i < _c.length; _i++) { var idx = _c[_i]; if (isSxTruthy((isSxTruthy(((idx + 1) < n)) && (typeOf(nth(items, idx)) == "keyword")))) { - (function() { - var key = keywordName(nth(items, idx)); - var val = nth(items, (idx + 1)); - return dictSet(result, key, (isSxTruthy((typeOf(val) == "list")) ? (String("(") + String(join(" ", map(serialize, val))) + String(")")) : (String(val)))); -})(); -} } } - return result; -})(); }; - - // categorize-special-forms - var categorizeSpecialForms = function(parsedExprs) { return (function() { - var categories = {}; - { var _c = parsedExprs; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(expr) == "list")) && isSxTruthy((len(expr) >= 2)) && isSxTruthy((typeOf(first(expr)) == "symbol")) && (symbolName(first(expr)) == "define-special-form")))) { - (function() { - var name = nth(expr, 1); - var kwargs = extractDefineKwargs(expr); - var category = sxOr(get(specialFormCategoryMap, name), "Other"); - if (isSxTruthy(!isSxTruthy(dictHas(categories, category)))) { - categories[category] = []; -} - return append_b(get(categories, category), {"name": name, "syntax": sxOr(get(kwargs, "syntax"), ""), "doc": sxOr(get(kwargs, "doc"), ""), "tail-position": sxOr(get(kwargs, "tail-position"), ""), "example": sxOr(get(kwargs, "example"), "")}); -})(); -} } } - return categories; -})(); }; - - // build-ref-items-with-href - var buildRefItemsWithHref = function(items, basePath, detailKeys, nFields) { return map(function(item) { return (isSxTruthy((nFields == 3)) ? (function() { - var name = nth(item, 0); - var field2 = nth(item, 1); - var field3 = nth(item, 2); - return {"name": name, "desc": field2, "exists": field3, "href": (isSxTruthy((isSxTruthy(field3) && some(function(k) { return (k == name); }, detailKeys))) ? (String(basePath) + String(name)) : NIL)}; -})() : (function() { - var name = nth(item, 0); - var desc = nth(item, 1); - return {"name": name, "desc": desc, "href": (isSxTruthy(some(function(k) { return (k == name); }, detailKeys)) ? (String(basePath) + String(name)) : NIL)}; -})()); }, items); }; - - // build-reference-data - var buildReferenceData = function(slug, rawData, detailKeys) { return (function() { var _m = slug; if (_m == "attributes") return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; if (_m == "headers") return {"req-headers": buildRefItemsWithHref(get(rawData, "req-headers"), "/hypermedia/reference/headers/", detailKeys, 3), "resp-headers": buildRefItemsWithHref(get(rawData, "resp-headers"), "/hypermedia/reference/headers/", detailKeys, 3)}; if (_m == "events") return {"events-list": buildRefItemsWithHref(get(rawData, "events-list"), "/hypermedia/reference/events/", detailKeys, 2)}; if (_m == "js-api") return {"js-api-list": map(function(item) { return {"name": nth(item, 0), "desc": nth(item, 1)}; }, get(rawData, "js-api-list"))}; return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; })(); }; - - // build-attr-detail - var buildAttrDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"attr-not-found": true} : {"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": (isSxTruthy(dictHas(detail, "handler")) ? (String("ref-wire-") + String(replace_(replace_(slug, ":", "-"), "*", "star"))) : NIL)}); }; - - // build-header-detail - var buildHeaderDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"header-not-found": true} : {"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 - var buildEventDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"event-not-found": true} : {"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 - var buildComponentSource = function(compData) { return (function() { - var compType = get(compData, "type"); - var name = get(compData, "name"); - var params = get(compData, "params"); - var hasChildren = get(compData, "has-children"); - var bodySx = get(compData, "body-sx"); - var affinity = get(compData, "affinity"); - return (isSxTruthy((compType == "not-found")) ? (String(";; component ") + String(name) + String(" not found")) : (function() { - var paramStrs = (isSxTruthy(isEmpty(params)) ? (isSxTruthy(hasChildren) ? ["&rest", "children"] : []) : (isSxTruthy(hasChildren) ? append(cons("&key", params), ["&rest", "children"]) : cons("&key", params))); - 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("\n ") + String(bodySx) + String(")")); -})()); -})(); }; - - // build-bundle-analysis - var buildBundleAnalysis = function(pagesRaw, componentsRaw, totalComponents, totalMacros, pureCount, ioCount) { return (function() { - var pagesData = []; - { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() { - var neededNames = get(page, "needed-names"); - var n = len(neededNames); - var pct = (isSxTruthy((totalComponents > 0)) ? round(((n / totalComponents) * 100)) : 0); - var savings = (100 - pct); - var pureInPage = 0; - var ioInPage = 0; - var pageIoRefs = []; - var compDetails = []; - { var _c = neededNames; for (var _i = 0; _i < _c.length; _i++) { var compName = _c[_i]; (function() { - var info = get(componentsRaw, compName); - return (isSxTruthy(!isSxTruthy(isNil(info))) ? ((isSxTruthy(get(info, "is-pure")) ? (pureInPage = (pureInPage + 1)) : ((ioInPage = (ioInPage + 1)), forEach(function(ref) { return (isSxTruthy(!isSxTruthy(some(function(r) { return (r == ref); }, pageIoRefs))) ? append_b(pageIoRefs, ref) : NIL); }, sxOr(get(info, "io-refs"), [])))), append_b(compDetails, {"name": compName, "is-pure": get(info, "is-pure"), "affinity": get(info, "affinity"), "render-target": get(info, "render-target"), "io-refs": sxOr(get(info, "io-refs"), []), "deps": sxOr(get(info, "deps"), []), "source": get(info, "source")})) : NIL); -})(); } } - return append_b(pagesData, {"name": get(page, "name"), "path": get(page, "path"), "direct": get(page, "direct"), "needed": n, "pct": pct, "savings": savings, "io-refs": len(pageIoRefs), "pure-in-page": pureInPage, "io-in-page": ioInPage, "components": compDetails}); -})(); } } - return {"pages": pagesData, "total-components": totalComponents, "total-macros": totalMacros, "pure-count": pureCount, "io-count": ioCount}; -})(); }; - - // build-routing-analysis - var buildRoutingAnalysis = function(pagesRaw) { return (function() { - var pagesData = []; - var clientCount = 0; - var serverCount = 0; - { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() { - var hasData = get(page, "has-data"); - var contentSrc = sxOr(get(page, "content-src"), ""); - var mode = NIL; - var reason = ""; - (isSxTruthy(hasData) ? ((mode = "server"), (reason = "Has :data expression — needs server IO"), (serverCount = (serverCount + 1))) : (isSxTruthy(isEmpty(contentSrc)) ? ((mode = "server"), (reason = "No content expression"), (serverCount = (serverCount + 1))) : ((mode = "client"), (clientCount = (clientCount + 1))))); - return append_b(pagesData, {"name": get(page, "name"), "path": get(page, "path"), "mode": mode, "has-data": hasData, "content-expr": (isSxTruthy((len(contentSrc) > 80)) ? (String(slice(contentSrc, 0, 80)) + String("...")) : contentSrc), "reason": reason}); -})(); } } - return {"pages": pagesData, "total-pages": (clientCount + serverCount), "client-count": clientCount, "server-count": serverCount}; -})(); }; - - // build-affinity-analysis - var buildAffinityAnalysis = function(demoComponents, pagePlans) { return {"components": demoComponents, "page-plans": pagePlans}; }; - - - // === Transpiled from router (client-side route matching) === - - // split-path-segments - var splitPathSegments = function(path) { return (function() { - var trimmed = (isSxTruthy(startsWith(path, "/")) ? slice(path, 1) : path); - return (function() { - var trimmed2 = (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(trimmed))) && endsWith(trimmed, "/"))) ? slice(trimmed, 0, (len(trimmed) - 1)) : trimmed); - return (isSxTruthy(isEmpty(trimmed2)) ? [] : split(trimmed2, "/")); -})(); -})(); }; - - // make-route-segment - var makeRouteSegment = function(seg) { return (isSxTruthy((isSxTruthy(startsWith(seg, "<")) && endsWith(seg, ">"))) ? (function() { - var paramName = slice(seg, 1, (len(seg) - 1)); - return (function() { - var d = {}; - d["type"] = "param"; - d["value"] = paramName; - return d; -})(); -})() : (function() { - var d = {}; - d["type"] = "literal"; - d["value"] = seg; - return d; -})()); }; - - // parse-route-pattern - var parseRoutePattern = function(pattern) { return (function() { - var segments = splitPathSegments(pattern); - return map(makeRouteSegment, segments); -})(); }; - - // match-route-segments - var matchRouteSegments = function(pathSegs, parsedSegs) { return (isSxTruthy(!isSxTruthy((len(pathSegs) == len(parsedSegs)))) ? NIL : (function() { - var params = {}; - var matched = true; - forEachIndexed(function(i, parsedSeg) { return (isSxTruthy(matched) ? (function() { - var pathSeg = nth(pathSegs, i); - var segType = get(parsedSeg, "type"); - return (isSxTruthy((segType == "literal")) ? (isSxTruthy(!isSxTruthy((pathSeg == get(parsedSeg, "value")))) ? (matched = false) : NIL) : (isSxTruthy((segType == "param")) ? dictSet(params, get(parsedSeg, "value"), pathSeg) : (matched = false))); -})() : NIL); }, parsedSegs); - return (isSxTruthy(matched) ? params : NIL); -})()); }; - - // match-route - var matchRoute = function(path, pattern) { return (function() { - var pathSegs = splitPathSegments(path); - var parsedSegs = parseRoutePattern(pattern); - return matchRouteSegments(pathSegs, parsedSegs); -})(); }; - - // find-matching-route - var findMatchingRoute = function(path, routes) { return (function() { - var pathSegs = splitPathSegments(path); - var result = NIL; - { var _c = routes; for (var _i = 0; _i < _c.length; _i++) { var route = _c[_i]; if (isSxTruthy(isNil(result))) { - (function() { - var params = matchRouteSegments(pathSegs, get(route, "parsed")); - return (isSxTruthy(!isSxTruthy(isNil(params))) ? (function() { - var matched = merge(route, {}); - matched["params"] = params; - return (result = matched); -})() : NIL); -})(); -} } } - return result; -})(); }; - - - // === Transpiled from signals (reactive signal runtime) === - - // signal - var signal = function(initialValue) { return makeSignal(initialValue); }; - - // deref - var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() { - var ctx = getTrackingContext(); - if (isSxTruthy(ctx)) { - trackingContextAddDep(ctx, s); - signalAddSub(s, trackingContextNotifyFn(ctx)); -} - return signalValue(s); -})()); }; - - // reset! - var reset_b = function(s, value) { return (isSxTruthy(isSignal(s)) ? (function() { - var old = signalValue(s); - return (isSxTruthy(!isSxTruthy(isIdentical(old, value))) ? (signalSetValue(s, value), notifySubscribers(s)) : NIL); -})() : NIL); }; - - // swap! - var swap_b = function(s, f) { var args = Array.prototype.slice.call(arguments, 2); return (isSxTruthy(isSignal(s)) ? (function() { - var old = signalValue(s); - var newVal = apply(f, cons(old, args)); - return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? (signalSetValue(s, newVal), notifySubscribers(s)) : NIL); -})() : NIL); }; - - // computed - var computed = function(computeFn) { return (function() { - var s = makeSignal(NIL); - var deps = []; - var computeCtx = NIL; - return (function() { - var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } } -signalSetDeps(s, []); -return (function() { - var ctx = makeTrackingContext(recompute); - return (function() { - var prev = getTrackingContext(); - setTrackingContext(ctx); - return (function() { - var newVal = invoke(computeFn); - setTrackingContext(prev); - signalSetDeps(s, trackingContextDeps(ctx)); - return (function() { - var old = signalValue(s); - signalSetValue(s, newVal); - return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? notifySubscribers(s) : NIL); -})(); -})(); -})(); -})(); }; - recompute(); - registerInScope(function() { return disposeComputed(s); }); - return s; -})(); -})(); }; - - // effect - var effect = function(effectFn) { return (function() { - var deps = []; - var disposed = false; - var cleanupFn = NIL; - return (function() { - var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? invoke(cleanupFn) : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (function() { - var ctx = makeTrackingContext(runEffect); - return (function() { - var prev = getTrackingContext(); - setTrackingContext(ctx); - return (function() { - var result = invoke(effectFn); - setTrackingContext(prev); - deps = trackingContextDeps(ctx); - return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL); -})(); -})(); -})()) : NIL); }; - runEffect(); - return (function() { - var disposeFn = function() { disposed = true; -if (isSxTruthy(cleanupFn)) { - invoke(cleanupFn); -} -{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } } -return (deps = []); }; - registerInScope(disposeFn); - return disposeFn; -})(); -})(); -})(); }; - - // *batch-depth* - var _batchDepth = 0; - - // *batch-queue* - var _batchQueue = []; - - // batch - var batch = function(thunk) { _batchDepth = (_batchDepth + 1); -invoke(thunk); -_batchDepth = (_batchDepth - 1); -return (isSxTruthy((_batchDepth == 0)) ? (function() { - var queue = _batchQueue; - _batchQueue = []; - return (function() { - var seen = []; - var pending = []; - { var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) { - seen.push(sub); - pending.push(sub); -} } } } } - return forEach(function(sub) { return sub(); }, pending); -})(); -})() : NIL); }; - - // notify-subscribers - var notifySubscribers = function(s) { return (isSxTruthy((_batchDepth > 0)) ? (isSxTruthy(!isSxTruthy(contains(_batchQueue, s))) ? append_b(_batchQueue, s) : NIL) : flushSubscribers(s)); }; - - // flush-subscribers - var flushSubscribers = function(s) { return forEach(function(sub) { return sub(); }, signalSubscribers(s)); }; - - // dispose-computed - var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); }; - - // *island-scope* - var _islandScope = NIL; - - // with-island-scope - var withIslandScope = function(scopeFn, bodyFn) { return (function() { - var prev = _islandScope; - _islandScope = scopeFn; - return (function() { - var result = bodyFn(); - _islandScope = prev; - return result; -})(); -})(); }; - - // register-in-scope - var registerInScope = function(disposable) { return (isSxTruthy(_islandScope) ? _islandScope(disposable) : NIL); }; - - // with-marsh-scope - var withMarshScope = function(marshEl, bodyFn) { return (function() { - var disposers = []; - withIslandScope(function(d) { return append_b(disposers, d); }, bodyFn); - return domSetData(marshEl, "sx-marsh-disposers", disposers); -})(); }; - - // dispose-marsh-scope - var disposeMarshScope = function(marshEl) { return (function() { - var disposers = domGetData(marshEl, "sx-marsh-disposers"); - return (isSxTruthy(disposers) ? (forEach(function(d) { return invoke(d); }, disposers), domSetData(marshEl, "sx-marsh-disposers", NIL)) : NIL); -})(); }; - - // *store-registry* - var _storeRegistry = {}; - - // def-store - var defStore = function(name, initFn) { return (function() { - var registry = _storeRegistry; - if (isSxTruthy(!isSxTruthy(dictHas(registry, name)))) { - _storeRegistry = assoc(registry, name, invoke(initFn)); -} - return get(_storeRegistry, name); -})(); }; - - // use-store - var useStore = function(name) { return (isSxTruthy(dictHas(_storeRegistry, name)) ? get(_storeRegistry, name) : error((String("Store not found: ") + String(name) + String(". Call (def-store ...) before (use-store ...).")))); }; - - // clear-stores - var clearStores = function() { return (_storeRegistry = {}); }; - - // emit-event - var emitEvent = function(el, eventName, detail) { return domDispatch(el, eventName, detail); }; - - // on-event - var onEvent = function(el, eventName, handler) { return domListen(el, eventName, handler); }; - - // bridge-event - var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() { - var remove = domListen(el, eventName, function(e) { return (function() { - var detail = eventDetail(e); - var newVal = (isSxTruthy(transformFn) ? invoke(transformFn, detail) : detail); - return reset_b(targetSignal, newVal); -})(); }); - return remove; -})(); }); }; - - // resource - var resource = function(fetchFn) { return (function() { - var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL}); - promiseThen(invoke(fetchFn), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); }); - return state; -})(); }; - - - // ========================================================================= - // Platform interface — DOM adapter (browser-only) - // ========================================================================= - - var _hasDom = typeof document !== "undefined"; - - // Register DOM adapter as the render dispatch target for the evaluator. - _renderExprFn = function(expr, env) { return renderToDom(expr, env, null); }; - _renderMode = true; // Browser always evaluates in render context. - - var SVG_NS = "http://www.w3.org/2000/svg"; - var MATH_NS = "http://www.w3.org/1998/Math/MathML"; - - function domCreateElement(tag, ns) { - if (!_hasDom) return null; - if (ns && ns !== NIL) return document.createElementNS(ns, tag); - return document.createElement(tag); - } - - function createTextNode(s) { - return _hasDom ? document.createTextNode(s) : null; - } - - function createComment(s) { - return _hasDom ? document.createComment(s || "") : null; - } - - function createFragment() { - return _hasDom ? document.createDocumentFragment() : null; - } - - function domAppend(parent, child) { - if (parent && child) parent.appendChild(child); - } - - function domPrepend(parent, child) { - if (parent && child) parent.insertBefore(child, parent.firstChild); - } - - function domSetAttr(el, name, val) { - if (el && el.setAttribute) el.setAttribute(name, val); - } - - function domGetAttr(el, name) { - if (!el || !el.getAttribute) return NIL; - var v = el.getAttribute(name); - return v === null ? NIL : v; - } - - function domRemoveAttr(el, name) { - if (el && el.removeAttribute) el.removeAttribute(name); - } - - function domHasAttr(el, name) { - return !!(el && el.hasAttribute && el.hasAttribute(name)); - } - - function domParseHtml(html) { - if (!_hasDom) return null; - var tpl = document.createElement("template"); - tpl.innerHTML = html; - return tpl.content; - } - - function domClone(node) { - return node && node.cloneNode ? node.cloneNode(true) : node; - } - - function domParent(el) { return el ? el.parentNode : null; } - function domId(el) { return el && el.id ? el.id : NIL; } - function domNodeType(el) { return el ? el.nodeType : 0; } - function domNodeName(el) { return el ? el.nodeName : ""; } - function domTextContent(el) { return el ? el.textContent || el.nodeValue || "" : ""; } - function domSetTextContent(el, s) { if (el) { if (el.nodeType === 3 || el.nodeType === 8) el.nodeValue = s; else el.textContent = s; } } - function domIsFragment(el) { return el ? el.nodeType === 11 : false; } - function domIsChildOf(child, parent) { return !!(parent && child && child.parentNode === parent); } - function domIsActiveElement(el) { return _hasDom && el === document.activeElement; } - function domIsInputElement(el) { - if (!el || !el.tagName) return false; - var t = el.tagName; - return t === "INPUT" || t === "TEXTAREA" || t === "SELECT"; - } - function domFirstChild(el) { return el ? el.firstChild : null; } - function domNextSibling(el) { return el ? el.nextSibling : null; } - - function domChildList(el) { - if (!el || !el.childNodes) return []; - return Array.prototype.slice.call(el.childNodes); - } - - function domAttrList(el) { - if (!el || !el.attributes) return []; - var r = []; - for (var i = 0; i < el.attributes.length; i++) { - r.push([el.attributes[i].name, el.attributes[i].value]); - } - return r; - } - - function domInsertBefore(parent, node, ref) { - if (parent && node) parent.insertBefore(node, ref || null); - } - - function domInsertAfter(ref, node) { - if (ref && ref.parentNode && node) { - ref.parentNode.insertBefore(node, ref.nextSibling); - } - } - - function domRemoveChild(parent, child) { - if (parent && child && child.parentNode === parent) parent.removeChild(child); - } - - function domReplaceChild(parent, newChild, oldChild) { - if (parent && newChild && oldChild) parent.replaceChild(newChild, oldChild); - } - - function domSetInnerHtml(el, html) { - if (el) el.innerHTML = html; - } - - function domInsertAdjacentHtml(el, pos, html) { - if (el && el.insertAdjacentHTML) el.insertAdjacentHTML(pos, html); - } - - function domGetStyle(el, prop) { - return el && el.style ? el.style[prop] || "" : ""; - } - - function domSetStyle(el, prop, val) { - if (el && el.style) el.style[prop] = val; - } - - function domGetProp(el, name) { return el ? el[name] : NIL; } - function domSetProp(el, name, val) { if (el) el[name] = val; } - - function domAddClass(el, cls) { - if (el && el.classList) el.classList.add(cls); - } - - function domRemoveClass(el, cls) { - if (el && el.classList) el.classList.remove(cls); - } - - function domDispatch(el, name, detail) { - if (!_hasDom || !el) return false; - var evt = new CustomEvent(name, { bubbles: true, cancelable: true, detail: detail || {} }); - return el.dispatchEvent(evt); - } - - function domListen(el, name, handler) { - if (!_hasDom || !el) return function() {}; - // Wrap SX lambdas from runtime-evaluated island code into native fns - // If lambda takes 0 params, call without event arg (convenience for on-click handlers) - var wrapped = isLambda(handler) - ? (lambdaParams(handler).length === 0 - ? function(e) { try { invoke(handler); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } - : function(e) { try { invoke(handler, e); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } }) - : handler; - if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler)); - el.addEventListener(name, wrapped); - return function() { el.removeEventListener(name, wrapped); }; - } - - function eventDetail(e) { - return (e && e.detail != null) ? e.detail : nil; - } - - function domQuery(sel) { - return _hasDom ? document.querySelector(sel) : null; - } - - function domEnsureElement(sel) { - if (!_hasDom) return null; - var el = document.querySelector(sel); - if (el) return el; - // Parse #id selector → create div with that id, append to body - if (sel.charAt(0) === '#') { - el = document.createElement('div'); - el.id = sel.slice(1); - document.body.appendChild(el); - return el; - } - return null; - } - - function domQueryAll(root, sel) { - if (!root || !root.querySelectorAll) return []; - return Array.prototype.slice.call(root.querySelectorAll(sel)); - } - - function domTagName(el) { return el && el.tagName ? el.tagName : ""; } - - // Island DOM helpers - function domRemove(node) { - if (node && node.parentNode) node.parentNode.removeChild(node); - } - function domChildNodes(el) { - if (!el || !el.childNodes) return []; - return Array.prototype.slice.call(el.childNodes); - } - function domRemoveChildrenAfter(marker) { - if (!marker || !marker.parentNode) return; - var parent = marker.parentNode; - while (marker.nextSibling) parent.removeChild(marker.nextSibling); - } - function domSetData(el, key, val) { - if (el) { if (!el._sxData) el._sxData = {}; el._sxData[key] = val; } - } - function domGetData(el, key) { - return (el && el._sxData) ? (el._sxData[key] != null ? el._sxData[key] : nil) : nil; - } - function domInnerHtml(el) { - return (el && el.innerHTML != null) ? el.innerHTML : ""; - } - function jsonParse(s) { - try { return JSON.parse(s); } catch(e) { return {}; } - } - - // renderDomComponent and renderDomElement are transpiled from - // adapter-dom.sx — no imperative overrides needed. - - - // ========================================================================= - // Platform interface — Engine pure logic (browser + node compatible) - // ========================================================================= - - function browserLocationHref() { - return typeof location !== "undefined" ? location.href : ""; - } - - function browserSameOrigin(url) { - try { return new URL(url, location.href).origin === location.origin; } - catch (e) { return true; } - } - - function browserPushState(url) { - if (typeof history !== "undefined") { - try { history.pushState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); } - catch (e) {} - } - } - - function browserReplaceState(url) { - if (typeof history !== "undefined") { - try { history.replaceState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); } - catch (e) {} - } - } - - function nowMs() { return (typeof performance !== "undefined") ? performance.now() : Date.now(); } - - function parseHeaderValue(s) { - if (!s) return null; - try { - if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s); - return JSON.parse(s); - } catch (e) { return null; } - } - - - // ========================================================================= - // Platform interface — Orchestration (browser-only) - // ========================================================================= - - // --- Browser/Network --- - - function browserNavigate(url) { - if (typeof location !== "undefined") location.assign(url); - } - - function browserReload() { - if (typeof location !== "undefined") location.reload(); - } - - function browserScrollTo(x, y) { - if (typeof window !== "undefined") window.scrollTo(x, y); - } - - function browserMediaMatches(query) { - if (typeof window === "undefined") return false; - return window.matchMedia(query).matches; - } - - function browserConfirm(msg) { - if (typeof window === "undefined") return false; - return window.confirm(msg); - } - - function browserPrompt(msg) { - if (typeof window === "undefined") return NIL; - var r = window.prompt(msg); - return r === null ? NIL : r; - } - - function csrfToken() { - if (!_hasDom) return NIL; - var m = document.querySelector('meta[name="csrf-token"]'); - return m ? m.getAttribute("content") : NIL; - } - - function isCrossOrigin(url) { - try { - var h = new URL(url, location.href).hostname; - return h !== location.hostname && - (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0); - } catch (e) { return false; } - } - - // --- Promises --- - - function promiseResolve(val) { return Promise.resolve(val); } - - function promiseThen(p, onResolve, onReject) { - if (!p || !p.then) return p; - return onReject ? p.then(onResolve, onReject) : p.then(onResolve); - } - - function promiseCatch(p, fn) { return p && p.catch ? p.catch(fn) : p; } - - function promiseDelayed(ms, value) { - return new Promise(function(resolve) { - setTimeout(function() { resolve(value); }, ms); - }); - } - - // --- Abort controllers --- - - var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null; - - function abortPrevious(el) { - if (_controllers) { - var prev = _controllers.get(el); - if (prev) prev.abort(); - } - } - - function trackController(el, ctrl) { - if (_controllers) _controllers.set(el, ctrl); - } - - var _targetControllers = typeof WeakMap !== "undefined" ? new WeakMap() : null; - - function abortPreviousTarget(el) { - if (_targetControllers) { - var prev = _targetControllers.get(el); - if (prev) prev.abort(); - } - } - - function trackControllerTarget(el, ctrl) { - if (_targetControllers) _targetControllers.set(el, ctrl); - } - - function newAbortController() { - return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} }; - } - - function controllerSignal(ctrl) { return ctrl ? ctrl.signal : null; } - - function isAbortError(err) { return err && err.name === "AbortError"; } - - // --- Timers --- - - function _wrapSxFn(fn) { - if (fn && fn._lambda) { - return function() { return trampoline(callLambda(fn, [], lambdaClosure(fn))); }; - } - return fn; - } - function setTimeout_(fn, ms) { return setTimeout(_wrapSxFn(fn), ms || 0); } - function setInterval_(fn, ms) { return setInterval(_wrapSxFn(fn), ms || 1000); } - function clearTimeout_(id) { clearTimeout(id); } - function clearInterval_(id) { clearInterval(id); } - function requestAnimationFrame_(fn) { - var cb = _wrapSxFn(fn); - if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(cb); - else setTimeout(cb, 16); - } - - // --- Fetch --- - - function fetchRequest(config, successFn, errorFn) { - var opts = { method: config.method, headers: config.headers }; - if (config.signal) opts.signal = config.signal; - if (config.body && config.method !== "GET") opts.body = config.body; - if (config["cross-origin"]) opts.credentials = "include"; - - var p = (config.preloaded && config.preloaded !== NIL) - ? Promise.resolve({ - ok: true, status: 200, - headers: new Headers({ "Content-Type": config.preloaded["content-type"] || "" }), - text: function() { return Promise.resolve(config.preloaded.text); } - }) - : fetch(config.url, opts); - - return p.then(function(resp) { - return resp.text().then(function(text) { - var getHeader = function(name) { - var v = resp.headers.get(name); - return v === null ? NIL : v; - }; - return successFn(resp.ok, resp.status, getHeader, text); - }); - }).catch(function(err) { - return errorFn(err); - }); - } - - function fetchLocation(headerVal) { - if (!_hasDom) return; - var locUrl = headerVal; - try { var obj = JSON.parse(headerVal); locUrl = obj.path || obj; } catch (e) {} - fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function(r) { - return r.text().then(function(t) { - var main = document.getElementById("main-panel"); - if (main) { - main.innerHTML = t; - postSwap(main); - try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {} - } - }); - }); - } - - function fetchAndRestore(main, url, headers, scrollY) { - var opts = { headers: headers }; - try { - var h = new URL(url, location.href).hostname; - if (h !== location.hostname && - (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) { - opts.credentials = "include"; - } - } catch (e) {} - - fetch(url, opts).then(function(resp) { - return resp.text().then(function(text) { - text = stripComponentScripts(text); - text = extractResponseCss(text); - text = text.trim(); - if (text.charAt(0) === "(") { - try { - var dom = sxRender(text); - var container = document.createElement("div"); - container.appendChild(dom); - processOobSwaps(container, function(t, oob, s) { - swapDomNodes(t, oob, s); - sxHydrate(t); - processElements(t); - }); - var newMain = container.querySelector("#main-panel"); - morphChildren(main, newMain || container); - postSwap(main); - if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0); - } catch (err) { - console.error("sx-ref popstate error:", err); - location.reload(); - } - } else { - var parser = new DOMParser(); - var doc = parser.parseFromString(text, "text/html"); - var newMain = doc.getElementById("main-panel"); - if (newMain) { - morphChildren(main, newMain); - postSwap(main); - if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0); - } else { - location.reload(); - } - } - }); - }).catch(function() { location.reload(); }); - } - - function fetchStreaming(target, url, headers) { - // Streaming fetch for multi-stream pages. - // First chunk = OOB SX swap (shell with skeletons). - // Subsequent chunks = __sxResolve script tags filling suspense slots. - var opts = { headers: headers }; - try { - var h = new URL(url, location.href).hostname; - if (h !== location.hostname && - (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) { - opts.credentials = "include"; - } - } catch (e) {} - - fetch(url, opts).then(function(resp) { - if (!resp.ok || !resp.body) { - // Fallback: non-streaming - return resp.text().then(function(text) { - text = stripComponentScripts(text); - text = extractResponseCss(text); - text = text.trim(); - if (text.charAt(0) === "(") { - var dom = sxRender(text); - var container = document.createElement("div"); - container.appendChild(dom); - processOobSwaps(container, function(t, oob, s) { - swapDomNodes(t, oob, s); - sxHydrate(t); - processElements(t); - }); - var newMain = container.querySelector("#main-panel"); - morphChildren(target, newMain || container); - postSwap(target); - } - }); - } - - var reader = resp.body.getReader(); - var decoder = new TextDecoder(); - var buffer = ""; - var initialSwapDone = false; - // Regex to match __sxResolve script tags - var RESOLVE_START = ""; - - function processResolveScripts() { - // Strip and load any extra component defs before resolve scripts - buffer = stripSxScripts(buffer); - var idx; - while ((idx = buffer.indexOf(RESOLVE_START)) >= 0) { - var endIdx = buffer.indexOf(RESOLVE_END, idx); - if (endIdx < 0) break; // incomplete, wait for more data - var argsStr = buffer.substring(idx + RESOLVE_START.length, endIdx); - buffer = buffer.substring(endIdx + RESOLVE_END.length); - // argsStr is: "stream-id","sx source" - var commaIdx = argsStr.indexOf(","); - if (commaIdx >= 0) { - try { - var id = JSON.parse(argsStr.substring(0, commaIdx)); - var sx = JSON.parse(argsStr.substring(commaIdx + 1)); - if (typeof Sx !== "undefined" && Sx.resolveSuspense) { - Sx.resolveSuspense(id, sx); - } - } catch (e) { - console.error("[sx-ref] resolve parse error:", e); - } - } - } - } - - function pump() { - return reader.read().then(function(result) { - buffer += decoder.decode(result.value || new Uint8Array(), { stream: !result.done }); - - if (!initialSwapDone) { - // Look for the first resolve script — everything before it is OOB content - var scriptIdx = buffer.indexOf(" (without data-components or data-init). - // These contain extra component defs from streaming resolve chunks. - // data-init scripts are preserved for process-sx-scripts to evaluate as side effects. - var SxObj = typeof Sx !== "undefined" ? Sx : null; - return text.replace(/]*type="text\/sx"[^>]*>[\s\S]*?<\/script>/gi, - function(match) { - if (/data-init/.test(match)) return match; // preserve data-init scripts - var m = match.match(/]*>([\s\S]*?)<\/script>/i); - if (m && SxObj && SxObj.loadComponents) SxObj.loadComponents(m[1]); - return ""; - }); - } - - function extractResponseCss(text) { - if (!_hasDom) return text; - var target = document.getElementById("sx-css"); - if (!target) return text; - return text.replace(/]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi, - function(_, css) { target.textContent += css; return ""; }); - } - - function selectFromContainer(container, sel) { - var frag = document.createDocumentFragment(); - sel.split(",").forEach(function(s) { - container.querySelectorAll(s.trim()).forEach(function(m) { frag.appendChild(m); }); - }); - return frag; - } - - function childrenToFragment(container) { - var frag = document.createDocumentFragment(); - while (container.firstChild) frag.appendChild(container.firstChild); - return frag; - } - - function selectHtmlFromDoc(doc, sel) { - var parts = sel.split(",").map(function(s) { return s.trim(); }); - var frags = []; - parts.forEach(function(s) { - doc.querySelectorAll(s).forEach(function(m) { frags.push(m.outerHTML); }); - }); - return frags.join(""); - } - - // --- Parsing --- - - function tryParseJson(s) { - if (!s) return NIL; - try { return JSON.parse(s); } catch (e) { return NIL; } - } - - - // ========================================================================= - // Platform interface — Boot (mount, hydrate, scripts, cookies) - // ========================================================================= - - function resolveMountTarget(target) { - if (typeof target === "string") return _hasDom ? document.querySelector(target) : null; - return target; - } - - function sxRenderWithEnv(source, extraEnv) { - var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; - var exprs = parse(source); - if (!_hasDom) return null; - var frag = document.createDocumentFragment(); - for (var i = 0; i < exprs.length; i++) { - var node = renderToDom(exprs[i], env, null); - if (node) frag.appendChild(node); - } - return frag; - } - - function getRenderEnv(extraEnv) { - return extraEnv ? merge(componentEnv, extraEnv) : componentEnv; - } - - function mergeEnvs(base, newEnv) { - return newEnv ? merge(componentEnv, base, newEnv) : merge(componentEnv, base); - } - - function sxLoadComponents(text) { - try { - var exprs = parse(text); - for (var i = 0; i < exprs.length; i++) trampoline(evalExpr(exprs[i], componentEnv)); - } catch (err) { - logParseError("loadComponents", text, err); - throw err; - } - } - - function setDocumentTitle(s) { - if (_hasDom) document.title = s || ""; - } - - function removeHeadElement(sel) { - if (!_hasDom) return; - var old = document.head.querySelector(sel); - if (old) old.parentNode.removeChild(old); - } - - function querySxScripts(root) { - if (!_hasDom) return []; - var r = (root && root !== NIL) ? root : document; - return Array.prototype.slice.call( - r.querySelectorAll('script[type="text/sx"]')); - } - - function queryPageScripts() { - if (!_hasDom) return []; - return Array.prototype.slice.call( - document.querySelectorAll('script[type="text/sx-pages"]')); - } - - // --- localStorage --- - - function localStorageGet(key) { - try { var v = localStorage.getItem(key); return v === null ? NIL : v; } - catch (e) { return NIL; } - } - - function localStorageSet(key, val) { - try { localStorage.setItem(key, val); } catch (e) {} - } - - function localStorageRemove(key) { - try { localStorage.removeItem(key); } catch (e) {} - } - - // --- Cookies --- - - function setSxCompCookie(hash) { - if (_hasDom) document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; - } - - function clearSxCompCookie() { - if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax"; - } - - // --- Env helpers --- - - function parseEnvAttr(el) { - var attr = el && el.getAttribute ? el.getAttribute("data-sx-env") : null; - if (!attr) return {}; - try { return JSON.parse(attr); } catch (e) { return {}; } - } - - function storeEnvAttr(el, base, newEnv) { - var merged = merge(base, newEnv); - if (el && el.setAttribute) el.setAttribute("data-sx-env", JSON.stringify(merged)); - } - - function toKebab(s) { return s.replace(/_/g, "-"); } - - // --- Logging --- - - function logInfo(msg) { - if (typeof console !== "undefined") console.log("[sx-ref] " + msg); - } - - function logWarn(msg) { - if (typeof console !== "undefined") console.warn("[sx-ref] " + msg); - } - - function logParseError(label, text, err) { - if (typeof console === "undefined") return; - var msg = err && err.message ? err.message : String(err); - var colMatch = msg.match(/col (\d+)/); - var lineMatch = msg.match(/line (\d+)/); - if (colMatch && text) { - var errLine = lineMatch ? parseInt(lineMatch[1]) : 1; - var errCol = parseInt(colMatch[1]); - var lines = text.split("\n"); - var pos = 0; - for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1; - pos += errCol; - var ws = 80; - var start = Math.max(0, pos - ws); - var end = Math.min(text.length, pos + ws); - console.error("[sx-ref] " + label + ":", msg, - "\n around error (pos ~" + pos + "):", - "\n \u00ab" + text.substring(start, pos) + "\u26d4" + text.substring(pos, end) + "\u00bb"); - } else { - console.error("[sx-ref] " + label + ":", msg); - } - } - - - - // ========================================================================= - // Post-transpilation fixups - // ========================================================================= - // The reference spec's call-lambda only handles Lambda objects, but HO forms - // (map, reduce, etc.) may receive native primitives. Wrap to handle both. - var _rawCallLambda = callLambda; - callLambda = function(f, args, callerEnv) { - if (typeof f === "function") return f.apply(null, args); - return _rawCallLambda(f, args, callerEnv); - }; - - // Expose render functions as primitives so SX code can call them - if (typeof renderToHtml === "function") PRIMITIVES["render-to-html"] = renderToHtml; - if (typeof renderToSx === "function") PRIMITIVES["render-to-sx"] = renderToSx; - if (typeof aser === "function") PRIMITIVES["aser"] = aser; - if (typeof renderToDom === "function") PRIMITIVES["render-to-dom"] = renderToDom; - - // Expose signal functions as primitives so runtime-evaluated SX code - // (e.g. island bodies from .sx files) can call them - PRIMITIVES["signal"] = signal; - PRIMITIVES["signal?"] = isSignal; - PRIMITIVES["deref"] = deref; - PRIMITIVES["reset!"] = reset_b; - PRIMITIVES["swap!"] = swap_b; - PRIMITIVES["computed"] = computed; - PRIMITIVES["effect"] = effect; - PRIMITIVES["batch"] = batch; - // Timer primitives for island code - PRIMITIVES["set-interval"] = setInterval_; - PRIMITIVES["clear-interval"] = clearInterval_; - // Reactive DOM helpers for island code - PRIMITIVES["reactive-text"] = reactiveText; - PRIMITIVES["create-text-node"] = createTextNode; - PRIMITIVES["dom-set-text-content"] = domSetTextContent; - PRIMITIVES["dom-listen"] = domListen; - PRIMITIVES["dom-dispatch"] = domDispatch; - PRIMITIVES["event-detail"] = eventDetail; - PRIMITIVES["resource"] = resource; - PRIMITIVES["promise-delayed"] = promiseDelayed; - PRIMITIVES["promise-then"] = promiseThen; - PRIMITIVES["def-store"] = defStore; - PRIMITIVES["use-store"] = useStore; - PRIMITIVES["emit-event"] = emitEvent; - PRIMITIVES["on-event"] = onEvent; - PRIMITIVES["bridge-event"] = bridgeEvent; - // DOM primitives for island code - PRIMITIVES["dom-focus"] = domFocus; - PRIMITIVES["dom-tag-name"] = domTagName; - PRIMITIVES["dom-get-prop"] = domGetProp; - PRIMITIVES["stop-propagation"] = stopPropagation_; - PRIMITIVES["error-message"] = errorMessage; - PRIMITIVES["schedule-idle"] = scheduleIdle; - PRIMITIVES["invoke"] = invoke; - PRIMITIVES["error"] = function(msg) { throw new Error(msg); }; - PRIMITIVES["filter"] = filter; - // DOM primitives for sx-on:* handlers and data-init scripts - if (typeof domBody === "function") PRIMITIVES["dom-body"] = domBody; - if (typeof domQuery === "function") PRIMITIVES["dom-query"] = domQuery; - if (typeof domQueryAll === "function") PRIMITIVES["dom-query-all"] = domQueryAll; - if (typeof domQueryById === "function") PRIMITIVES["dom-query-by-id"] = domQueryById; - if (typeof domSetAttr === "function") PRIMITIVES["dom-set-attr"] = domSetAttr; - if (typeof domGetAttr === "function") PRIMITIVES["dom-get-attr"] = domGetAttr; - if (typeof domRemoveAttr === "function") PRIMITIVES["dom-remove-attr"] = domRemoveAttr; - if (typeof domHasAttr === "function") PRIMITIVES["dom-has-attr?"] = domHasAttr; - if (typeof domAddClass === "function") PRIMITIVES["dom-add-class"] = domAddClass; - if (typeof domRemoveClass === "function") PRIMITIVES["dom-remove-class"] = domRemoveClass; - if (typeof domHasClass === "function") PRIMITIVES["dom-has-class?"] = domHasClass; - if (typeof domClosest === "function") PRIMITIVES["dom-closest"] = domClosest; - if (typeof domMatches === "function") PRIMITIVES["dom-matches?"] = domMatches; - if (typeof preventDefault_ === "function") PRIMITIVES["prevent-default"] = preventDefault_; - if (typeof elementValue === "function") PRIMITIVES["element-value"] = elementValue; - if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml; - if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml; - if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent; - if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse; - if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs; - PRIMITIVES["sx-parse"] = sxParse; - - // Expose deps module functions as primitives so runtime-evaluated SX code - // (e.g. test-deps.sx in browser) can call them - // Platform functions (from PLATFORM_DEPS_JS) - PRIMITIVES["component-deps"] = componentDeps; - PRIMITIVES["component-set-deps!"] = componentSetDeps; - PRIMITIVES["component-css-classes"] = componentCssClasses; - PRIMITIVES["env-components"] = envComponents; - PRIMITIVES["regex-find-all"] = regexFindAll; - PRIMITIVES["scan-css-classes"] = scanCssClasses; - // Transpiled functions (from deps.sx) - PRIMITIVES["scan-refs"] = scanRefs; - PRIMITIVES["scan-refs-walk"] = scanRefsWalk; - PRIMITIVES["transitive-deps"] = transitiveDeps; - PRIMITIVES["transitive-deps-walk"] = transitiveDepsWalk; - PRIMITIVES["compute-all-deps"] = computeAllDeps; - PRIMITIVES["scan-components-from-source"] = scanComponentsFromSource; - PRIMITIVES["components-needed"] = componentsNeeded; - PRIMITIVES["page-component-bundle"] = pageComponentBundle; - PRIMITIVES["page-css-classes"] = pageCssClasses; - PRIMITIVES["scan-io-refs-walk"] = scanIoRefsWalk; - PRIMITIVES["scan-io-refs"] = scanIoRefs; - PRIMITIVES["transitive-io-refs-walk"] = transitiveIoRefsWalk; - PRIMITIVES["transitive-io-refs"] = transitiveIoRefs; - PRIMITIVES["compute-all-io-refs"] = computeAllIoRefs; - PRIMITIVES["component-io-refs-cached"] = componentIoRefsCached; - PRIMITIVES["component-pure?"] = componentPure_p; - PRIMITIVES["render-target"] = renderTarget; - PRIMITIVES["page-render-plan"] = pageRenderPlan; - - // Expose page-helper functions as primitives - PRIMITIVES["categorize-special-forms"] = categorizeSpecialForms; - PRIMITIVES["extract-define-kwargs"] = extractDefineKwargs; - PRIMITIVES["build-reference-data"] = buildReferenceData; - PRIMITIVES["build-ref-items-with-href"] = buildRefItemsWithHref; - PRIMITIVES["build-attr-detail"] = buildAttrDetail; - PRIMITIVES["build-header-detail"] = buildHeaderDetail; - PRIMITIVES["build-event-detail"] = buildEventDetail; - PRIMITIVES["build-component-source"] = buildComponentSource; - PRIMITIVES["build-bundle-analysis"] = buildBundleAnalysis; - PRIMITIVES["build-routing-analysis"] = buildRoutingAnalysis; - PRIMITIVES["build-affinity-analysis"] = buildAffinityAnalysis; - - // ========================================================================= - // Async IO: Promise-aware rendering for client-side IO primitives - // ========================================================================= - // - // IO primitives (query, current-user, etc.) return Promises on the client. - // asyncRenderToDom walks the component tree; when it encounters an IO - // primitive, it awaits the Promise and continues rendering. - // - // The sync evaluator/renderer is untouched. This is a separate async path - // used only when a page's component tree contains IO references. - - var IO_PRIMITIVES = {}; - - function registerIoPrimitive(name, fn) { - IO_PRIMITIVES[name] = fn; - } - - function isPromise(x) { - return x != null && typeof x === "object" && typeof x.then === "function"; - } - - // Async trampoline: resolves thunks, awaits Promises - function asyncTrampoline(val) { - if (isPromise(val)) return val.then(asyncTrampoline); - if (isThunk(val)) return asyncTrampoline(evalExpr(thunkExpr(val), thunkEnv(val))); - return val; - } - - // Async eval: like trampoline(evalExpr(...)) but handles IO primitives - function asyncEval(expr, env) { - // Intercept IO primitive calls at the AST level - if (Array.isArray(expr) && expr.length > 0) { - var head = expr[0]; - if (head && head._sym) { - var name = head.name; - if (IO_PRIMITIVES[name]) { - // Evaluate args, then call the IO primitive - return asyncEvalIoCall(name, expr.slice(1), env); - } - } - } - // Non-IO: use sync eval, but result might be a thunk - var result = evalExpr(expr, env); - return asyncTrampoline(result); - } - - function asyncEvalIoCall(name, rawArgs, env) { - // Parse keyword args and positional args, evaluating each (may be async) - var kwargs = {}; - var args = []; - var promises = []; - var i = 0; - while (i < rawArgs.length) { - var arg = rawArgs[i]; - if (arg && arg._kw && (i + 1) < rawArgs.length) { - var kName = arg.name; - var kVal = asyncEval(rawArgs[i + 1], env); - if (isPromise(kVal)) { - (function(k) { promises.push(kVal.then(function(v) { kwargs[k] = v; })); })(kName); - } else { - kwargs[kName] = kVal; - } - i += 2; - } else { - var aVal = asyncEval(arg, env); - if (isPromise(aVal)) { - (function(idx) { promises.push(aVal.then(function(v) { args[idx] = v; })); })(args.length); - args.push(null); // placeholder - } else { - args.push(aVal); - } - i++; - } - } - var ioFn = IO_PRIMITIVES[name]; - if (promises.length > 0) { - return Promise.all(promises).then(function() { return ioFn(args, kwargs); }); - } - return ioFn(args, kwargs); - } - - // Async render-to-dom: returns Promise or Node - function asyncRenderToDom(expr, env, ns) { - // Literals - if (expr === NIL || expr === null || expr === undefined) return null; - if (expr === true || expr === false) return null; - if (typeof expr === "string") return document.createTextNode(expr); - if (typeof expr === "number") return document.createTextNode(String(expr)); - - // Symbol -> async eval then render - if (expr && expr._sym) { - var val = asyncEval(expr, env); - if (isPromise(val)) return val.then(function(v) { return asyncRenderToDom(v, env, ns); }); - return asyncRenderToDom(val, env, ns); - } - - // Keyword - if (expr && expr._kw) return document.createTextNode(expr.name); - - // DocumentFragment / DOM nodes pass through - if (expr instanceof DocumentFragment || (expr && expr.nodeType)) return expr; - - // Dict -> skip - if (expr && typeof expr === "object" && !Array.isArray(expr)) return null; - - // List - if (!Array.isArray(expr) || expr.length === 0) return null; - - var head = expr[0]; - if (!head) return null; - - // Symbol head - if (head._sym) { - var hname = head.name; - - // IO primitive - if (IO_PRIMITIVES[hname]) { - var ioResult = asyncEval(expr, env); - if (isPromise(ioResult)) return ioResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); - return asyncRenderToDom(ioResult, env, ns); - } - - // Fragment - if (hname === "<>") return asyncRenderChildren(expr.slice(1), env, ns); - - // raw! - if (hname === "raw!") { - return asyncEvalRaw(expr.slice(1), env); - } - - // Special forms that need async handling - if (hname === "if") return asyncRenderIf(expr, env, ns); - if (hname === "when") return asyncRenderWhen(expr, env, ns); - if (hname === "cond") return asyncRenderCond(expr, env, ns); - if (hname === "case") return asyncRenderCase(expr, env, ns); - if (hname === "let" || hname === "let*") return asyncRenderLet(expr, env, ns); - if (hname === "begin" || hname === "do") return asyncRenderChildren(expr.slice(1), env, ns); - if (hname === "map") return asyncRenderMap(expr, env, ns); - if (hname === "map-indexed") return asyncRenderMapIndexed(expr, env, ns); - if (hname === "for-each") return asyncRenderMap(expr, env, ns); - - // define/defcomp/defmacro — eval for side effects - if (hname === "define" || hname === "defcomp" || hname === "defmacro" || - hname === "defstyle" || hname === "defhandler") { - trampoline(evalExpr(expr, env)); - return null; - } - - // quote - if (hname === "quote") return null; - - // lambda/fn - if (hname === "lambda" || hname === "fn") { - trampoline(evalExpr(expr, env)); - return null; - } - - // and/or — eval and render result - if (hname === "and" || hname === "or" || hname === "->") { - var aoResult = asyncEval(expr, env); - if (isPromise(aoResult)) return aoResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); - return asyncRenderToDom(aoResult, env, ns); - } - - // set! - if (hname === "set!") { - asyncEval(expr, env); - return null; - } - - // Component or Island - if (hname.charAt(0) === "~") { - var comp = env[hname]; - if (comp && comp._island) return renderDomIsland(comp, expr.slice(1), env, ns); - if (comp && comp._component) return asyncRenderComponent(comp, expr.slice(1), env, ns); - if (comp && comp._macro) { - var expanded = trampoline(expandMacro(comp, expr.slice(1), env)); - return asyncRenderToDom(expanded, env, ns); - } - } - - // Macro - if (env[hname] && env[hname]._macro) { - var mac = env[hname]; - var expanded = trampoline(expandMacro(mac, expr.slice(1), env)); - return asyncRenderToDom(expanded, env, ns); - } - - // HTML tag - if (typeof renderDomElement === "function" && contains(HTML_TAGS, hname)) { - return asyncRenderElement(hname, expr.slice(1), env, ns); - } - - // html: prefix - if (hname.indexOf("html:") === 0) { - return asyncRenderElement(hname.slice(5), expr.slice(1), env, ns); - } - - // Custom element - if (hname.indexOf("-") >= 0 && expr.length > 1 && expr[1] && expr[1]._kw) { - return asyncRenderElement(hname, expr.slice(1), env, ns); - } - - // SVG context - if (ns) return asyncRenderElement(hname, expr.slice(1), env, ns); - - // Fallback: eval and render - var fResult = asyncEval(expr, env); - if (isPromise(fResult)) return fResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); - return asyncRenderToDom(fResult, env, ns); - } - - // Non-symbol head: eval call - var cResult = asyncEval(expr, env); - if (isPromise(cResult)) return cResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); - return asyncRenderToDom(cResult, env, ns); - } - - function asyncRenderChildren(exprs, env, ns) { - var frag = document.createDocumentFragment(); - var pending = []; - for (var i = 0; i < exprs.length; i++) { - var result = asyncRenderToDom(exprs[i], env, ns); - if (isPromise(result)) { - // Insert placeholder, replace when resolved - var placeholder = document.createComment("async"); - frag.appendChild(placeholder); - (function(ph) { - pending.push(result.then(function(node) { - if (node) ph.parentNode.replaceChild(node, ph); - else ph.parentNode.removeChild(ph); - })); - })(placeholder); - } else if (result) { - frag.appendChild(result); - } - } - if (pending.length > 0) { - return Promise.all(pending).then(function() { return frag; }); - } - return frag; - } - - function asyncRenderElement(tag, args, env, ns) { - var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns; - var el = domCreateElement(tag, newNs); - var pending = []; - var isVoid = contains(VOID_ELEMENTS, tag); - for (var i = 0; i < args.length; i++) { - var arg = args[i]; - if (arg && arg._kw && (i + 1) < args.length) { - var attrName = arg.name; - var attrVal = asyncEval(args[i + 1], env); - i++; - if (isPromise(attrVal)) { - (function(an, av) { - pending.push(av.then(function(v) { - if (!isNil(v) && v !== false) { - if (contains(BOOLEAN_ATTRS, an)) { if (isSxTruthy(v)) el.setAttribute(an, ""); } - else if (v === true) el.setAttribute(an, ""); - else el.setAttribute(an, String(v)); - } - })); - })(attrName, attrVal); - } else { - if (!isNil(attrVal) && attrVal !== false) { - if (contains(BOOLEAN_ATTRS, attrName)) { - if (isSxTruthy(attrVal)) el.setAttribute(attrName, ""); - } else if (attrVal === true) { - el.setAttribute(attrName, ""); - } else { - el.setAttribute(attrName, String(attrVal)); - } - } - } - } else if (!isVoid) { - var child = asyncRenderToDom(arg, env, newNs); - if (isPromise(child)) { - var placeholder = document.createComment("async"); - el.appendChild(placeholder); - (function(ph) { - pending.push(child.then(function(node) { - if (node) ph.parentNode.replaceChild(node, ph); - else ph.parentNode.removeChild(ph); - })); - })(placeholder); - } else if (child) { - el.appendChild(child); - } - } - } - if (pending.length > 0) return Promise.all(pending).then(function() { return el; }); - return el; - } - - function asyncRenderComponent(comp, args, env, ns) { - var kwargs = {}; - var children = []; - var pending = []; - for (var i = 0; i < args.length; i++) { - var arg = args[i]; - if (arg && arg._kw && (i + 1) < args.length) { - var kName = arg.name; - var kVal = asyncEval(args[i + 1], env); - if (isPromise(kVal)) { - (function(k) { pending.push(kVal.then(function(v) { kwargs[k] = v; })); })(kName); - } else { - kwargs[kName] = kVal; - } - i++; - } else { - children.push(arg); - } - } - - function doRender() { - var local = Object.create(componentClosure(comp)); - for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; - var params = componentParams(comp); - for (var j = 0; j < params.length; j++) { - local[params[j]] = params[j] in kwargs ? kwargs[params[j]] : NIL; - } - if (componentHasChildren(comp)) { - var childResult = asyncRenderChildren(children, env, ns); - if (isPromise(childResult)) { - return childResult.then(function(childFrag) { - local["children"] = childFrag; - return asyncRenderToDom(componentBody(comp), local, ns); - }); - } - local["children"] = childResult; - } - return asyncRenderToDom(componentBody(comp), local, ns); - } - - if (pending.length > 0) return Promise.all(pending).then(doRender); - return doRender(); - } - - function asyncRenderIf(expr, env, ns) { - var cond = asyncEval(expr[1], env); - if (isPromise(cond)) { - return cond.then(function(v) { - return isSxTruthy(v) - ? asyncRenderToDom(expr[2], env, ns) - : (expr.length > 3 ? asyncRenderToDom(expr[3], env, ns) : null); - }); - } - return isSxTruthy(cond) - ? asyncRenderToDom(expr[2], env, ns) - : (expr.length > 3 ? asyncRenderToDom(expr[3], env, ns) : null); - } - - function asyncRenderWhen(expr, env, ns) { - var cond = asyncEval(expr[1], env); - if (isPromise(cond)) { - return cond.then(function(v) { - return isSxTruthy(v) ? asyncRenderChildren(expr.slice(2), env, ns) : null; - }); - } - return isSxTruthy(cond) ? asyncRenderChildren(expr.slice(2), env, ns) : null; - } - - function asyncRenderCond(expr, env, ns) { - var clauses = expr.slice(1); - function step(idx) { - if (idx >= clauses.length) return null; - var clause = clauses[idx]; - if (!Array.isArray(clause) || clause.length < 2) return step(idx + 1); - var test = clause[0]; - if ((test && test._sym && (test.name === "else" || test.name === ":else")) || - (test && test._kw && test.name === "else")) { - return asyncRenderToDom(clause[1], env, ns); - } - var v = asyncEval(test, env); - if (isPromise(v)) return v.then(function(r) { return isSxTruthy(r) ? asyncRenderToDom(clause[1], env, ns) : step(idx + 1); }); - return isSxTruthy(v) ? asyncRenderToDom(clause[1], env, ns) : step(idx + 1); - } - return step(0); - } - - function asyncRenderCase(expr, env, ns) { - var matchVal = asyncEval(expr[1], env); - function doCase(mv) { - var clauses = expr.slice(2); - for (var i = 0; i < clauses.length - 1; i += 2) { - var test = clauses[i]; - if ((test && test._kw && test.name === "else") || - (test && test._sym && (test.name === "else" || test.name === ":else"))) { - return asyncRenderToDom(clauses[i + 1], env, ns); - } - var tv = trampoline(evalExpr(test, env)); - if (mv === tv || (typeof mv === "string" && typeof tv === "string" && mv === tv)) { - return asyncRenderToDom(clauses[i + 1], env, ns); - } - } - return null; - } - if (isPromise(matchVal)) return matchVal.then(doCase); - return doCase(matchVal); - } - - function asyncRenderLet(expr, env, ns) { - var bindings = expr[1]; - var local = Object.create(env); - for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; - function bindStep(idx) { - if (!Array.isArray(bindings)) return asyncRenderChildren(expr.slice(2), local, ns); - // Nested pairs: ((a 1) (b 2)) - if (bindings.length > 0 && Array.isArray(bindings[0])) { - if (idx >= bindings.length) return asyncRenderChildren(expr.slice(2), local, ns); - var b = bindings[idx]; - var vname = b[0]._sym ? b[0].name : String(b[0]); - var val = asyncEval(b[1], local); - if (isPromise(val)) return val.then(function(v) { local[vname] = v; return bindStep(idx + 1); }); - local[vname] = val; - return bindStep(idx + 1); - } - // Flat pairs: (a 1 b 2) - if (idx >= bindings.length) return asyncRenderChildren(expr.slice(2), local, ns); - var vn = bindings[idx]._sym ? bindings[idx].name : String(bindings[idx]); - var vv = asyncEval(bindings[idx + 1], local); - if (isPromise(vv)) return vv.then(function(v) { local[vn] = v; return bindStep(idx + 2); }); - local[vn] = vv; - return bindStep(idx + 2); - } - return bindStep(0); - } - - function asyncRenderMap(expr, env, ns) { - var fn = asyncEval(expr[1], env); - var coll = asyncEval(expr[2], env); - function doMap(f, c) { - if (!Array.isArray(c)) return null; - var frag = document.createDocumentFragment(); - var pending = []; - for (var i = 0; i < c.length; i++) { - var item = c[i]; - var result; - if (f && f._lambda) { - var lenv = Object.create(f.closure || env); - for (var k in env) if (env.hasOwnProperty(k)) lenv[k] = env[k]; - lenv[f.params[0]] = item; - result = asyncRenderToDom(f.body, lenv, null); - } else if (typeof f === "function") { - var r = f(item); - result = isPromise(r) ? r.then(function(v) { return asyncRenderToDom(v, env, null); }) : asyncRenderToDom(r, env, null); - } else { - result = asyncRenderToDom(item, env, null); - } - if (isPromise(result)) { - var ph = document.createComment("async"); - frag.appendChild(ph); - (function(p) { pending.push(result.then(function(n) { if (n) p.parentNode.replaceChild(n, p); else p.parentNode.removeChild(p); })); })(ph); - } else if (result) { - frag.appendChild(result); - } - } - if (pending.length) return Promise.all(pending).then(function() { return frag; }); - return frag; - } - if (isPromise(fn) || isPromise(coll)) { - return Promise.all([isPromise(fn) ? fn : Promise.resolve(fn), isPromise(coll) ? coll : Promise.resolve(coll)]) - .then(function(r) { return doMap(r[0], r[1]); }); - } - return doMap(fn, coll); - } - - function asyncRenderMapIndexed(expr, env, ns) { - var fn = asyncEval(expr[1], env); - var coll = asyncEval(expr[2], env); - function doMap(f, c) { - if (!Array.isArray(c)) return null; - var frag = document.createDocumentFragment(); - var pending = []; - for (var i = 0; i < c.length; i++) { - var item = c[i]; - var result; - if (f && f._lambda) { - var lenv = Object.create(f.closure || env); - for (var k in env) if (env.hasOwnProperty(k)) lenv[k] = env[k]; - lenv[f.params[0]] = i; - lenv[f.params[1]] = item; - result = asyncRenderToDom(f.body, lenv, null); - } else if (typeof f === "function") { - var r = f(i, item); - result = isPromise(r) ? r.then(function(v) { return asyncRenderToDom(v, env, null); }) : asyncRenderToDom(r, env, null); - } else { - result = asyncRenderToDom(item, env, null); - } - if (isPromise(result)) { - var ph = document.createComment("async"); - frag.appendChild(ph); - (function(p) { pending.push(result.then(function(n) { if (n) p.parentNode.replaceChild(n, p); else p.parentNode.removeChild(p); })); })(ph); - } else if (result) { - frag.appendChild(result); - } - } - if (pending.length) return Promise.all(pending).then(function() { return frag; }); - return frag; - } - if (isPromise(fn) || isPromise(coll)) { - return Promise.all([isPromise(fn) ? fn : Promise.resolve(fn), isPromise(coll) ? coll : Promise.resolve(coll)]) - .then(function(r) { return doMap(r[0], r[1]); }); - } - return doMap(fn, coll); - } - - function asyncEvalRaw(args, env) { - var parts = []; - var pending = []; - for (var i = 0; i < args.length; i++) { - var val = asyncEval(args[i], env); - if (isPromise(val)) { - (function(idx) { - pending.push(val.then(function(v) { parts[idx] = v; })); - })(parts.length); - parts.push(null); - } else { - parts.push(val); - } - } - function assemble() { - var html = ""; - for (var j = 0; j < parts.length; j++) { - var p = parts[j]; - if (p && p._rawHtml) html += p.html; - else if (typeof p === "string") html += p; - else if (p != null && !isNil(p)) html += String(p); - } - var el = document.createElement("span"); - el.innerHTML = html; - var frag = document.createDocumentFragment(); - while (el.firstChild) frag.appendChild(el.firstChild); - return frag; - } - if (pending.length) return Promise.all(pending).then(assemble); - return assemble(); - } - - // Async version of sxRenderWithEnv — returns Promise - function asyncSxRenderWithEnv(source, extraEnv) { - var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; - var exprs = parse(source); - if (!_hasDom) return Promise.resolve(null); - return asyncRenderChildren(exprs, env, null); - } - - // IO proxy cache: key → { value, expires } - var _ioCache = {}; - var IO_CACHE_TTL = 300000; // 5 minutes - - // Register a server-proxied IO primitive: fetches from /sx/io/ - // Uses GET for short args, POST for long payloads (URL length safety). - // Results are cached client-side by (name + args) with a TTL. - function registerProxiedIo(name) { - registerIoPrimitive(name, function(args, kwargs) { - // Cache key: name + serialized args - var cacheKey = name; - for (var ci = 0; ci < args.length; ci++) cacheKey += "" + String(args[ci]); - for (var ck in kwargs) { - if (kwargs.hasOwnProperty(ck)) cacheKey += "" + ck + "=" + String(kwargs[ck]); - } - var cached = _ioCache[cacheKey]; - if (cached && cached.expires > Date.now()) return cached.value; - - var url = "/sx/io/" + encodeURIComponent(name); - var qs = []; - for (var i = 0; i < args.length; i++) { - qs.push("_arg" + i + "=" + encodeURIComponent(String(args[i]))); - } - for (var k in kwargs) { - if (kwargs.hasOwnProperty(k)) { - qs.push(encodeURIComponent(k) + "=" + encodeURIComponent(String(kwargs[k]))); - } - } - var queryStr = qs.join("&"); - var fetchOpts; - if (queryStr.length > 1500) { - // POST with JSON body for long payloads - var sArgs = []; - for (var j = 0; j < args.length; j++) sArgs.push(String(args[j])); - var sKwargs = {}; - for (var kk in kwargs) { - if (kwargs.hasOwnProperty(kk)) sKwargs[kk] = String(kwargs[kk]); - } - var postHeaders = { "SX-Request": "true", "Content-Type": "application/json" }; - var csrf = csrfToken(); - if (csrf && csrf !== NIL) postHeaders["X-CSRFToken"] = csrf; - fetchOpts = { - method: "POST", - headers: postHeaders, - body: JSON.stringify({ args: sArgs, kwargs: sKwargs }) - }; - } else { - if (queryStr) url += "?" + queryStr; - fetchOpts = { headers: { "SX-Request": "true" } }; - } - var result = fetch(url, fetchOpts) - .then(function(resp) { - if (!resp.ok) { - logWarn("sx:io " + name + " failed " + resp.status); - return NIL; - } - return resp.text(); - }) - .then(function(text) { - if (!text || text === "nil") return NIL; - try { - var exprs = parse(text); - var val = exprs.length === 1 ? exprs[0] : exprs; - _ioCache[cacheKey] = { value: val, expires: Date.now() + IO_CACHE_TTL }; - return val; - } catch (e) { - logWarn("sx:io " + name + " parse error: " + (e && e.message ? e.message : e)); - return NIL; - } - }) - .catch(function(e) { - logWarn("sx:io " + name + " network error: " + (e && e.message ? e.message : e)); - return NIL; - }); - // Cache the in-flight promise too (dedup concurrent calls for same args) - _ioCache[cacheKey] = { value: result, expires: Date.now() + IO_CACHE_TTL }; - return result; - }); - } - - // Register IO deps as proxied primitives (idempotent, called per-page) - function registerIoDeps(names) { - if (!names || !names.length) return; - var registered = 0; - for (var i = 0; i < names.length; i++) { - var name = names[i]; - if (!IO_PRIMITIVES[name]) { - registerProxiedIo(name); - registered++; - } - } - if (registered > 0) { - logInfo("sx:io registered " + registered + " proxied primitives: " + names.join(", ")); - } - } - - - // Parser — compiled from parser.sx (see PLATFORM_PARSER_JS for ident char classes) - var parse = sxParse; - - // ========================================================================= - // Public API - // ========================================================================= - - var componentEnv = {}; - - function loadComponents(source) { - var exprs = parse(source); - for (var i = 0; i < exprs.length; i++) { - trampoline(evalExpr(exprs[i], componentEnv)); - } - } - - function render(source) { - if (!_hasDom) { - var exprs = parse(source); - var parts = []; - for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); - return parts.join(""); - } - var exprs = parse(source); - var frag = document.createDocumentFragment(); - for (var i = 0; i < exprs.length; i++) frag.appendChild(renderToDom(exprs[i], merge(componentEnv), null)); - return frag; - } - - function renderToString(source) { - var exprs = parse(source); - var parts = []; - for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); - return parts.join(""); - } - - var Sx = { - VERSION: "ref-2.0", - parse: parse, - parseAll: parse, - eval: function(expr, env) { return trampoline(evalExpr(expr, env || merge(componentEnv))); }, - loadComponents: loadComponents, - render: render, - renderToString: renderToString, - serialize: serialize, - NIL: NIL, - Symbol: Symbol, - Keyword: Keyword, - isTruthy: isSxTruthy, - isNil: isNil, - componentEnv: componentEnv, - renderToHtml: function(expr, env) { return renderToHtml(expr, env || merge(componentEnv)); }, - renderToSx: function(expr, env) { return renderToSx(expr, env || merge(componentEnv)); }, - renderToDom: _hasDom ? function(expr, env, ns) { return renderToDom(expr, env || merge(componentEnv), ns || null); } : null, - parseTriggerSpec: typeof parseTriggerSpec === "function" ? parseTriggerSpec : null, - parseTime: typeof parseTime === "function" ? parseTime : null, - defaultTrigger: typeof defaultTrigger === "function" ? defaultTrigger : null, - parseSwapSpec: typeof parseSwapSpec === "function" ? parseSwapSpec : null, - parseRetrySpec: typeof parseRetrySpec === "function" ? parseRetrySpec : null, - nextRetryMs: typeof nextRetryMs === "function" ? nextRetryMs : null, - filterParams: typeof filterParams === "function" ? filterParams : null, - morphNode: typeof morphNode === "function" ? morphNode : null, - morphChildren: typeof morphChildren === "function" ? morphChildren : null, - swapDomNodes: typeof swapDomNodes === "function" ? swapDomNodes : null, - process: typeof processElements === "function" ? processElements : null, - executeRequest: typeof executeRequest === "function" ? executeRequest : null, - postSwap: typeof postSwap === "function" ? postSwap : null, - processScripts: typeof processSxScripts === "function" ? processSxScripts : null, - mount: typeof sxMount === "function" ? sxMount : null, - hydrate: typeof sxHydrateElements === "function" ? sxHydrateElements : null, - update: typeof sxUpdateElement === "function" ? sxUpdateElement : null, - renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null, - getEnv: function() { return componentEnv; }, - resolveSuspense: typeof resolveSuspense === "function" ? resolveSuspense : null, - hydrateIslands: typeof sxHydrateIslands === "function" ? sxHydrateIslands : null, - disposeIsland: typeof disposeIsland === "function" ? disposeIsland : null, - init: typeof bootInit === "function" ? bootInit : null, - scanRefs: scanRefs, - scanComponentsFromSource: scanComponentsFromSource, - transitiveDeps: transitiveDeps, - computeAllDeps: computeAllDeps, - componentsNeeded: componentsNeeded, - pageComponentBundle: pageComponentBundle, - pageCssClasses: pageCssClasses, - scanIoRefs: scanIoRefs, - transitiveIoRefs: transitiveIoRefs, - computeAllIoRefs: computeAllIoRefs, - componentPure_p: componentPure_p, - categorizeSpecialForms: categorizeSpecialForms, - buildReferenceData: buildReferenceData, - buildAttrDetail: buildAttrDetail, - buildHeaderDetail: buildHeaderDetail, - buildEventDetail: buildEventDetail, - buildComponentSource: buildComponentSource, - buildBundleAnalysis: buildBundleAnalysis, - buildRoutingAnalysis: buildRoutingAnalysis, - buildAffinityAnalysis: buildAffinityAnalysis, - splitPathSegments: splitPathSegments, - parseRoutePattern: parseRoutePattern, - matchRoute: matchRoute, - findMatchingRoute: findMatchingRoute, - registerIo: typeof registerIoPrimitive === "function" ? registerIoPrimitive : null, - registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null, - asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null, - asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null, - signal: signal, - deref: deref, - reset: reset_b, - swap: swap_b, - computed: computed, - effect: effect, - batch: batch, - isSignal: isSignal, - makeSignal: makeSignal, - defStore: defStore, - useStore: useStore, - clearStores: clearStores, - emitEvent: emitEvent, - onEvent: onEvent, - bridgeEvent: bridgeEvent, - _version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)" - }; - - - // --- Popstate listener --- - if (typeof window !== "undefined") { - window.addEventListener("popstate", function(e) { - handlePopstate(e && e.state ? e.state.scrollY || 0 : 0); - }); - } - - // --- Auto-init --- - if (typeof document !== "undefined") { - var _sxInit = function() { - bootInit(); - // Process any suspense resolutions that arrived before init - if (global.__sxPending) { - for (var pi = 0; pi < global.__sxPending.length; pi++) { - resolveSuspense(global.__sxPending[pi].id, global.__sxPending[pi].sx); - } - global.__sxPending = null; - } - // Set up direct resolution for future chunks - global.__sxResolve = function(id, sx) { resolveSuspense(id, sx); }; - // Register service worker for offline data caching - if ("serviceWorker" in navigator) { - navigator.serviceWorker.register("/sx-sw.js", { scope: "/" }).then(function(reg) { - logInfo("sx:sw registered (scope: " + reg.scope + ")"); - }).catch(function(err) { - logWarn("sx:sw registration failed: " + (err && err.message ? err.message : err)); - }); - } - }; - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", _sxInit); - } else { - _sxInit(); - } - } - if (typeof module !== "undefined" && module.exports) module.exports = Sx; - else global.Sx = Sx; - -})(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : this); \ No newline at end of file diff --git a/shared/sx/ref/bootstrap_py.py b/shared/sx/ref/bootstrap_py.py index 2a168063..4cd506b8 100644 --- a/shared/sx/ref/bootstrap_py.py +++ b/shared/sx/ref/bootstrap_py.py @@ -539,6 +539,9 @@ class PyEmitter: clauses = expr[1:] if not clauses: return "NIL" + # Check ALL clauses are 2-element lists (scheme-style). + # Checking only the first is ambiguous — (nil? x) is a 2-element + # function call, not a scheme clause ((test body)). is_scheme = ( all(isinstance(c, list) and len(c) == 2 for c in clauses) and not any(isinstance(c, Keyword) for c in clauses) diff --git a/shared/sx/ref/engine.sx b/shared/sx/ref/engine.sx index e626d56d..cdbfb140 100644 --- a/shared/sx/ref/engine.sx +++ b/shared/sx/ref/engine.sx @@ -34,11 +34,12 @@ (define parse-time (fn (s) ;; Parse time string: "2s" → 2000, "500ms" → 500 - (cond - (nil? s) 0 - (ends-with? s "ms") (parse-int s 0) - (ends-with? s "s") (* (parse-int (replace s "s" "") 0) 1000) - :else (parse-int s 0)))) + ;; Uses nested if (not cond) because cond misclassifies 2-element + ;; function calls like (nil? s) as scheme-style ((test body)) clauses. + (if (nil? s) 0 + (if (ends-with? s "ms") (parse-int s 0) + (if (ends-with? s "s") (* (parse-int (replace s "s" "") 0) 1000) + (parse-int s 0)))))) (define parse-trigger-spec @@ -219,20 +220,19 @@ ;; Filter form parameters by sx-params spec. ;; all-params is a list of (key value) pairs. ;; Returns filtered list of (key value) pairs. - (cond - (nil? params-spec) all-params - (= params-spec "none") (list) - (= params-spec "*") all-params - (starts-with? params-spec "not ") - (let ((excluded (map trim (split (slice params-spec 4) ",")))) - (filter - (fn (p) (not (contains? excluded (first p)))) - all-params)) - :else - (let ((allowed (map trim (split params-spec ",")))) - (filter - (fn (p) (contains? allowed (first p))) - all-params))))) + ;; Uses nested if (not cond) — see parse-time comment. + (if (nil? params-spec) all-params + (if (= params-spec "none") (list) + (if (= params-spec "*") all-params + (if (starts-with? params-spec "not ") + (let ((excluded (map trim (split (slice params-spec 4) ",")))) + (filter + (fn (p) (not (contains? excluded (first p)))) + all-params)) + (let ((allowed (map trim (split params-spec ",")))) + (filter + (fn (p) (contains? allowed (first p))) + all-params)))))))) ;; -------------------------------------------------------------------------- diff --git a/shared/sx/ref/eval.sx b/shared/sx/ref/eval.sx index 1aa55dbd..b8ea21fe 100644 --- a/shared/sx/ref/eval.sx +++ b/shared/sx/ref/eval.sx @@ -309,14 +309,18 @@ nil)))) +;; cond-scheme? — check if ALL clauses are 2-element lists (scheme-style). +;; Checking only the first arg is ambiguous — (nil? x) is a 2-element +;; function call, not a scheme clause ((test body)). +(define cond-scheme? + (fn (clauses) + (every? (fn (c) (and (= (type-of c) "list") (= (len c) 2))) + clauses))) + (define sf-cond (fn (args env) - ;; Detect scheme-style: first arg is a 2-element list - (if (and (= (type-of (first args)) "list") - (= (len (first args)) 2)) - ;; Scheme-style: ((test body) ...) + (if (cond-scheme? args) (sf-cond-scheme args env) - ;; Clojure-style: test body test body ... (sf-cond-clojure args env)))) (define sf-cond-scheme diff --git a/shared/sx/ref/render.sx b/shared/sx/ref/render.sx index e6793ca7..f967eadf 100644 --- a/shared/sx/ref/render.sx +++ b/shared/sx/ref/render.sx @@ -134,12 +134,8 @@ ;; (test body test body ...). (define eval-cond (fn (clauses env) - (if (and (not (empty? clauses)) - (= (type-of (first clauses)) "list") - (= (len (first clauses)) 2)) - ;; Scheme-style + (if (cond-scheme? clauses) (eval-cond-scheme clauses env) - ;; Clojure-style (eval-cond-clojure clauses env)))) (define eval-cond-scheme diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py index f7bc12a5..d937b2da 100644 --- a/shared/sx/ref/sx_ref.py +++ b/shared/sx/ref/sx_ref.py @@ -1357,9 +1357,13 @@ def sf_when(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(((type_of(first(args)) == 'list') if not sx_truthy((type_of(first(args)) == 'list')) else (len(first(args)) == 2))): + if sx_truthy(cond_scheme_p(args)): return sf_cond_scheme(args, env) else: return sf_cond_clojure(args, env) @@ -1859,7 +1863,7 @@ def render_attrs(attrs): # eval-cond def eval_cond(clauses, env): - if sx_truthy(((not sx_truthy(empty_p(clauses))) if not sx_truthy((not sx_truthy(empty_p(clauses)))) else ((type_of(first(clauses)) == 'list') if not sx_truthy((type_of(first(clauses)) == 'list')) else (len(first(clauses)) == 2)))): + if sx_truthy(cond_scheme_p(clauses)): return eval_cond_scheme(clauses, env) else: return eval_cond_clojure(clauses, env) @@ -3833,4 +3837,4 @@ def render(expr, env=None): def make_env(**kwargs): """Create an environment with initial bindings.""" - return _Env(dict(kwargs)) \ No newline at end of file + return _Env(dict(kwargs)) diff --git a/shared/sx/ref/test-aser.sx b/shared/sx/ref/test-aser.sx index 217b0a44..b70ae768 100644 --- a/shared/sx/ref/test-aser.sx +++ b/shared/sx/ref/test-aser.sx @@ -115,6 +115,13 @@ (assert-equal "(p \"two\")" (render-sx "(cond false (p \"one\") true (p \"two\") :else (p \"three\"))"))) + (deftest "cond with 2-element predicate test" + ;; Regression: cond misclassifies (nil? x) as scheme-style clause. + (assert-equal "(p \"yes\")" + (render-sx "(cond (nil? nil) (p \"yes\") :else (p \"no\"))")) + (assert-equal "(p \"no\")" + (render-sx "(cond (nil? \"x\") (p \"yes\") :else (p \"no\"))"))) + (deftest "let binds then serializes" (assert-equal "(p \"hello\")" (render-sx "(let ((x \"hello\")) (p x))"))) diff --git a/shared/sx/ref/test-eval.sx b/shared/sx/ref/test-eval.sx index 7e8c4a8f..33885f7a 100644 --- a/shared/sx/ref/test-eval.sx +++ b/shared/sx/ref/test-eval.sx @@ -277,6 +277,29 @@ false "b" :else "c"))) + (deftest "cond with 2-element predicate as first test" + ;; Regression: cond misclassifies Clojure-style as scheme-style when + ;; the first test is a 2-element list like (nil? x) or (empty? x). + ;; The evaluator checks: is first arg a 2-element list? If yes, treats + ;; as scheme-style ((test body) ...) — returning the arg instead of + ;; evaluating the predicate call. + (assert-equal 0 (cond (nil? nil) 0 :else 1)) + (assert-equal 1 (cond (nil? "x") 0 :else 1)) + (assert-equal "empty" (cond (empty? (list)) "empty" :else "not-empty")) + (assert-equal "not-empty" (cond (empty? (list 1)) "empty" :else "not-empty")) + (assert-equal "yes" (cond (not false) "yes" :else "no")) + (assert-equal "no" (cond (not true) "yes" :else "no"))) + + (deftest "cond with 2-element predicate and no :else" + ;; Same bug, but without :else — this is the worst case because the + ;; bootstrapper heuristic also breaks (all clauses are 2-element lists). + (assert-equal "found" + (cond (nil? nil) "found" + (nil? "x") "other")) + (assert-equal "b" + (cond (nil? "x") "a" + (not false) "b"))) + (deftest "and" (assert-true (and true true)) (assert-false (and true false)) diff --git a/shared/sx/ref/test-render.sx b/shared/sx/ref/test-render.sx index ccca649b..1097d7a2 100644 --- a/shared/sx/ref/test-render.sx +++ b/shared/sx/ref/test-render.sx @@ -151,6 +151,13 @@ (assert-equal "

    hello

    " (render-html "(let ((x \"hello\")) (p x))"))) + (deftest "cond with 2-element predicate test" + ;; Regression: cond misclassifies (nil? x) as scheme-style clause. + (assert-equal "

    yes

    " + (render-html "(cond (nil? nil) (p \"yes\") :else (p \"no\"))")) + (assert-equal "

    no

    " + (render-html "(cond (nil? \"x\") (p \"yes\") :else (p \"no\"))"))) + (deftest "let preserves outer scope bindings" ;; Regression: process-bindings must preserve parent env scope chain. ;; Using merge() on Env objects returns empty dict (Env is not dict subclass). From 0174fbfea3b07b490c264f17234a8c8eb8c34198 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 11 Mar 2026 16:56:51 +0000 Subject: [PATCH 6/6] =?UTF-8?q?Regenerate=20sx-browser.js=20=E2=80=94=20fi?= =?UTF-8?q?le=20was=20accidentally=20emptied=20in=20previous=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- shared/static/scripts/sx-browser.js | 6069 +++++++++++++++++++++++++++ 1 file changed, 6069 insertions(+) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index e69de29b..a5cf2edc 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -0,0 +1,6069 @@ +/** + * sx-ref.js — Generated from reference SX evaluator specification. + * + * Bootstrap-compiled from shared/sx/ref/{eval,render,primitives}.sx + * Compare against hand-written sx.js for correctness verification. + * + * DO NOT EDIT — regenerate with: python bootstrap_js.py + */ +;(function(global) { + "use strict"; + + // ========================================================================= + // Types + // ========================================================================= + + var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); + var SX_VERSION = "2026-03-11T16:56:11Z"; + + function isNil(x) { return x === NIL || x === null || x === undefined; } + function isSxTruthy(x) { return x !== false && !isNil(x); } + + function Symbol(name) { this.name = name; } + Symbol.prototype.toString = function() { return this.name; }; + Symbol.prototype._sym = true; + + function Keyword(name) { this.name = name; } + Keyword.prototype.toString = function() { return ":" + this.name; }; + Keyword.prototype._kw = true; + + function Lambda(params, body, closure, name) { + this.params = params; + this.body = body; + this.closure = closure || {}; + this.name = name || null; + } + Lambda.prototype._lambda = true; + + function Component(name, params, hasChildren, body, closure, affinity) { + this.name = name; + this.params = params; + this.hasChildren = hasChildren; + this.body = body; + this.closure = closure || {}; + this.affinity = affinity || "auto"; + } + Component.prototype._component = true; + + function Island(name, params, hasChildren, body, closure) { + this.name = name; + this.params = params; + this.hasChildren = hasChildren; + this.body = body; + this.closure = closure || {}; + } + Island.prototype._island = true; + + function SxSignal(value) { + this.value = value; + this.subscribers = []; + this.deps = []; + } + SxSignal.prototype._signal = true; + + function TrackingCtx(notifyFn) { + this.notifyFn = notifyFn; + this.deps = []; + } + + var _trackingContext = null; + + function Macro(params, restParam, body, closure, name) { + this.params = params; + this.restParam = restParam; + this.body = body; + this.closure = closure || {}; + this.name = name || null; + } + Macro.prototype._macro = true; + + function Thunk(expr, env) { this.expr = expr; this.env = env; } + Thunk.prototype._thunk = true; + + function RawHTML(html) { this.html = html; } + RawHTML.prototype._raw = true; + + function isSym(x) { return x != null && x._sym === true; } + function isKw(x) { return x != null && x._kw === true; } + + function merge() { + var out = {}; + for (var i = 0; i < arguments.length; i++) { + var d = arguments[i]; + if (d) for (var k in d) out[k] = d[k]; + } + return out; + } + + function sxOr() { + for (var i = 0; i < arguments.length; i++) { + if (isSxTruthy(arguments[i])) return arguments[i]; + } + return arguments.length ? arguments[arguments.length - 1] : false; + } + + // ========================================================================= + // Platform interface — JS implementation + // ========================================================================= + + function typeOf(x) { + if (isNil(x)) return "nil"; + if (typeof x === "number") return "number"; + if (typeof x === "string") return "string"; + if (typeof x === "boolean") return "boolean"; + if (x._sym) return "symbol"; + if (x._kw) return "keyword"; + if (x._thunk) return "thunk"; + if (x._lambda) return "lambda"; + if (x._component) return "component"; + if (x._island) return "island"; + if (x._signal) return "signal"; + if (x._macro) return "macro"; + if (x._raw) return "raw-html"; + if (typeof Node !== "undefined" && x instanceof Node) return "dom-node"; + if (Array.isArray(x)) return "list"; + if (typeof x === "object") return "dict"; + return "unknown"; + } + + function symbolName(s) { return s.name; } + function keywordName(k) { return k.name; } + function makeSymbol(n) { return new Symbol(n); } + function makeKeyword(n) { return new Keyword(n); } + + function makeLambda(params, body, env) { return new Lambda(params, body, merge(env)); } + function makeComponent(name, params, hasChildren, body, env, affinity) { + return new Component(name, params, hasChildren, body, merge(env), affinity); + } + function makeMacro(params, restParam, body, env, name) { + return new Macro(params, restParam, body, merge(env), name); + } + function makeThunk(expr, env) { return new Thunk(expr, env); } + + function lambdaParams(f) { return f.params; } + function lambdaBody(f) { return f.body; } + function lambdaClosure(f) { return f.closure; } + function lambdaName(f) { return f.name; } + function setLambdaName(f, n) { f.name = n; } + + function componentParams(c) { return c.params; } + function componentBody(c) { return c.body; } + function componentClosure(c) { return c.closure; } + function componentHasChildren(c) { return c.hasChildren; } + function componentName(c) { return c.name; } + function componentAffinity(c) { return c.affinity || "auto"; } + + function macroParams(m) { return m.params; } + function macroRestParam(m) { return m.restParam; } + function macroBody(m) { return m.body; } + function macroClosure(m) { return m.closure; } + + function isThunk(x) { return x != null && x._thunk === true; } + function thunkExpr(t) { return t.expr; } + function thunkEnv(t) { return t.env; } + + function isCallable(x) { return typeof x === "function" || (x != null && x._lambda === true); } + function isLambda(x) { return x != null && x._lambda === true; } + function isComponent(x) { return x != null && x._component === true; } + function isIsland(x) { return x != null && x._island === true; } + function isMacro(x) { return x != null && x._macro === true; } + function isIdentical(a, b) { return a === b; } + + // Island platform + function makeIsland(name, params, hasChildren, body, env) { + return new Island(name, params, hasChildren, body, merge(env)); + } + + // Signal platform + function makeSignal(value) { return new SxSignal(value); } + function isSignal(x) { return x != null && x._signal === true; } + function signalValue(s) { return s.value; } + function signalSetValue(s, v) { s.value = v; } + function signalSubscribers(s) { return s.subscribers.slice(); } + function signalAddSub(s, fn) { if (s.subscribers.indexOf(fn) < 0) s.subscribers.push(fn); } + function signalRemoveSub(s, fn) { var i = s.subscribers.indexOf(fn); if (i >= 0) s.subscribers.splice(i, 1); } + function signalDeps(s) { return s.deps.slice(); } + function signalSetDeps(s, deps) { s.deps = Array.isArray(deps) ? deps.slice() : []; } + function setTrackingContext(ctx) { _trackingContext = ctx; } + function getTrackingContext() { return _trackingContext || NIL; } + function makeTrackingContext(notifyFn) { return new TrackingCtx(notifyFn); } + function trackingContextDeps(ctx) { return ctx ? ctx.deps : []; } + function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); } + function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; } + + // invoke — call any callable (native fn or SX lambda) with args. + // Transpiled code emits direct calls f(args) which fail on SX lambdas + // from runtime-evaluated island bodies. invoke dispatches correctly. + function invoke() { + var f = arguments[0]; + var args = Array.prototype.slice.call(arguments, 1); + if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f))); + if (typeof f === 'function') return f.apply(null, args); + return NIL; + } + + // JSON / dict helpers for island state serialization + function jsonSerialize(obj) { + return JSON.stringify(obj); + } + function isEmptyDict(d) { + if (!d || typeof d !== "object") return true; + for (var k in d) if (d.hasOwnProperty(k)) return false; + return true; + } + + function envHas(env, name) { return name in env; } + function envGet(env, name) { return env[name]; } + function envSet(env, name, val) { + // Walk prototype chain to find where the variable is defined (for set!) + var obj = env; + while (obj !== null && obj !== Object.prototype) { + if (obj.hasOwnProperty(name)) { obj[name] = val; return; } + obj = Object.getPrototypeOf(obj); + } + // Not found in any parent scope — set on the immediate env + env[name] = val; + } + function envExtend(env) { return Object.create(env); } + function envMerge(base, overlay) { + // Same env or overlay is descendant of base — just extend, no copy. + // This prevents set! inside lambdas from modifying shadow copies. + if (base === overlay) return Object.create(base); + var p = overlay; + for (var d = 0; p && p !== Object.prototype && d < 100; d++) { + if (p === base) return Object.create(base); + p = Object.getPrototypeOf(p); + } + // General case: extend base, copy ONLY overlay properties that don't + // exist in the base chain (avoids shadowing closure bindings). + var child = Object.create(base); + if (overlay) { + for (var k in overlay) { + if (overlay.hasOwnProperty(k) && !(k in base)) child[k] = overlay[k]; + } + } + return child; + } + + function dictSet(d, k, v) { d[k] = v; return v; } + function dictGet(d, k) { var v = d[k]; return v !== undefined ? v : NIL; } + + // Render-expression detection — lets the evaluator delegate to the active adapter. + // Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements. + // Placeholder — overridden by transpiled version from render.sx + function isRenderExpr(expr) { return false; } + + // Render dispatch — call the active adapter's render function. + // Set by each adapter when loaded; defaults to identity (no rendering). + var _renderExprFn = null; + + // Render mode flag — set by render-to-html/aser, checked by eval-list. + // When false, render expressions fall through to evalCall. + var _renderMode = false; + function renderActiveP() { return _renderMode; } + function setRenderActiveB(val) { _renderMode = !!val; } + + function renderExpr(expr, env) { + if (_renderExprFn) return _renderExprFn(expr, env); + // No adapter loaded — fall through to evalCall + return evalCall(first(expr), rest(expr), env); + } + + function stripPrefix(s, prefix) { + return s.indexOf(prefix) === 0 ? s.slice(prefix.length) : s; + } + + function error(msg) { throw new Error(msg); } + function inspect(x) { return JSON.stringify(x); } + + + + // ========================================================================= + // Primitives + // ========================================================================= + + var PRIMITIVES = {}; + + // core.arithmetic + PRIMITIVES["+"] = function() { var s = 0; for (var i = 0; i < arguments.length; i++) s += arguments[i]; return s; }; + PRIMITIVES["-"] = function(a, b) { return arguments.length === 1 ? -a : a - b; }; + PRIMITIVES["*"] = function() { var s = 1; for (var i = 0; i < arguments.length; i++) s *= arguments[i]; return s; }; + PRIMITIVES["/"] = function(a, b) { return a / b; }; + PRIMITIVES["mod"] = function(a, b) { return a % b; }; + PRIMITIVES["inc"] = function(n) { return n + 1; }; + PRIMITIVES["dec"] = function(n) { return n - 1; }; + PRIMITIVES["abs"] = Math.abs; + PRIMITIVES["floor"] = Math.floor; + PRIMITIVES["ceil"] = Math.ceil; + PRIMITIVES["round"] = function(x, n) { + if (n === undefined || n === 0) return Math.round(x); + var f = Math.pow(10, n); return Math.round(x * f) / f; + }; + PRIMITIVES["min"] = Math.min; + PRIMITIVES["max"] = Math.max; + PRIMITIVES["sqrt"] = Math.sqrt; + PRIMITIVES["pow"] = Math.pow; + PRIMITIVES["clamp"] = function(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); }; + + + // core.comparison + PRIMITIVES["="] = function(a, b) { return a === b; }; + PRIMITIVES["!="] = function(a, b) { return a !== b; }; + PRIMITIVES["<"] = function(a, b) { return a < b; }; + PRIMITIVES[">"] = function(a, b) { return a > b; }; + PRIMITIVES["<="] = function(a, b) { return a <= b; }; + PRIMITIVES[">="] = function(a, b) { return a >= b; }; + + + // core.logic + PRIMITIVES["not"] = function(x) { return !isSxTruthy(x); }; + + + // core.predicates + PRIMITIVES["nil?"] = isNil; + PRIMITIVES["number?"] = function(x) { return typeof x === "number"; }; + PRIMITIVES["string?"] = function(x) { return typeof x === "string"; }; + PRIMITIVES["list?"] = Array.isArray; + PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; }; + PRIMITIVES["empty?"] = function(c) { return isNil(c) || (Array.isArray(c) ? c.length === 0 : typeof c === "string" ? c.length === 0 : Object.keys(c).length === 0); }; + PRIMITIVES["contains?"] = function(c, k) { + if (typeof c === "string") return c.indexOf(String(k)) !== -1; + if (Array.isArray(c)) return c.indexOf(k) !== -1; + return k in c; + }; + PRIMITIVES["odd?"] = function(n) { return n % 2 !== 0; }; + PRIMITIVES["even?"] = function(n) { return n % 2 === 0; }; + PRIMITIVES["zero?"] = function(n) { return n === 0; }; + PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; }; + PRIMITIVES["component-affinity"] = componentAffinity; + + + // core.strings + PRIMITIVES["str"] = function() { + var p = []; + for (var i = 0; i < arguments.length; i++) { + var v = arguments[i]; if (isNil(v)) continue; p.push(String(v)); + } + return p.join(""); + }; + PRIMITIVES["upper"] = function(s) { return String(s).toUpperCase(); }; + PRIMITIVES["lower"] = function(s) { return String(s).toLowerCase(); }; + PRIMITIVES["trim"] = function(s) { return String(s).trim(); }; + PRIMITIVES["split"] = function(s, sep) { return String(s).split(sep || " "); }; + PRIMITIVES["join"] = function(sep, coll) { return coll.join(sep); }; + PRIMITIVES["replace"] = function(s, old, nw) { return s.split(old).join(nw); }; + PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); }; + PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; }; + PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; }; + PRIMITIVES["slice"] = function(c, a, b) { return b !== undefined ? c.slice(a, b) : c.slice(a); }; + PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); }; + PRIMITIVES["string-length"] = function(s) { return String(s).length; }; + PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; }; + PRIMITIVES["concat"] = function() { + var out = []; + for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]); + return out; + }; + + + // core.collections + PRIMITIVES["list"] = function() { return Array.prototype.slice.call(arguments); }; + PRIMITIVES["dict"] = function() { + var d = {}; + for (var i = 0; i < arguments.length - 1; i += 2) d[arguments[i]] = arguments[i + 1]; + return d; + }; + PRIMITIVES["range"] = function(a, b, step) { + var r = []; step = step || 1; + for (var i = a; step > 0 ? i < b : i > b; i += step) r.push(i); + return r; + }; + PRIMITIVES["get"] = function(c, k, def) { var v = (c && c[k]); return v !== undefined ? v : (def !== undefined ? def : NIL); }; + PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; }; + PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; }; + PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; }; + PRIMITIVES["rest"] = function(c) { return c ? c.slice(1) : []; }; + PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; }; + PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); }; + PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); }; + PRIMITIVES["append!"] = function(arr, x) { arr.push(x); return arr; }; + PRIMITIVES["chunk-every"] = function(c, n) { + var r = []; for (var i = 0; i < c.length; i += n) r.push(c.slice(i, i + n)); return r; + }; + PRIMITIVES["zip-pairs"] = function(c) { + var r = []; for (var i = 0; i < c.length - 1; i++) r.push([c[i], c[i + 1]]); return r; + }; + PRIMITIVES["reverse"] = function(c) { return Array.isArray(c) ? c.slice().reverse() : String(c).split("").reverse().join(""); }; + PRIMITIVES["flatten"] = function(c) { + var out = []; + function walk(a) { for (var i = 0; i < a.length; i++) Array.isArray(a[i]) ? walk(a[i]) : out.push(a[i]); } + walk(c || []); return out; + }; + + + // core.dict + PRIMITIVES["keys"] = function(d) { return Object.keys(d || {}); }; + PRIMITIVES["vals"] = function(d) { var r = []; for (var k in d) r.push(d[k]); return r; }; + PRIMITIVES["merge"] = function() { + var out = {}; + for (var i = 0; i < arguments.length; i++) { var d = arguments[i]; if (d && !isNil(d)) for (var k in d) out[k] = d[k]; } + return out; + }; + PRIMITIVES["assoc"] = function(d) { + var out = {}; if (d && !isNil(d)) for (var k in d) out[k] = d[k]; + for (var i = 1; i < arguments.length - 1; i += 2) out[arguments[i]] = arguments[i + 1]; + return out; + }; + PRIMITIVES["dissoc"] = function(d) { + var out = {}; for (var k in d) out[k] = d[k]; + for (var i = 1; i < arguments.length; i++) delete out[arguments[i]]; + return out; + }; + PRIMITIVES["dict-set!"] = function(d, k, v) { d[k] = v; return v; }; + PRIMITIVES["has-key?"] = function(d, k) { return d !== null && d !== undefined && k in d; }; + PRIMITIVES["into"] = function(target, coll) { + if (Array.isArray(target)) return Array.isArray(coll) ? coll.slice() : Object.entries(coll); + var r = {}; for (var i = 0; i < coll.length; i++) { var p = coll[i]; if (Array.isArray(p) && p.length >= 2) r[p[0]] = p[1]; } + return r; + }; + + + // stdlib.format + PRIMITIVES["format-decimal"] = function(v, p) { return Number(v).toFixed(p || 2); }; + PRIMITIVES["parse-int"] = function(v, d) { var n = parseInt(v, 10); return isNaN(n) ? (d || 0) : n; }; + PRIMITIVES["format-date"] = function(s, fmt) { + if (!s) return ""; + try { + var d = new Date(s); + if (isNaN(d.getTime())) return String(s); + var months = ["January","February","March","April","May","June","July","August","September","October","November","December"]; + var short_months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; + return fmt.replace(/%-d/g, d.getDate()).replace(/%d/g, ("0"+d.getDate()).slice(-2)) + .replace(/%B/g, months[d.getMonth()]).replace(/%b/g, short_months[d.getMonth()]) + .replace(/%Y/g, d.getFullYear()).replace(/%m/g, ("0"+(d.getMonth()+1)).slice(-2)) + .replace(/%H/g, ("0"+d.getHours()).slice(-2)).replace(/%M/g, ("0"+d.getMinutes()).slice(-2)); + } catch (e) { return String(s); } + }; + PRIMITIVES["parse-datetime"] = function(s) { return s ? String(s) : NIL; }; + + + // stdlib.text + PRIMITIVES["pluralize"] = function(n, s, p) { + if (s || (p && p !== "s")) return n == 1 ? (s || "") : (p || "s"); + return n == 1 ? "" : "s"; + }; + PRIMITIVES["escape"] = function(s) { + return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"); + }; + PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); }; + + + // stdlib.debug + PRIMITIVES["assert"] = function(cond, msg) { + if (!isSxTruthy(cond)) throw new Error("Assertion error: " + (msg || "Assertion failed")); + return true; + }; + + + function isPrimitive(name) { return name in PRIMITIVES; } + function getPrimitive(name) { return PRIMITIVES[name]; } + + // Higher-order helpers used by the transpiled code + function map(fn, coll) { return coll.map(fn); } + function mapIndexed(fn, coll) { return coll.map(function(item, i) { return fn(i, item); }); } + function filter(fn, coll) { return coll.filter(function(x) { return isSxTruthy(fn(x)); }); } + function reduce(fn, init, coll) { + var acc = init; + for (var i = 0; i < coll.length; i++) acc = fn(acc, coll[i]); + return acc; + } + function some(fn, coll) { + for (var i = 0; i < coll.length; i++) { var r = fn(coll[i]); if (isSxTruthy(r)) return r; } + return NIL; + } + function forEach(fn, coll) { for (var i = 0; i < coll.length; i++) fn(coll[i]); return NIL; } + function isEvery(fn, coll) { + for (var i = 0; i < coll.length; i++) { if (!isSxTruthy(fn(coll[i]))) return false; } + return true; + } + function mapDict(fn, d) { var r = {}; for (var k in d) r[k] = fn(k, d[k]); return r; } + + // List primitives used directly by transpiled code + var len = PRIMITIVES["len"]; + var first = PRIMITIVES["first"]; + var last = PRIMITIVES["last"]; + var rest = PRIMITIVES["rest"]; + var nth = PRIMITIVES["nth"]; + var cons = PRIMITIVES["cons"]; + var append = PRIMITIVES["append"]; + var isEmpty = PRIMITIVES["empty?"]; + var contains = PRIMITIVES["contains?"]; + var startsWith = PRIMITIVES["starts-with?"]; + var slice = PRIMITIVES["slice"]; + var concat = PRIMITIVES["concat"]; + var str = PRIMITIVES["str"]; + var join = PRIMITIVES["join"]; + var keys = PRIMITIVES["keys"]; + var get = PRIMITIVES["get"]; + var assoc = PRIMITIVES["assoc"]; + var range = PRIMITIVES["range"]; + function zip(a, b) { var r = []; for (var i = 0; i < Math.min(a.length, b.length); i++) r.push([a[i], b[i]]); return r; } + function append_b(arr, x) { arr.push(x); return arr; } + var apply = function(f, args) { + if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f))); + return f.apply(null, args); + }; + + // Additional primitive aliases used by adapter/engine transpiled code + var split = PRIMITIVES["split"]; + var trim = PRIMITIVES["trim"]; + var upper = PRIMITIVES["upper"]; + var lower = PRIMITIVES["lower"]; + var replace_ = function(s, old, nw) { return s.split(old).join(nw); }; + var endsWith = PRIMITIVES["ends-with?"]; + var parseInt_ = PRIMITIVES["parse-int"]; + var dict_fn = PRIMITIVES["dict"]; + + // HTML rendering helpers + function escapeHtml(s) { + return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,"""); + } + function escapeAttr(s) { return escapeHtml(s); } + function rawHtmlContent(r) { return r.html; } + function makeRawHtml(s) { return { _raw: true, html: s }; } + function sxExprSource(x) { return x && x.source ? x.source : String(x); } + + // Placeholders — overridden by transpiled spec from parser.sx / adapter-sx.sx + function serialize(val) { return String(val); } + function isSpecialForm(n) { return false; } + function isHoForm(n) { return false; } + + // processBindings and evalCond — now specced in render.sx, bootstrapped above + + function isDefinitionForm(name) { + return name === "define" || name === "defcomp" || name === "defmacro" || + name === "defstyle" || name === "defhandler"; + } + + function indexOf_(s, ch) { + return typeof s === "string" ? s.indexOf(ch) : -1; + } + + function dictHas(d, k) { return d != null && k in d; } + function dictDelete(d, k) { delete d[k]; } + + function forEachIndexed(fn, coll) { + for (var i = 0; i < coll.length; i++) fn(i, coll[i]); + return NIL; + } + + // ========================================================================= + // Performance overrides — evaluator hot path + // ========================================================================= + + // Override parseKeywordArgs: imperative loop instead of reduce+assoc + parseKeywordArgs = function(rawArgs, env) { + var kwargs = {}; + var children = []; + for (var i = 0; i < rawArgs.length; i++) { + var arg = rawArgs[i]; + if (arg && arg._kw && (i + 1) < rawArgs.length) { + kwargs[arg.name] = trampoline(evalExpr(rawArgs[i + 1], env)); + i++; + } else { + children.push(trampoline(evalExpr(arg, env))); + } + } + return [kwargs, children]; + }; + + // Override callComponent: use prototype chain env, imperative kwarg binding + callComponent = function(comp, rawArgs, env) { + var kwargs = {}; + var children = []; + for (var i = 0; i < rawArgs.length; i++) { + var arg = rawArgs[i]; + if (arg && arg._kw && (i + 1) < rawArgs.length) { + kwargs[arg.name] = trampoline(evalExpr(rawArgs[i + 1], env)); + i++; + } else { + children.push(trampoline(evalExpr(arg, env))); + } + } + var local = Object.create(componentClosure(comp)); + for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; + var params = componentParams(comp); + for (var j = 0; j < params.length; j++) { + var p = params[j]; + local[p] = p in kwargs ? kwargs[p] : NIL; + } + if (componentHasChildren(comp)) { + local["children"] = children; + } + return makeThunk(componentBody(comp), local); + }; + + // ========================================================================= + // Platform: deps module — component dependency analysis + // ========================================================================= + + function componentDeps(c) { + return c.deps ? c.deps.slice() : []; + } + + function componentSetDeps(c, deps) { + c.deps = deps; + } + + function componentCssClasses(c) { + return c.cssClasses ? c.cssClasses.slice() : []; + } + + function envComponents(env) { + var names = []; + for (var k in env) { + var v = env[k]; + if (v && (v._component || v._macro)) names.push(k); + } + return names; + } + + function regexFindAll(pattern, source) { + var re = new RegExp(pattern, "g"); + var results = []; + var m; + while ((m = re.exec(source)) !== null) { + if (m[1] !== undefined) results.push(m[1]); + else results.push(m[0]); + } + return results; + } + + function scanCssClasses(source) { + var classes = {}; + var result = []; + var m; + var re1 = /:class\s+"([^"]*)"/g; + while ((m = re1.exec(source)) !== null) { + var parts = m[1].split(/\s+/); + for (var i = 0; i < parts.length; i++) { + if (parts[i] && !classes[parts[i]]) { + classes[parts[i]] = true; + result.push(parts[i]); + } + } + } + var re2 = /:class\s+\(str\s+((?:"[^"]*"\s*)+)\)/g; + while ((m = re2.exec(source)) !== null) { + var re3 = /"([^"]*)"/g; + var m2; + while ((m2 = re3.exec(m[1])) !== null) { + var parts2 = m2[1].split(/\s+/); + for (var j = 0; j < parts2.length; j++) { + if (parts2[j] && !classes[parts2[j]]) { + classes[parts2[j]] = true; + result.push(parts2[j]); + } + } + } + } + var re4 = /;;\s*@css\s+(.+)/g; + while ((m = re4.exec(source)) !== null) { + var parts3 = m[1].split(/\s+/); + for (var k = 0; k < parts3.length; k++) { + if (parts3[k] && !classes[parts3[k]]) { + classes[parts3[k]] = true; + result.push(parts3[k]); + } + } + } + return result; + } + + function componentIoRefs(c) { + return c.ioRefs ? c.ioRefs.slice() : []; + } + + function componentSetIoRefs(c, refs) { + c.ioRefs = refs; + } + + + // ========================================================================= + // Platform interface — Parser + // ========================================================================= + // Character classification derived from the grammar: + // ident-start → [a-zA-Z_~*+\-><=/!?&] + // ident-char → ident-start + [0-9.:\/\[\]#,] + + var _identStartRe = /[a-zA-Z_~*+\-><=/!?&]/; + var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]/; + + function isIdentStart(ch) { return _identStartRe.test(ch); } + function isIdentChar(ch) { return _identCharRe.test(ch); } + function parseNumber(s) { return Number(s); } + function escapeString(s) { + return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t"); + } + function sxExprSource(e) { return typeof e === "string" ? e : String(e); } + + + // === Transpiled from eval === + + // trampoline + var trampoline = function(val) { return (function() { + var result = val; + return (isSxTruthy(isThunk(result)) ? trampoline(evalExpr(thunkExpr(result), thunkEnv(result))) : result); +})(); }; + + // eval-expr + var evalExpr = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() { + var name = symbolName(expr); + return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name)))))))); +})(); if (_m == "keyword") return keywordName(expr); if (_m == "dict") return mapDict(function(k, v) { return trampoline(evalExpr(v, env)); }, expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : evalList(expr, env)); return expr; })(); }; + + // eval-list + var evalList = function(expr, env) { return (function() { + var head = first(expr); + var args = rest(expr); + return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() { + var name = symbolName(head); + return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { + var mac = envGet(env, name); + return makeThunk(expandMacro(mac, args, env), env); +})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))))); +})() : evalCall(head, args, env))); +})(); }; + + // eval-call + var evalCall = function(head, args, env) { return (function() { + var f = trampoline(evalExpr(head, env)); + var evaluatedArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args); + return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaluatedArgs) : (isSxTruthy(isLambda(f)) ? callLambda(f, evaluatedArgs, env) : (isSxTruthy(isComponent(f)) ? callComponent(f, args, env) : (isSxTruthy(isIsland(f)) ? callComponent(f, args, env) : error((String("Not callable: ") + String(inspect(f)))))))); +})(); }; + + // call-lambda + var callLambda = function(f, args, callerEnv) { return (function() { + var params = lambdaParams(f); + var local = envMerge(lambdaClosure(f), callerEnv); + return (isSxTruthy((len(args) > len(params))) ? error((String(sxOr(lambdaName(f), "lambda")) + String(" expects ") + String(len(params)) + String(" args, got ") + String(len(args)))) : (forEach(function(pair) { return envSet(local, first(pair), nth(pair, 1)); }, zip(params, args)), forEach(function(p) { return envSet(local, p, NIL); }, slice(params, len(args))), makeThunk(lambdaBody(f), local))); +})(); }; + + // call-component + var callComponent = function(comp, rawArgs, env) { return (function() { + var parsed = parseKeywordArgs(rawArgs, env); + var kwargs = first(parsed); + var children = nth(parsed, 1); + var local = envMerge(componentClosure(comp), env); + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, sxOr(dictGet(kwargs, p), NIL)); } } + if (isSxTruthy(componentHasChildren(comp))) { + envSet(local, "children", children); +} + return makeThunk(componentBody(comp), local); +})(); }; + + // parse-keyword-args + var parseKeywordArgs = function(rawArgs, env) { return (function() { + var kwargs = {}; + var children = []; + var i = 0; + reduce(function(state, arg) { return (function() { + var idx = get(state, "i"); + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (idx + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((idx + 1) < len(rawArgs)))) ? (dictSet(kwargs, keywordName(arg), trampoline(evalExpr(nth(rawArgs, (idx + 1)), env))), assoc(state, "skip", true, "i", (idx + 1))) : (append_b(children, trampoline(evalExpr(arg, env))), assoc(state, "i", (idx + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, rawArgs); + return [kwargs, children]; +})(); }; + + // sf-if + var sfIf = function(args, env) { return (function() { + var condition = trampoline(evalExpr(first(args), env)); + return (isSxTruthy((isSxTruthy(condition) && !isSxTruthy(isNil(condition)))) ? makeThunk(nth(args, 1), env) : (isSxTruthy((len(args) > 2)) ? makeThunk(nth(args, 2), env) : NIL)); +})(); }; + + // sf-when + var sfWhen = function(args, env) { return (function() { + var condition = trampoline(evalExpr(first(args), env)); + return (isSxTruthy((isSxTruthy(condition) && !isSxTruthy(isNil(condition)))) ? (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 1, (len(args) - 1))), makeThunk(last(args), env)) : NIL); +})(); }; + + // cond-scheme? + var condScheme_p = function(clauses) { return isEvery(function(c) { return (isSxTruthy((typeOf(c) == "list")) && (len(c) == 2)); }, clauses); }; + + // sf-cond + var sfCond = function(args, env) { return (isSxTruthy(condScheme_p(args)) ? sfCondScheme(args, env) : sfCondClojure(args, env)); }; + + // sf-cond-scheme + var sfCondScheme = function(clauses, env) { return (isSxTruthy(isEmpty(clauses)) ? NIL : (function() { + var clause = first(clauses); + var test = first(clause); + var body = nth(clause, 1); + return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))), (isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")))) ? makeThunk(body, env) : (isSxTruthy(trampoline(evalExpr(test, env))) ? makeThunk(body, env) : sfCondScheme(rest(clauses), env))); +})()); }; + + // sf-cond-clojure + var sfCondClojure = function(clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() { + var test = first(clauses); + var body = nth(clauses, 1); + return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? makeThunk(body, env) : (isSxTruthy(trampoline(evalExpr(test, env))) ? makeThunk(body, env) : sfCondClojure(slice(clauses, 2), env))); +})()); }; + + // sf-case + var sfCase = function(args, env) { return (function() { + var matchVal = trampoline(evalExpr(first(args), env)); + var clauses = rest(args); + return sfCaseLoop(matchVal, clauses, env); +})(); }; + + // sf-case-loop + var sfCaseLoop = function(matchVal, clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() { + var test = first(clauses); + var body = nth(clauses, 1); + return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? makeThunk(body, env) : (isSxTruthy((matchVal == trampoline(evalExpr(test, env)))) ? makeThunk(body, env) : sfCaseLoop(matchVal, slice(clauses, 2), env))); +})()); }; + + // sf-and + var sfAnd = function(args, env) { return (isSxTruthy(isEmpty(args)) ? true : (function() { + var val = trampoline(evalExpr(first(args), env)); + return (isSxTruthy(!isSxTruthy(val)) ? val : (isSxTruthy((len(args) == 1)) ? val : sfAnd(rest(args), env))); +})()); }; + + // sf-or + var sfOr = function(args, env) { return (isSxTruthy(isEmpty(args)) ? false : (function() { + var val = trampoline(evalExpr(first(args), env)); + return (isSxTruthy(val) ? val : sfOr(rest(args), env)); +})()); }; + + // sf-let + var sfLet = function(args, env) { return (isSxTruthy((typeOf(first(args)) == "symbol")) ? sfNamedLet(args, env) : (function() { + var bindings = first(args); + var body = rest(args); + var local = envExtend(env); + (isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { return (function() { + var vname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding)); + return envSet(local, vname, trampoline(evalExpr(nth(binding, 1), local))); +})(); }, bindings) : (function() { + var i = 0; + return reduce(function(acc, pairIdx) { return (function() { + var vname = (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2))); + var valExpr = nth(bindings, ((pairIdx * 2) + 1)); + return envSet(local, vname, trampoline(evalExpr(valExpr, local))); +})(); }, NIL, range(0, (len(bindings) / 2))); +})()); + { var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } } + return makeThunk(last(body), local); +})()); }; + + // sf-named-let + var sfNamedLet = function(args, env) { return (function() { + var loopName = symbolName(first(args)); + var bindings = nth(args, 1); + var body = slice(args, 2); + var params = []; + var inits = []; + (isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { params.push((isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding))); +return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pairIdx) { return (append_b(params, (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2)))), append_b(inits, nth(bindings, ((pairIdx * 2) + 1)))); }, NIL, range(0, (len(bindings) / 2)))); + return (function() { + var loopBody = (isSxTruthy((len(body) == 1)) ? first(body) : cons(makeSymbol("begin"), body)); + var loopFn = makeLambda(params, loopBody, env); + loopFn.name = loopName; + envSet(lambdaClosure(loopFn), loopName, loopFn); + return (function() { + var initVals = map(function(e) { return trampoline(evalExpr(e, env)); }, inits); + return callLambda(loopFn, initVals, env); +})(); +})(); +})(); }; + + // sf-lambda + var sfLambda = function(args, env) { return (function() { + var paramsExpr = first(args); + var bodyExprs = rest(args); + var body = (isSxTruthy((len(bodyExprs) == 1)) ? first(bodyExprs) : cons(makeSymbol("begin"), bodyExprs)); + var paramNames = map(function(p) { return (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p); }, paramsExpr); + return makeLambda(paramNames, body, env); +})(); }; + + // sf-define + var sfDefine = function(args, env) { return (function() { + var nameSym = first(args); + var value = trampoline(evalExpr(nth(args, 1), env)); + if (isSxTruthy((isSxTruthy(isLambda(value)) && isNil(lambdaName(value))))) { + value.name = symbolName(nameSym); +} + envSet(env, symbolName(nameSym), value); + return value; +})(); }; + + // sf-defcomp + var sfDefcomp = function(args, env) { return (function() { + var nameSym = first(args); + var paramsRaw = nth(args, 1); + var body = last(args); + var compName = stripPrefix(symbolName(nameSym), "~"); + var parsed = parseCompParams(paramsRaw); + var params = first(parsed); + var hasChildren = nth(parsed, 1); + var affinity = defcompKwarg(args, "affinity", "auto"); + return (function() { + var comp = makeComponent(compName, params, hasChildren, body, env, affinity); + envSet(env, symbolName(nameSym), comp); + return comp; +})(); +})(); }; + + // defcomp-kwarg + var defcompKwarg = function(args, key, default_) { return (function() { + var end = (len(args) - 1); + var result = default_; + { var _c = range(2, end, 1); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(nth(args, i)) == "keyword")) && isSxTruthy((keywordName(nth(args, i)) == key)) && ((i + 1) < end)))) { + (function() { + var val = nth(args, (i + 1)); + return (result = (isSxTruthy((typeOf(val) == "keyword")) ? keywordName(val) : val)); +})(); +} } } + return result; +})(); }; + + // parse-comp-params + var parseCompParams = function(paramsExpr) { return (function() { + var params = []; + var hasChildren = false; + var inKey = false; + { var _c = paramsExpr; for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; if (isSxTruthy((typeOf(p) == "symbol"))) { + (function() { + var name = symbolName(p); + return (isSxTruthy((name == "&key")) ? (inKey = true) : (isSxTruthy((name == "&rest")) ? (hasChildren = true) : (isSxTruthy((name == "&children")) ? (hasChildren = true) : (isSxTruthy(hasChildren) ? NIL : (isSxTruthy(inKey) ? append_b(params, name) : append_b(params, name)))))); +})(); +} } } + return [params, hasChildren]; +})(); }; + + // sf-defisland + var sfDefisland = function(args, env) { return (function() { + var nameSym = first(args); + var paramsRaw = nth(args, 1); + var body = last(args); + var compName = stripPrefix(symbolName(nameSym), "~"); + var parsed = parseCompParams(paramsRaw); + var params = first(parsed); + var hasChildren = nth(parsed, 1); + return (function() { + var island = makeIsland(compName, params, hasChildren, body, env); + envSet(env, symbolName(nameSym), island); + return island; +})(); +})(); }; + + // sf-defmacro + var sfDefmacro = function(args, env) { return (function() { + var nameSym = first(args); + var paramsRaw = nth(args, 1); + var body = nth(args, 2); + var parsed = parseMacroParams(paramsRaw); + var params = first(parsed); + var restParam = nth(parsed, 1); + return (function() { + var mac = makeMacro(params, restParam, body, env, symbolName(nameSym)); + envSet(env, symbolName(nameSym), mac); + return mac; +})(); +})(); }; + + // parse-macro-params + var parseMacroParams = function(paramsExpr) { return (function() { + var params = []; + var restParam = NIL; + reduce(function(state, p) { return (isSxTruthy((isSxTruthy((typeOf(p) == "symbol")) && (symbolName(p) == "&rest"))) ? assoc(state, "in-rest", true) : (isSxTruthy(get(state, "in-rest")) ? ((restParam = (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p)), state) : (append_b(params, (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p)), state))); }, {["in-rest"]: false}, paramsExpr); + return [params, restParam]; +})(); }; + + // sf-defstyle + var sfDefstyle = function(args, env) { return (function() { + var nameSym = first(args); + var value = trampoline(evalExpr(nth(args, 1), env)); + envSet(env, symbolName(nameSym), value); + return value; +})(); }; + + // sf-begin + var sfBegin = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 0, (len(args) - 1))), makeThunk(last(args), env))); }; + + // sf-quote + var sfQuote = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : first(args)); }; + + // sf-quasiquote + var sfQuasiquote = function(args, env) { return qqExpand(first(args), env); }; + + // qq-expand + var qqExpand = function(template, env) { return (isSxTruthy(!isSxTruthy((typeOf(template) == "list"))) ? template : (isSxTruthy(isEmpty(template)) ? [] : (function() { + var head = first(template); + return (isSxTruthy((isSxTruthy((typeOf(head) == "symbol")) && (symbolName(head) == "unquote"))) ? trampoline(evalExpr(nth(template, 1), env)) : reduce(function(result, item) { return (isSxTruthy((isSxTruthy((typeOf(item) == "list")) && isSxTruthy((len(item) == 2)) && isSxTruthy((typeOf(first(item)) == "symbol")) && (symbolName(first(item)) == "splice-unquote"))) ? (function() { + var spliced = trampoline(evalExpr(nth(item, 1), env)); + return (isSxTruthy((typeOf(spliced) == "list")) ? concat(result, spliced) : (isSxTruthy(isNil(spliced)) ? result : concat(result, [spliced]))); +})() : concat(result, [qqExpand(item, env)])); }, [], template)); +})())); }; + + // sf-thread-first + var sfThreadFirst = function(args, env) { return (function() { + var val = trampoline(evalExpr(first(args), env)); + return reduce(function(result, form) { return (isSxTruthy((typeOf(form) == "list")) ? (function() { + var f = trampoline(evalExpr(first(form), env)); + var restArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, rest(form)); + var allArgs = cons(result, restArgs); + return (isSxTruthy((isSxTruthy(isCallable(f)) && !isSxTruthy(isLambda(f)))) ? apply(f, allArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, allArgs, env)) : error((String("-> form not callable: ") + String(inspect(f)))))); +})() : (function() { + var f = trampoline(evalExpr(form, env)); + return (isSxTruthy((isSxTruthy(isCallable(f)) && !isSxTruthy(isLambda(f)))) ? f(result) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, [result], env)) : error((String("-> form not callable: ") + String(inspect(f)))))); +})()); }, val, rest(args)); +})(); }; + + // sf-set! + var sfSetBang = function(args, env) { return (function() { + var name = symbolName(first(args)); + var value = trampoline(evalExpr(nth(args, 1), env)); + envSet(env, name, value); + return value; +})(); }; + + // sf-letrec + var sfLetrec = function(args, env) { return (function() { + var bindings = first(args); + var body = rest(args); + var local = envExtend(env); + var names = []; + var valExprs = []; + (isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { return (function() { + var vname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding)); + names.push(vname); + valExprs.push(nth(binding, 1)); + return envSet(local, vname, NIL); +})(); }, bindings) : reduce(function(acc, pairIdx) { return (function() { + var vname = (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2))); + var valExpr = nth(bindings, ((pairIdx * 2) + 1)); + names.push(vname); + valExprs.push(valExpr); + return envSet(local, vname, NIL); +})(); }, NIL, range(0, (len(bindings) / 2)))); + (function() { + var values = map(function(e) { return trampoline(evalExpr(e, local)); }, valExprs); + { var _c = zip(names, values); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envSet(local, first(pair), nth(pair, 1)); } } + return forEach(function(val) { return (isSxTruthy(isLambda(val)) ? forEach(function(n) { return envSet(lambdaClosure(val), n, envGet(local, n)); }, names) : NIL); }, values); +})(); + { var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } } + return makeThunk(last(body), local); +})(); }; + + // sf-dynamic-wind + var sfDynamicWind = function(args, env) { return (function() { + var before = trampoline(evalExpr(first(args), env)); + var body = trampoline(evalExpr(nth(args, 1), env)); + var after = trampoline(evalExpr(nth(args, 2), env)); + callThunk(before, env); + pushWind(before, after); + return (function() { + var result = callThunk(body, env); + popWind(); + callThunk(after, env); + return result; +})(); +})(); }; + + // expand-macro + var expandMacro = function(mac, rawArgs, env) { return (function() { + var local = envMerge(macroClosure(mac), env); + { var _c = mapIndexed(function(i, p) { return [p, i]; }, macroParams(mac)); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envSet(local, first(pair), (isSxTruthy((nth(pair, 1) < len(rawArgs))) ? nth(rawArgs, nth(pair, 1)) : NIL)); } } + if (isSxTruthy(macroRestParam(mac))) { + envSet(local, macroRestParam(mac), slice(rawArgs, len(macroParams(mac)))); +} + return trampoline(evalExpr(macroBody(mac), local)); +})(); }; + + // call-fn + var callFn = function(f, args, env) { return (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, args, env)) : (isSxTruthy(isCallable(f)) ? apply(f, args) : error((String("Not callable in HO form: ") + String(inspect(f)))))); }; + + // ho-map + var hoMap = function(args, env) { return (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + return map(function(item) { return callFn(f, [item], env); }, coll); +})(); }; + + // ho-map-indexed + var hoMapIndexed = function(args, env) { return (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + return mapIndexed(function(i, item) { return callFn(f, [i, item], env); }, coll); +})(); }; + + // ho-filter + var hoFilter = function(args, env) { return (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + return filter(function(item) { return callFn(f, [item], env); }, coll); +})(); }; + + // ho-reduce + var hoReduce = function(args, env) { return (function() { + var f = trampoline(evalExpr(first(args), env)); + var init = trampoline(evalExpr(nth(args, 1), env)); + var coll = trampoline(evalExpr(nth(args, 2), env)); + return reduce(function(acc, item) { return callFn(f, [acc, item], env); }, init, coll); +})(); }; + + // ho-some + var hoSome = function(args, env) { return (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + return some(function(item) { return callFn(f, [item], env); }, coll); +})(); }; + + // ho-every + var hoEvery = function(args, env) { return (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + return isEvery(function(item) { return callFn(f, [item], env); }, coll); +})(); }; + + // ho-for-each + var hoForEach = function(args, env) { return (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + return forEach(function(item) { return callFn(f, [item], env); }, coll); +})(); }; + + + // === Transpiled from render (core) === + + // HTML_TAGS + var 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 + var VOID_ELEMENTS = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]; + + // BOOLEAN_ATTRS + var 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? + var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler")); }; + + // parse-element-args + var parseElementArgs = function(args, env) { return (function() { + var attrs = {}; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + attrs[keywordName(arg)] = val; + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return [attrs, children]; +})(); }; + + // render-attrs + var renderAttrs = function(attrs) { return join("", map(function(key) { return (function() { + var val = dictGet(attrs, key); + return (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && val)) ? (String(" ") + String(key)) : (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && !isSxTruthy(val))) ? "" : (isSxTruthy(isNil(val)) ? "" : (String(" ") + String(key) + String("=\"") + String(escapeAttr((String(val)))) + String("\""))))); +})(); }, keys(attrs))); }; + + // eval-cond + var evalCond = function(clauses, env) { return (isSxTruthy(condScheme_p(clauses)) ? evalCondScheme(clauses, env) : evalCondClojure(clauses, env)); }; + + // eval-cond-scheme + var evalCondScheme = function(clauses, env) { return (isSxTruthy(isEmpty(clauses)) ? NIL : (function() { + var clause = first(clauses); + var test = first(clause); + var body = nth(clause, 1); + return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))), (isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")))) ? body : (isSxTruthy(trampoline(evalExpr(test, env))) ? body : evalCondScheme(rest(clauses), env))); +})()); }; + + // eval-cond-clojure + var evalCondClojure = function(clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() { + var test = first(clauses); + var body = nth(clauses, 1); + return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? body : (isSxTruthy(trampoline(evalExpr(test, env))) ? body : evalCondClojure(slice(clauses, 2), env))); +})()); }; + + // process-bindings + var processBindings = function(bindings, env) { return (function() { + var local = envExtend(env); + { var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) { + (function() { + var name = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair)))); + return envSet(local, name, trampoline(evalExpr(nth(pair, 1), local))); +})(); +} } } + return local; +})(); }; + + // is-render-expr? + var isRenderExpr = function(expr) { return (isSxTruthy(sxOr(!isSxTruthy((typeOf(expr) == "list")), isEmpty(expr))) ? false : (function() { + var h = first(expr); + return (isSxTruthy(!isSxTruthy((typeOf(h) == "symbol"))) ? false : (function() { + var n = symbolName(h); + return sxOr((n == "<>"), (n == "raw!"), startsWith(n, "~"), startsWith(n, "html:"), contains(HTML_TAGS, n), (isSxTruthy((indexOf_(n, "-") > 0)) && isSxTruthy((len(expr) > 1)) && (typeOf(nth(expr, 1)) == "keyword"))); +})()); +})()); }; + + + // === Transpiled from parser === + + // sx-parse + 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) == "\n"))))) { pos = (pos + 1); +continue; } else { return NIL; } } }; + var skipWs = function() { while(true) { if (isSxTruthy((pos < lenSrc))) { { var ch = nth(source, pos); +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; } } }; + var readString = function() { pos = (pos + 1); +return (function() { + var buf = ""; + var readStrLoop = function() { while(true) { if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated string"); } else { { var ch = nth(source, pos); +if (isSxTruthy((ch == "\""))) { pos = (pos + 1); +return NIL; } else if (isSxTruthy((ch == "\\"))) { pos = (pos + 1); +{ var esc = nth(source, pos); +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); +continue; } } } } }; + readStrLoop(); + return buf; +})(); }; + var readIdent = function() { return (function() { + var start = pos; + var readIdentLoop = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && isIdentChar(nth(source, pos))))) { pos = (pos + 1); +continue; } else { return NIL; } } }; + readIdentLoop(); + return slice(source, start, pos); +})(); }; + var readKeyword = function() { pos = (pos + 1); +return makeKeyword(readIdent()); }; + var readNumber = function() { return (function() { + var start = pos; + if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "-")))) { + pos = (pos + 1); +} + var readDigits = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (function() { + var c = nth(source, pos); + return (isSxTruthy((c >= "0")) && (c <= "9")); +})()))) { pos = (pos + 1); +continue; } else { return NIL; } } }; + readDigits(); + if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == ".")))) { + pos = (pos + 1); + readDigits(); +} + if (isSxTruthy((isSxTruthy((pos < lenSrc)) && sxOr((nth(source, pos) == "e"), (nth(source, pos) == "E"))))) { + pos = (pos + 1); + if (isSxTruthy((isSxTruthy((pos < lenSrc)) && sxOr((nth(source, pos) == "+"), (nth(source, pos) == "-"))))) { + pos = (pos + 1); +} + readDigits(); +} + return parseNumber(slice(source, start, pos)); +})(); }; + var readSymbol = function() { return (function() { + var name = readIdent(); + return (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : makeSymbol(name)))); +})(); }; + var readList = function(closeCh) { return (function() { + var items = []; + var readListLoop = function() { while(true) { skipWs(); +if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated list"); } else { if (isSxTruthy((nth(source, pos) == closeCh))) { pos = (pos + 1); +return NIL; } else { items.push(readExpr()); +continue; } } } }; + readListLoop(); + return items; +})(); }; + var readMap = function() { return (function() { + var result = {}; + var readMapLoop = function() { while(true) { skipWs(); +if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated map"); } else { if (isSxTruthy((nth(source, pos) == "}"))) { pos = (pos + 1); +return NIL; } else { { var keyExpr = readExpr(); +var keyStr = (isSxTruthy((typeOf(keyExpr) == "keyword")) ? keywordName(keyExpr) : (String(keyExpr))); +var valExpr = readExpr(); +result[keyStr] = valExpr; +continue; } } } } }; + readMapLoop(); + return result; +})(); }; + var readRawString = function() { return (function() { + var buf = ""; + var rawLoop = function() { while(true) { if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated raw string"); } else { { var ch = nth(source, pos); +if (isSxTruthy((ch == "|"))) { pos = (pos + 1); +return NIL; } else { buf = (String(buf) + String(ch)); +pos = (pos + 1); +continue; } } } } }; + rawLoop(); + return buf; +})(); }; + var readExpr = function() { while(true) { skipWs(); +if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input"); } else { { var ch = nth(source, pos); +if (isSxTruthy((ch == "("))) { pos = (pos + 1); +return readList(")"); } else if (isSxTruthy((ch == "["))) { pos = (pos + 1); +return readList("]"); } else if (isSxTruthy((ch == "{"))) { pos = (pos + 1); +return readMap(); } else if (isSxTruthy((ch == "\""))) { return readString(); } else if (isSxTruthy((ch == ":"))) { return readKeyword(); } else if (isSxTruthy((ch == "`"))) { pos = (pos + 1); +return [makeSymbol("quasiquote"), readExpr()]; } else if (isSxTruthy((ch == ","))) { pos = (pos + 1); +if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "@")))) { pos = (pos + 1); +return [makeSymbol("splice-unquote"), readExpr()]; } else { return [makeSymbol("unquote"), readExpr()]; } } else if (isSxTruthy((ch == "#"))) { pos = (pos + 1); +if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input after #"); } else { { var dispatchCh = nth(source, pos); +if (isSxTruthy((dispatchCh == ";"))) { pos = (pos + 1); +readExpr(); +continue; } else if (isSxTruthy((dispatchCh == "|"))) { pos = (pos + 1); +return readRawString(); } else if (isSxTruthy((dispatchCh == "'"))) { pos = (pos + 1); +return [makeSymbol("quote"), readExpr()]; } else if (isSxTruthy(isIdentStart(dispatchCh))) { { var macroName = readIdent(); +{ var handler = readerMacroGet(macroName); +if (isSxTruthy(handler)) { return handler(readExpr()); } else { return error((String("Unknown reader macro: #") + String(macroName))); } } } } else { return error((String("Unknown reader macro: #") + String(dispatchCh))); } } } } else if (isSxTruthy(sxOr((isSxTruthy((ch >= "0")) && (ch <= "9")), (isSxTruthy((ch == "-")) && isSxTruthy(((pos + 1) < lenSrc)) && (function() { + var nextCh = nth(source, (pos + 1)); + return (isSxTruthy((nextCh >= "0")) && (nextCh <= "9")); +})())))) { return readNumber(); } else if (isSxTruthy((isSxTruthy((ch == ".")) && isSxTruthy(((pos + 2) < lenSrc)) && isSxTruthy((nth(source, (pos + 1)) == ".")) && (nth(source, (pos + 2)) == ".")))) { pos = (pos + 3); +return makeSymbol("..."); } else if (isSxTruthy(isIdentStart(ch))) { return readSymbol(); } else { return error((String("Unexpected character: ") + String(ch))); } } } } }; + return (function() { + var exprs = []; + var parseLoop = function() { while(true) { skipWs(); +if (isSxTruthy((pos < lenSrc))) { exprs.push(readExpr()); +continue; } else { return NIL; } } }; + parseLoop(); + return exprs; +})(); +})(); }; + + // sx-serialize + var sxSerialize = function(val) { return (function() { var _m = typeOf(val); if (_m == "nil") return "nil"; if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "number") return (String(val)); if (_m == "string") return (String("\"") + String(escapeString(val)) + String("\"")); if (_m == "symbol") return symbolName(val); if (_m == "keyword") return (String(":") + String(keywordName(val))); if (_m == "list") return (String("(") + String(join(" ", map(sxSerialize, val))) + String(")")); if (_m == "dict") return sxSerializeDict(val); if (_m == "sx-expr") return sxExprSource(val); return (String(val)); })(); }; + + // sx-serialize-dict + var sxSerializeDict = function(d) { return (String("{") + String(join(" ", reduce(function(acc, key) { return concat(acc, [(String(":") + String(key)), sxSerialize(dictGet(d, key))]); }, [], keys(d)))) + String("}")); }; + + // serialize + var serialize = sxSerialize; + + + // === Transpiled from adapter-html === + + // render-to-html + var renderToHtml = function(expr, env) { setRenderActiveB(true); +return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); }; + + // render-value-to-html + var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); return escapeHtml((String(val))); })(); }; + + // RENDER_HTML_FORMS + var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each"]; + + // render-html-form? + var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); }; + + // render-list-to-html + var renderListToHtml = function(expr, env) { return (isSxTruthy(isEmpty(expr)) ? "" : (function() { + var head = first(expr); + return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", map(function(x) { return renderValueToHtml(x, env); }, expr)) : (function() { + var name = symbolName(head); + var args = rest(expr); + return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy((name == "marsh")) ? renderHtmlMarsh(args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() { + var val = envGet(env, name); + return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name))))); +})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))); +})()); +})()); }; + + // dispatch-html-form + var dispatchHtmlForm = function(name, expr, env) { return (isSxTruthy((name == "if")) ? (function() { + var condVal = trampoline(evalExpr(nth(expr, 1), env)); + return (isSxTruthy(condVal) ? renderToHtml(nth(expr, 2), env) : (isSxTruthy((len(expr) > 3)) ? renderToHtml(nth(expr, 3), env) : "")); +})() : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? "" : join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(2, len(expr))))) : (isSxTruthy((name == "cond")) ? (function() { + var branch = evalCond(rest(expr), env); + return (isSxTruthy(branch) ? renderToHtml(branch, env) : ""); +})() : (isSxTruthy((name == "case")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() { + var local = processBindings(nth(expr, 1), env); + return join("", map(function(i) { return renderToHtml(nth(expr, i), local); }, range(2, len(expr)))); +})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(1, len(expr)))) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), "") : (isSxTruthy((name == "map")) ? (function() { + var f = trampoline(evalExpr(nth(expr, 1), env)); + var coll = trampoline(evalExpr(nth(expr, 2), env)); + return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll)); +})() : (isSxTruthy((name == "map-indexed")) ? (function() { + var f = trampoline(evalExpr(nth(expr, 1), env)); + var coll = trampoline(evalExpr(nth(expr, 2), env)); + return join("", mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [i, item], env) : renderToHtml(apply(f, [i, item]), env)); }, coll)); +})() : (isSxTruthy((name == "filter")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy((name == "for-each")) ? (function() { + var f = trampoline(evalExpr(nth(expr, 1), env)); + var coll = trampoline(evalExpr(nth(expr, 2), env)); + return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll)); +})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))))); }; + + // render-lambda-html + var renderLambdaHtml = function(f, args, env) { return (function() { + var local = envMerge(lambdaClosure(f), env); + forEachIndexed(function(i, p) { return envSet(local, p, nth(args, i)); }, lambdaParams(f)); + return renderToHtml(lambdaBody(f), local); +})(); }; + + // render-html-component + var renderHtmlComponent = function(comp, args, env) { return (function() { + var kwargs = {}; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + kwargs[keywordName(arg)] = val; + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return (function() { + var local = envMerge(componentClosure(comp), env); + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + if (isSxTruthy(componentHasChildren(comp))) { + envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); +} + return renderToHtml(componentBody(comp), local); +})(); +})(); }; + + // render-html-element + var renderHtmlElement = function(tag, args, env) { return (function() { + var parsed = parseElementArgs(args, env); + var attrs = first(parsed); + var children = nth(parsed, 1); + var isVoid = contains(VOID_ELEMENTS, tag); + return (String("<") + String(tag) + String(renderAttrs(attrs)) + String((isSxTruthy(isVoid) ? " />" : (String(">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String(""))))); +})(); }; + + // render-html-lake + var renderHtmlLake = function(args, env) { return (function() { + var lakeId = NIL; + var lakeTag = "div"; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var kname = keywordName(arg); + var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + (isSxTruthy((kname == "id")) ? (lakeId = kval) : (isSxTruthy((kname == "tag")) ? (lakeTag = kval) : NIL)); + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return (String("<") + String(lakeTag) + String(" data-sx-lake=\"") + String(escapeAttr(sxOr(lakeId, ""))) + String("\">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("")); +})(); }; + + // render-html-marsh + var renderHtmlMarsh = function(args, env) { return (function() { + var marshId = NIL; + var marshTag = "div"; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var kname = keywordName(arg); + var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + (isSxTruthy((kname == "id")) ? (marshId = kval) : (isSxTruthy((kname == "tag")) ? (marshTag = kval) : (isSxTruthy((kname == "transform")) ? NIL : NIL))); + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return (String("<") + String(marshTag) + String(" data-sx-marsh=\"") + String(escapeAttr(sxOr(marshId, ""))) + String("\">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("")); +})(); }; + + // render-html-island + var renderHtmlIsland = function(island, args, env) { return (function() { + var kwargs = {}; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + kwargs[keywordName(arg)] = val; + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return (function() { + var local = envMerge(componentClosure(island), env); + var islandName = componentName(island); + { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + if (isSxTruthy(componentHasChildren(island))) { + envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); +} + return (function() { + var bodyHtml = renderToHtml(componentBody(island), local); + var stateSx = serializeIslandState(kwargs); + return (String("") + String(bodyHtml) + String("")); +})(); +})(); +})(); }; + + // serialize-island-state + var serializeIslandState = function(kwargs) { return (isSxTruthy(isEmptyDict(kwargs)) ? NIL : sxSerialize(kwargs)); }; + + + // === Transpiled from adapter-sx === + + // render-to-sx + var renderToSx = function(expr, env) { return (function() { + var result = aser(expr, env); + return (isSxTruthy((typeOf(result) == "string")) ? result : serialize(result)); +})(); }; + + // aser + var aser = function(expr, env) { setRenderActiveB(true); +return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() { + var name = symbolName(expr); + return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name)))))))); +})(); if (_m == "keyword") return keywordName(expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : aserList(expr, env)); return expr; })(); }; + + // aser-list + var aserList = function(expr, env) { return (function() { + var head = first(expr); + var args = rest(expr); + return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? map(function(x) { return aser(x, env); }, expr) : (function() { + var name = symbolName(head); + return (isSxTruthy((name == "<>")) ? aserFragment(args, env) : (isSxTruthy(startsWith(name, "~")) ? aserCall(name, args, env) : (isSxTruthy((name == "lake")) ? aserCall(name, args, env) : (isSxTruthy((name == "marsh")) ? aserCall(name, args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? aserCall(name, args, env) : (isSxTruthy(sxOr(isSpecialForm(name), isHoForm(name))) ? aserSpecial(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? aser(expandMacro(envGet(env, name), args, env), env) : (function() { + var f = trampoline(evalExpr(head, env)); + var evaledArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args); + return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaledArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, evaledArgs, env)) : (isSxTruthy(isComponent(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : (isSxTruthy(isIsland(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : error((String("Not callable: ") + String(inspect(f)))))))); +})()))))))); +})()); +})(); }; + + // aser-fragment + var aserFragment = function(children, env) { return (function() { + var parts = []; + { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() { + var result = aser(c, env); + return (isSxTruthy((typeOf(result) == "list")) ? forEach(function(item) { return (isSxTruthy(!isSxTruthy(isNil(item))) ? append_b(parts, serialize(item)) : NIL); }, result) : (isSxTruthy(!isSxTruthy(isNil(result))) ? append_b(parts, serialize(result)) : NIL)); +})(); } } + return (isSxTruthy(isEmpty(parts)) ? "" : (String("(<> ") + String(join(" ", parts)) + String(")"))); +})(); }; + + // aser-call + var aserCall = function(name, args, env) { return (function() { + var parts = [name]; + var skip = false; + var i = 0; + { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (isSxTruthy(skip) ? ((skip = false), (i = (i + 1))) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((i + 1) < len(args)))) ? (function() { + var val = aser(nth(args, (i + 1)), env); + if (isSxTruthy(!isSxTruthy(isNil(val)))) { + parts.push((String(":") + String(keywordName(arg)))); + parts.push(serialize(val)); +} + skip = true; + return (i = (i + 1)); +})() : (function() { + var val = aser(arg, env); + if (isSxTruthy(!isSxTruthy(isNil(val)))) { + (isSxTruthy((typeOf(val) == "list")) ? forEach(function(item) { return (isSxTruthy(!isSxTruthy(isNil(item))) ? append_b(parts, serialize(item)) : NIL); }, val) : append_b(parts, serialize(val))); +} + return (i = (i + 1)); +})())); } } + return (String("(") + String(join(" ", parts)) + String(")")); +})(); }; + + // SPECIAL_FORM_NAMES + var 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"]; + + // HO_FORM_NAMES + var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"]; + + // special-form? + var isSpecialForm = function(name) { return contains(SPECIAL_FORM_NAMES, name); }; + + // ho-form? + var isHoForm = function(name) { return contains(HO_FORM_NAMES, name); }; + + // aser-special + var aserSpecial = function(name, expr, env) { return (function() { + var args = rest(expr); + return (isSxTruthy((name == "if")) ? (isSxTruthy(trampoline(evalExpr(first(args), env))) ? aser(nth(args, 1), env) : (isSxTruthy((len(args) > 2)) ? aser(nth(args, 2), env) : NIL)) : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(first(args), env)))) ? NIL : (function() { + var result = NIL; + { var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } } + return result; +})()) : (isSxTruthy((name == "cond")) ? (function() { + var branch = evalCond(args, env); + return (isSxTruthy(branch) ? aser(branch, env) : NIL); +})() : (isSxTruthy((name == "case")) ? (function() { + var matchVal = trampoline(evalExpr(first(args), env)); + var clauses = rest(args); + return evalCaseAser(matchVal, clauses, env); +})() : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() { + var local = processBindings(first(args), env); + var result = NIL; + { var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, local); } } + return result; +})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (function() { + var result = NIL; + { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } } + return result; +})() : (isSxTruthy((name == "and")) ? (function() { + var result = true; + some(function(arg) { result = trampoline(evalExpr(arg, env)); +return !isSxTruthy(result); }, args); + return result; +})() : (isSxTruthy((name == "or")) ? (function() { + var result = false; + some(function(arg) { result = trampoline(evalExpr(arg, env)); +return result; }, args); + return result; +})() : (isSxTruthy((name == "map")) ? (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + return map(function(item) { return (isSxTruthy(isLambda(f)) ? (function() { + var local = envMerge(lambdaClosure(f), env); + envSet(local, first(lambdaParams(f)), item); + return aser(lambdaBody(f), local); +})() : invoke(f, item)); }, coll); +})() : (isSxTruthy((name == "map-indexed")) ? (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + return mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? (function() { + var local = envMerge(lambdaClosure(f), env); + envSet(local, first(lambdaParams(f)), i); + envSet(local, nth(lambdaParams(f), 1), item); + return aser(lambdaBody(f), local); +})() : invoke(f, i, item)); }, coll); +})() : (isSxTruthy((name == "for-each")) ? (function() { + var f = trampoline(evalExpr(first(args), env)); + var coll = trampoline(evalExpr(nth(args, 1), env)); + var results = []; + { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (isSxTruthy(isLambda(f)) ? (function() { + var local = envMerge(lambdaClosure(f), env); + envSet(local, first(lambdaParams(f)), item); + return append_b(results, aser(lambdaBody(f), local)); +})() : invoke(f, item)); } } + return (isSxTruthy(isEmpty(results)) ? NIL : results); +})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"))) ? (trampoline(evalExpr(expr, env)), NIL) : trampoline(evalExpr(expr, env))))))))))))))); +})(); }; + + // eval-case-aser + var evalCaseAser = function(matchVal, clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() { + var test = first(clauses); + var body = nth(clauses, 1); + return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == ":else"), (symbolName(test) == "else"))))) ? aser(body, env) : (isSxTruthy((matchVal == trampoline(evalExpr(test, env)))) ? aser(body, env) : evalCaseAser(matchVal, slice(clauses, 2), env))); +})()); }; + + + // === Transpiled from adapter-dom === + + // SVG_NS + var SVG_NS = "http://www.w3.org/2000/svg"; + + // MATH_NS + var MATH_NS = "http://www.w3.org/1998/Math/MathML"; + + // render-to-dom + var renderToDom = function(expr, env, ns) { setRenderActiveB(true); +return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(_islandScope) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); }; + + // render-dom-list + var renderDomList = function(expr, env, ns) { return (function() { + var head = first(expr); + return (isSxTruthy((typeOf(head) == "symbol")) ? (function() { + var name = symbolName(head); + var args = rest(expr); + return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy((name == "lake")) ? renderDomLake(args, env, ns) : (isSxTruthy((name == "marsh")) ? renderDomMarsh(args, env, ns) : (isSxTruthy(startsWith(name, "html:")) ? renderDomElement(slice(name, 5), args, env, ns) : (isSxTruthy(isRenderDomForm(name)) ? (isSxTruthy((isSxTruthy(contains(HTML_TAGS, name)) && sxOr((isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword")), ns))) ? renderDomElement(name, args, env, ns) : dispatchRenderForm(name, expr, env, ns)) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToDom(expandMacro(envGet(env, name), args, env), env, ns) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderDomIsland(envGet(env, name), args, env, ns) : (isSxTruthy(startsWith(name, "~")) ? (function() { + var comp = envGet(env, name); + return (isSxTruthy(isComponent(comp)) ? renderDomComponent(comp, args, env, ns) : renderDomUnknownComponent(name)); +})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && _islandScope)) ? (function() { + var sigOrVal = trampoline(evalExpr(first(args), env)); + return (isSxTruthy(isSignal(sigOrVal)) ? reactiveText(sigOrVal) : createTextNode((String(deref(sigOrVal))))); +})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); +})() : (isSxTruthy(sxOr(isLambda(head), (typeOf(head) == "list"))) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (function() { + var frag = createFragment(); + { var _c = expr; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; domAppend(frag, renderToDom(x, env, ns)); } } + return frag; +})())); +})(); }; + + // render-dom-element + var renderDomElement = function(tag, args, env, ns) { return (function() { + var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns)); + var el = domCreateElement(tag, newNs); + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var attrName = keywordName(arg); + var attrExpr = nth(args, (get(state, "i") + 1)); + (isSxTruthy(startsWith(attrName, "on-")) ? (function() { + var attrVal = trampoline(evalExpr(attrExpr, env)); + return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), attrVal) : NIL); +})() : (isSxTruthy((attrName == "bind")) ? (function() { + var attrVal = trampoline(evalExpr(attrExpr, env)); + return (isSxTruthy(isSignal(attrVal)) ? bindInput(el, attrVal) : NIL); +})() : (isSxTruthy((attrName == "ref")) ? (function() { + var attrVal = trampoline(evalExpr(attrExpr, env)); + return dictSet(attrVal, "current", el); +})() : (isSxTruthy((attrName == "key")) ? (function() { + var attrVal = trampoline(evalExpr(attrExpr, env)); + return domSetAttr(el, "key", (String(attrVal))); +})() : (isSxTruthy(_islandScope) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() { + var attrVal = trampoline(evalExpr(attrExpr, env)); + return (isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal)))))); +})()))))); + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return el; +})(); }; + + // render-dom-component + var renderDomComponent = function(comp, args, env, ns) { return (function() { + var kwargs = {}; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + kwargs[keywordName(arg)] = val; + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return (function() { + var local = envMerge(componentClosure(comp), env); + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + if (isSxTruthy(componentHasChildren(comp))) { + (function() { + var childFrag = createFragment(); + { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(childFrag, renderToDom(c, env, ns)); } } + return envSet(local, "children", childFrag); +})(); +} + return renderToDom(componentBody(comp), local, ns); +})(); +})(); }; + + // render-dom-fragment + var renderDomFragment = function(args, env, ns) { return (function() { + var frag = createFragment(); + { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; domAppend(frag, renderToDom(x, env, ns)); } } + return frag; +})(); }; + + // render-dom-raw + var renderDomRaw = function(args, env) { return (function() { + var frag = createFragment(); + { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (function() { + var val = trampoline(evalExpr(arg, env)); + return (isSxTruthy((typeOf(val) == "string")) ? domAppend(frag, domParseHtml(val)) : (isSxTruthy((typeOf(val) == "dom-node")) ? domAppend(frag, domClone(val)) : (isSxTruthy(!isSxTruthy(isNil(val))) ? domAppend(frag, createTextNode((String(val)))) : NIL))); +})(); } } + return frag; +})(); }; + + // render-dom-unknown-component + var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); }; + + // RENDER_DOM_FORMS + var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary"]; + + // render-dom-form? + var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); }; + + // dispatch-render-form + var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(_islandScope) ? (function() { + var marker = createComment("r-if"); + var currentNodes = []; + var initialResult = NIL; + effect(function() { return (function() { + var result = (function() { + var condVal = trampoline(evalExpr(nth(expr, 1), env)); + return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment())); +})(); + return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result])), domInsertAfter(marker, result)) : (initialResult = result)); +})(); }); + return (function() { + var frag = createFragment(); + domAppend(frag, marker); + if (isSxTruthy(initialResult)) { + currentNodes = (isSxTruthy(domIsFragment(initialResult)) ? domChildNodes(initialResult) : [initialResult]); + domAppend(frag, initialResult); +} + return frag; +})(); +})() : (function() { + var condVal = trampoline(evalExpr(nth(expr, 1), env)); + return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment())); +})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(_islandScope) ? (function() { + var marker = createComment("r-when"); + var currentNodes = []; + var initialResult = NIL; + effect(function() { return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = []), (isSxTruthy(trampoline(evalExpr(nth(expr, 1), env))) ? (function() { + var frag = createFragment(); + { var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } + currentNodes = domChildNodes(frag); + return domInsertAfter(marker, frag); +})() : NIL)) : (isSxTruthy(trampoline(evalExpr(nth(expr, 1), env))) ? (function() { + var frag = createFragment(); + { var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } + currentNodes = domChildNodes(frag); + return (initialResult = frag); +})() : NIL)); }); + return (function() { + var frag = createFragment(); + domAppend(frag, marker); + if (isSxTruthy(initialResult)) { + domAppend(frag, initialResult); +} + return frag; +})(); +})() : (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? createFragment() : (function() { + var frag = createFragment(); + { var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } + return frag; +})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(_islandScope) ? (function() { + var marker = createComment("r-cond"); + var currentNodes = []; + var initialResult = NIL; + effect(function() { return (function() { + var branch = evalCond(rest(expr), env); + return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = []), (isSxTruthy(branch) ? (function() { + var result = renderToDom(branch, env, ns); + currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result]); + return domInsertAfter(marker, result); +})() : NIL)) : (isSxTruthy(branch) ? (function() { + var result = renderToDom(branch, env, ns); + currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result]); + return (initialResult = result); +})() : NIL)); +})(); }); + return (function() { + var frag = createFragment(); + domAppend(frag, marker); + if (isSxTruthy(initialResult)) { + domAppend(frag, initialResult); +} + return frag; +})(); +})() : (function() { + var branch = evalCond(rest(expr), env); + return (isSxTruthy(branch) ? renderToDom(branch, env, ns) : createFragment()); +})()) : (isSxTruthy((name == "case")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() { + var local = processBindings(nth(expr, 1), env); + var frag = createFragment(); + { var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), local, ns)); } } + return frag; +})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (function() { + var frag = createFragment(); + { var _c = range(1, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } + return frag; +})() : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), createFragment()) : (isSxTruthy((name == "map")) ? (function() { + var collExpr = nth(expr, 2); + return (isSxTruthy((isSxTruthy(_islandScope) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && isSxTruthy((typeOf(first(collExpr)) == "symbol")) && (symbolName(first(collExpr)) == "deref"))) ? (function() { + var f = trampoline(evalExpr(nth(expr, 1), env)); + var sig = trampoline(evalExpr(nth(collExpr, 1), env)); + return (isSxTruthy(isSignal(sig)) ? reactiveList(f, sig, env, ns) : (function() { + var coll = deref(sig); + var frag = createFragment(); + { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() { + var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns)); + return domAppend(frag, val); +})(); } } + return frag; +})()); +})() : (function() { + var f = trampoline(evalExpr(nth(expr, 1), env)); + var coll = trampoline(evalExpr(nth(expr, 2), env)); + var frag = createFragment(); + { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() { + var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns)); + return domAppend(frag, val); +})(); } } + return frag; +})()); +})() : (isSxTruthy((name == "map-indexed")) ? (function() { + var f = trampoline(evalExpr(nth(expr, 1), env)); + var coll = trampoline(evalExpr(nth(expr, 2), env)); + var frag = createFragment(); + forEachIndexed(function(i, item) { return (function() { + var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [i, item], env, ns) : renderToDom(apply(f, [i, item]), env, ns)); + return domAppend(frag, val); +})(); }, coll); + return frag; +})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(rest(expr), env, ns) : (isSxTruthy((name == "error-boundary")) ? renderDomErrorBoundary(rest(expr), env, ns) : (isSxTruthy((name == "for-each")) ? (function() { + var f = trampoline(evalExpr(nth(expr, 1), env)); + var coll = trampoline(evalExpr(nth(expr, 2), env)); + var frag = createFragment(); + { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() { + var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns)); + return domAppend(frag, val); +})(); } } + return frag; +})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); }; + + // render-lambda-dom + var renderLambdaDom = function(f, args, env, ns) { return (function() { + var local = envMerge(lambdaClosure(f), env); + forEachIndexed(function(i, p) { return envSet(local, p, nth(args, i)); }, lambdaParams(f)); + return renderToDom(lambdaBody(f), local, ns); +})(); }; + + // render-dom-island + var renderDomIsland = function(island, args, env, ns) { return (function() { + var kwargs = {}; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + kwargs[keywordName(arg)] = val; + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return (function() { + var local = envMerge(componentClosure(island), env); + var islandName = componentName(island); + { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + if (isSxTruthy(componentHasChildren(island))) { + (function() { + var childFrag = createFragment(); + { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(childFrag, renderToDom(c, env, ns)); } } + return envSet(local, "children", childFrag); +})(); +} + return (function() { + var container = domCreateElement("span", NIL); + var disposers = []; + domSetAttr(container, "data-sx-island", islandName); + markProcessed(container, "island-hydrated"); + return (function() { + var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(island), local, ns); }); + domAppend(container, bodyDom); + domSetData(container, "sx-disposers", disposers); + return container; +})(); +})(); +})(); +})(); }; + + // render-dom-lake + var renderDomLake = function(args, env, ns) { return (function() { + var lakeId = NIL; + var lakeTag = "div"; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var kname = keywordName(arg); + var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + (isSxTruthy((kname == "id")) ? (lakeId = kval) : (isSxTruthy((kname == "tag")) ? (lakeTag = kval) : NIL)); + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return (function() { + var el = domCreateElement(lakeTag, NIL); + domSetAttr(el, "data-sx-lake", sxOr(lakeId, "")); + { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(el, renderToDom(c, env, ns)); } } + return el; +})(); +})(); }; + + // render-dom-marsh + var renderDomMarsh = function(args, env, ns) { return (function() { + var marshId = NIL; + var marshTag = "div"; + var marshTransform = NIL; + var children = []; + reduce(function(state, arg) { return (function() { + var skip = get(state, "skip"); + return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { + var kname = keywordName(arg); + var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env)); + (isSxTruthy((kname == "id")) ? (marshId = kval) : (isSxTruthy((kname == "tag")) ? (marshTag = kval) : (isSxTruthy((kname == "transform")) ? (marshTransform = kval) : NIL))); + return assoc(state, "skip", true, "i", (get(state, "i") + 1)); +})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1))))); +})(); }, {["i"]: 0, ["skip"]: false}, args); + return (function() { + var el = domCreateElement(marshTag, NIL); + domSetAttr(el, "data-sx-marsh", sxOr(marshId, "")); + if (isSxTruthy(marshTransform)) { + domSetData(el, "sx-marsh-transform", marshTransform); +} + domSetData(el, "sx-marsh-env", env); + { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(el, renderToDom(c, env, ns)); } } + return el; +})(); +})(); }; + + // reactive-text + var reactiveText = function(sig) { return (function() { + var node = createTextNode((String(deref(sig)))); + effect(function() { return domSetTextContent(node, (String(deref(sig)))); }); + return node; +})(); }; + + // reactive-attr + var reactiveAttr = function(el, attrName, computeFn) { (function() { + var existing = sxOr(domGetAttr(el, "data-sx-reactive-attrs"), ""); + var updated = (isSxTruthy(isEmpty(existing)) ? attrName : (String(existing) + String(",") + String(attrName))); + return domSetAttr(el, "data-sx-reactive-attrs", updated); +})(); +return effect(function() { return (function() { + var raw = computeFn(); + return (function() { + var val = (isSxTruthy(isSignal(raw)) ? deref(raw) : raw); + return (isSxTruthy(sxOr(isNil(val), (val == false))) ? domRemoveAttr(el, attrName) : (isSxTruthy((val == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(val))))); +})(); +})(); }); }; + + // reactive-fragment + var reactiveFragment = function(testFn, renderFn, env, ns) { return (function() { + var marker = createComment("island-fragment"); + var currentNodes = []; + effect(function() { { var _c = currentNodes; for (var _i = 0; _i < _c.length; _i++) { var n = _c[_i]; domRemove(n); } } +currentNodes = []; +return (isSxTruthy(testFn()) ? (function() { + var frag = renderFn(); + currentNodes = domChildNodes(frag); + return domInsertAfter(marker, frag); +})() : NIL); }); + return marker; +})(); }; + + // render-list-item + var renderListItem = function(mapFn, item, env, ns) { return (isSxTruthy(isLambda(mapFn)) ? renderLambdaDom(mapFn, [item], env, ns) : renderToDom(apply(mapFn, [item]), env, ns)); }; + + // extract-key + var extractKey = function(node, index) { return (function() { + var k = domGetAttr(node, "key"); + return (isSxTruthy(k) ? (domRemoveAttr(node, "key"), k) : (function() { + var dk = domGetData(node, "key"); + return (isSxTruthy(dk) ? (String(dk)) : (String("__idx_") + String(index))); +})()); +})(); }; + + // reactive-list + var reactiveList = function(mapFn, itemsSig, env, ns) { return (function() { + var container = createFragment(); + var marker = createComment("island-list"); + var keyMap = {}; + var keyOrder = []; + domAppend(container, marker); + effect(function() { return (function() { + var items = deref(itemsSig); + return (isSxTruthy(domParent(marker)) ? (function() { + var newMap = {}; + var newKeys = []; + var hasKeys = false; + forEachIndexed(function(idx, item) { return (function() { + var rendered = renderListItem(mapFn, item, env, ns); + var key = extractKey(rendered, idx); + if (isSxTruthy((isSxTruthy(!isSxTruthy(hasKeys)) && !isSxTruthy(startsWith(key, "__idx_"))))) { + hasKeys = true; +} + (isSxTruthy(dictHas(keyMap, key)) ? dictSet(newMap, key, dictGet(keyMap, key)) : dictSet(newMap, key, rendered)); + return append_b(newKeys, key); +})(); }, items); + (isSxTruthy(!isSxTruthy(hasKeys)) ? (domRemoveChildrenAfter(marker), (function() { + var frag = createFragment(); + { var _c = newKeys; for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; domAppend(frag, dictGet(newMap, k)); } } + return domInsertAfter(marker, frag); +})()) : (forEach(function(oldKey) { return (isSxTruthy(!isSxTruthy(dictHas(newMap, oldKey))) ? domRemove(dictGet(keyMap, oldKey)) : NIL); }, keyOrder), (function() { + var cursor = marker; + return forEach(function(k) { return (function() { + var node = dictGet(newMap, k); + var next = domNextSibling(cursor); + if (isSxTruthy(!isSxTruthy(isIdentical(node, next)))) { + domInsertAfter(cursor, node); +} + return (cursor = node); +})(); }, newKeys); +})())); + keyMap = newMap; + return (keyOrder = newKeys); +})() : forEachIndexed(function(idx, item) { return (function() { + var rendered = renderListItem(mapFn, item, env, ns); + var key = extractKey(rendered, idx); + keyMap[key] = rendered; + keyOrder.push(key); + return domAppend(container, rendered); +})(); }, items)); +})(); }); + return container; +})(); }; + + // bind-input + var bindInput = function(el, sig) { return (function() { + var inputType = lower(sxOr(domGetAttr(el, "type"), "")); + var isCheckbox = sxOr((inputType == "checkbox"), (inputType == "radio")); + (isSxTruthy(isCheckbox) ? domSetProp(el, "checked", deref(sig)) : domSetProp(el, "value", (String(deref(sig))))); + effect(function() { return (isSxTruthy(isCheckbox) ? domSetProp(el, "checked", deref(sig)) : (function() { + var v = (String(deref(sig))); + return (isSxTruthy((domGetProp(el, "value") != v)) ? domSetProp(el, "value", v) : NIL); +})()); }); + return domListen(el, (isSxTruthy(isCheckbox) ? "change" : "input"), function(e) { return (isSxTruthy(isCheckbox) ? reset_b(sig, domGetProp(el, "checked")) : reset_b(sig, domGetProp(el, "value"))); }); +})(); }; + + // render-dom-portal + var renderDomPortal = function(args, env, ns) { return (function() { + var selector = trampoline(evalExpr(first(args), env)); + var target = sxOr(domQuery(selector), domEnsureElement(selector)); + return (isSxTruthy(!isSxTruthy(target)) ? createComment((String("portal: ") + String(selector) + String(" (not found)"))) : (function() { + var marker = createComment((String("portal: ") + String(selector))); + var frag = createFragment(); + { var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } } + (function() { + var portalNodes = domChildNodes(frag); + domAppend(target, frag); + return registerInScope(function() { return forEach(function(n) { return domRemove(n); }, portalNodes); }); +})(); + return marker; +})()); +})(); }; + + // render-dom-error-boundary + var renderDomErrorBoundary = function(args, env, ns) { return (function() { + var fallbackExpr = first(args); + var bodyExprs = rest(args); + var container = domCreateElement("div", NIL); + var retryVersion = signal(0); + domSetAttr(container, "data-sx-boundary", "true"); + effect(function() { deref(retryVersion); +domSetProp(container, "innerHTML", ""); +return (function() { + var savedScope = _islandScope; + _islandScope = NIL; + return tryCatch(function() { (function() { + var frag = createFragment(); + { var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } } + return domAppend(container, frag); +})(); +return (_islandScope = savedScope); }, function(err) { _islandScope = savedScope; +return (function() { + var fallbackFn = trampoline(evalExpr(fallbackExpr, env)); + var retryFn = function() { return swap_b(retryVersion, function(n) { return (n + 1); }); }; + return (function() { + var fallbackDom = (isSxTruthy(isLambda(fallbackFn)) ? renderLambdaDom(fallbackFn, [err, retryFn], env, ns) : renderToDom(apply(fallbackFn, [err, retryFn]), env, ns)); + return domAppend(container, fallbackDom); +})(); +})(); }); +})(); }); + return container; +})(); }; + + + // === Transpiled from engine === + + // ENGINE_VERBS + var ENGINE_VERBS = ["get", "post", "put", "delete", "patch"]; + + // DEFAULT_SWAP + var DEFAULT_SWAP = "outerHTML"; + + // parse-time + var parseTime = function(s) { return (isSxTruthy(isNil(s)) ? 0 : (isSxTruthy(endsWith(s, "ms")) ? parseInt_(s, 0) : (isSxTruthy(endsWith(s, "s")) ? (parseInt_(replace_(s, "s", ""), 0) * 1000) : parseInt_(s, 0)))); }; + + // parse-trigger-spec + var parseTriggerSpec = function(spec) { return (isSxTruthy(isNil(spec)) ? NIL : (function() { + var rawParts = split(spec, ","); + return filter(function(x) { return !isSxTruthy(isNil(x)); }, map(function(part) { return (function() { + var tokens = split(trim(part), " "); + return (isSxTruthy(isEmpty(tokens)) ? NIL : (isSxTruthy((isSxTruthy((first(tokens) == "every")) && (len(tokens) >= 2))) ? {["event"]: "every", ["modifiers"]: {["interval"]: parseTime(nth(tokens, 1))}} : (function() { + var mods = {}; + { var _c = rest(tokens); for (var _i = 0; _i < _c.length; _i++) { var tok = _c[_i]; (isSxTruthy((tok == "once")) ? dictSet(mods, "once", true) : (isSxTruthy((tok == "changed")) ? dictSet(mods, "changed", true) : (isSxTruthy(startsWith(tok, "delay:")) ? dictSet(mods, "delay", parseTime(slice(tok, 6))) : (isSxTruthy(startsWith(tok, "from:")) ? dictSet(mods, "from", slice(tok, 5)) : NIL)))); } } + return {["event"]: first(tokens), ["modifiers"]: mods}; +})())); +})(); }, rawParts)); +})()); }; + + // default-trigger + var defaultTrigger = function(tagName) { return (isSxTruthy((tagName == "FORM")) ? [{["event"]: "submit", ["modifiers"]: {}}] : (isSxTruthy(sxOr((tagName == "INPUT"), (tagName == "SELECT"), (tagName == "TEXTAREA"))) ? [{["event"]: "change", ["modifiers"]: {}}] : [{["event"]: "click", ["modifiers"]: {}}])); }; + + // get-verb-info + var getVerbInfo = function(el) { return some(function(verb) { return (function() { + var url = domGetAttr(el, (String("sx-") + String(verb))); + return (isSxTruthy(url) ? {["method"]: upper(verb), ["url"]: url} : NIL); +})(); }, ENGINE_VERBS); }; + + // build-request-headers + var buildRequestHeaders = function(el, loadedComponents, cssHash) { return (function() { + var headers = {["SX-Request"]: "true", ["SX-Current-URL"]: browserLocationHref()}; + (function() { + var targetSel = domGetAttr(el, "sx-target"); + return (isSxTruthy(targetSel) ? dictSet(headers, "SX-Target", targetSel) : NIL); +})(); + if (isSxTruthy(!isSxTruthy(isEmpty(loadedComponents)))) { + headers["SX-Components"] = join(",", loadedComponents); +} + if (isSxTruthy(cssHash)) { + headers["SX-Css"] = cssHash; +} + (function() { + var extraH = domGetAttr(el, "sx-headers"); + return (isSxTruthy(extraH) ? (function() { + var parsed = parseHeaderValue(extraH); + return (isSxTruthy(parsed) ? forEach(function(key) { return dictSet(headers, key, (String(get(parsed, key)))); }, keys(parsed)) : NIL); +})() : NIL); +})(); + return headers; +})(); }; + + // process-response-headers + var processResponseHeaders = function(getHeader) { return {["redirect"]: getHeader("SX-Redirect"), ["refresh"]: getHeader("SX-Refresh"), ["trigger"]: getHeader("SX-Trigger"), ["retarget"]: getHeader("SX-Retarget"), ["reswap"]: getHeader("SX-Reswap"), ["location"]: getHeader("SX-Location"), ["replace-url"]: getHeader("SX-Replace-Url"), ["css-hash"]: getHeader("SX-Css-Hash"), ["trigger-swap"]: getHeader("SX-Trigger-After-Swap"), ["trigger-settle"]: getHeader("SX-Trigger-After-Settle"), ["content-type"]: getHeader("Content-Type"), ["cache-invalidate"]: getHeader("SX-Cache-Invalidate"), ["cache-update"]: getHeader("SX-Cache-Update")}; }; + + // parse-swap-spec + var parseSwapSpec = function(rawSwap, globalTransitions_p) { return (function() { + var parts = split(sxOr(rawSwap, DEFAULT_SWAP), " "); + var style = first(parts); + var useTransition = globalTransitions_p; + { var _c = rest(parts); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; (isSxTruthy((p == "transition:true")) ? (useTransition = true) : (isSxTruthy((p == "transition:false")) ? (useTransition = false) : NIL)); } } + return {["style"]: style, ["transition"]: useTransition}; +})(); }; + + // parse-retry-spec + var parseRetrySpec = function(retryAttr) { return (isSxTruthy(isNil(retryAttr)) ? NIL : (function() { + var parts = split(retryAttr, ":"); + return {["strategy"]: first(parts), ["start-ms"]: parseInt_(nth(parts, 1), 1000), ["cap-ms"]: parseInt_(nth(parts, 2), 30000)}; +})()); }; + + // next-retry-ms + var nextRetryMs = function(currentMs, capMs) { return min((currentMs * 2), capMs); }; + + // filter-params + var filterParams = function(paramsSpec, allParams) { return (isSxTruthy(isNil(paramsSpec)) ? allParams : (isSxTruthy((paramsSpec == "none")) ? [] : (isSxTruthy((paramsSpec == "*")) ? allParams : (isSxTruthy(startsWith(paramsSpec, "not ")) ? (function() { + var excluded = map(trim, split(slice(paramsSpec, 4), ",")); + return filter(function(p) { return !isSxTruthy(contains(excluded, first(p))); }, allParams); +})() : (function() { + var allowed = map(trim, split(paramsSpec, ",")); + return filter(function(p) { return contains(allowed, first(p)); }, allParams); +})())))); }; + + // resolve-target + var resolveTarget = function(el) { return (function() { + var sel = domGetAttr(el, "sx-target"); + return (isSxTruthy(sxOr(isNil(sel), (sel == "this"))) ? el : (isSxTruthy((sel == "closest")) ? domParent(el) : domQuery(sel))); +})(); }; + + // apply-optimistic + var applyOptimistic = function(el) { return (function() { + var directive = domGetAttr(el, "sx-optimistic"); + return (isSxTruthy(isNil(directive)) ? NIL : (function() { + var target = sxOr(resolveTarget(el), el); + var state = {["target"]: target, ["directive"]: directive}; + (isSxTruthy((directive == "remove")) ? (dictSet(state, "opacity", domGetStyle(target, "opacity")), domSetStyle(target, "opacity", "0"), domSetStyle(target, "pointer-events", "none")) : (isSxTruthy((directive == "disable")) ? (dictSet(state, "disabled", domGetProp(target, "disabled")), domSetProp(target, "disabled", true)) : (isSxTruthy(startsWith(directive, "add-class:")) ? (function() { + var cls = slice(directive, 10); + state["add-class"] = cls; + return domAddClass(target, cls); +})() : NIL))); + return state; +})()); +})(); }; + + // revert-optimistic + var revertOptimistic = function(state) { return (isSxTruthy(state) ? (function() { + var target = get(state, "target"); + var directive = get(state, "directive"); + return (isSxTruthy((directive == "remove")) ? (domSetStyle(target, "opacity", sxOr(get(state, "opacity"), "")), domSetStyle(target, "pointer-events", "")) : (isSxTruthy((directive == "disable")) ? domSetProp(target, "disabled", sxOr(get(state, "disabled"), false)) : (isSxTruthy(get(state, "add-class")) ? domRemoveClass(target, get(state, "add-class")) : NIL))); +})() : NIL); }; + + // find-oob-swaps + var findOobSwaps = function(container) { return (function() { + var results = []; + { var _c = ["sx-swap-oob", "hx-swap-oob"]; for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() { + var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]"))); + return forEach(function(oob) { return (function() { + var swapType = sxOr(domGetAttr(oob, attr), "outerHTML"); + var targetId = domId(oob); + domRemoveAttr(oob, attr); + return (isSxTruthy(targetId) ? append_b(results, {["element"]: oob, ["swap-type"]: swapType, ["target-id"]: targetId}) : NIL); +})(); }, oobEls); +})(); } } + return results; +})(); }; + + // morph-node + var morphNode = function(oldNode, newNode) { return (isSxTruthy(sxOr(domHasAttr(oldNode, "sx-preserve"), domHasAttr(oldNode, "sx-ignore"))) ? NIL : (isSxTruthy((isSxTruthy(domHasAttr(oldNode, "data-sx-island")) && isSxTruthy(isProcessed(oldNode, "island-hydrated")) && isSxTruthy(domHasAttr(newNode, "data-sx-island")) && (domGetAttr(oldNode, "data-sx-island") == domGetAttr(newNode, "data-sx-island")))) ? morphIslandChildren(oldNode, newNode) : (isSxTruthy(sxOr(!isSxTruthy((domNodeType(oldNode) == domNodeType(newNode))), !isSxTruthy((domNodeName(oldNode) == domNodeName(newNode))))) ? domReplaceChild(domParent(oldNode), domClone(newNode), oldNode) : (isSxTruthy(sxOr((domNodeType(oldNode) == 3), (domNodeType(oldNode) == 8))) ? (isSxTruthy(!isSxTruthy((domTextContent(oldNode) == domTextContent(newNode)))) ? domSetTextContent(oldNode, domTextContent(newNode)) : NIL) : (isSxTruthy((domNodeType(oldNode) == 1)) ? (syncAttrs(oldNode, newNode), (isSxTruthy(!isSxTruthy((isSxTruthy(domIsActiveElement(oldNode)) && domIsInputElement(oldNode)))) ? morphChildren(oldNode, newNode) : NIL)) : NIL))))); }; + + // sync-attrs + var syncAttrs = function(oldEl, newEl) { return (function() { + var raStr = sxOr(domGetAttr(oldEl, "data-sx-reactive-attrs"), ""); + var reactiveAttrs = (isSxTruthy(isEmpty(raStr)) ? [] : split(raStr, ",")); + { var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() { + var name = first(attr); + var val = nth(attr, 1); + return (isSxTruthy((isSxTruthy(!isSxTruthy((domGetAttr(oldEl, name) == val))) && !isSxTruthy(contains(reactiveAttrs, name)))) ? domSetAttr(oldEl, name, val) : NIL); +})(); } } + return forEach(function(attr) { return (function() { + var aname = first(attr); + return (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(newEl, aname))) && isSxTruthy(!isSxTruthy(contains(reactiveAttrs, aname))) && !isSxTruthy((aname == "data-sx-reactive-attrs")))) ? domRemoveAttr(oldEl, aname) : NIL); +})(); }, domAttrList(oldEl)); +})(); }; + + // morph-children + var morphChildren = function(oldParent, newParent) { return (function() { + var oldKids = domChildList(oldParent); + var newKids = domChildList(newParent); + var oldById = reduce(function(acc, kid) { return (function() { + var id = domId(kid); + return (isSxTruthy(id) ? (dictSet(acc, id, kid), acc) : acc); +})(); }, {}, oldKids); + var oi = 0; + { var _c = newKids; for (var _i = 0; _i < _c.length; _i++) { var newChild = _c[_i]; (function() { + var matchId = domId(newChild); + var matchById = (isSxTruthy(matchId) ? dictGet(oldById, matchId) : NIL); + return (isSxTruthy((isSxTruthy(matchById) && !isSxTruthy(isNil(matchById)))) ? ((isSxTruthy((isSxTruthy((oi < len(oldKids))) && !isSxTruthy((matchById == nth(oldKids, oi))))) ? domInsertBefore(oldParent, matchById, (isSxTruthy((oi < len(oldKids))) ? nth(oldKids, oi) : NIL)) : NIL), morphNode(matchById, newChild), (oi = (oi + 1))) : (isSxTruthy((oi < len(oldKids))) ? (function() { + var oldChild = nth(oldKids, oi); + return (isSxTruthy((isSxTruthy(domId(oldChild)) && !isSxTruthy(matchId))) ? domInsertBefore(oldParent, domClone(newChild), oldChild) : (morphNode(oldChild, newChild), (oi = (oi + 1)))); +})() : domAppend(oldParent, domClone(newChild)))); +})(); } } + return forEach(function(i) { return (isSxTruthy((i >= oi)) ? (function() { + var leftover = nth(oldKids, i); + return (isSxTruthy((isSxTruthy(domIsChildOf(leftover, oldParent)) && isSxTruthy(!isSxTruthy(domHasAttr(leftover, "sx-preserve"))) && !isSxTruthy(domHasAttr(leftover, "sx-ignore")))) ? domRemoveChild(oldParent, leftover) : NIL); +})() : NIL); }, range(oi, len(oldKids))); +})(); }; + + // morph-island-children + var morphIslandChildren = function(oldIsland, newIsland) { return (function() { + var oldLakes = domQueryAll(oldIsland, "[data-sx-lake]"); + var newLakes = domQueryAll(newIsland, "[data-sx-lake]"); + var oldMarshes = domQueryAll(oldIsland, "[data-sx-marsh]"); + var newMarshes = domQueryAll(newIsland, "[data-sx-marsh]"); + return (function() { + var newLakeMap = {}; + var newMarshMap = {}; + { var _c = newLakes; for (var _i = 0; _i < _c.length; _i++) { var lake = _c[_i]; (function() { + var id = domGetAttr(lake, "data-sx-lake"); + return (isSxTruthy(id) ? dictSet(newLakeMap, id, lake) : NIL); +})(); } } + { var _c = newMarshes; for (var _i = 0; _i < _c.length; _i++) { var marsh = _c[_i]; (function() { + var id = domGetAttr(marsh, "data-sx-marsh"); + return (isSxTruthy(id) ? dictSet(newMarshMap, id, marsh) : NIL); +})(); } } + { var _c = oldLakes; for (var _i = 0; _i < _c.length; _i++) { var oldLake = _c[_i]; (function() { + var id = domGetAttr(oldLake, "data-sx-lake"); + return (function() { + var newLake = dictGet(newLakeMap, id); + return (isSxTruthy(newLake) ? (syncAttrs(oldLake, newLake), morphChildren(oldLake, newLake)) : NIL); +})(); +})(); } } + { var _c = oldMarshes; for (var _i = 0; _i < _c.length; _i++) { var oldMarsh = _c[_i]; (function() { + var id = domGetAttr(oldMarsh, "data-sx-marsh"); + return (function() { + var newMarsh = dictGet(newMarshMap, id); + return (isSxTruthy(newMarsh) ? morphMarsh(oldMarsh, newMarsh, oldIsland) : NIL); +})(); +})(); } } + return processSignalUpdates(newIsland); +})(); +})(); }; + + // morph-marsh + var morphMarsh = function(oldMarsh, newMarsh, islandEl) { return (function() { + var transform = domGetData(oldMarsh, "sx-marsh-transform"); + var env = domGetData(oldMarsh, "sx-marsh-env"); + var newHtml = domInnerHtml(newMarsh); + return (isSxTruthy((isSxTruthy(env) && isSxTruthy(newHtml) && !isSxTruthy(isEmpty(newHtml)))) ? (function() { + var parsed = parse(newHtml); + return (function() { + var sxContent = (isSxTruthy(transform) ? invoke(transform, parsed) : parsed); + disposeMarshScope(oldMarsh); + return withMarshScope(oldMarsh, function() { return (function() { + var newDom = renderToDom(sxContent, env, NIL); + domRemoveChildrenAfter(oldMarsh, NIL); + return domAppend(oldMarsh, newDom); +})(); }); +})(); +})() : (syncAttrs(oldMarsh, newMarsh), morphChildren(oldMarsh, newMarsh))); +})(); }; + + // process-signal-updates + var processSignalUpdates = function(root) { return (function() { + var signalEls = domQueryAll(root, "[data-sx-signal]"); + return forEach(function(el) { return (function() { + var spec = domGetAttr(el, "data-sx-signal"); + return (isSxTruthy(spec) ? (function() { + var colonIdx = indexOf_(spec, ":"); + return (isSxTruthy((colonIdx > 0)) ? (function() { + var storeName = slice(spec, 0, colonIdx); + var rawValue = slice(spec, (colonIdx + 1)); + (function() { + var parsed = jsonParse(rawValue); + return reset_b(useStore(storeName), parsed); +})(); + return domRemoveAttr(el, "data-sx-signal"); +})() : NIL); +})() : NIL); +})(); }, signalEls); +})(); }; + + // swap-dom-nodes + var swapDomNodes = function(target, newNodes, strategy) { return (function() { var _m = strategy; if (_m == "innerHTML") return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() { + var wrapper = domCreateElement("div", NIL); + domAppend(wrapper, newNodes); + return morphChildren(target, wrapper); +})()); if (_m == "outerHTML") return (function() { + var parent = domParent(target); + (isSxTruthy(domIsFragment(newNodes)) ? (function() { + var fc = domFirstChild(newNodes); + return (isSxTruthy(fc) ? (morphNode(target, fc), (function() { + var sib = domNextSibling(fc); + return insertRemainingSiblings(parent, target, sib); +})()) : domRemoveChild(parent, target)); +})() : morphNode(target, newNodes)); + return parent; +})(); if (_m == "afterend") return domInsertAfter(target, newNodes); if (_m == "beforeend") return domAppend(target, newNodes); if (_m == "afterbegin") return domPrepend(target, newNodes); if (_m == "beforebegin") return domInsertBefore(domParent(target), newNodes, target); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() { + var wrapper = domCreateElement("div", NIL); + domAppend(wrapper, newNodes); + return morphChildren(target, wrapper); +})()); })(); }; + + // insert-remaining-siblings + var insertRemainingSiblings = function(parent, refNode, sib) { return (isSxTruthy(sib) ? (function() { + var next = domNextSibling(sib); + domInsertAfter(refNode, sib); + return insertRemainingSiblings(parent, sib, next); +})() : NIL); }; + + // swap-html-string + var swapHtmlString = function(target, html, strategy) { return (function() { var _m = strategy; if (_m == "innerHTML") return domSetInnerHtml(target, html); if (_m == "outerHTML") return (function() { + var parent = domParent(target); + domInsertAdjacentHtml(target, "afterend", html); + domRemoveChild(parent, target); + return parent; +})(); if (_m == "afterend") return domInsertAdjacentHtml(target, "afterend", html); if (_m == "beforeend") return domInsertAdjacentHtml(target, "beforeend", html); if (_m == "afterbegin") return domInsertAdjacentHtml(target, "afterbegin", html); if (_m == "beforebegin") return domInsertAdjacentHtml(target, "beforebegin", html); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return domSetInnerHtml(target, html); })(); }; + + // handle-history + var handleHistory = function(el, url, respHeaders) { return (function() { + var pushUrl = domGetAttr(el, "sx-push-url"); + var replaceUrl = domGetAttr(el, "sx-replace-url"); + var hdrReplace = get(respHeaders, "replace-url"); + return (isSxTruthy(hdrReplace) ? browserReplaceState(hdrReplace) : (isSxTruthy((isSxTruthy(pushUrl) && !isSxTruthy((pushUrl == "false")))) ? browserPushState((isSxTruthy((pushUrl == "true")) ? url : pushUrl)) : (isSxTruthy((isSxTruthy(replaceUrl) && !isSxTruthy((replaceUrl == "false")))) ? browserReplaceState((isSxTruthy((replaceUrl == "true")) ? url : replaceUrl)) : NIL))); +})(); }; + + // PRELOAD_TTL + var PRELOAD_TTL = 30000; + + // preload-cache-get + var preloadCacheGet = function(cache, url) { return (function() { + var entry = dictGet(cache, url); + return (isSxTruthy(isNil(entry)) ? NIL : (isSxTruthy(((nowMs() - get(entry, "timestamp")) > PRELOAD_TTL)) ? (dictDelete(cache, url), NIL) : (dictDelete(cache, url), entry))); +})(); }; + + // preload-cache-set + var preloadCacheSet = function(cache, url, text, contentType) { return dictSet(cache, url, {["text"]: text, ["content-type"]: contentType, ["timestamp"]: nowMs()}); }; + + // classify-trigger + var classifyTrigger = function(trigger) { return (function() { + var event = get(trigger, "event"); + return (isSxTruthy((event == "every")) ? "poll" : (isSxTruthy((event == "intersect")) ? "intersect" : (isSxTruthy((event == "load")) ? "load" : (isSxTruthy((event == "revealed")) ? "revealed" : "event")))); +})(); }; + + // should-boost-link? + var shouldBoostLink = function(link) { return (function() { + var href = domGetAttr(link, "href"); + return (isSxTruthy(href) && isSxTruthy(!isSxTruthy(startsWith(href, "#"))) && isSxTruthy(!isSxTruthy(startsWith(href, "javascript:"))) && isSxTruthy(!isSxTruthy(startsWith(href, "mailto:"))) && isSxTruthy(browserSameOrigin(href)) && isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-get"))) && isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-post"))) && !isSxTruthy(domHasAttr(link, "sx-disable"))); +})(); }; + + // should-boost-form? + var shouldBoostForm = function(form) { return (isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-get"))) && isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-post"))) && !isSxTruthy(domHasAttr(form, "sx-disable"))); }; + + // parse-sse-swap + var parseSseSwap = function(el) { return sxOr(domGetAttr(el, "sx-sse-swap"), "message"); }; + + + // === Transpiled from orchestration === + + // _preload-cache + var _preloadCache = {}; + + // _css-hash + var _cssHash = ""; + + // dispatch-trigger-events + var dispatchTriggerEvents = function(el, headerVal) { return (isSxTruthy(headerVal) ? (function() { + var parsed = tryParseJson(headerVal); + return (isSxTruthy(parsed) ? forEach(function(key) { return domDispatch(el, key, get(parsed, key)); }, keys(parsed)) : forEach(function(name) { return (function() { + var trimmed = trim(name); + return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? domDispatch(el, trimmed, {}) : NIL); +})(); }, split(headerVal, ","))); +})() : NIL); }; + + // init-css-tracking + var initCssTracking = function() { return (function() { + var meta = domQuery("meta[name=\"sx-css-classes\"]"); + return (isSxTruthy(meta) ? (function() { + var content = domGetAttr(meta, "content"); + return (isSxTruthy(content) ? (_cssHash = content) : NIL); +})() : NIL); +})(); }; + + // execute-request + var executeRequest = function(el, verbInfo, extraParams) { return (function() { + var info = sxOr(getVerbInfo(el), verbInfo); + return (isSxTruthy(isNil(info)) ? promiseResolve(NIL) : (function() { + var verb = get(info, "method"); + var url = get(info, "url"); + return (isSxTruthy((function() { + var media = domGetAttr(el, "sx-media"); + return (isSxTruthy(media) && !isSxTruthy(browserMediaMatches(media))); +})()) ? promiseResolve(NIL) : (isSxTruthy((function() { + var confirmMsg = domGetAttr(el, "sx-confirm"); + return (isSxTruthy(confirmMsg) && !isSxTruthy(browserConfirm(confirmMsg))); +})()) ? promiseResolve(NIL) : (function() { + var promptMsg = domGetAttr(el, "sx-prompt"); + var promptVal = (isSxTruthy(promptMsg) ? browserPrompt(promptMsg) : NIL); + return (isSxTruthy((isSxTruthy(promptMsg) && isNil(promptVal))) ? promiseResolve(NIL) : (isSxTruthy(!isSxTruthy(validateForRequest(el))) ? promiseResolve(NIL) : doFetch(el, verb, verb, url, (isSxTruthy(promptVal) ? assoc(sxOr(extraParams, {}), "SX-Prompt", promptVal) : extraParams)))); +})())); +})()); +})(); }; + + // do-fetch + var doFetch = function(el, verb, method, url, extraParams) { return (function() { + var sync = domGetAttr(el, "sx-sync"); + if (isSxTruthy((sync == "replace"))) { + abortPrevious(el); +} + (function() { + var targetEl = resolveTarget(el); + return (isSxTruthy((isSxTruthy(targetEl) && !isSxTruthy(isIdentical(el, targetEl)))) ? abortPreviousTarget(targetEl) : NIL); +})(); + return (function() { + var ctrl = newAbortController(); + trackController(el, ctrl); + (function() { + var targetEl = resolveTarget(el); + return (isSxTruthy(targetEl) ? trackControllerTarget(targetEl, ctrl) : NIL); +})(); + return (function() { + var bodyInfo = buildRequestBody(el, method, url); + var finalUrl = get(bodyInfo, "url"); + var body = get(bodyInfo, "body"); + var ct = get(bodyInfo, "content-type"); + var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash); + var csrf = csrfToken(); + if (isSxTruthy(extraParams)) { + { var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; headers[k] = get(extraParams, k); } } +} + if (isSxTruthy(ct)) { + headers["Content-Type"] = ct; +} + if (isSxTruthy(csrf)) { + headers["X-CSRFToken"] = csrf; +} + return (function() { + var cached = preloadCacheGet(_preloadCache, finalUrl); + var optimisticState = applyOptimistic(el); + var indicator = showIndicator(el); + var disabledElts = disableElements(el); + domAddClass(el, "sx-request"); + domSetAttr(el, "aria-busy", "true"); + domDispatch(el, "sx:beforeRequest", {["url"]: finalUrl, ["method"]: method}); + return fetchRequest({["url"]: finalUrl, ["method"]: method, ["headers"]: headers, ["body"]: body, ["signal"]: controllerSignal(ctrl), ["cross-origin"]: isCrossOrigin(finalUrl), ["preloaded"]: cached}, function(respOk, status, getHeader, text) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isSxTruthy(respOk)) ? (domDispatch(el, "sx:responseError", {["status"]: status, ["text"]: text}), (isSxTruthy((isSxTruthy(text) && (len(text) > 0))) ? handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text) : handleRetry(el, verb, method, finalUrl, extraParams))) : (domDispatch(el, "sx:afterRequest", {["status"]: status}), handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text)))); }, function(err) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isSxTruthy(isAbortError(err))) ? domDispatch(el, "sx:requestError", {["error"]: err}) : NIL)); }); +})(); +})(); +})(); +})(); }; + + // handle-fetch-success + var handleFetchSuccess = function(el, url, verb, extraParams, getHeader, text) { return (function() { + var respHeaders = processResponseHeaders(getHeader); + (function() { + var newHash = get(respHeaders, "css-hash"); + return (isSxTruthy(newHash) ? (_cssHash = newHash) : NIL); +})(); + dispatchTriggerEvents(el, get(respHeaders, "trigger")); + processCacheDirectives(el, respHeaders, text); + return (isSxTruthy(get(respHeaders, "redirect")) ? browserNavigate(get(respHeaders, "redirect")) : (isSxTruthy(get(respHeaders, "refresh")) ? browserReload() : (isSxTruthy(get(respHeaders, "location")) ? fetchLocation(get(respHeaders, "location")) : (function() { + var targetEl = (isSxTruthy(get(respHeaders, "retarget")) ? domQuery(get(respHeaders, "retarget")) : resolveTarget(el)); + var swapSpec = parseSwapSpec(sxOr(get(respHeaders, "reswap"), domGetAttr(el, "sx-swap")), domHasClass(domBody(), "sx-transitions")); + var swapStyle = get(swapSpec, "style"); + var useTransition = get(swapSpec, "transition"); + var ct = sxOr(get(respHeaders, "content-type"), ""); + (isSxTruthy(contains(ct, "text/sx")) ? handleSxResponse(el, targetEl, text, swapStyle, useTransition) : handleHtmlResponse(el, targetEl, text, swapStyle, useTransition)); + dispatchTriggerEvents(el, get(respHeaders, "trigger-swap")); + handleHistory(el, url, respHeaders); + setTimeout_(function() { if (isSxTruthy(get(respHeaders, "trigger-settle"))) { + dispatchTriggerEvents(el, get(respHeaders, "trigger-settle")); +} +return processSettleHooks(el); }, 20); + return domDispatch(el, "sx:afterSwap", {["target"]: targetEl, ["swap"]: swapStyle}); +})()))); +})(); }; + + // handle-sx-response + var handleSxResponse = function(el, target, text, swapStyle, useTransition) { return (function() { + var cleaned = stripComponentScripts(text); + return (function() { + var final_ = extractResponseCss(cleaned); + return (function() { + var trimmed = trim(final_); + return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? (function() { + var rendered = sxRender(trimmed); + var container = domCreateElement("div", NIL); + domAppend(container, rendered); + processOobSwaps(container, function(t, oob, s) { disposeIslandsIn(t); +swapDomNodes(t, oob, s); +sxHydrate(t); +return processElements(t); }); + return (function() { + var selectSel = domGetAttr(el, "sx-select"); + var content = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container)); + disposeIslandsIn(target); + return withTransition(useTransition, function() { swapDomNodes(target, content, swapStyle); +return postSwap(target); }); +})(); +})() : NIL); +})(); +})(); +})(); }; + + // handle-html-response + var handleHtmlResponse = function(el, target, text, swapStyle, useTransition) { return (function() { + var doc = domParseHtmlDocument(text); + return (isSxTruthy(doc) ? (function() { + var selectSel = domGetAttr(el, "sx-select"); + disposeIslandsIn(target); + return (isSxTruthy(selectSel) ? (function() { + var html = selectHtmlFromDoc(doc, selectSel); + return withTransition(useTransition, function() { swapHtmlString(target, html, swapStyle); +return postSwap(target); }); +})() : (function() { + var container = domCreateElement("div", NIL); + domSetInnerHtml(container, domBodyInnerHtml(doc)); + processOobSwaps(container, function(t, oob, s) { disposeIslandsIn(t); +swapDomNodes(t, oob, s); +return postSwap(t); }); + hoistHeadElements(container); + return withTransition(useTransition, function() { swapDomNodes(target, childrenToFragment(container), swapStyle); +return postSwap(target); }); +})()); +})() : NIL); +})(); }; + + // handle-retry + var handleRetry = function(el, verb, method, url, extraParams) { return (function() { + var retryAttr = domGetAttr(el, "sx-retry"); + var spec = parseRetrySpec(retryAttr); + return (isSxTruthy(spec) ? (function() { + var currentMs = sxOr(domGetAttr(el, "data-sx-retry-ms"), get(spec, "start-ms")); + return (function() { + var ms = parseInt_(currentMs, get(spec, "start-ms")); + domSetAttr(el, "data-sx-retry-ms", (String(nextRetryMs(ms, get(spec, "cap-ms"))))); + return setTimeout_(function() { return doFetch(el, verb, method, url, extraParams); }, ms); +})(); +})() : NIL); +})(); }; + + // bind-triggers + var bindTriggers = function(el, verbInfo) { return (function() { + var triggers = sxOr(parseTriggerSpec(domGetAttr(el, "sx-trigger")), defaultTrigger(domTagName(el))); + return forEach(function(trigger) { return (function() { + var kind = classifyTrigger(trigger); + var mods = get(trigger, "modifiers"); + return (isSxTruthy((kind == "poll")) ? setInterval_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "interval")) : (isSxTruthy((kind == "intersect")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, false, get(mods, "delay")) : (isSxTruthy((kind == "load")) ? setTimeout_(function() { return executeRequest(el, NIL, NIL); }, sxOr(get(mods, "delay"), 0)) : (isSxTruthy((kind == "revealed")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, true, get(mods, "delay")) : (isSxTruthy((kind == "event")) ? bindEvent(el, get(trigger, "event"), mods, verbInfo) : NIL))))); +})(); }, triggers); +})(); }; + + // bind-event + var bindEvent = function(el, eventName, mods, verbInfo) { return (function() { + var timer = NIL; + var lastVal = NIL; + var listenTarget = (isSxTruthy(get(mods, "from")) ? domQuery(get(mods, "from")) : el); + return (isSxTruthy(listenTarget) ? domAddListener(listenTarget, eventName, function(e) { return (function() { + var shouldFire = true; + if (isSxTruthy(get(mods, "changed"))) { + (function() { + var val = elementValue(el); + return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val)); +})(); +} + return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() { + var liveInfo = sxOr(getVerbInfo(el), verbInfo); + var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(liveInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay"))); + var clientRouted = false; + if (isSxTruthy(isGetLink)) { + clientRouted = tryClientRoute(urlPathname(get(liveInfo, "url")), domGetAttr(el, "sx-target")); +} + return (isSxTruthy(clientRouted) ? (browserPushState(get(liveInfo, "url")), browserScrollTo(0, 0)) : ((isSxTruthy(isGetLink) ? logInfo((String("sx:route server fetch ") + String(get(liveInfo, "url")))) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "delay")))) : executeRequest(el, NIL, NIL)))); +})()) : NIL); +})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL); +})(); }; + + // post-swap + var postSwap = function(root) { activateScripts(root); +sxProcessScripts(root); +sxHydrate(root); +sxHydrateIslands(root); +return processElements(root); }; + + // process-settle-hooks + var processSettleHooks = function(el) { return (function() { + var settleExpr = domGetAttr(el, "sx-on-settle"); + return (isSxTruthy((isSxTruthy(settleExpr) && !isSxTruthy(isEmpty(settleExpr)))) ? (function() { + var exprs = sxParse(settleExpr); + return forEach(function(expr) { return evalExpr(expr, envExtend({})); }, exprs); +})() : NIL); +})(); }; + + // activate-scripts + var activateScripts = function(root) { return (isSxTruthy(root) ? (function() { + var scripts = domQueryAll(root, "script"); + return forEach(function(dead) { return (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(dead, "data-components"))) && !isSxTruthy(domHasAttr(dead, "data-sx-activated")))) ? (function() { + var live = createScriptClone(dead); + domSetAttr(live, "data-sx-activated", "true"); + return domReplaceChild(domParent(dead), live, dead); +})() : NIL); }, scripts); +})() : NIL); }; + + // process-oob-swaps + var processOobSwaps = function(container, swapFn) { return (function() { + var oobs = findOobSwaps(container); + return forEach(function(oob) { return (function() { + var targetId = get(oob, "target-id"); + var target = domQueryById(targetId); + var oobEl = get(oob, "element"); + var swapType = get(oob, "swap-type"); + if (isSxTruthy(domParent(oobEl))) { + domRemoveChild(domParent(oobEl), oobEl); +} + return (isSxTruthy(target) ? swapFn(target, oobEl, swapType) : NIL); +})(); }, oobs); +})(); }; + + // hoist-head-elements + var hoistHeadElements = function(container) { { var _c = domQueryAll(container, "style[data-sx-css]"); for (var _i = 0; _i < _c.length; _i++) { var style = _c[_i]; if (isSxTruthy(domParent(style))) { + domRemoveChild(domParent(style), style); +} +domAppendToHead(style); } } +return forEach(function(link) { if (isSxTruthy(domParent(link))) { + domRemoveChild(domParent(link), link); +} +return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"]")); }; + + // process-boosted + var processBoosted = function(root) { return forEach(function(container) { return boostDescendants(container); }, domQueryAll(sxOr(root, domBody()), "[sx-boost]")); }; + + // boost-descendants + var boostDescendants = function(container) { return (function() { + var boostTarget = domGetAttr(container, "sx-boost"); + { var _c = domQueryAll(container, "a[href]"); for (var _i = 0; _i < _c.length; _i++) { var link = _c[_i]; if (isSxTruthy((isSxTruthy(!isSxTruthy(isProcessed(link, "boost"))) && shouldBoostLink(link)))) { + markProcessed(link, "boost"); + if (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-target"))) && isSxTruthy(boostTarget) && !isSxTruthy((boostTarget == "true"))))) { + domSetAttr(link, "sx-target", boostTarget); +} + if (isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-swap")))) { + domSetAttr(link, "sx-swap", "innerHTML"); +} + if (isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-push-url")))) { + domSetAttr(link, "sx-push-url", "true"); +} + bindClientRouteLink(link, domGetAttr(link, "href")); +} } } + return forEach(function(form) { return (isSxTruthy((isSxTruthy(!isSxTruthy(isProcessed(form, "boost"))) && shouldBoostForm(form))) ? (markProcessed(form, "boost"), (function() { + var method = upper(sxOr(domGetAttr(form, "method"), "GET")); + var action = sxOr(domGetAttr(form, "action"), browserLocationHref()); + if (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-target"))) && isSxTruthy(boostTarget) && !isSxTruthy((boostTarget == "true"))))) { + domSetAttr(form, "sx-target", boostTarget); +} + if (isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-swap")))) { + domSetAttr(form, "sx-swap", "innerHTML"); +} + return bindBoostForm(form, method, action); +})()) : NIL); }, domQueryAll(container, "form")); +})(); }; + + // _page-data-cache + var _pageDataCache = {}; + + // _page-data-cache-ttl + var _pageDataCacheTtl = 30000; + + // page-data-cache-key + var pageDataCacheKey = function(pageName, params) { return (function() { + var base = pageName; + return (isSxTruthy(sxOr(isNil(params), isEmpty(keys(params)))) ? base : (function() { + var parts = []; + { var _c = keys(params); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; parts.push((String(k) + String("=") + String(get(params, k)))); } } + return (String(base) + String(":") + String(join("&", parts))); +})()); +})(); }; + + // page-data-cache-get + var pageDataCacheGet = function(cacheKey) { return (function() { + var entry = get(_pageDataCache, cacheKey); + return (isSxTruthy(isNil(entry)) ? NIL : (isSxTruthy(((nowMs() - get(entry, "ts")) > _pageDataCacheTtl)) ? (dictSet(_pageDataCache, cacheKey, NIL), NIL) : get(entry, "data"))); +})(); }; + + // page-data-cache-set + var pageDataCacheSet = function(cacheKey, data) { return dictSet(_pageDataCache, cacheKey, {"data": data, "ts": nowMs()}); }; + + // invalidate-page-cache + var invalidatePageCache = function(pageName) { { var _c = keys(_pageDataCache); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; if (isSxTruthy(sxOr((k == pageName), startsWith(k, (String(pageName) + String(":")))))) { + _pageDataCache[k] = NIL; +} } } +swPostMessage({"type": "invalidate", "page": pageName}); +return logInfo((String("sx:cache invalidate ") + String(pageName))); }; + + // invalidate-all-page-cache + var invalidateAllPageCache = function() { _pageDataCache = {}; +swPostMessage({"type": "invalidate", "page": "*"}); +return logInfo("sx:cache invalidate *"); }; + + // update-page-cache + var updatePageCache = function(pageName, data) { return (function() { + var cacheKey = pageDataCacheKey(pageName, {}); + pageDataCacheSet(cacheKey, data); + return logInfo((String("sx:cache update ") + String(pageName))); +})(); }; + + // process-cache-directives + var processCacheDirectives = function(el, respHeaders, responseText) { (function() { + var elInvalidate = domGetAttr(el, "sx-cache-invalidate"); + return (isSxTruthy(elInvalidate) ? (isSxTruthy((elInvalidate == "*")) ? invalidateAllPageCache() : invalidatePageCache(elInvalidate)) : NIL); +})(); +(function() { + var hdrInvalidate = get(respHeaders, "cache-invalidate"); + return (isSxTruthy(hdrInvalidate) ? (isSxTruthy((hdrInvalidate == "*")) ? invalidateAllPageCache() : invalidatePageCache(hdrInvalidate)) : NIL); +})(); +return (function() { + var hdrUpdate = get(respHeaders, "cache-update"); + return (isSxTruthy(hdrUpdate) ? (function() { + var data = parseSxData(responseText); + return (isSxTruthy(data) ? updatePageCache(hdrUpdate, data) : NIL); +})() : NIL); +})(); }; + + // _optimistic-snapshots + var _optimisticSnapshots = {}; + + // optimistic-cache-update + var optimisticCacheUpdate = function(cacheKey, mutator) { return (function() { + var cached = pageDataCacheGet(cacheKey); + return (isSxTruthy(cached) ? (function() { + var predicted = mutator(cached); + _optimisticSnapshots[cacheKey] = cached; + pageDataCacheSet(cacheKey, predicted); + return predicted; +})() : NIL); +})(); }; + + // optimistic-cache-revert + var optimisticCacheRevert = function(cacheKey) { return (function() { + var snapshot = get(_optimisticSnapshots, cacheKey); + return (isSxTruthy(snapshot) ? (pageDataCacheSet(cacheKey, snapshot), dictDelete(_optimisticSnapshots, cacheKey), snapshot) : NIL); +})(); }; + + // optimistic-cache-confirm + var optimisticCacheConfirm = function(cacheKey) { return dictDelete(_optimisticSnapshots, cacheKey); }; + + // submit-mutation + var submitMutation = function(pageName, params, actionName, payload, mutatorFn, onComplete) { return (function() { + var cacheKey = pageDataCacheKey(pageName, params); + var predicted = optimisticCacheUpdate(cacheKey, mutatorFn); + if (isSxTruthy(predicted)) { + tryRerenderPage(pageName, params, predicted); +} + return executeAction(actionName, payload, function(result) { if (isSxTruthy(result)) { + pageDataCacheSet(cacheKey, result); +} +optimisticCacheConfirm(cacheKey); +if (isSxTruthy(result)) { + tryRerenderPage(pageName, params, result); +} +logInfo((String("sx:optimistic confirmed ") + String(pageName))); +return (isSxTruthy(onComplete) ? onComplete("confirmed") : NIL); }, function(error) { return (function() { + var reverted = optimisticCacheRevert(cacheKey); + if (isSxTruthy(reverted)) { + tryRerenderPage(pageName, params, reverted); +} + logWarn((String("sx:optimistic reverted ") + String(pageName) + String(": ") + String(error))); + return (isSxTruthy(onComplete) ? onComplete("reverted") : NIL); +})(); }); +})(); }; + + // _is-online + var _isOnline = true; + + // _offline-queue + var _offlineQueue = []; + + // offline-is-online? + var offlineIsOnline_p = function() { return _isOnline; }; + + // offline-set-online! + var offlineSetOnline_b = function(val) { return (_isOnline = val); }; + + // offline-queue-mutation + var offlineQueueMutation = function(actionName, payload, pageName, params, mutatorFn) { return (function() { + var cacheKey = pageDataCacheKey(pageName, params); + var entry = {["action"]: actionName, ["payload"]: payload, ["page"]: pageName, ["params"]: params, ["timestamp"]: nowMs(), ["status"]: "pending"}; + _offlineQueue.push(entry); + (function() { + var predicted = optimisticCacheUpdate(cacheKey, mutatorFn); + return (isSxTruthy(predicted) ? tryRerenderPage(pageName, params, predicted) : NIL); +})(); + logInfo((String("sx:offline queued ") + String(actionName) + String(" (") + String(len(_offlineQueue)) + String(" pending)"))); + return entry; +})(); }; + + // offline-sync + var offlineSync = function() { return (function() { + var pending = filter(function(e) { return (get(e, "status") == "pending"); }, _offlineQueue); + return (isSxTruthy(!isSxTruthy(isEmpty(pending))) ? (logInfo((String("sx:offline syncing ") + String(len(pending)) + String(" mutations"))), forEach(function(entry) { return executeAction(get(entry, "action"), get(entry, "payload"), function(result) { entry["status"] = "synced"; +return logInfo((String("sx:offline synced ") + String(get(entry, "action")))); }, function(error) { entry["status"] = "failed"; +return logWarn((String("sx:offline sync failed ") + String(get(entry, "action")) + String(": ") + String(error))); }); }, pending)) : NIL); +})(); }; + + // offline-pending-count + var offlinePendingCount = function() { return len(filter(function(e) { return (get(e, "status") == "pending"); }, _offlineQueue)); }; + + // offline-aware-mutation + var offlineAwareMutation = function(pageName, params, actionName, payload, mutatorFn, onComplete) { return (isSxTruthy(_isOnline) ? submitMutation(pageName, params, actionName, payload, mutatorFn, onComplete) : (offlineQueueMutation(actionName, payload, pageName, params, mutatorFn), (isSxTruthy(onComplete) ? onComplete("queued") : NIL))); }; + + // current-page-layout + var currentPageLayout = function() { return (function() { + var pathname = urlPathname(browserLocationHref()); + var match = findMatchingRoute(pathname, _pageRoutes); + return (isSxTruthy(isNil(match)) ? "" : sxOr(get(match, "layout"), "")); +})(); }; + + // swap-rendered-content + var swapRenderedContent = function(target, rendered, pathname) { return (disposeIslandsIn(target), domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); }; + + // resolve-route-target + var resolveRouteTarget = function(targetSel) { return (isSxTruthy((isSxTruthy(targetSel) && !isSxTruthy((targetSel == "true")))) ? domQuery(targetSel) : NIL); }; + + // deps-satisfied? + var depsSatisfied_p = function(match) { return (function() { + var deps = get(match, "deps"); + var loaded = loadedComponentNames(); + return (isSxTruthy(sxOr(isNil(deps), isEmpty(deps))) ? true : isEvery(function(dep) { return contains(loaded, dep); }, deps)); +})(); }; + + // try-client-route + var tryClientRoute = function(pathname, targetSel) { return (function() { + var match = findMatchingRoute(pathname, _pageRoutes); + return (isSxTruthy(isNil(match)) ? (logInfo((String("sx:route no match (") + String(len(_pageRoutes)) + String(" routes) ") + String(pathname))), false) : (function() { + var targetLayout = sxOr(get(match, "layout"), ""); + var curLayout = currentPageLayout(); + return (isSxTruthy(!isSxTruthy((targetLayout == curLayout))) ? (logInfo((String("sx:route server (layout: ") + String(curLayout) + String(" -> ") + String(targetLayout) + String(") ") + String(pathname))), false) : (function() { + var contentSrc = get(match, "content"); + var closure = sxOr(get(match, "closure"), {}); + var params = get(match, "params"); + var pageName = get(match, "name"); + return (isSxTruthy(sxOr(isNil(contentSrc), isEmpty(contentSrc))) ? (logWarn((String("sx:route no content for ") + String(pathname))), false) : (function() { + var target = resolveRouteTarget(targetSel); + return (isSxTruthy(isNil(target)) ? (logWarn((String("sx:route target not found: ") + String(targetSel))), false) : (isSxTruthy(!isSxTruthy(depsSatisfied_p(match))) ? (logInfo((String("sx:route deps miss for ") + String(pageName))), false) : (function() { + var ioDeps = get(match, "io-deps"); + var hasIo = (isSxTruthy(ioDeps) && !isSxTruthy(isEmpty(ioDeps))); + var renderPlan = get(match, "render-plan"); + if (isSxTruthy(renderPlan)) { + (function() { + var srv = sxOr(get(renderPlan, "server"), []); + var cli = sxOr(get(renderPlan, "client"), []); + return logInfo((String("sx:route plan ") + String(pageName) + String(" — ") + String(len(srv)) + String(" server, ") + String(len(cli)) + String(" client"))); +})(); +} + if (isSxTruthy(hasIo)) { + registerIoDeps(ioDeps); +} + return (isSxTruthy(get(match, "stream")) ? (logInfo((String("sx:route streaming ") + String(pathname))), fetchStreaming(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash)), true) : (isSxTruthy(get(match, "has-data")) ? (function() { + var cacheKey = pageDataCacheKey(pageName, params); + var cached = pageDataCacheGet(cacheKey); + return (isSxTruthy(cached) ? (function() { + var env = merge(closure, params, cached); + return (isSxTruthy(hasIo) ? (logInfo((String("sx:route client+cache+async ") + String(pathname))), tryAsyncEvalContent(contentSrc, env, function(rendered) { return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route cache+async eval failed for ") + String(pathname) + String(" — server fallback"))), fetchAndRestore(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash), 0)) : swapRenderedContent(target, rendered, pathname)); }), true) : (function() { + var rendered = tryEvalContent(contentSrc, env); + return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route cached eval failed for ") + String(pathname))), false) : (logInfo((String("sx:route client+cache ") + String(pathname))), swapRenderedContent(target, rendered, pathname), true)); +})()); +})() : (logInfo((String("sx:route client+data ") + String(pathname))), resolvePageData(pageName, params, function(data) { pageDataCacheSet(cacheKey, data); +return (function() { + var env = merge(closure, params, data); + return (isSxTruthy(hasIo) ? tryAsyncEvalContent(contentSrc, env, function(rendered) { return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route data+async eval failed for ") + String(pathname) + String(" — server fallback"))), fetchAndRestore(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash), 0)) : swapRenderedContent(target, rendered, pathname)); }) : (function() { + var rendered = tryEvalContent(contentSrc, env); + return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route data eval failed for ") + String(pathname) + String(" — server fallback"))), fetchAndRestore(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash), 0)) : swapRenderedContent(target, rendered, pathname)); +})()); +})(); }), true)); +})() : (isSxTruthy(hasIo) ? (logInfo((String("sx:route client+async ") + String(pathname))), tryAsyncEvalContent(contentSrc, merge(closure, params), function(rendered) { return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route async eval failed for ") + String(pathname) + String(" — server fallback"))), fetchAndRestore(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash), 0)) : swapRenderedContent(target, rendered, pathname)); }), true) : (function() { + var env = merge(closure, params); + var rendered = tryEvalContent(contentSrc, env); + return (isSxTruthy(isNil(rendered)) ? (logInfo((String("sx:route server (eval failed) ") + String(pathname))), false) : (swapRenderedContent(target, rendered, pathname), true)); +})()))); +})())); +})()); +})()); +})()); +})(); }; + + // bind-client-route-link + var bindClientRouteLink = function(link, href) { return bindClientRouteClick(link, href, function() { return bindBoostLink(link, href); }); }; + + // process-sse + var processSse = function(root) { return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "sse"))) ? (markProcessed(el, "sse"), bindSse(el)) : NIL); }, domQueryAll(sxOr(root, domBody()), "[sx-sse]")); }; + + // bind-sse + var bindSse = function(el) { return (function() { + var url = domGetAttr(el, "sx-sse"); + return (isSxTruthy(url) ? (function() { + var source = eventSourceConnect(url, el); + var eventName = parseSseSwap(el); + return eventSourceListen(source, eventName, function(data) { return bindSseSwap(el, data); }); +})() : NIL); +})(); }; + + // bind-sse-swap + var bindSseSwap = function(el, data) { return (function() { + var target = resolveTarget(el); + var swapSpec = parseSwapSpec(domGetAttr(el, "sx-swap"), domHasClass(domBody(), "sx-transitions")); + var swapStyle = get(swapSpec, "style"); + var useTransition = get(swapSpec, "transition"); + var trimmed = trim(data); + return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? (disposeIslandsIn(target), (isSxTruthy(startsWith(trimmed, "(")) ? (function() { + var rendered = sxRender(trimmed); + var container = domCreateElement("div", NIL); + domAppend(container, rendered); + return withTransition(useTransition, function() { swapDomNodes(target, childrenToFragment(container), swapStyle); +return postSwap(target); }); +})() : withTransition(useTransition, function() { swapHtmlString(target, trimmed, swapStyle); +return postSwap(target); }))) : NIL); +})(); }; + + // bind-inline-handlers + var bindInlineHandlers = function(root) { return forEach(function(el) { return forEach(function(attr) { return (function() { + var name = first(attr); + var body = nth(attr, 1); + return (isSxTruthy(startsWith(name, "sx-on:")) ? (function() { + var eventName = slice(name, 6); + return (isSxTruthy(!isSxTruthy(isProcessed(el, (String("on:") + String(eventName))))) ? (markProcessed(el, (String("on:") + String(eventName))), (function() { + var exprs = sxParse(body); + return domListen(el, eventName, function(e) { return (function() { + var handlerEnv = envExtend({}); + envSet(handlerEnv, "event", e); + envSet(handlerEnv, "this", el); + envSet(handlerEnv, "detail", eventDetail(e)); + return forEach(function(expr) { return evalExpr(expr, handlerEnv); }, exprs); +})(); }); +})()) : NIL); +})() : NIL); +})(); }, domAttrList(el)); }, domQueryAll(sxOr(root, domBody()), "[sx-on\\:]")); }; + + // bind-preload-for + var bindPreloadFor = function(el) { return (function() { + var preloadAttr = domGetAttr(el, "sx-preload"); + return (isSxTruthy(preloadAttr) ? (function() { + var events = (isSxTruthy((preloadAttr == "mousedown")) ? ["mousedown", "touchstart"] : ["mouseover"]); + var debounceMs = (isSxTruthy((preloadAttr == "mousedown")) ? 0 : 100); + return bindPreload(el, events, debounceMs, function() { return (function() { + var info = getVerbInfo(el); + return (isSxTruthy(info) ? doPreload(get(info, "url"), buildRequestHeaders(el, loadedComponentNames(), _cssHash)) : NIL); +})(); }); +})() : NIL); +})(); }; + + // do-preload + var doPreload = function(url, headers) { return (isSxTruthy(isNil(preloadCacheGet(_preloadCache, url))) ? fetchPreload(url, headers, _preloadCache) : NIL); }; + + // VERB_SELECTOR + var VERB_SELECTOR = (String("[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]")); + + // process-elements + var processElements = function(root) { (function() { + var els = domQueryAll(sxOr(root, domBody()), VERB_SELECTOR); + return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "verb"))) ? (markProcessed(el, "verb"), processOne(el)) : NIL); }, els); +})(); +processBoosted(root); +processSse(root); +bindInlineHandlers(root); +return processEmitElements(root); }; + + // process-one + var processOne = function(el) { return (function() { + var verbInfo = getVerbInfo(el); + return (isSxTruthy(verbInfo) ? (isSxTruthy(!isSxTruthy(domHasAttr(el, "sx-disable"))) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : NIL) : NIL); +})(); }; + + // process-emit-elements + var processEmitElements = function(root) { return (function() { + var els = domQueryAll(sxOr(root, domBody()), "[data-sx-emit]"); + return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "emit"))) ? (markProcessed(el, "emit"), (function() { + var eventName = domGetAttr(el, "data-sx-emit"); + return (isSxTruthy(eventName) ? domListen(el, "click", function(e) { return (function() { + var detailJson = domGetAttr(el, "data-sx-emit-detail"); + var detail = (isSxTruthy(detailJson) ? jsonParse(detailJson) : {}); + return domDispatch(el, eventName, detail); +})(); }) : NIL); +})()) : NIL); }, els); +})(); }; + + // handle-popstate + var handlePopstate = function(scrollY) { return (function() { + var url = browserLocationHref(); + var boostEl = domQuery("[sx-boost]"); + var targetSel = (isSxTruthy(boostEl) ? (function() { + var attr = domGetAttr(boostEl, "sx-boost"); + return (isSxTruthy((isSxTruthy(attr) && !isSxTruthy((attr == "true")))) ? attr : NIL); +})() : NIL); + var targetSel = sxOr(targetSel, "#main-panel"); + var target = domQuery(targetSel); + var pathname = urlPathname(url); + return (isSxTruthy(target) ? (isSxTruthy(tryClientRoute(pathname, targetSel)) ? browserScrollTo(0, scrollY) : (function() { + var headers = buildRequestHeaders(target, loadedComponentNames(), _cssHash); + return fetchAndRestore(target, url, headers, scrollY); +})()) : NIL); +})(); }; + + // engine-init + var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); }; + + + // === Transpiled from boot === + + // HEAD_HOIST_SELECTOR + var HEAD_HOIST_SELECTOR = "meta, title, link[rel='canonical'], script[type='application/ld+json']"; + + // hoist-head-elements-full + var hoistHeadElementsFull = function(root) { return (function() { + var els = domQueryAll(root, HEAD_HOIST_SELECTOR); + return forEach(function(el) { return (function() { + var tag = lower(domTagName(el)); + return (isSxTruthy((tag == "title")) ? (setDocumentTitle(domTextContent(el)), domRemoveChild(domParent(el), el)) : (isSxTruthy((tag == "meta")) ? ((function() { + var name = domGetAttr(el, "name"); + var prop = domGetAttr(el, "property"); + if (isSxTruthy(name)) { + removeHeadElement((String("meta[name=\"") + String(name) + String("\"]"))); +} + return (isSxTruthy(prop) ? removeHeadElement((String("meta[property=\"") + String(prop) + String("\"]"))) : NIL); +})(), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (isSxTruthy((isSxTruthy((tag == "link")) && (domGetAttr(el, "rel") == "canonical"))) ? (removeHeadElement("link[rel=\"canonical\"]"), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (domRemoveChild(domParent(el), el), domAppendToHead(el))))); +})(); }, els); +})(); }; + + // sx-mount + var sxMount = function(target, source, extraEnv) { return (function() { + var el = resolveMountTarget(target); + return (isSxTruthy(el) ? (function() { + var node = sxRenderWithEnv(source, extraEnv); + domSetTextContent(el, ""); + domAppend(el, node); + hoistHeadElementsFull(el); + processElements(el); + sxHydrateElements(el); + return sxHydrateIslands(el); +})() : NIL); +})(); }; + + // resolve-suspense + var resolveSuspense = function(id, sx) { processSxScripts(NIL); +return (function() { + var el = domQuery((String("[data-suspense=\"") + String(id) + String("\"]"))); + return (isSxTruthy(el) ? (function() { + var exprs = parse(sx); + var env = getRenderEnv(NIL); + domSetTextContent(el, ""); + { var _c = exprs; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; domAppend(el, renderToDom(expr, env, NIL)); } } + processElements(el); + sxHydrateElements(el); + sxHydrateIslands(el); + return domDispatch(el, "sx:resolved", {"id": id}); +})() : logWarn((String("resolveSuspense: no element for id=") + String(id)))); +})(); }; + + // sx-hydrate-elements + var sxHydrateElements = function(root) { return (function() { + var els = domQueryAll(sxOr(root, domBody()), "[data-sx]"); + return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "hydrated"))) ? (markProcessed(el, "hydrated"), sxUpdateElement(el, NIL)) : NIL); }, els); +})(); }; + + // sx-update-element + var sxUpdateElement = function(el, newEnv) { return (function() { + var target = resolveMountTarget(el); + return (isSxTruthy(target) ? (function() { + var source = domGetAttr(target, "data-sx"); + return (isSxTruthy(source) ? (function() { + var baseEnv = parseEnvAttr(target); + var env = mergeEnvs(baseEnv, newEnv); + return (function() { + var node = sxRenderWithEnv(source, env); + domSetTextContent(target, ""); + domAppend(target, node); + return (isSxTruthy(newEnv) ? storeEnvAttr(target, baseEnv, newEnv) : NIL); +})(); +})() : NIL); +})() : NIL); +})(); }; + + // sx-render-component + var sxRenderComponent = function(name, kwargs, extraEnv) { return (function() { + var fullName = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); + return (function() { + var env = getRenderEnv(extraEnv); + var comp = envGet(env, fullName); + return (isSxTruthy(!isSxTruthy(isComponent(comp))) ? error((String("Unknown component: ") + String(fullName))) : (function() { + var callExpr = [makeSymbol(fullName)]; + { var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; callExpr.push(makeKeyword(toKebab(k))); +callExpr.push(dictGet(kwargs, k)); } } + return renderToDom(callExpr, env, NIL); +})()); +})(); +})(); }; + + // process-sx-scripts + var processSxScripts = function(root) { return (function() { + var scripts = querySxScripts(root); + return forEach(function(s) { return (isSxTruthy(!isSxTruthy(isProcessed(s, "script"))) ? (markProcessed(s, "script"), (function() { + var text = domTextContent(s); + return (isSxTruthy(domHasAttr(s, "data-components")) ? processComponentScript(s, text) : (isSxTruthy(sxOr(isNil(text), isEmpty(trim(text)))) ? NIL : (isSxTruthy(domHasAttr(s, "data-init")) ? (function() { + var exprs = sxParse(text); + return forEach(function(expr) { return evalExpr(expr, envExtend({})); }, exprs); +})() : (isSxTruthy(domHasAttr(s, "data-mount")) ? (function() { + var mountSel = domGetAttr(s, "data-mount"); + var target = domQuery(mountSel); + return (isSxTruthy(target) ? sxMount(target, text, NIL) : NIL); +})() : sxLoadComponents(text))))); +})()) : NIL); }, scripts); +})(); }; + + // process-component-script + var processComponentScript = function(script, text) { return (function() { + var hash = domGetAttr(script, "data-hash"); + return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))))) ? sxLoadComponents(text) : NIL) : (function() { + var hasInline = (isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text)))); + (function() { + var cachedHash = localStorageGet("sx-components-hash"); + return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo("components: downloaded (cookie stale)")) : (function() { + var cached = localStorageGet("sx-components-src"); + return (isSxTruthy(cached) ? (sxLoadComponents(cached), logInfo((String("components: cached (") + String(hash) + String(")")))) : (clearSxCompCookie(), browserReload())); +})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo((String("components: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-components-hash"), localStorageRemove("sx-components-src"), clearSxCompCookie(), browserReload()))); +})(); + return setSxCompCookie(hash); +})()); +})(); }; + + // _page-routes + var _pageRoutes = []; + + // process-page-scripts + var processPageScripts = function() { return (function() { + var scripts = queryPageScripts(); + logInfo((String("pages: found ") + String(len(scripts)) + String(" script tags"))); + { var _c = scripts; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; if (isSxTruthy(!isSxTruthy(isProcessed(s, "pages")))) { + markProcessed(s, "pages"); + (function() { + var text = domTextContent(s); + logInfo((String("pages: script text length=") + String((isSxTruthy(text) ? len(text) : 0)))); + return (isSxTruthy((isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))))) ? (function() { + var pages = parse(text); + logInfo((String("pages: parsed ") + String(len(pages)) + String(" entries"))); + return forEach(function(page) { return append_b(_pageRoutes, merge(page, {"parsed": parseRoutePattern(get(page, "path"))})); }, pages); +})() : logWarn("pages: script tag is empty")); +})(); +} } } + return logInfo((String("pages: ") + String(len(_pageRoutes)) + String(" routes loaded"))); +})(); }; + + // sx-hydrate-islands + var sxHydrateIslands = function(root) { return (function() { + var els = domQueryAll(sxOr(root, domBody()), "[data-sx-island]"); + return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "island-hydrated"))) ? (markProcessed(el, "island-hydrated"), hydrateIsland(el)) : NIL); }, els); +})(); }; + + // hydrate-island + var hydrateIsland = function(el) { return (function() { + var name = domGetAttr(el, "data-sx-island"); + var stateSx = sxOr(domGetAttr(el, "data-sx-state"), "{}"); + return (function() { + var compName = (String("~") + String(name)); + var env = getRenderEnv(NIL); + return (function() { + var comp = envGet(env, compName); + return (isSxTruthy(!isSxTruthy(sxOr(isComponent(comp), isIsland(comp)))) ? logWarn((String("hydrate-island: unknown island ") + String(compName))) : (function() { + var kwargs = sxOr(first(sxParse(stateSx)), {}); + var disposers = []; + var local = envMerge(componentClosure(comp), env); + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + return (function() { + var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); }); + domSetTextContent(el, ""); + domAppend(el, bodyDom); + domSetData(el, "sx-disposers", disposers); + processElements(el); + return logInfo((String("hydrated island: ") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)"))); +})(); +})()); +})(); +})(); +})(); }; + + // dispose-island + var disposeIsland = function(el) { return (function() { + var disposers = domGetData(el, "sx-disposers"); + return (isSxTruthy(disposers) ? (forEach(function(d) { return (isSxTruthy(isCallable(d)) ? d() : NIL); }, disposers), domSetData(el, "sx-disposers", NIL)) : NIL); +})(); }; + + // dispose-islands-in + var disposeIslandsIn = function(root) { return (isSxTruthy(root) ? (function() { + var islands = domQueryAll(root, "[data-sx-island]"); + return (isSxTruthy((isSxTruthy(islands) && !isSxTruthy(isEmpty(islands)))) ? (function() { + var toDispose = filter(function(el) { return !isSxTruthy(isProcessed(el, "island-hydrated")); }, islands); + return (isSxTruthy(!isSxTruthy(isEmpty(toDispose))) ? (logInfo((String("disposing ") + String(len(toDispose)) + String(" island(s)"))), forEach(disposeIsland, toDispose)) : NIL); +})() : NIL); +})() : NIL); }; + + // boot-init + var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), processElements(NIL)); }; + + + // === Transpiled from deps (component dependency analysis) === + + // scan-refs + var scanRefs = function(node) { return (function() { + var refs = []; + scanRefsWalk(node, refs); + return refs; +})(); }; + + // scan-refs-walk + var scanRefsWalk = function(node, refs) { return (isSxTruthy((typeOf(node) == "symbol")) ? (function() { + var name = symbolName(node); + return (isSxTruthy(startsWith(name, "~")) ? (isSxTruthy(!isSxTruthy(contains(refs, name))) ? append_b(refs, name) : NIL) : NIL); +})() : (isSxTruthy((typeOf(node) == "list")) ? forEach(function(item) { return scanRefsWalk(item, refs); }, node) : (isSxTruthy((typeOf(node) == "dict")) ? forEach(function(key) { return scanRefsWalk(dictGet(node, key), refs); }, keys(node)) : NIL))); }; + + // transitive-deps-walk + var transitiveDepsWalk = function(n, seen, env) { return (isSxTruthy(!isSxTruthy(contains(seen, n))) ? (append_b(seen, n), (function() { + var val = envGet(env, n); + return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(componentBody(val))) : (isSxTruthy((typeOf(val) == "macro")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(macroBody(val))) : NIL)); +})()) : NIL); }; + + // transitive-deps + var transitiveDeps = function(name, env) { return (function() { + var seen = []; + var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); + transitiveDepsWalk(key, seen, env); + return filter(function(x) { return !isSxTruthy((x == key)); }, seen); +})(); }; + + // compute-all-deps + var computeAllDeps = function(env) { return forEach(function(name) { return (function() { + var val = envGet(env, name); + return (isSxTruthy((typeOf(val) == "component")) ? componentSetDeps(val, transitiveDeps(name, env)) : NIL); +})(); }, envComponents(env)); }; + + // scan-components-from-source + var scanComponentsFromSource = function(source) { return (function() { + var matches = regexFindAll("\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)", source); + return map(function(m) { return (String("~") + String(m)); }, matches); +})(); }; + + // components-needed + var componentsNeeded = function(pageSource, env) { return (function() { + var direct = scanComponentsFromSource(pageSource); + var allNeeded = []; + { var _c = direct; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(allNeeded, name)))) { + allNeeded.push(name); +} +(function() { + var val = envGet(env, name); + return (function() { + var deps = (isSxTruthy((isSxTruthy((typeOf(val) == "component")) && !isSxTruthy(isEmpty(componentDeps(val))))) ? componentDeps(val) : transitiveDeps(name, env)); + return forEach(function(dep) { return (isSxTruthy(!isSxTruthy(contains(allNeeded, dep))) ? append_b(allNeeded, dep) : NIL); }, deps); +})(); +})(); } } + return allNeeded; +})(); }; + + // page-component-bundle + var pageComponentBundle = function(pageSource, env) { return componentsNeeded(pageSource, env); }; + + // page-css-classes + var pageCssClasses = function(pageSource, env) { return (function() { + var needed = componentsNeeded(pageSource, env); + var classes = []; + { var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() { + var val = envGet(env, name); + return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(cls) { return (isSxTruthy(!isSxTruthy(contains(classes, cls))) ? append_b(classes, cls) : NIL); }, componentCssClasses(val)) : NIL); +})(); } } + { var _c = scanCssClasses(pageSource); for (var _i = 0; _i < _c.length; _i++) { var cls = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(classes, cls)))) { + classes.push(cls); +} } } + return classes; +})(); }; + + // scan-io-refs-walk + var scanIoRefsWalk = function(node, ioNames, refs) { return (isSxTruthy((typeOf(node) == "symbol")) ? (function() { + var name = symbolName(node); + return (isSxTruthy(contains(ioNames, name)) ? (isSxTruthy(!isSxTruthy(contains(refs, name))) ? append_b(refs, name) : NIL) : NIL); +})() : (isSxTruthy((typeOf(node) == "list")) ? forEach(function(item) { return scanIoRefsWalk(item, ioNames, refs); }, node) : (isSxTruthy((typeOf(node) == "dict")) ? forEach(function(key) { return scanIoRefsWalk(dictGet(node, key), ioNames, refs); }, keys(node)) : NIL))); }; + + // scan-io-refs + var scanIoRefs = function(node, ioNames) { return (function() { + var refs = []; + scanIoRefsWalk(node, ioNames, refs); + return refs; +})(); }; + + // transitive-io-refs-walk + var transitiveIoRefsWalk = function(n, seen, allRefs, env, ioNames) { return (isSxTruthy(!isSxTruthy(contains(seen, n))) ? (append_b(seen, n), (function() { + var val = envGet(env, n); + return (isSxTruthy((typeOf(val) == "component")) ? (forEach(function(ref) { return (isSxTruthy(!isSxTruthy(contains(allRefs, ref))) ? append_b(allRefs, ref) : NIL); }, scanIoRefs(componentBody(val), ioNames)), forEach(function(dep) { return transitiveIoRefsWalk(dep, seen, allRefs, env, ioNames); }, scanRefs(componentBody(val)))) : (isSxTruthy((typeOf(val) == "macro")) ? (forEach(function(ref) { return (isSxTruthy(!isSxTruthy(contains(allRefs, ref))) ? append_b(allRefs, ref) : NIL); }, scanIoRefs(macroBody(val), ioNames)), forEach(function(dep) { return transitiveIoRefsWalk(dep, seen, allRefs, env, ioNames); }, scanRefs(macroBody(val)))) : NIL)); +})()) : NIL); }; + + // transitive-io-refs + var transitiveIoRefs = function(name, env, ioNames) { return (function() { + var allRefs = []; + var seen = []; + var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); + transitiveIoRefsWalk(key, seen, allRefs, env, ioNames); + return allRefs; +})(); }; + + // compute-all-io-refs + var computeAllIoRefs = function(env, ioNames) { return forEach(function(name) { return (function() { + var val = envGet(env, name); + return (isSxTruthy((typeOf(val) == "component")) ? componentSetIoRefs(val, transitiveIoRefs(name, env, ioNames)) : NIL); +})(); }, envComponents(env)); }; + + // component-io-refs-cached + var componentIoRefsCached = function(name, env, ioNames) { return (function() { + var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); + return (function() { + var val = envGet(env, key); + return (isSxTruthy((isSxTruthy((typeOf(val) == "component")) && isSxTruthy(!isSxTruthy(isNil(componentIoRefs(val)))) && !isSxTruthy(isEmpty(componentIoRefs(val))))) ? componentIoRefs(val) : transitiveIoRefs(name, env, ioNames)); +})(); +})(); }; + + // component-pure? + var componentPure_p = function(name, env, ioNames) { return (function() { + var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); + return (function() { + var val = envGet(env, key); + return (isSxTruthy((isSxTruthy((typeOf(val) == "component")) && !isSxTruthy(isNil(componentIoRefs(val))))) ? isEmpty(componentIoRefs(val)) : isEmpty(transitiveIoRefs(name, env, ioNames))); +})(); +})(); }; + + // render-target + var renderTarget = function(name, env, ioNames) { return (function() { + var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); + return (function() { + var val = envGet(env, key); + return (isSxTruthy(!isSxTruthy((typeOf(val) == "component"))) ? "server" : (function() { + var affinity = componentAffinity(val); + return (isSxTruthy((affinity == "server")) ? "server" : (isSxTruthy((affinity == "client")) ? "client" : (isSxTruthy(!isSxTruthy(componentPure_p(name, env, ioNames))) ? "server" : "client"))); +})()); +})(); +})(); }; + + // page-render-plan + var pageRenderPlan = function(pageSource, env, ioNames) { return (function() { + var needed = componentsNeeded(pageSource, env); + var compTargets = {}; + var serverList = []; + var clientList = []; + var ioDeps = []; + { var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() { + var target = renderTarget(name, env, ioNames); + compTargets[name] = target; + return (isSxTruthy((target == "server")) ? (append_b(serverList, name), forEach(function(ioRef) { return (isSxTruthy(!isSxTruthy(contains(ioDeps, ioRef))) ? append_b(ioDeps, ioRef) : NIL); }, componentIoRefsCached(name, env, ioNames))) : append_b(clientList, name)); +})(); } } + return {"components": compTargets, "server": serverList, "client": clientList, "io-deps": ioDeps}; +})(); }; + + // env-components + var envComponents = function(env) { return filter(function(k) { return (function() { + var v = envGet(env, k); + return sxOr(isComponent(v), isMacro(v)); +})(); }, keys(env)); }; + + + // === Transpiled from page-helpers (pure data transformation helpers) === + + // special-form-category-map + var specialFormCategoryMap = {"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 + var extractDefineKwargs = function(expr) { return (function() { + var result = {}; + var items = slice(expr, 2); + var n = len(items); + { var _c = range(0, n); for (var _i = 0; _i < _c.length; _i++) { var idx = _c[_i]; if (isSxTruthy((isSxTruthy(((idx + 1) < n)) && (typeOf(nth(items, idx)) == "keyword")))) { + (function() { + var key = keywordName(nth(items, idx)); + var val = nth(items, (idx + 1)); + return dictSet(result, key, (isSxTruthy((typeOf(val) == "list")) ? (String("(") + String(join(" ", map(serialize, val))) + String(")")) : (String(val)))); +})(); +} } } + return result; +})(); }; + + // categorize-special-forms + var categorizeSpecialForms = function(parsedExprs) { return (function() { + var categories = {}; + { var _c = parsedExprs; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(expr) == "list")) && isSxTruthy((len(expr) >= 2)) && isSxTruthy((typeOf(first(expr)) == "symbol")) && (symbolName(first(expr)) == "define-special-form")))) { + (function() { + var name = nth(expr, 1); + var kwargs = extractDefineKwargs(expr); + var category = sxOr(get(specialFormCategoryMap, name), "Other"); + if (isSxTruthy(!isSxTruthy(dictHas(categories, category)))) { + categories[category] = []; +} + return append_b(get(categories, category), {"name": name, "syntax": sxOr(get(kwargs, "syntax"), ""), "doc": sxOr(get(kwargs, "doc"), ""), "tail-position": sxOr(get(kwargs, "tail-position"), ""), "example": sxOr(get(kwargs, "example"), "")}); +})(); +} } } + return categories; +})(); }; + + // build-ref-items-with-href + var buildRefItemsWithHref = function(items, basePath, detailKeys, nFields) { return map(function(item) { return (isSxTruthy((nFields == 3)) ? (function() { + var name = nth(item, 0); + var field2 = nth(item, 1); + var field3 = nth(item, 2); + return {"name": name, "desc": field2, "exists": field3, "href": (isSxTruthy((isSxTruthy(field3) && some(function(k) { return (k == name); }, detailKeys))) ? (String(basePath) + String(name)) : NIL)}; +})() : (function() { + var name = nth(item, 0); + var desc = nth(item, 1); + return {"name": name, "desc": desc, "href": (isSxTruthy(some(function(k) { return (k == name); }, detailKeys)) ? (String(basePath) + String(name)) : NIL)}; +})()); }, items); }; + + // build-reference-data + var buildReferenceData = function(slug, rawData, detailKeys) { return (function() { var _m = slug; if (_m == "attributes") return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; if (_m == "headers") return {"req-headers": buildRefItemsWithHref(get(rawData, "req-headers"), "/hypermedia/reference/headers/", detailKeys, 3), "resp-headers": buildRefItemsWithHref(get(rawData, "resp-headers"), "/hypermedia/reference/headers/", detailKeys, 3)}; if (_m == "events") return {"events-list": buildRefItemsWithHref(get(rawData, "events-list"), "/hypermedia/reference/events/", detailKeys, 2)}; if (_m == "js-api") return {"js-api-list": map(function(item) { return {"name": nth(item, 0), "desc": nth(item, 1)}; }, get(rawData, "js-api-list"))}; return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; })(); }; + + // build-attr-detail + var buildAttrDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"attr-not-found": true} : {"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": (isSxTruthy(dictHas(detail, "handler")) ? (String("ref-wire-") + String(replace_(replace_(slug, ":", "-"), "*", "star"))) : NIL)}); }; + + // build-header-detail + var buildHeaderDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"header-not-found": true} : {"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 + var buildEventDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"event-not-found": true} : {"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 + var buildComponentSource = function(compData) { return (function() { + var compType = get(compData, "type"); + var name = get(compData, "name"); + var params = get(compData, "params"); + var hasChildren = get(compData, "has-children"); + var bodySx = get(compData, "body-sx"); + var affinity = get(compData, "affinity"); + return (isSxTruthy((compType == "not-found")) ? (String(";; component ") + String(name) + String(" not found")) : (function() { + var paramStrs = (isSxTruthy(isEmpty(params)) ? (isSxTruthy(hasChildren) ? ["&rest", "children"] : []) : (isSxTruthy(hasChildren) ? append(cons("&key", params), ["&rest", "children"]) : cons("&key", params))); + 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("\n ") + String(bodySx) + String(")")); +})()); +})(); }; + + // build-bundle-analysis + var buildBundleAnalysis = function(pagesRaw, componentsRaw, totalComponents, totalMacros, pureCount, ioCount) { return (function() { + var pagesData = []; + { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() { + var neededNames = get(page, "needed-names"); + var n = len(neededNames); + var pct = (isSxTruthy((totalComponents > 0)) ? round(((n / totalComponents) * 100)) : 0); + var savings = (100 - pct); + var pureInPage = 0; + var ioInPage = 0; + var pageIoRefs = []; + var compDetails = []; + { var _c = neededNames; for (var _i = 0; _i < _c.length; _i++) { var compName = _c[_i]; (function() { + var info = get(componentsRaw, compName); + return (isSxTruthy(!isSxTruthy(isNil(info))) ? ((isSxTruthy(get(info, "is-pure")) ? (pureInPage = (pureInPage + 1)) : ((ioInPage = (ioInPage + 1)), forEach(function(ref) { return (isSxTruthy(!isSxTruthy(some(function(r) { return (r == ref); }, pageIoRefs))) ? append_b(pageIoRefs, ref) : NIL); }, sxOr(get(info, "io-refs"), [])))), append_b(compDetails, {"name": compName, "is-pure": get(info, "is-pure"), "affinity": get(info, "affinity"), "render-target": get(info, "render-target"), "io-refs": sxOr(get(info, "io-refs"), []), "deps": sxOr(get(info, "deps"), []), "source": get(info, "source")})) : NIL); +})(); } } + return append_b(pagesData, {"name": get(page, "name"), "path": get(page, "path"), "direct": get(page, "direct"), "needed": n, "pct": pct, "savings": savings, "io-refs": len(pageIoRefs), "pure-in-page": pureInPage, "io-in-page": ioInPage, "components": compDetails}); +})(); } } + return {"pages": pagesData, "total-components": totalComponents, "total-macros": totalMacros, "pure-count": pureCount, "io-count": ioCount}; +})(); }; + + // build-routing-analysis + var buildRoutingAnalysis = function(pagesRaw) { return (function() { + var pagesData = []; + var clientCount = 0; + var serverCount = 0; + { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() { + var hasData = get(page, "has-data"); + var contentSrc = sxOr(get(page, "content-src"), ""); + var mode = NIL; + var reason = ""; + (isSxTruthy(hasData) ? ((mode = "server"), (reason = "Has :data expression — needs server IO"), (serverCount = (serverCount + 1))) : (isSxTruthy(isEmpty(contentSrc)) ? ((mode = "server"), (reason = "No content expression"), (serverCount = (serverCount + 1))) : ((mode = "client"), (clientCount = (clientCount + 1))))); + return append_b(pagesData, {"name": get(page, "name"), "path": get(page, "path"), "mode": mode, "has-data": hasData, "content-expr": (isSxTruthy((len(contentSrc) > 80)) ? (String(slice(contentSrc, 0, 80)) + String("...")) : contentSrc), "reason": reason}); +})(); } } + return {"pages": pagesData, "total-pages": (clientCount + serverCount), "client-count": clientCount, "server-count": serverCount}; +})(); }; + + // build-affinity-analysis + var buildAffinityAnalysis = function(demoComponents, pagePlans) { return {"components": demoComponents, "page-plans": pagePlans}; }; + + + // === Transpiled from router (client-side route matching) === + + // split-path-segments + var splitPathSegments = function(path) { return (function() { + var trimmed = (isSxTruthy(startsWith(path, "/")) ? slice(path, 1) : path); + return (function() { + var trimmed2 = (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(trimmed))) && endsWith(trimmed, "/"))) ? slice(trimmed, 0, (len(trimmed) - 1)) : trimmed); + return (isSxTruthy(isEmpty(trimmed2)) ? [] : split(trimmed2, "/")); +})(); +})(); }; + + // make-route-segment + var makeRouteSegment = function(seg) { return (isSxTruthy((isSxTruthy(startsWith(seg, "<")) && endsWith(seg, ">"))) ? (function() { + var paramName = slice(seg, 1, (len(seg) - 1)); + return (function() { + var d = {}; + d["type"] = "param"; + d["value"] = paramName; + return d; +})(); +})() : (function() { + var d = {}; + d["type"] = "literal"; + d["value"] = seg; + return d; +})()); }; + + // parse-route-pattern + var parseRoutePattern = function(pattern) { return (function() { + var segments = splitPathSegments(pattern); + return map(makeRouteSegment, segments); +})(); }; + + // match-route-segments + var matchRouteSegments = function(pathSegs, parsedSegs) { return (isSxTruthy(!isSxTruthy((len(pathSegs) == len(parsedSegs)))) ? NIL : (function() { + var params = {}; + var matched = true; + forEachIndexed(function(i, parsedSeg) { return (isSxTruthy(matched) ? (function() { + var pathSeg = nth(pathSegs, i); + var segType = get(parsedSeg, "type"); + return (isSxTruthy((segType == "literal")) ? (isSxTruthy(!isSxTruthy((pathSeg == get(parsedSeg, "value")))) ? (matched = false) : NIL) : (isSxTruthy((segType == "param")) ? dictSet(params, get(parsedSeg, "value"), pathSeg) : (matched = false))); +})() : NIL); }, parsedSegs); + return (isSxTruthy(matched) ? params : NIL); +})()); }; + + // match-route + var matchRoute = function(path, pattern) { return (function() { + var pathSegs = splitPathSegments(path); + var parsedSegs = parseRoutePattern(pattern); + return matchRouteSegments(pathSegs, parsedSegs); +})(); }; + + // find-matching-route + var findMatchingRoute = function(path, routes) { return (function() { + var pathSegs = splitPathSegments(path); + var result = NIL; + { var _c = routes; for (var _i = 0; _i < _c.length; _i++) { var route = _c[_i]; if (isSxTruthy(isNil(result))) { + (function() { + var params = matchRouteSegments(pathSegs, get(route, "parsed")); + return (isSxTruthy(!isSxTruthy(isNil(params))) ? (function() { + var matched = merge(route, {}); + matched["params"] = params; + return (result = matched); +})() : NIL); +})(); +} } } + return result; +})(); }; + + + // === Transpiled from signals (reactive signal runtime) === + + // signal + var signal = function(initialValue) { return makeSignal(initialValue); }; + + // deref + var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() { + var ctx = getTrackingContext(); + if (isSxTruthy(ctx)) { + trackingContextAddDep(ctx, s); + signalAddSub(s, trackingContextNotifyFn(ctx)); +} + return signalValue(s); +})()); }; + + // reset! + var reset_b = function(s, value) { return (isSxTruthy(isSignal(s)) ? (function() { + var old = signalValue(s); + return (isSxTruthy(!isSxTruthy(isIdentical(old, value))) ? (signalSetValue(s, value), notifySubscribers(s)) : NIL); +})() : NIL); }; + + // swap! + var swap_b = function(s, f) { var args = Array.prototype.slice.call(arguments, 2); return (isSxTruthy(isSignal(s)) ? (function() { + var old = signalValue(s); + var newVal = apply(f, cons(old, args)); + return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? (signalSetValue(s, newVal), notifySubscribers(s)) : NIL); +})() : NIL); }; + + // computed + var computed = function(computeFn) { return (function() { + var s = makeSignal(NIL); + var deps = []; + var computeCtx = NIL; + return (function() { + var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } } +signalSetDeps(s, []); +return (function() { + var ctx = makeTrackingContext(recompute); + return (function() { + var prev = getTrackingContext(); + setTrackingContext(ctx); + return (function() { + var newVal = invoke(computeFn); + setTrackingContext(prev); + signalSetDeps(s, trackingContextDeps(ctx)); + return (function() { + var old = signalValue(s); + signalSetValue(s, newVal); + return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? notifySubscribers(s) : NIL); +})(); +})(); +})(); +})(); }; + recompute(); + registerInScope(function() { return disposeComputed(s); }); + return s; +})(); +})(); }; + + // effect + var effect = function(effectFn) { return (function() { + var deps = []; + var disposed = false; + var cleanupFn = NIL; + return (function() { + var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? invoke(cleanupFn) : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (function() { + var ctx = makeTrackingContext(runEffect); + return (function() { + var prev = getTrackingContext(); + setTrackingContext(ctx); + return (function() { + var result = invoke(effectFn); + setTrackingContext(prev); + deps = trackingContextDeps(ctx); + return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL); +})(); +})(); +})()) : NIL); }; + runEffect(); + return (function() { + var disposeFn = function() { disposed = true; +if (isSxTruthy(cleanupFn)) { + invoke(cleanupFn); +} +{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } } +return (deps = []); }; + registerInScope(disposeFn); + return disposeFn; +})(); +})(); +})(); }; + + // *batch-depth* + var _batchDepth = 0; + + // *batch-queue* + var _batchQueue = []; + + // batch + var batch = function(thunk) { _batchDepth = (_batchDepth + 1); +invoke(thunk); +_batchDepth = (_batchDepth - 1); +return (isSxTruthy((_batchDepth == 0)) ? (function() { + var queue = _batchQueue; + _batchQueue = []; + return (function() { + var seen = []; + var pending = []; + { var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) { + seen.push(sub); + pending.push(sub); +} } } } } + return forEach(function(sub) { return sub(); }, pending); +})(); +})() : NIL); }; + + // notify-subscribers + var notifySubscribers = function(s) { return (isSxTruthy((_batchDepth > 0)) ? (isSxTruthy(!isSxTruthy(contains(_batchQueue, s))) ? append_b(_batchQueue, s) : NIL) : flushSubscribers(s)); }; + + // flush-subscribers + var flushSubscribers = function(s) { return forEach(function(sub) { return sub(); }, signalSubscribers(s)); }; + + // dispose-computed + var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); }; + + // *island-scope* + var _islandScope = NIL; + + // with-island-scope + var withIslandScope = function(scopeFn, bodyFn) { return (function() { + var prev = _islandScope; + _islandScope = scopeFn; + return (function() { + var result = bodyFn(); + _islandScope = prev; + return result; +})(); +})(); }; + + // register-in-scope + var registerInScope = function(disposable) { return (isSxTruthy(_islandScope) ? _islandScope(disposable) : NIL); }; + + // with-marsh-scope + var withMarshScope = function(marshEl, bodyFn) { return (function() { + var disposers = []; + withIslandScope(function(d) { return append_b(disposers, d); }, bodyFn); + return domSetData(marshEl, "sx-marsh-disposers", disposers); +})(); }; + + // dispose-marsh-scope + var disposeMarshScope = function(marshEl) { return (function() { + var disposers = domGetData(marshEl, "sx-marsh-disposers"); + return (isSxTruthy(disposers) ? (forEach(function(d) { return invoke(d); }, disposers), domSetData(marshEl, "sx-marsh-disposers", NIL)) : NIL); +})(); }; + + // *store-registry* + var _storeRegistry = {}; + + // def-store + var defStore = function(name, initFn) { return (function() { + var registry = _storeRegistry; + if (isSxTruthy(!isSxTruthy(dictHas(registry, name)))) { + _storeRegistry = assoc(registry, name, invoke(initFn)); +} + return get(_storeRegistry, name); +})(); }; + + // use-store + var useStore = function(name) { return (isSxTruthy(dictHas(_storeRegistry, name)) ? get(_storeRegistry, name) : error((String("Store not found: ") + String(name) + String(". Call (def-store ...) before (use-store ...).")))); }; + + // clear-stores + var clearStores = function() { return (_storeRegistry = {}); }; + + // emit-event + var emitEvent = function(el, eventName, detail) { return domDispatch(el, eventName, detail); }; + + // on-event + var onEvent = function(el, eventName, handler) { return domListen(el, eventName, handler); }; + + // bridge-event + var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() { + var remove = domListen(el, eventName, function(e) { return (function() { + var detail = eventDetail(e); + var newVal = (isSxTruthy(transformFn) ? invoke(transformFn, detail) : detail); + return reset_b(targetSignal, newVal); +})(); }); + return remove; +})(); }); }; + + // resource + var resource = function(fetchFn) { return (function() { + var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL}); + promiseThen(invoke(fetchFn), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); }); + return state; +})(); }; + + + // ========================================================================= + // Platform interface — DOM adapter (browser-only) + // ========================================================================= + + var _hasDom = typeof document !== "undefined"; + + // Register DOM adapter as the render dispatch target for the evaluator. + _renderExprFn = function(expr, env) { return renderToDom(expr, env, null); }; + _renderMode = true; // Browser always evaluates in render context. + + var SVG_NS = "http://www.w3.org/2000/svg"; + var MATH_NS = "http://www.w3.org/1998/Math/MathML"; + + function domCreateElement(tag, ns) { + if (!_hasDom) return null; + if (ns && ns !== NIL) return document.createElementNS(ns, tag); + return document.createElement(tag); + } + + function createTextNode(s) { + return _hasDom ? document.createTextNode(s) : null; + } + + function createComment(s) { + return _hasDom ? document.createComment(s || "") : null; + } + + function createFragment() { + return _hasDom ? document.createDocumentFragment() : null; + } + + function domAppend(parent, child) { + if (parent && child) parent.appendChild(child); + } + + function domPrepend(parent, child) { + if (parent && child) parent.insertBefore(child, parent.firstChild); + } + + function domSetAttr(el, name, val) { + if (el && el.setAttribute) el.setAttribute(name, val); + } + + function domGetAttr(el, name) { + if (!el || !el.getAttribute) return NIL; + var v = el.getAttribute(name); + return v === null ? NIL : v; + } + + function domRemoveAttr(el, name) { + if (el && el.removeAttribute) el.removeAttribute(name); + } + + function domHasAttr(el, name) { + return !!(el && el.hasAttribute && el.hasAttribute(name)); + } + + function domParseHtml(html) { + if (!_hasDom) return null; + var tpl = document.createElement("template"); + tpl.innerHTML = html; + return tpl.content; + } + + function domClone(node) { + return node && node.cloneNode ? node.cloneNode(true) : node; + } + + function domParent(el) { return el ? el.parentNode : null; } + function domId(el) { return el && el.id ? el.id : NIL; } + function domNodeType(el) { return el ? el.nodeType : 0; } + function domNodeName(el) { return el ? el.nodeName : ""; } + function domTextContent(el) { return el ? el.textContent || el.nodeValue || "" : ""; } + function domSetTextContent(el, s) { if (el) { if (el.nodeType === 3 || el.nodeType === 8) el.nodeValue = s; else el.textContent = s; } } + function domIsFragment(el) { return el ? el.nodeType === 11 : false; } + function domIsChildOf(child, parent) { return !!(parent && child && child.parentNode === parent); } + function domIsActiveElement(el) { return _hasDom && el === document.activeElement; } + function domIsInputElement(el) { + if (!el || !el.tagName) return false; + var t = el.tagName; + return t === "INPUT" || t === "TEXTAREA" || t === "SELECT"; + } + function domFirstChild(el) { return el ? el.firstChild : null; } + function domNextSibling(el) { return el ? el.nextSibling : null; } + + function domChildList(el) { + if (!el || !el.childNodes) return []; + return Array.prototype.slice.call(el.childNodes); + } + + function domAttrList(el) { + if (!el || !el.attributes) return []; + var r = []; + for (var i = 0; i < el.attributes.length; i++) { + r.push([el.attributes[i].name, el.attributes[i].value]); + } + return r; + } + + function domInsertBefore(parent, node, ref) { + if (parent && node) parent.insertBefore(node, ref || null); + } + + function domInsertAfter(ref, node) { + if (ref && ref.parentNode && node) { + ref.parentNode.insertBefore(node, ref.nextSibling); + } + } + + function domRemoveChild(parent, child) { + if (parent && child && child.parentNode === parent) parent.removeChild(child); + } + + function domReplaceChild(parent, newChild, oldChild) { + if (parent && newChild && oldChild) parent.replaceChild(newChild, oldChild); + } + + function domSetInnerHtml(el, html) { + if (el) el.innerHTML = html; + } + + function domInsertAdjacentHtml(el, pos, html) { + if (el && el.insertAdjacentHTML) el.insertAdjacentHTML(pos, html); + } + + function domGetStyle(el, prop) { + return el && el.style ? el.style[prop] || "" : ""; + } + + function domSetStyle(el, prop, val) { + if (el && el.style) el.style[prop] = val; + } + + function domGetProp(el, name) { return el ? el[name] : NIL; } + function domSetProp(el, name, val) { if (el) el[name] = val; } + + function domAddClass(el, cls) { + if (el && el.classList) el.classList.add(cls); + } + + function domRemoveClass(el, cls) { + if (el && el.classList) el.classList.remove(cls); + } + + function domDispatch(el, name, detail) { + if (!_hasDom || !el) return false; + var evt = new CustomEvent(name, { bubbles: true, cancelable: true, detail: detail || {} }); + return el.dispatchEvent(evt); + } + + function domListen(el, name, handler) { + if (!_hasDom || !el) return function() {}; + // Wrap SX lambdas from runtime-evaluated island code into native fns + // If lambda takes 0 params, call without event arg (convenience for on-click handlers) + var wrapped = isLambda(handler) + ? (lambdaParams(handler).length === 0 + ? function(e) { try { invoke(handler); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } + : function(e) { try { invoke(handler, e); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } }) + : handler; + if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler)); + el.addEventListener(name, wrapped); + return function() { el.removeEventListener(name, wrapped); }; + } + + function eventDetail(e) { + return (e && e.detail != null) ? e.detail : nil; + } + + function domQuery(sel) { + return _hasDom ? document.querySelector(sel) : null; + } + + function domEnsureElement(sel) { + if (!_hasDom) return null; + var el = document.querySelector(sel); + if (el) return el; + // Parse #id selector → create div with that id, append to body + if (sel.charAt(0) === '#') { + el = document.createElement('div'); + el.id = sel.slice(1); + document.body.appendChild(el); + return el; + } + return null; + } + + function domQueryAll(root, sel) { + if (!root || !root.querySelectorAll) return []; + return Array.prototype.slice.call(root.querySelectorAll(sel)); + } + + function domTagName(el) { return el && el.tagName ? el.tagName : ""; } + + // Island DOM helpers + function domRemove(node) { + if (node && node.parentNode) node.parentNode.removeChild(node); + } + function domChildNodes(el) { + if (!el || !el.childNodes) return []; + return Array.prototype.slice.call(el.childNodes); + } + function domRemoveChildrenAfter(marker) { + if (!marker || !marker.parentNode) return; + var parent = marker.parentNode; + while (marker.nextSibling) parent.removeChild(marker.nextSibling); + } + function domSetData(el, key, val) { + if (el) { if (!el._sxData) el._sxData = {}; el._sxData[key] = val; } + } + function domGetData(el, key) { + return (el && el._sxData) ? (el._sxData[key] != null ? el._sxData[key] : nil) : nil; + } + function domInnerHtml(el) { + return (el && el.innerHTML != null) ? el.innerHTML : ""; + } + function jsonParse(s) { + try { return JSON.parse(s); } catch(e) { return {}; } + } + + // renderDomComponent and renderDomElement are transpiled from + // adapter-dom.sx — no imperative overrides needed. + + + // ========================================================================= + // Platform interface — Engine pure logic (browser + node compatible) + // ========================================================================= + + function browserLocationHref() { + return typeof location !== "undefined" ? location.href : ""; + } + + function browserSameOrigin(url) { + try { return new URL(url, location.href).origin === location.origin; } + catch (e) { return true; } + } + + function browserPushState(url) { + if (typeof history !== "undefined") { + try { history.pushState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); } + catch (e) {} + } + } + + function browserReplaceState(url) { + if (typeof history !== "undefined") { + try { history.replaceState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); } + catch (e) {} + } + } + + function nowMs() { return (typeof performance !== "undefined") ? performance.now() : Date.now(); } + + function parseHeaderValue(s) { + if (!s) return null; + try { + if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s); + return JSON.parse(s); + } catch (e) { return null; } + } + + + // ========================================================================= + // Platform interface — Orchestration (browser-only) + // ========================================================================= + + // --- Browser/Network --- + + function browserNavigate(url) { + if (typeof location !== "undefined") location.assign(url); + } + + function browserReload() { + if (typeof location !== "undefined") location.reload(); + } + + function browserScrollTo(x, y) { + if (typeof window !== "undefined") window.scrollTo(x, y); + } + + function browserMediaMatches(query) { + if (typeof window === "undefined") return false; + return window.matchMedia(query).matches; + } + + function browserConfirm(msg) { + if (typeof window === "undefined") return false; + return window.confirm(msg); + } + + function browserPrompt(msg) { + if (typeof window === "undefined") return NIL; + var r = window.prompt(msg); + return r === null ? NIL : r; + } + + function csrfToken() { + if (!_hasDom) return NIL; + var m = document.querySelector('meta[name="csrf-token"]'); + return m ? m.getAttribute("content") : NIL; + } + + function isCrossOrigin(url) { + try { + var h = new URL(url, location.href).hostname; + return h !== location.hostname && + (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0); + } catch (e) { return false; } + } + + // --- Promises --- + + function promiseResolve(val) { return Promise.resolve(val); } + + function promiseThen(p, onResolve, onReject) { + if (!p || !p.then) return p; + return onReject ? p.then(onResolve, onReject) : p.then(onResolve); + } + + function promiseCatch(p, fn) { return p && p.catch ? p.catch(fn) : p; } + + function promiseDelayed(ms, value) { + return new Promise(function(resolve) { + setTimeout(function() { resolve(value); }, ms); + }); + } + + // --- Abort controllers --- + + var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null; + + function abortPrevious(el) { + if (_controllers) { + var prev = _controllers.get(el); + if (prev) prev.abort(); + } + } + + function trackController(el, ctrl) { + if (_controllers) _controllers.set(el, ctrl); + } + + var _targetControllers = typeof WeakMap !== "undefined" ? new WeakMap() : null; + + function abortPreviousTarget(el) { + if (_targetControllers) { + var prev = _targetControllers.get(el); + if (prev) prev.abort(); + } + } + + function trackControllerTarget(el, ctrl) { + if (_targetControllers) _targetControllers.set(el, ctrl); + } + + function newAbortController() { + return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} }; + } + + function controllerSignal(ctrl) { return ctrl ? ctrl.signal : null; } + + function isAbortError(err) { return err && err.name === "AbortError"; } + + // --- Timers --- + + function _wrapSxFn(fn) { + if (fn && fn._lambda) { + return function() { return trampoline(callLambda(fn, [], lambdaClosure(fn))); }; + } + return fn; + } + function setTimeout_(fn, ms) { return setTimeout(_wrapSxFn(fn), ms || 0); } + function setInterval_(fn, ms) { return setInterval(_wrapSxFn(fn), ms || 1000); } + function clearTimeout_(id) { clearTimeout(id); } + function clearInterval_(id) { clearInterval(id); } + function requestAnimationFrame_(fn) { + var cb = _wrapSxFn(fn); + if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(cb); + else setTimeout(cb, 16); + } + + // --- Fetch --- + + function fetchRequest(config, successFn, errorFn) { + var opts = { method: config.method, headers: config.headers }; + if (config.signal) opts.signal = config.signal; + if (config.body && config.method !== "GET") opts.body = config.body; + if (config["cross-origin"]) opts.credentials = "include"; + + var p = (config.preloaded && config.preloaded !== NIL) + ? Promise.resolve({ + ok: true, status: 200, + headers: new Headers({ "Content-Type": config.preloaded["content-type"] || "" }), + text: function() { return Promise.resolve(config.preloaded.text); } + }) + : fetch(config.url, opts); + + return p.then(function(resp) { + return resp.text().then(function(text) { + var getHeader = function(name) { + var v = resp.headers.get(name); + return v === null ? NIL : v; + }; + return successFn(resp.ok, resp.status, getHeader, text); + }); + }).catch(function(err) { + return errorFn(err); + }); + } + + function fetchLocation(headerVal) { + if (!_hasDom) return; + var locUrl = headerVal; + try { var obj = JSON.parse(headerVal); locUrl = obj.path || obj; } catch (e) {} + fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function(r) { + return r.text().then(function(t) { + var main = document.getElementById("main-panel"); + if (main) { + main.innerHTML = t; + postSwap(main); + try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {} + } + }); + }); + } + + function fetchAndRestore(main, url, headers, scrollY) { + var opts = { headers: headers }; + try { + var h = new URL(url, location.href).hostname; + if (h !== location.hostname && + (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) { + opts.credentials = "include"; + } + } catch (e) {} + + fetch(url, opts).then(function(resp) { + return resp.text().then(function(text) { + text = stripComponentScripts(text); + text = extractResponseCss(text); + text = text.trim(); + if (text.charAt(0) === "(") { + try { + var dom = sxRender(text); + var container = document.createElement("div"); + container.appendChild(dom); + processOobSwaps(container, function(t, oob, s) { + swapDomNodes(t, oob, s); + sxHydrate(t); + processElements(t); + }); + var newMain = container.querySelector("#main-panel"); + morphChildren(main, newMain || container); + postSwap(main); + if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0); + } catch (err) { + console.error("sx-ref popstate error:", err); + location.reload(); + } + } else { + var parser = new DOMParser(); + var doc = parser.parseFromString(text, "text/html"); + var newMain = doc.getElementById("main-panel"); + if (newMain) { + morphChildren(main, newMain); + postSwap(main); + if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0); + } else { + location.reload(); + } + } + }); + }).catch(function() { location.reload(); }); + } + + function fetchStreaming(target, url, headers) { + // Streaming fetch for multi-stream pages. + // First chunk = OOB SX swap (shell with skeletons). + // Subsequent chunks = __sxResolve script tags filling suspense slots. + var opts = { headers: headers }; + try { + var h = new URL(url, location.href).hostname; + if (h !== location.hostname && + (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) { + opts.credentials = "include"; + } + } catch (e) {} + + fetch(url, opts).then(function(resp) { + if (!resp.ok || !resp.body) { + // Fallback: non-streaming + return resp.text().then(function(text) { + text = stripComponentScripts(text); + text = extractResponseCss(text); + text = text.trim(); + if (text.charAt(0) === "(") { + var dom = sxRender(text); + var container = document.createElement("div"); + container.appendChild(dom); + processOobSwaps(container, function(t, oob, s) { + swapDomNodes(t, oob, s); + sxHydrate(t); + processElements(t); + }); + var newMain = container.querySelector("#main-panel"); + morphChildren(target, newMain || container); + postSwap(target); + } + }); + } + + var reader = resp.body.getReader(); + var decoder = new TextDecoder(); + var buffer = ""; + var initialSwapDone = false; + // Regex to match __sxResolve script tags + var RESOLVE_START = ""; + + function processResolveScripts() { + // Strip and load any extra component defs before resolve scripts + buffer = stripSxScripts(buffer); + var idx; + while ((idx = buffer.indexOf(RESOLVE_START)) >= 0) { + var endIdx = buffer.indexOf(RESOLVE_END, idx); + if (endIdx < 0) break; // incomplete, wait for more data + var argsStr = buffer.substring(idx + RESOLVE_START.length, endIdx); + buffer = buffer.substring(endIdx + RESOLVE_END.length); + // argsStr is: "stream-id","sx source" + var commaIdx = argsStr.indexOf(","); + if (commaIdx >= 0) { + try { + var id = JSON.parse(argsStr.substring(0, commaIdx)); + var sx = JSON.parse(argsStr.substring(commaIdx + 1)); + if (typeof Sx !== "undefined" && Sx.resolveSuspense) { + Sx.resolveSuspense(id, sx); + } + } catch (e) { + console.error("[sx-ref] resolve parse error:", e); + } + } + } + } + + function pump() { + return reader.read().then(function(result) { + buffer += decoder.decode(result.value || new Uint8Array(), { stream: !result.done }); + + if (!initialSwapDone) { + // Look for the first resolve script — everything before it is OOB content + var scriptIdx = buffer.indexOf(" (without data-components or data-init). + // These contain extra component defs from streaming resolve chunks. + // data-init scripts are preserved for process-sx-scripts to evaluate as side effects. + var SxObj = typeof Sx !== "undefined" ? Sx : null; + return text.replace(/]*type="text\/sx"[^>]*>[\s\S]*?<\/script>/gi, + function(match) { + if (/data-init/.test(match)) return match; // preserve data-init scripts + var m = match.match(/]*>([\s\S]*?)<\/script>/i); + if (m && SxObj && SxObj.loadComponents) SxObj.loadComponents(m[1]); + return ""; + }); + } + + function extractResponseCss(text) { + if (!_hasDom) return text; + var target = document.getElementById("sx-css"); + if (!target) return text; + return text.replace(/]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi, + function(_, css) { target.textContent += css; return ""; }); + } + + function selectFromContainer(container, sel) { + var frag = document.createDocumentFragment(); + sel.split(",").forEach(function(s) { + container.querySelectorAll(s.trim()).forEach(function(m) { frag.appendChild(m); }); + }); + return frag; + } + + function childrenToFragment(container) { + var frag = document.createDocumentFragment(); + while (container.firstChild) frag.appendChild(container.firstChild); + return frag; + } + + function selectHtmlFromDoc(doc, sel) { + var parts = sel.split(",").map(function(s) { return s.trim(); }); + var frags = []; + parts.forEach(function(s) { + doc.querySelectorAll(s).forEach(function(m) { frags.push(m.outerHTML); }); + }); + return frags.join(""); + } + + // --- Parsing --- + + function tryParseJson(s) { + if (!s) return NIL; + try { return JSON.parse(s); } catch (e) { return NIL; } + } + + + // ========================================================================= + // Platform interface — Boot (mount, hydrate, scripts, cookies) + // ========================================================================= + + function resolveMountTarget(target) { + if (typeof target === "string") return _hasDom ? document.querySelector(target) : null; + return target; + } + + function sxRenderWithEnv(source, extraEnv) { + var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + var exprs = parse(source); + if (!_hasDom) return null; + var frag = document.createDocumentFragment(); + for (var i = 0; i < exprs.length; i++) { + var node = renderToDom(exprs[i], env, null); + if (node) frag.appendChild(node); + } + return frag; + } + + function getRenderEnv(extraEnv) { + return extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + } + + function mergeEnvs(base, newEnv) { + return newEnv ? merge(componentEnv, base, newEnv) : merge(componentEnv, base); + } + + function sxLoadComponents(text) { + try { + var exprs = parse(text); + for (var i = 0; i < exprs.length; i++) trampoline(evalExpr(exprs[i], componentEnv)); + } catch (err) { + logParseError("loadComponents", text, err); + throw err; + } + } + + function setDocumentTitle(s) { + if (_hasDom) document.title = s || ""; + } + + function removeHeadElement(sel) { + if (!_hasDom) return; + var old = document.head.querySelector(sel); + if (old) old.parentNode.removeChild(old); + } + + function querySxScripts(root) { + if (!_hasDom) return []; + var r = (root && root !== NIL) ? root : document; + return Array.prototype.slice.call( + r.querySelectorAll('script[type="text/sx"]')); + } + + function queryPageScripts() { + if (!_hasDom) return []; + return Array.prototype.slice.call( + document.querySelectorAll('script[type="text/sx-pages"]')); + } + + // --- localStorage --- + + function localStorageGet(key) { + try { var v = localStorage.getItem(key); return v === null ? NIL : v; } + catch (e) { return NIL; } + } + + function localStorageSet(key, val) { + try { localStorage.setItem(key, val); } catch (e) {} + } + + function localStorageRemove(key) { + try { localStorage.removeItem(key); } catch (e) {} + } + + // --- Cookies --- + + function setSxCompCookie(hash) { + if (_hasDom) document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; + } + + function clearSxCompCookie() { + if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax"; + } + + // --- Env helpers --- + + function parseEnvAttr(el) { + var attr = el && el.getAttribute ? el.getAttribute("data-sx-env") : null; + if (!attr) return {}; + try { return JSON.parse(attr); } catch (e) { return {}; } + } + + function storeEnvAttr(el, base, newEnv) { + var merged = merge(base, newEnv); + if (el && el.setAttribute) el.setAttribute("data-sx-env", JSON.stringify(merged)); + } + + function toKebab(s) { return s.replace(/_/g, "-"); } + + // --- Logging --- + + function logInfo(msg) { + if (typeof console !== "undefined") console.log("[sx-ref] " + msg); + } + + function logWarn(msg) { + if (typeof console !== "undefined") console.warn("[sx-ref] " + msg); + } + + function logParseError(label, text, err) { + if (typeof console === "undefined") return; + var msg = err && err.message ? err.message : String(err); + var colMatch = msg.match(/col (\d+)/); + var lineMatch = msg.match(/line (\d+)/); + if (colMatch && text) { + var errLine = lineMatch ? parseInt(lineMatch[1]) : 1; + var errCol = parseInt(colMatch[1]); + var lines = text.split("\n"); + var pos = 0; + for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1; + pos += errCol; + var ws = 80; + var start = Math.max(0, pos - ws); + var end = Math.min(text.length, pos + ws); + console.error("[sx-ref] " + label + ":", msg, + "\n around error (pos ~" + pos + "):", + "\n \u00ab" + text.substring(start, pos) + "\u26d4" + text.substring(pos, end) + "\u00bb"); + } else { + console.error("[sx-ref] " + label + ":", msg); + } + } + + + + // ========================================================================= + // Post-transpilation fixups + // ========================================================================= + // The reference spec's call-lambda only handles Lambda objects, but HO forms + // (map, reduce, etc.) may receive native primitives. Wrap to handle both. + var _rawCallLambda = callLambda; + callLambda = function(f, args, callerEnv) { + if (typeof f === "function") return f.apply(null, args); + return _rawCallLambda(f, args, callerEnv); + }; + + // Expose render functions as primitives so SX code can call them + if (typeof renderToHtml === "function") PRIMITIVES["render-to-html"] = renderToHtml; + if (typeof renderToSx === "function") PRIMITIVES["render-to-sx"] = renderToSx; + if (typeof aser === "function") PRIMITIVES["aser"] = aser; + if (typeof renderToDom === "function") PRIMITIVES["render-to-dom"] = renderToDom; + + // Expose signal functions as primitives so runtime-evaluated SX code + // (e.g. island bodies from .sx files) can call them + PRIMITIVES["signal"] = signal; + PRIMITIVES["signal?"] = isSignal; + PRIMITIVES["deref"] = deref; + PRIMITIVES["reset!"] = reset_b; + PRIMITIVES["swap!"] = swap_b; + PRIMITIVES["computed"] = computed; + PRIMITIVES["effect"] = effect; + PRIMITIVES["batch"] = batch; + // Timer primitives for island code + PRIMITIVES["set-interval"] = setInterval_; + PRIMITIVES["clear-interval"] = clearInterval_; + // Reactive DOM helpers for island code + PRIMITIVES["reactive-text"] = reactiveText; + PRIMITIVES["create-text-node"] = createTextNode; + PRIMITIVES["dom-set-text-content"] = domSetTextContent; + PRIMITIVES["dom-listen"] = domListen; + PRIMITIVES["dom-dispatch"] = domDispatch; + PRIMITIVES["event-detail"] = eventDetail; + PRIMITIVES["resource"] = resource; + PRIMITIVES["promise-delayed"] = promiseDelayed; + PRIMITIVES["promise-then"] = promiseThen; + PRIMITIVES["def-store"] = defStore; + PRIMITIVES["use-store"] = useStore; + PRIMITIVES["emit-event"] = emitEvent; + PRIMITIVES["on-event"] = onEvent; + PRIMITIVES["bridge-event"] = bridgeEvent; + // DOM primitives for island code + PRIMITIVES["dom-focus"] = domFocus; + PRIMITIVES["dom-tag-name"] = domTagName; + PRIMITIVES["dom-get-prop"] = domGetProp; + PRIMITIVES["stop-propagation"] = stopPropagation_; + PRIMITIVES["error-message"] = errorMessage; + PRIMITIVES["schedule-idle"] = scheduleIdle; + PRIMITIVES["invoke"] = invoke; + PRIMITIVES["error"] = function(msg) { throw new Error(msg); }; + PRIMITIVES["filter"] = filter; + // DOM primitives for sx-on:* handlers and data-init scripts + if (typeof domBody === "function") PRIMITIVES["dom-body"] = domBody; + if (typeof domQuery === "function") PRIMITIVES["dom-query"] = domQuery; + if (typeof domQueryAll === "function") PRIMITIVES["dom-query-all"] = domQueryAll; + if (typeof domQueryById === "function") PRIMITIVES["dom-query-by-id"] = domQueryById; + if (typeof domSetAttr === "function") PRIMITIVES["dom-set-attr"] = domSetAttr; + if (typeof domGetAttr === "function") PRIMITIVES["dom-get-attr"] = domGetAttr; + if (typeof domRemoveAttr === "function") PRIMITIVES["dom-remove-attr"] = domRemoveAttr; + if (typeof domHasAttr === "function") PRIMITIVES["dom-has-attr?"] = domHasAttr; + if (typeof domAddClass === "function") PRIMITIVES["dom-add-class"] = domAddClass; + if (typeof domRemoveClass === "function") PRIMITIVES["dom-remove-class"] = domRemoveClass; + if (typeof domHasClass === "function") PRIMITIVES["dom-has-class?"] = domHasClass; + if (typeof domClosest === "function") PRIMITIVES["dom-closest"] = domClosest; + if (typeof domMatches === "function") PRIMITIVES["dom-matches?"] = domMatches; + if (typeof preventDefault_ === "function") PRIMITIVES["prevent-default"] = preventDefault_; + if (typeof elementValue === "function") PRIMITIVES["element-value"] = elementValue; + if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml; + if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml; + if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent; + if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse; + if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs; + PRIMITIVES["sx-parse"] = sxParse; + + // Expose deps module functions as primitives so runtime-evaluated SX code + // (e.g. test-deps.sx in browser) can call them + // Platform functions (from PLATFORM_DEPS_JS) + PRIMITIVES["component-deps"] = componentDeps; + PRIMITIVES["component-set-deps!"] = componentSetDeps; + PRIMITIVES["component-css-classes"] = componentCssClasses; + PRIMITIVES["env-components"] = envComponents; + PRIMITIVES["regex-find-all"] = regexFindAll; + PRIMITIVES["scan-css-classes"] = scanCssClasses; + // Transpiled functions (from deps.sx) + PRIMITIVES["scan-refs"] = scanRefs; + PRIMITIVES["scan-refs-walk"] = scanRefsWalk; + PRIMITIVES["transitive-deps"] = transitiveDeps; + PRIMITIVES["transitive-deps-walk"] = transitiveDepsWalk; + PRIMITIVES["compute-all-deps"] = computeAllDeps; + PRIMITIVES["scan-components-from-source"] = scanComponentsFromSource; + PRIMITIVES["components-needed"] = componentsNeeded; + PRIMITIVES["page-component-bundle"] = pageComponentBundle; + PRIMITIVES["page-css-classes"] = pageCssClasses; + PRIMITIVES["scan-io-refs-walk"] = scanIoRefsWalk; + PRIMITIVES["scan-io-refs"] = scanIoRefs; + PRIMITIVES["transitive-io-refs-walk"] = transitiveIoRefsWalk; + PRIMITIVES["transitive-io-refs"] = transitiveIoRefs; + PRIMITIVES["compute-all-io-refs"] = computeAllIoRefs; + PRIMITIVES["component-io-refs-cached"] = componentIoRefsCached; + PRIMITIVES["component-pure?"] = componentPure_p; + PRIMITIVES["render-target"] = renderTarget; + PRIMITIVES["page-render-plan"] = pageRenderPlan; + + // Expose page-helper functions as primitives + PRIMITIVES["categorize-special-forms"] = categorizeSpecialForms; + PRIMITIVES["extract-define-kwargs"] = extractDefineKwargs; + PRIMITIVES["build-reference-data"] = buildReferenceData; + PRIMITIVES["build-ref-items-with-href"] = buildRefItemsWithHref; + PRIMITIVES["build-attr-detail"] = buildAttrDetail; + PRIMITIVES["build-header-detail"] = buildHeaderDetail; + PRIMITIVES["build-event-detail"] = buildEventDetail; + PRIMITIVES["build-component-source"] = buildComponentSource; + PRIMITIVES["build-bundle-analysis"] = buildBundleAnalysis; + PRIMITIVES["build-routing-analysis"] = buildRoutingAnalysis; + PRIMITIVES["build-affinity-analysis"] = buildAffinityAnalysis; + + // ========================================================================= + // Async IO: Promise-aware rendering for client-side IO primitives + // ========================================================================= + // + // IO primitives (query, current-user, etc.) return Promises on the client. + // asyncRenderToDom walks the component tree; when it encounters an IO + // primitive, it awaits the Promise and continues rendering. + // + // The sync evaluator/renderer is untouched. This is a separate async path + // used only when a page's component tree contains IO references. + + var IO_PRIMITIVES = {}; + + function registerIoPrimitive(name, fn) { + IO_PRIMITIVES[name] = fn; + } + + function isPromise(x) { + return x != null && typeof x === "object" && typeof x.then === "function"; + } + + // Async trampoline: resolves thunks, awaits Promises + function asyncTrampoline(val) { + if (isPromise(val)) return val.then(asyncTrampoline); + if (isThunk(val)) return asyncTrampoline(evalExpr(thunkExpr(val), thunkEnv(val))); + return val; + } + + // Async eval: like trampoline(evalExpr(...)) but handles IO primitives + function asyncEval(expr, env) { + // Intercept IO primitive calls at the AST level + if (Array.isArray(expr) && expr.length > 0) { + var head = expr[0]; + if (head && head._sym) { + var name = head.name; + if (IO_PRIMITIVES[name]) { + // Evaluate args, then call the IO primitive + return asyncEvalIoCall(name, expr.slice(1), env); + } + } + } + // Non-IO: use sync eval, but result might be a thunk + var result = evalExpr(expr, env); + return asyncTrampoline(result); + } + + function asyncEvalIoCall(name, rawArgs, env) { + // Parse keyword args and positional args, evaluating each (may be async) + var kwargs = {}; + var args = []; + var promises = []; + var i = 0; + while (i < rawArgs.length) { + var arg = rawArgs[i]; + if (arg && arg._kw && (i + 1) < rawArgs.length) { + var kName = arg.name; + var kVal = asyncEval(rawArgs[i + 1], env); + if (isPromise(kVal)) { + (function(k) { promises.push(kVal.then(function(v) { kwargs[k] = v; })); })(kName); + } else { + kwargs[kName] = kVal; + } + i += 2; + } else { + var aVal = asyncEval(arg, env); + if (isPromise(aVal)) { + (function(idx) { promises.push(aVal.then(function(v) { args[idx] = v; })); })(args.length); + args.push(null); // placeholder + } else { + args.push(aVal); + } + i++; + } + } + var ioFn = IO_PRIMITIVES[name]; + if (promises.length > 0) { + return Promise.all(promises).then(function() { return ioFn(args, kwargs); }); + } + return ioFn(args, kwargs); + } + + // Async render-to-dom: returns Promise or Node + function asyncRenderToDom(expr, env, ns) { + // Literals + if (expr === NIL || expr === null || expr === undefined) return null; + if (expr === true || expr === false) return null; + if (typeof expr === "string") return document.createTextNode(expr); + if (typeof expr === "number") return document.createTextNode(String(expr)); + + // Symbol -> async eval then render + if (expr && expr._sym) { + var val = asyncEval(expr, env); + if (isPromise(val)) return val.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(val, env, ns); + } + + // Keyword + if (expr && expr._kw) return document.createTextNode(expr.name); + + // DocumentFragment / DOM nodes pass through + if (expr instanceof DocumentFragment || (expr && expr.nodeType)) return expr; + + // Dict -> skip + if (expr && typeof expr === "object" && !Array.isArray(expr)) return null; + + // List + if (!Array.isArray(expr) || expr.length === 0) return null; + + var head = expr[0]; + if (!head) return null; + + // Symbol head + if (head._sym) { + var hname = head.name; + + // IO primitive + if (IO_PRIMITIVES[hname]) { + var ioResult = asyncEval(expr, env); + if (isPromise(ioResult)) return ioResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(ioResult, env, ns); + } + + // Fragment + if (hname === "<>") return asyncRenderChildren(expr.slice(1), env, ns); + + // raw! + if (hname === "raw!") { + return asyncEvalRaw(expr.slice(1), env); + } + + // Special forms that need async handling + if (hname === "if") return asyncRenderIf(expr, env, ns); + if (hname === "when") return asyncRenderWhen(expr, env, ns); + if (hname === "cond") return asyncRenderCond(expr, env, ns); + if (hname === "case") return asyncRenderCase(expr, env, ns); + if (hname === "let" || hname === "let*") return asyncRenderLet(expr, env, ns); + if (hname === "begin" || hname === "do") return asyncRenderChildren(expr.slice(1), env, ns); + if (hname === "map") return asyncRenderMap(expr, env, ns); + if (hname === "map-indexed") return asyncRenderMapIndexed(expr, env, ns); + if (hname === "for-each") return asyncRenderMap(expr, env, ns); + + // define/defcomp/defmacro — eval for side effects + if (hname === "define" || hname === "defcomp" || hname === "defmacro" || + hname === "defstyle" || hname === "defhandler") { + trampoline(evalExpr(expr, env)); + return null; + } + + // quote + if (hname === "quote") return null; + + // lambda/fn + if (hname === "lambda" || hname === "fn") { + trampoline(evalExpr(expr, env)); + return null; + } + + // and/or — eval and render result + if (hname === "and" || hname === "or" || hname === "->") { + var aoResult = asyncEval(expr, env); + if (isPromise(aoResult)) return aoResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(aoResult, env, ns); + } + + // set! + if (hname === "set!") { + asyncEval(expr, env); + return null; + } + + // Component or Island + if (hname.charAt(0) === "~") { + var comp = env[hname]; + if (comp && comp._island) return renderDomIsland(comp, expr.slice(1), env, ns); + if (comp && comp._component) return asyncRenderComponent(comp, expr.slice(1), env, ns); + if (comp && comp._macro) { + var expanded = trampoline(expandMacro(comp, expr.slice(1), env)); + return asyncRenderToDom(expanded, env, ns); + } + } + + // Macro + if (env[hname] && env[hname]._macro) { + var mac = env[hname]; + var expanded = trampoline(expandMacro(mac, expr.slice(1), env)); + return asyncRenderToDom(expanded, env, ns); + } + + // HTML tag + if (typeof renderDomElement === "function" && contains(HTML_TAGS, hname)) { + return asyncRenderElement(hname, expr.slice(1), env, ns); + } + + // html: prefix + if (hname.indexOf("html:") === 0) { + return asyncRenderElement(hname.slice(5), expr.slice(1), env, ns); + } + + // Custom element + if (hname.indexOf("-") >= 0 && expr.length > 1 && expr[1] && expr[1]._kw) { + return asyncRenderElement(hname, expr.slice(1), env, ns); + } + + // SVG context + if (ns) return asyncRenderElement(hname, expr.slice(1), env, ns); + + // Fallback: eval and render + var fResult = asyncEval(expr, env); + if (isPromise(fResult)) return fResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(fResult, env, ns); + } + + // Non-symbol head: eval call + var cResult = asyncEval(expr, env); + if (isPromise(cResult)) return cResult.then(function(v) { return asyncRenderToDom(v, env, ns); }); + return asyncRenderToDom(cResult, env, ns); + } + + function asyncRenderChildren(exprs, env, ns) { + var frag = document.createDocumentFragment(); + var pending = []; + for (var i = 0; i < exprs.length; i++) { + var result = asyncRenderToDom(exprs[i], env, ns); + if (isPromise(result)) { + // Insert placeholder, replace when resolved + var placeholder = document.createComment("async"); + frag.appendChild(placeholder); + (function(ph) { + pending.push(result.then(function(node) { + if (node) ph.parentNode.replaceChild(node, ph); + else ph.parentNode.removeChild(ph); + })); + })(placeholder); + } else if (result) { + frag.appendChild(result); + } + } + if (pending.length > 0) { + return Promise.all(pending).then(function() { return frag; }); + } + return frag; + } + + function asyncRenderElement(tag, args, env, ns) { + var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns; + var el = domCreateElement(tag, newNs); + var pending = []; + var isVoid = contains(VOID_ELEMENTS, tag); + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + if (arg && arg._kw && (i + 1) < args.length) { + var attrName = arg.name; + var attrVal = asyncEval(args[i + 1], env); + i++; + if (isPromise(attrVal)) { + (function(an, av) { + pending.push(av.then(function(v) { + if (!isNil(v) && v !== false) { + if (contains(BOOLEAN_ATTRS, an)) { if (isSxTruthy(v)) el.setAttribute(an, ""); } + else if (v === true) el.setAttribute(an, ""); + else el.setAttribute(an, String(v)); + } + })); + })(attrName, attrVal); + } else { + if (!isNil(attrVal) && attrVal !== false) { + if (contains(BOOLEAN_ATTRS, attrName)) { + if (isSxTruthy(attrVal)) el.setAttribute(attrName, ""); + } else if (attrVal === true) { + el.setAttribute(attrName, ""); + } else { + el.setAttribute(attrName, String(attrVal)); + } + } + } + } else if (!isVoid) { + var child = asyncRenderToDom(arg, env, newNs); + if (isPromise(child)) { + var placeholder = document.createComment("async"); + el.appendChild(placeholder); + (function(ph) { + pending.push(child.then(function(node) { + if (node) ph.parentNode.replaceChild(node, ph); + else ph.parentNode.removeChild(ph); + })); + })(placeholder); + } else if (child) { + el.appendChild(child); + } + } + } + if (pending.length > 0) return Promise.all(pending).then(function() { return el; }); + return el; + } + + function asyncRenderComponent(comp, args, env, ns) { + var kwargs = {}; + var children = []; + var pending = []; + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + if (arg && arg._kw && (i + 1) < args.length) { + var kName = arg.name; + var kVal = asyncEval(args[i + 1], env); + if (isPromise(kVal)) { + (function(k) { pending.push(kVal.then(function(v) { kwargs[k] = v; })); })(kName); + } else { + kwargs[kName] = kVal; + } + i++; + } else { + children.push(arg); + } + } + + function doRender() { + var local = Object.create(componentClosure(comp)); + for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; + var params = componentParams(comp); + for (var j = 0; j < params.length; j++) { + local[params[j]] = params[j] in kwargs ? kwargs[params[j]] : NIL; + } + if (componentHasChildren(comp)) { + var childResult = asyncRenderChildren(children, env, ns); + if (isPromise(childResult)) { + return childResult.then(function(childFrag) { + local["children"] = childFrag; + return asyncRenderToDom(componentBody(comp), local, ns); + }); + } + local["children"] = childResult; + } + return asyncRenderToDom(componentBody(comp), local, ns); + } + + if (pending.length > 0) return Promise.all(pending).then(doRender); + return doRender(); + } + + function asyncRenderIf(expr, env, ns) { + var cond = asyncEval(expr[1], env); + if (isPromise(cond)) { + return cond.then(function(v) { + return isSxTruthy(v) + ? asyncRenderToDom(expr[2], env, ns) + : (expr.length > 3 ? asyncRenderToDom(expr[3], env, ns) : null); + }); + } + return isSxTruthy(cond) + ? asyncRenderToDom(expr[2], env, ns) + : (expr.length > 3 ? asyncRenderToDom(expr[3], env, ns) : null); + } + + function asyncRenderWhen(expr, env, ns) { + var cond = asyncEval(expr[1], env); + if (isPromise(cond)) { + return cond.then(function(v) { + return isSxTruthy(v) ? asyncRenderChildren(expr.slice(2), env, ns) : null; + }); + } + return isSxTruthy(cond) ? asyncRenderChildren(expr.slice(2), env, ns) : null; + } + + function asyncRenderCond(expr, env, ns) { + var clauses = expr.slice(1); + function step(idx) { + if (idx >= clauses.length) return null; + var clause = clauses[idx]; + if (!Array.isArray(clause) || clause.length < 2) return step(idx + 1); + var test = clause[0]; + if ((test && test._sym && (test.name === "else" || test.name === ":else")) || + (test && test._kw && test.name === "else")) { + return asyncRenderToDom(clause[1], env, ns); + } + var v = asyncEval(test, env); + if (isPromise(v)) return v.then(function(r) { return isSxTruthy(r) ? asyncRenderToDom(clause[1], env, ns) : step(idx + 1); }); + return isSxTruthy(v) ? asyncRenderToDom(clause[1], env, ns) : step(idx + 1); + } + return step(0); + } + + function asyncRenderCase(expr, env, ns) { + var matchVal = asyncEval(expr[1], env); + function doCase(mv) { + var clauses = expr.slice(2); + for (var i = 0; i < clauses.length - 1; i += 2) { + var test = clauses[i]; + if ((test && test._kw && test.name === "else") || + (test && test._sym && (test.name === "else" || test.name === ":else"))) { + return asyncRenderToDom(clauses[i + 1], env, ns); + } + var tv = trampoline(evalExpr(test, env)); + if (mv === tv || (typeof mv === "string" && typeof tv === "string" && mv === tv)) { + return asyncRenderToDom(clauses[i + 1], env, ns); + } + } + return null; + } + if (isPromise(matchVal)) return matchVal.then(doCase); + return doCase(matchVal); + } + + function asyncRenderLet(expr, env, ns) { + var bindings = expr[1]; + var local = Object.create(env); + for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k]; + function bindStep(idx) { + if (!Array.isArray(bindings)) return asyncRenderChildren(expr.slice(2), local, ns); + // Nested pairs: ((a 1) (b 2)) + if (bindings.length > 0 && Array.isArray(bindings[0])) { + if (idx >= bindings.length) return asyncRenderChildren(expr.slice(2), local, ns); + var b = bindings[idx]; + var vname = b[0]._sym ? b[0].name : String(b[0]); + var val = asyncEval(b[1], local); + if (isPromise(val)) return val.then(function(v) { local[vname] = v; return bindStep(idx + 1); }); + local[vname] = val; + return bindStep(idx + 1); + } + // Flat pairs: (a 1 b 2) + if (idx >= bindings.length) return asyncRenderChildren(expr.slice(2), local, ns); + var vn = bindings[idx]._sym ? bindings[idx].name : String(bindings[idx]); + var vv = asyncEval(bindings[idx + 1], local); + if (isPromise(vv)) return vv.then(function(v) { local[vn] = v; return bindStep(idx + 2); }); + local[vn] = vv; + return bindStep(idx + 2); + } + return bindStep(0); + } + + function asyncRenderMap(expr, env, ns) { + var fn = asyncEval(expr[1], env); + var coll = asyncEval(expr[2], env); + function doMap(f, c) { + if (!Array.isArray(c)) return null; + var frag = document.createDocumentFragment(); + var pending = []; + for (var i = 0; i < c.length; i++) { + var item = c[i]; + var result; + if (f && f._lambda) { + var lenv = Object.create(f.closure || env); + for (var k in env) if (env.hasOwnProperty(k)) lenv[k] = env[k]; + lenv[f.params[0]] = item; + result = asyncRenderToDom(f.body, lenv, null); + } else if (typeof f === "function") { + var r = f(item); + result = isPromise(r) ? r.then(function(v) { return asyncRenderToDom(v, env, null); }) : asyncRenderToDom(r, env, null); + } else { + result = asyncRenderToDom(item, env, null); + } + if (isPromise(result)) { + var ph = document.createComment("async"); + frag.appendChild(ph); + (function(p) { pending.push(result.then(function(n) { if (n) p.parentNode.replaceChild(n, p); else p.parentNode.removeChild(p); })); })(ph); + } else if (result) { + frag.appendChild(result); + } + } + if (pending.length) return Promise.all(pending).then(function() { return frag; }); + return frag; + } + if (isPromise(fn) || isPromise(coll)) { + return Promise.all([isPromise(fn) ? fn : Promise.resolve(fn), isPromise(coll) ? coll : Promise.resolve(coll)]) + .then(function(r) { return doMap(r[0], r[1]); }); + } + return doMap(fn, coll); + } + + function asyncRenderMapIndexed(expr, env, ns) { + var fn = asyncEval(expr[1], env); + var coll = asyncEval(expr[2], env); + function doMap(f, c) { + if (!Array.isArray(c)) return null; + var frag = document.createDocumentFragment(); + var pending = []; + for (var i = 0; i < c.length; i++) { + var item = c[i]; + var result; + if (f && f._lambda) { + var lenv = Object.create(f.closure || env); + for (var k in env) if (env.hasOwnProperty(k)) lenv[k] = env[k]; + lenv[f.params[0]] = i; + lenv[f.params[1]] = item; + result = asyncRenderToDom(f.body, lenv, null); + } else if (typeof f === "function") { + var r = f(i, item); + result = isPromise(r) ? r.then(function(v) { return asyncRenderToDom(v, env, null); }) : asyncRenderToDom(r, env, null); + } else { + result = asyncRenderToDom(item, env, null); + } + if (isPromise(result)) { + var ph = document.createComment("async"); + frag.appendChild(ph); + (function(p) { pending.push(result.then(function(n) { if (n) p.parentNode.replaceChild(n, p); else p.parentNode.removeChild(p); })); })(ph); + } else if (result) { + frag.appendChild(result); + } + } + if (pending.length) return Promise.all(pending).then(function() { return frag; }); + return frag; + } + if (isPromise(fn) || isPromise(coll)) { + return Promise.all([isPromise(fn) ? fn : Promise.resolve(fn), isPromise(coll) ? coll : Promise.resolve(coll)]) + .then(function(r) { return doMap(r[0], r[1]); }); + } + return doMap(fn, coll); + } + + function asyncEvalRaw(args, env) { + var parts = []; + var pending = []; + for (var i = 0; i < args.length; i++) { + var val = asyncEval(args[i], env); + if (isPromise(val)) { + (function(idx) { + pending.push(val.then(function(v) { parts[idx] = v; })); + })(parts.length); + parts.push(null); + } else { + parts.push(val); + } + } + function assemble() { + var html = ""; + for (var j = 0; j < parts.length; j++) { + var p = parts[j]; + if (p && p._rawHtml) html += p.html; + else if (typeof p === "string") html += p; + else if (p != null && !isNil(p)) html += String(p); + } + var el = document.createElement("span"); + el.innerHTML = html; + var frag = document.createDocumentFragment(); + while (el.firstChild) frag.appendChild(el.firstChild); + return frag; + } + if (pending.length) return Promise.all(pending).then(assemble); + return assemble(); + } + + // Async version of sxRenderWithEnv — returns Promise + function asyncSxRenderWithEnv(source, extraEnv) { + var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + var exprs = parse(source); + if (!_hasDom) return Promise.resolve(null); + return asyncRenderChildren(exprs, env, null); + } + + // IO proxy cache: key → { value, expires } + var _ioCache = {}; + var IO_CACHE_TTL = 300000; // 5 minutes + + // Register a server-proxied IO primitive: fetches from /sx/io/ + // Uses GET for short args, POST for long payloads (URL length safety). + // Results are cached client-side by (name + args) with a TTL. + function registerProxiedIo(name) { + registerIoPrimitive(name, function(args, kwargs) { + // Cache key: name + serialized args + var cacheKey = name; + for (var ci = 0; ci < args.length; ci++) cacheKey += "" + String(args[ci]); + for (var ck in kwargs) { + if (kwargs.hasOwnProperty(ck)) cacheKey += "" + ck + "=" + String(kwargs[ck]); + } + var cached = _ioCache[cacheKey]; + if (cached && cached.expires > Date.now()) return cached.value; + + var url = "/sx/io/" + encodeURIComponent(name); + var qs = []; + for (var i = 0; i < args.length; i++) { + qs.push("_arg" + i + "=" + encodeURIComponent(String(args[i]))); + } + for (var k in kwargs) { + if (kwargs.hasOwnProperty(k)) { + qs.push(encodeURIComponent(k) + "=" + encodeURIComponent(String(kwargs[k]))); + } + } + var queryStr = qs.join("&"); + var fetchOpts; + if (queryStr.length > 1500) { + // POST with JSON body for long payloads + var sArgs = []; + for (var j = 0; j < args.length; j++) sArgs.push(String(args[j])); + var sKwargs = {}; + for (var kk in kwargs) { + if (kwargs.hasOwnProperty(kk)) sKwargs[kk] = String(kwargs[kk]); + } + var postHeaders = { "SX-Request": "true", "Content-Type": "application/json" }; + var csrf = csrfToken(); + if (csrf && csrf !== NIL) postHeaders["X-CSRFToken"] = csrf; + fetchOpts = { + method: "POST", + headers: postHeaders, + body: JSON.stringify({ args: sArgs, kwargs: sKwargs }) + }; + } else { + if (queryStr) url += "?" + queryStr; + fetchOpts = { headers: { "SX-Request": "true" } }; + } + var result = fetch(url, fetchOpts) + .then(function(resp) { + if (!resp.ok) { + logWarn("sx:io " + name + " failed " + resp.status); + return NIL; + } + return resp.text(); + }) + .then(function(text) { + if (!text || text === "nil") return NIL; + try { + var exprs = parse(text); + var val = exprs.length === 1 ? exprs[0] : exprs; + _ioCache[cacheKey] = { value: val, expires: Date.now() + IO_CACHE_TTL }; + return val; + } catch (e) { + logWarn("sx:io " + name + " parse error: " + (e && e.message ? e.message : e)); + return NIL; + } + }) + .catch(function(e) { + logWarn("sx:io " + name + " network error: " + (e && e.message ? e.message : e)); + return NIL; + }); + // Cache the in-flight promise too (dedup concurrent calls for same args) + _ioCache[cacheKey] = { value: result, expires: Date.now() + IO_CACHE_TTL }; + return result; + }); + } + + // Register IO deps as proxied primitives (idempotent, called per-page) + function registerIoDeps(names) { + if (!names || !names.length) return; + var registered = 0; + for (var i = 0; i < names.length; i++) { + var name = names[i]; + if (!IO_PRIMITIVES[name]) { + registerProxiedIo(name); + registered++; + } + } + if (registered > 0) { + logInfo("sx:io registered " + registered + " proxied primitives: " + names.join(", ")); + } + } + + + // Parser — compiled from parser.sx (see PLATFORM_PARSER_JS for ident char classes) + var parse = sxParse; + + // ========================================================================= + // Public API + // ========================================================================= + + var componentEnv = {}; + + function loadComponents(source) { + var exprs = parse(source); + for (var i = 0; i < exprs.length; i++) { + trampoline(evalExpr(exprs[i], componentEnv)); + } + } + + function render(source) { + if (!_hasDom) { + var exprs = parse(source); + var parts = []; + for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); + return parts.join(""); + } + var exprs = parse(source); + var frag = document.createDocumentFragment(); + for (var i = 0; i < exprs.length; i++) frag.appendChild(renderToDom(exprs[i], merge(componentEnv), null)); + return frag; + } + + function renderToString(source) { + var exprs = parse(source); + var parts = []; + for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); + return parts.join(""); + } + + var Sx = { + VERSION: "ref-2.0", + parse: parse, + parseAll: parse, + eval: function(expr, env) { return trampoline(evalExpr(expr, env || merge(componentEnv))); }, + loadComponents: loadComponents, + render: render, + renderToString: renderToString, + serialize: serialize, + NIL: NIL, + Symbol: Symbol, + Keyword: Keyword, + isTruthy: isSxTruthy, + isNil: isNil, + componentEnv: componentEnv, + renderToHtml: function(expr, env) { return renderToHtml(expr, env || merge(componentEnv)); }, + renderToSx: function(expr, env) { return renderToSx(expr, env || merge(componentEnv)); }, + renderToDom: _hasDom ? function(expr, env, ns) { return renderToDom(expr, env || merge(componentEnv), ns || null); } : null, + parseTriggerSpec: typeof parseTriggerSpec === "function" ? parseTriggerSpec : null, + parseTime: typeof parseTime === "function" ? parseTime : null, + defaultTrigger: typeof defaultTrigger === "function" ? defaultTrigger : null, + parseSwapSpec: typeof parseSwapSpec === "function" ? parseSwapSpec : null, + parseRetrySpec: typeof parseRetrySpec === "function" ? parseRetrySpec : null, + nextRetryMs: typeof nextRetryMs === "function" ? nextRetryMs : null, + filterParams: typeof filterParams === "function" ? filterParams : null, + morphNode: typeof morphNode === "function" ? morphNode : null, + morphChildren: typeof morphChildren === "function" ? morphChildren : null, + swapDomNodes: typeof swapDomNodes === "function" ? swapDomNodes : null, + process: typeof processElements === "function" ? processElements : null, + executeRequest: typeof executeRequest === "function" ? executeRequest : null, + postSwap: typeof postSwap === "function" ? postSwap : null, + processScripts: typeof processSxScripts === "function" ? processSxScripts : null, + mount: typeof sxMount === "function" ? sxMount : null, + hydrate: typeof sxHydrateElements === "function" ? sxHydrateElements : null, + update: typeof sxUpdateElement === "function" ? sxUpdateElement : null, + renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null, + getEnv: function() { return componentEnv; }, + resolveSuspense: typeof resolveSuspense === "function" ? resolveSuspense : null, + hydrateIslands: typeof sxHydrateIslands === "function" ? sxHydrateIslands : null, + disposeIsland: typeof disposeIsland === "function" ? disposeIsland : null, + init: typeof bootInit === "function" ? bootInit : null, + scanRefs: scanRefs, + scanComponentsFromSource: scanComponentsFromSource, + transitiveDeps: transitiveDeps, + computeAllDeps: computeAllDeps, + componentsNeeded: componentsNeeded, + pageComponentBundle: pageComponentBundle, + pageCssClasses: pageCssClasses, + scanIoRefs: scanIoRefs, + transitiveIoRefs: transitiveIoRefs, + computeAllIoRefs: computeAllIoRefs, + componentPure_p: componentPure_p, + categorizeSpecialForms: categorizeSpecialForms, + buildReferenceData: buildReferenceData, + buildAttrDetail: buildAttrDetail, + buildHeaderDetail: buildHeaderDetail, + buildEventDetail: buildEventDetail, + buildComponentSource: buildComponentSource, + buildBundleAnalysis: buildBundleAnalysis, + buildRoutingAnalysis: buildRoutingAnalysis, + buildAffinityAnalysis: buildAffinityAnalysis, + splitPathSegments: splitPathSegments, + parseRoutePattern: parseRoutePattern, + matchRoute: matchRoute, + findMatchingRoute: findMatchingRoute, + registerIo: typeof registerIoPrimitive === "function" ? registerIoPrimitive : null, + registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null, + asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null, + asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null, + signal: signal, + deref: deref, + reset: reset_b, + swap: swap_b, + computed: computed, + effect: effect, + batch: batch, + isSignal: isSignal, + makeSignal: makeSignal, + defStore: defStore, + useStore: useStore, + clearStores: clearStores, + emitEvent: emitEvent, + onEvent: onEvent, + bridgeEvent: bridgeEvent, + _version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)" + }; + + + // --- Popstate listener --- + if (typeof window !== "undefined") { + window.addEventListener("popstate", function(e) { + handlePopstate(e && e.state ? e.state.scrollY || 0 : 0); + }); + } + + // --- Auto-init --- + if (typeof document !== "undefined") { + var _sxInit = function() { + bootInit(); + // Process any suspense resolutions that arrived before init + if (global.__sxPending) { + for (var pi = 0; pi < global.__sxPending.length; pi++) { + resolveSuspense(global.__sxPending[pi].id, global.__sxPending[pi].sx); + } + global.__sxPending = null; + } + // Set up direct resolution for future chunks + global.__sxResolve = function(id, sx) { resolveSuspense(id, sx); }; + // Register service worker for offline data caching + if ("serviceWorker" in navigator) { + navigator.serviceWorker.register("/sx-sw.js", { scope: "/" }).then(function(reg) { + logInfo("sx:sw registered (scope: " + reg.scope + ")"); + }).catch(function(err) { + logWarn("sx:sw registration failed: " + (err && err.message ? err.message : err)); + }); + } + }; + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", _sxInit); + } else { + _sxInit(); + } + } + if (typeof module !== "undefined" && module.exports) module.exports = Sx; + else global.Sx = Sx; + +})(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : this); \ No newline at end of file