From d8cddbd971d2319cbe101498c041cdefc6d6fb5f Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 11 Mar 2026 09:18:17 +0000 Subject: [PATCH] Replace hand-written evaluator with bootstrapped spec, emit flat Python MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - evaluator.py: replace 1200 lines of hand-written eval with thin shim that re-exports from bootstrapped sx_ref.py - bootstrap_py.py: emit all fn-bodied defines as `def` (not `lambda`), flatten tail-position if/cond/case/when to if/elif with returns, fix &rest handling in _emit_define_as_def - platform_py.py: EvalError imports from evaluator.py so catches work - __init__.py: remove SX_USE_REF conditional, always use bootstrapped - tests/run.py: reset render_active after render tests for isolation - Removes setrecursionlimit(5000) hack — no longer needed with flat code Co-Authored-By: Claude Opus 4.6 --- shared/sx/__init__.py | 19 +- shared/sx/evaluator.py | 1223 ++---------------------- shared/sx/ref/bootstrap_py.py | 141 ++- shared/sx/ref/platform_py.py | 7 +- shared/sx/ref/sx_ref.py | 1635 +++++++++++++++++++++++++++++---- shared/sx/tests/run.py | 9 + 6 files changed, 1684 insertions(+), 1350 deletions(-) diff --git a/shared/sx/__init__.py b/shared/sx/__init__.py index 93a0eb2..20d357c 100644 --- a/shared/sx/__init__.py +++ b/shared/sx/__init__.py @@ -31,20 +31,11 @@ from .parser import ( parse_all, serialize, ) -import os as _os - -if _os.environ.get("SX_USE_REF") == "1": - from .ref.sx_ref import ( - EvalError, - evaluate, - make_env, - ) -else: - from .evaluator import ( - EvalError, - evaluate, - make_env, - ) +from .evaluator import ( + EvalError, + evaluate, + make_env, +) from .primitives import ( all_primitives, diff --git a/shared/sx/evaluator.py b/shared/sx/evaluator.py index 1f61c99..4e70352 100644 --- a/shared/sx/evaluator.py +++ b/shared/sx/evaluator.py @@ -1,1199 +1,94 @@ """ -S-expression evaluator. +S-expression evaluator — thin shim over bootstrapped sx_ref.py. -Walks a parsed s-expression tree and evaluates it in an environment. +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. -Special forms: - (if cond then else?) - (when cond body) - (cond clause...) — Scheme-style ((test body)...) or Clojure-style (test body...) - (case expr val body... :else default) - (and expr...) (or expr...) - (let ((name val)...) body) or (let (name val name val...) body) - (lambda (params...) body) or (fn (params...) body) - (define name value) - (defcomp ~name (&key param...) body) - (defrelation :name :from "type" :to "type" :cardinality :card ...) - (begin expr...) - (quote expr) - (do expr...) — alias for begin - (-> val form...) — thread-first macro - -Higher-order forms (operate on lambdas): - (map fn coll) - (map-indexed fn coll) - (filter fn coll) - (reduce fn init coll) - (some fn coll) - (every? fn coll) - (for-each fn coll) +Imports are lazy (inside functions/properties) to avoid circular imports +during bootstrapping: bootstrap_py.py → parser → __init__ → evaluator → sx_ref. """ from __future__ import annotations -from typing import Any -from .types import Component, Continuation, HandlerDef, Island, Keyword, Lambda, Macro, NIL, PageDef, RelationDef, Symbol, _ShiftSignal -from .primitives import _PRIMITIVES +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.""" + """Error during expression evaluation. + + Delegates to the bootstrapped EvalError at runtime but is defined here + so imports don't fail during bootstrapping. + """ pass -class _Thunk: - """Deferred evaluation — returned from tail positions for TCO.""" - __slots__ = ("expr", "env") - - def __init__(self, expr: Any, env: dict[str, Any]): - self.expr = expr - self.env = env +def evaluate(expr, env=None): + return _ref().evaluate(expr, env) -def _trampoline(val: Any) -> Any: - """Unwrap thunks by re-entering the evaluator until we get an actual value.""" - while isinstance(val, _Thunk): - val = _eval(val.expr, val.env) - return val +def make_env(**kwargs): + return _ref().make_env(**kwargs) # --------------------------------------------------------------------------- -# Public API +# Internal helpers — used by html.py, async_eval.py, handlers.py, etc. # --------------------------------------------------------------------------- -def evaluate(expr: Any, env: dict[str, Any] | None = None) -> Any: - """Evaluate *expr* in *env* and return the result.""" - if env is None: - env = {} - result = _eval(expr, env) - while isinstance(result, _Thunk): - result = _eval(result.expr, result.env) - return result +def _eval(expr, env): + return _ref().eval_expr(expr, env) -def make_env(**kwargs: Any) -> dict[str, Any]: - """Convenience: create an environment dict with initial bindings.""" - return dict(kwargs) +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) # --------------------------------------------------------------------------- -# Internal evaluator +# 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 _eval(expr: Any, env: dict[str, Any]) -> Any: - # --- literals --------------------------------------------------------- - if isinstance(expr, (int, float, str, bool)): - return expr - if expr is None or expr is NIL: - return NIL +def _sf_defcomp(expr, env): + return _ref().sf_defcomp(expr[1:], env) - # --- symbol lookup ---------------------------------------------------- - if isinstance(expr, Symbol): - name = expr.name - if name in env: - return env[name] - if name in _PRIMITIVES: - return _PRIMITIVES[name] - if name == "true": - return True - if name == "false": - return False - if name == "nil": - return NIL - raise EvalError(f"Undefined symbol: {name}") +def _sf_defisland(expr, env): + return _ref().sf_defisland(expr[1:], env) - # --- keyword → its string name ---------------------------------------- - if isinstance(expr, Keyword): - return expr.name +def _sf_defstyle(expr, env): + return _ref().sf_defstyle(expr[1:], env) - # --- dict literal ----------------------------------------------------- - if isinstance(expr, dict): - return {k: _trampoline(_eval(v, env)) for k, v in expr.items()} +def _sf_defmacro(expr, env): + return _ref().sf_defmacro(expr[1:], env) - # --- list = call or special form -------------------------------------- - if not isinstance(expr, list): - return expr +def _sf_defhandler(expr, env): + return _ref().sf_defhandler(expr[1:], env) - if not expr: - return [] +def _sf_defpage(expr, env): + return _ref().sf_defpage(expr[1:], env) - head = expr[0] +def _sf_defquery(expr, env): + return _ref().sf_defquery(expr[1:], env) - # If head is not a symbol/lambda/list, treat entire list as data - if not isinstance(head, (Symbol, Lambda, list)): - return [_trampoline(_eval(x, env)) for x in expr] - - # --- special forms ---------------------------------------------------- - if isinstance(head, Symbol): - name = head.name - handler = _SPECIAL_FORMS.get(name) - if handler is not None: - return handler(expr, env) - - # Higher-order forms (need lazy eval of lambda arg) - ho = _HO_FORMS.get(name) - if ho is not None: - return ho(expr, env) - - # Macro expansion — if head resolves to a Macro, expand then eval - if name in env: - val = env[name] - if isinstance(val, Macro): - expanded = _expand_macro(val, expr[1:], env) - return _Thunk(expanded, env) - - # --- function / lambda call ------------------------------------------- - fn = _trampoline(_eval(head, env)) - args = [_trampoline(_eval(a, env)) for a in expr[1:]] - - if callable(fn) and not isinstance(fn, (Lambda, Component, Island)): - return fn(*args) - - if isinstance(fn, Lambda): - return _call_lambda(fn, args, env) - - if isinstance(fn, (Component, Island)): - return _call_component(fn, expr[1:], env) - - raise EvalError(f"Not callable: {fn!r}") - - -# --------------------------------------------------------------------------- -# Lambda / component invocation -# --------------------------------------------------------------------------- - -def _call_lambda(fn: Lambda, args: list[Any], caller_env: dict[str, Any]) -> Any: - # Too many args is an error; too few pads with nil - if len(args) > len(fn.params): - raise EvalError(f"{fn!r} expects {len(fn.params)} args, got {len(args)}") - local = dict(fn.closure) - local.update(caller_env) - for p, v in zip(fn.params, args): - local[p] = v - # Pad missing params with nil - for p in fn.params[len(args):]: - local[p] = None - return _Thunk(fn.body, local) - - -def _call_component(comp: Component, raw_args: list[Any], env: dict[str, Any]) -> Any: - """Evaluate a component invocation with keyword arguments. - - ``(~card :title "Hello" (p "child"))`` - → comp.params gets ``title="Hello"``, comp children gets ``[(p "child")]`` - """ - kwargs: dict[str, Any] = {} - children: list[Any] = [] - i = 0 - while i < len(raw_args): - arg = raw_args[i] - if isinstance(arg, Keyword) and i + 1 < len(raw_args): - kwargs[arg.name] = _trampoline(_eval(raw_args[i + 1], env)) - i += 2 - else: - children.append(_trampoline(_eval(arg, env))) - i += 1 - - local = dict(comp.closure) - local.update(env) - for p in comp.params: - if p in kwargs: - local[p] = kwargs[p] - else: - local[p] = NIL - if comp.has_children: - local["children"] = children - return _Thunk(comp.body, local) - - -# --------------------------------------------------------------------------- -# Special forms -# --------------------------------------------------------------------------- - -def _sf_if(expr: list, env: dict) -> Any: - if len(expr) < 3: - raise EvalError("if requires condition and then-branch") - cond = _trampoline(_eval(expr[1], env)) - if cond and cond is not NIL: - return _Thunk(expr[2], env) - if len(expr) > 3: - return _Thunk(expr[3], env) - return NIL - - -def _sf_when(expr: list, env: dict) -> Any: - if len(expr) < 3: - raise EvalError("when requires condition and body") - cond = _trampoline(_eval(expr[1], env)) - if cond and cond is not NIL: - for body_expr in expr[2:-1]: - _trampoline(_eval(body_expr, env)) - return _Thunk(expr[-1], env) - return NIL - - -def _sf_cond(expr: list, env: dict) -> Any: - clauses = expr[1:] - if not clauses: - return NIL - # Detect scheme-style: first clause is a 2-element list that isn't a - # comparison or predicate call (predicates end in ?) - def _is_clojure_test(clause): - if not isinstance(clause, list) or len(clause) != 2: - return False - head = clause[0] - if not isinstance(head, Symbol): - return False - return (head.name in ("=", "<", ">", "<=", ">=", "!=", "and", "or") - or head.name.endswith("?")) - if ( - isinstance(clauses[0], list) - and len(clauses[0]) == 2 - and not _is_clojure_test(clauses[0]) - ): - for clause in clauses: - if not isinstance(clause, list) or len(clause) < 2: - raise EvalError("cond clause must be (test result)") - test = clause[0] - if isinstance(test, Symbol) and test.name in ("else", ":else"): - return _Thunk(clause[1], env) - if isinstance(test, Keyword) and test.name == "else": - return _Thunk(clause[1], env) - if _trampoline(_eval(test, env)): - return _Thunk(clause[1], env) - else: - i = 0 - while i < len(clauses) - 1: - test = clauses[i] - result = clauses[i + 1] - if isinstance(test, Keyword) and test.name == "else": - return _Thunk(result, env) - if isinstance(test, Symbol) and test.name in (":else", "else"): - return _Thunk(result, env) - if _trampoline(_eval(test, env)): - return _Thunk(result, env) - i += 2 - return NIL - - -def _sf_case(expr: list, env: dict) -> Any: - if len(expr) < 2: - raise EvalError("case requires expression to match") - match_val = _trampoline(_eval(expr[1], env)) - clauses = expr[2:] - i = 0 - while i < len(clauses) - 1: - test = clauses[i] - result = clauses[i + 1] - if isinstance(test, Keyword) and test.name == "else": - return _Thunk(result, env) - if isinstance(test, Symbol) and test.name in (":else", "else"): - return _Thunk(result, env) - if match_val == _trampoline(_eval(test, env)): - return _Thunk(result, env) - i += 2 - return NIL - - -def _sf_and(expr: list, env: dict) -> Any: - result: Any = True - for arg in expr[1:]: - result = _trampoline(_eval(arg, env)) - if not result: - return result - return result - - -def _sf_or(expr: list, env: dict) -> Any: - result: Any = False - for arg in expr[1:]: - result = _trampoline(_eval(arg, env)) - if result: - return result - return result - - -def _sf_let(expr: list, env: dict) -> Any: - if len(expr) < 3: - raise EvalError("let requires bindings and body") - - # Named let: (let name ((x 0) ...) body) - if isinstance(expr[1], Symbol): - return _sf_named_let(expr, env) - - bindings = expr[1] - local = dict(env) - - if isinstance(bindings, list): - if bindings and isinstance(bindings[0], list): - # Scheme-style: ((name val) ...) - for binding in bindings: - if len(binding) != 2: - raise EvalError("let binding must be (name value)") - var = binding[0] - vname = var.name if isinstance(var, Symbol) else var - local[vname] = _trampoline(_eval(binding[1], local)) - elif len(bindings) % 2 == 0: - # Clojure-style: (name val name val ...) - for i in range(0, len(bindings), 2): - var = bindings[i] - vname = var.name if isinstance(var, Symbol) else var - local[vname] = _trampoline(_eval(bindings[i + 1], local)) - else: - raise EvalError("let bindings must be (name val ...) pairs") - else: - raise EvalError("let bindings must be a list") - - # Evaluate body expressions — all but last non-tail, last is tail - body = expr[2:] - for body_expr in body[:-1]: - _trampoline(_eval(body_expr, local)) - return _Thunk(body[-1], local) - - -def _sf_named_let(expr: list, env: dict) -> Any: - """``(let name ((x 0) (y 1)) body...)`` — self-recursive loop. - - Desugars to a lambda bound to *name* whose closure includes itself, - called with the initial values. Tail calls to *name* produce TCO thunks. - """ - loop_name = expr[1].name - bindings = expr[2] - body = expr[3:] - - params: list[str] = [] - inits: list[Any] = [] - - if isinstance(bindings, list): - if bindings and isinstance(bindings[0], list): - for binding in bindings: - var = binding[0] - params.append(var.name if isinstance(var, Symbol) else var) - inits.append(binding[1]) - elif len(bindings) % 2 == 0: - for i in range(0, len(bindings), 2): - var = bindings[i] - params.append(var.name if isinstance(var, Symbol) else var) - inits.append(bindings[i + 1]) - - # Build loop body (wrap in begin if multiple expressions) - loop_body = body[0] if len(body) == 1 else [Symbol("begin")] + list(body) - - # Create self-recursive lambda - loop_fn = Lambda(params, loop_body, dict(env), name=loop_name) - loop_fn.closure[loop_name] = loop_fn - - # Evaluate initial values in enclosing env, then call - init_vals = [_trampoline(_eval(init, env)) for init in inits] - return _call_lambda(loop_fn, init_vals, env) - - -def _sf_letrec(expr: list, env: dict) -> Any: - """``(letrec ((name1 val1) ...) body)`` — mutually recursive bindings. - - All names are bound to NIL first, then values are evaluated (so they - can reference each other), then lambda closures are patched. - """ - if len(expr) < 3: - raise EvalError("letrec requires bindings and body") - bindings = expr[1] - local = dict(env) - - names: list[str] = [] - val_exprs: list[Any] = [] - - if isinstance(bindings, list): - if bindings and isinstance(bindings[0], list): - for binding in bindings: - var = binding[0] - vname = var.name if isinstance(var, Symbol) else var - names.append(vname) - val_exprs.append(binding[1]) - local[vname] = NIL - 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 - names.append(vname) - val_exprs.append(bindings[i + 1]) - local[vname] = NIL - - # Evaluate all values — they can see each other's names (initially NIL) - values = [_trampoline(_eval(ve, local)) for ve in val_exprs] - - # Bind final values - for name, val in zip(names, values): - local[name] = val - - # Patch lambda closures so they see the final bindings - for val in values: - if isinstance(val, Lambda): - for name in names: - val.closure[name] = local[name] - - body = expr[2:] - for body_expr in body[:-1]: - _trampoline(_eval(body_expr, local)) - return _Thunk(body[-1], local) - - -def _sf_dynamic_wind(expr: list, env: dict) -> Any: - """``(dynamic-wind before body after)`` — entry/exit guards. - - All three arguments are thunks (zero-arg functions). - *before* is called on entry, *after* is always called on exit (even on - error). The wind stack is maintained for future continuation support. - """ - if len(expr) != 4: - raise EvalError("dynamic-wind requires 3 arguments (before, body, after)") - before = _trampoline(_eval(expr[1], env)) - body_fn = _trampoline(_eval(expr[2], env)) - after = _trampoline(_eval(expr[3], env)) - - def _call_thunk(fn: Any) -> Any: - if isinstance(fn, Lambda): - return _trampoline(_call_lambda(fn, [], env)) - if callable(fn): - return fn() - raise EvalError(f"dynamic-wind: expected thunk, got {type(fn).__name__}") - - # Entry - _call_thunk(before) - _WIND_STACK.append((before, after)) - try: - result = _call_thunk(body_fn) - finally: - _WIND_STACK.pop() - _call_thunk(after) - return result - - -# Wind stack for dynamic-wind (thread-safe enough for sync evaluator) -_WIND_STACK: list[tuple] = [] - - -def _sf_lambda(expr: list, env: dict) -> Lambda: - if len(expr) < 3: - raise EvalError("lambda requires params and body") - params_expr = expr[1] - if not isinstance(params_expr, list): - raise EvalError("lambda params must be a list") - param_names = [] - for p in params_expr: - if isinstance(p, Symbol): - param_names.append(p.name) - elif isinstance(p, str): - param_names.append(p) - else: - raise EvalError(f"Invalid lambda param: {p}") - body_exprs = expr[2:] - body = body_exprs[0] if len(body_exprs) == 1 else [Symbol("begin")] + body_exprs - return Lambda(param_names, body, dict(env)) - - -def _sf_define(expr: list, env: dict) -> Any: - if len(expr) < 3: - raise EvalError("define requires name and value") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"define name must be symbol, got {type(name_sym).__name__}") - value = _trampoline(_eval(expr[2], env)) - if isinstance(value, Lambda) and value.name is None: - value.name = name_sym.name - env[name_sym.name] = value - return value - - -def _sf_defstyle(expr: list, env: dict) -> Any: - """``(defstyle card-base ...)`` - - Evaluates body and binds to name in env. - """ - if len(expr) < 3: - raise EvalError("defstyle requires name and body") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"defstyle name must be symbol, got {type(name_sym).__name__}") - value = _trampoline(_eval(expr[2], env)) - env[name_sym.name] = value - return value - - -def _sf_defcomp(expr: list, env: dict) -> Component: - """``(defcomp ~name (&key ...) [:affinity :client|:server] body)``""" - if len(expr) < 4: - raise EvalError("defcomp requires name, params, and body") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"defcomp name must be symbol, got {type(name_sym).__name__}") - comp_name = name_sym.name.lstrip("~") - - params_expr = expr[2] - if not isinstance(params_expr, list): - raise EvalError("defcomp params must be a list") - - params: list[str] = [] - has_children = False - in_key = False - for p in params_expr: - if isinstance(p, Symbol): - if p.name == "&key": - in_key = True - continue - if p.name == "&rest": - has_children = True - continue - if in_key or has_children: - if not has_children: - params.append(p.name) - else: - params.append(p.name) - elif isinstance(p, str): - params.append(p) - - # Body is always last element; keyword annotations between params and body - body = expr[-1] - affinity = _defcomp_kwarg(expr, "affinity", "auto") - - comp = Component( - name=comp_name, - params=params, - has_children=has_children, - body=body, - closure=dict(env), - affinity=affinity, - ) - env[name_sym.name] = comp - return comp - - -def _sf_defisland(expr: list, env: dict) -> Island: - """``(defisland ~name (&key ...) body)``""" - if len(expr) < 4: - raise EvalError("defisland requires name, params, and body") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"defisland name must be symbol, got {type(name_sym).__name__}") - comp_name = name_sym.name.lstrip("~") - - params_expr = expr[2] - if not isinstance(params_expr, list): - raise EvalError("defisland params must be a list") - - params: list[str] = [] - has_children = False - in_key = False - for p in params_expr: - if isinstance(p, Symbol): - if p.name == "&key": - in_key = True - continue - if p.name == "&rest": - has_children = True - continue - if in_key or has_children: - if not has_children: - params.append(p.name) - else: - params.append(p.name) - elif isinstance(p, str): - params.append(p) - - body = expr[-1] - - island = Island( - name=comp_name, - params=params, - has_children=has_children, - body=body, - closure=dict(env), - ) - env[name_sym.name] = island - return island - - -def _defcomp_kwarg(expr: list, key: str, default: str) -> str: - """Extract a keyword annotation from defcomp, e.g. :affinity :client.""" - # Scan from index 3 to second-to-last for :key value pairs - for i in range(3, len(expr) - 1): - item = expr[i] - if isinstance(item, Keyword) and item.name == key: - val = expr[i + 1] - if isinstance(val, Keyword): - return val.name - return str(val) - return default - - -def _sf_begin(expr: list, env: dict) -> Any: - if len(expr) < 2: - return NIL - for sub in expr[1:-1]: - _trampoline(_eval(sub, env)) - return _Thunk(expr[-1], env) - - -def _sf_quote(expr: list, _env: dict) -> Any: - return expr[1] if len(expr) > 1 else NIL - - -def _sf_thread_first(expr: list, env: dict) -> Any: - """``(-> val (f a) (g b))`` → ``(g (f val a) b)``""" - if len(expr) < 2: - raise EvalError("-> requires at least a value") - result = _trampoline(_eval(expr[1], env)) - for form in expr[2:]: - if isinstance(form, list): - fn = _trampoline(_eval(form[0], env)) - args = [result] + [_trampoline(_eval(a, env)) for a in form[1:]] - else: - fn = _trampoline(_eval(form, env)) - args = [result] - if callable(fn) and not isinstance(fn, (Lambda, Component, Island)): - result = fn(*args) - elif isinstance(fn, Lambda): - result = _trampoline(_call_lambda(fn, args, env)) - else: - raise EvalError(f"-> form not callable: {fn!r}") - return result - - -def _sf_defmacro(expr: list, env: dict) -> Macro: - """``(defmacro name (params... &rest rest) body)``""" - if len(expr) < 4: - raise EvalError("defmacro requires name, params, and body") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"defmacro name must be symbol, got {type(name_sym).__name__}") - - params_expr = expr[2] - if not isinstance(params_expr, list): - raise EvalError("defmacro params must be a list") - - params: list[str] = [] - rest_param: str | None = None - i = 0 - while i < len(params_expr): - p = params_expr[i] - if isinstance(p, Symbol) and p.name == "&rest": - if i + 1 < len(params_expr): - rp = params_expr[i + 1] - rest_param = rp.name if isinstance(rp, Symbol) else str(rp) - break - if isinstance(p, Symbol): - params.append(p.name) - elif isinstance(p, str): - params.append(p) - i += 1 - - macro = Macro( - params=params, - rest_param=rest_param, - body=expr[3], - closure=dict(env), - name=name_sym.name, - ) - env[name_sym.name] = macro - return macro - - -def _sf_quasiquote(expr: list, env: dict) -> Any: - """``(quasiquote template)`` — process quasiquote template.""" - if len(expr) < 2: - raise EvalError("quasiquote requires a template") - return _qq_expand(expr[1], env) - - -def _qq_expand(template: Any, env: dict) -> Any: - """Walk a quasiquote template, replacing unquote/splice-unquote.""" - if not isinstance(template, list): - return template - if not template: - return [] - # Check for (unquote x) or (splice-unquote x) - head = template[0] - if isinstance(head, Symbol): - if head.name == "unquote": - if len(template) < 2: - raise EvalError("unquote requires an expression") - return _trampoline(_eval(template[1], env)) - if head.name == "splice-unquote": - raise EvalError("splice-unquote not inside a list") - # Walk children, handling splice-unquote - result: list[Any] = [] - for item in template: - if isinstance(item, list) and len(item) == 2 and isinstance(item[0], Symbol) and item[0].name == "splice-unquote": - spliced = _trampoline(_eval(item[1], env)) - if isinstance(spliced, list): - result.extend(spliced) - elif spliced is not None and spliced is not NIL: - result.append(spliced) - else: - result.append(_qq_expand(item, env)) - return result - - -def _expand_macro(macro: Macro, raw_args: list[Any], env: dict) -> Any: - """Expand a macro: bind unevaluated args, evaluate body to get new AST.""" - local = dict(macro.closure) - local.update(env) - - # Bind positional params - for i, param in enumerate(macro.params): - if i < len(raw_args): - local[param] = raw_args[i] - else: - local[param] = NIL - - # Bind &rest param - if macro.rest_param is not None: - rest_start = len(macro.params) - local[macro.rest_param] = list(raw_args[rest_start:]) - - return _trampoline(_eval(macro.body, local)) - - -def _sf_defhandler(expr: list, env: dict) -> HandlerDef: - """``(defhandler name (&key param...) body)``""" - if len(expr) < 4: - raise EvalError("defhandler requires name, params, and body") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"defhandler name must be symbol, got {type(name_sym).__name__}") - - params_expr = expr[2] - if not isinstance(params_expr, list): - raise EvalError("defhandler params must be a list") - - params: list[str] = [] - in_key = False - for p in params_expr: - if isinstance(p, Symbol): - if p.name == "&key": - in_key = True - continue - if in_key: - params.append(p.name) - elif isinstance(p, str): - params.append(p) - - handler = HandlerDef( - name=name_sym.name, - params=params, - body=expr[3], - closure=dict(env), - ) - env[f"handler:{name_sym.name}"] = handler - return handler - - -def _parse_key_params(params_expr: list) -> list[str]: - """Parse ``(&key param1 param2 ...)`` into a list of param name strings.""" - params: list[str] = [] - in_key = False - for p in params_expr: - if isinstance(p, Symbol): - if p.name == "&key": - in_key = True - continue - if in_key: - params.append(p.name) - elif isinstance(p, str): - params.append(p) - return params - - -def _sf_defquery(expr: list, env: dict): - """``(defquery name (&key param...) "docstring" body)``""" - from .types import QueryDef - if len(expr) < 4: - raise EvalError("defquery requires name, params, and body") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"defquery name must be symbol, got {type(name_sym).__name__}") - params_expr = expr[2] - if not isinstance(params_expr, list): - raise EvalError("defquery params must be a list") - params = _parse_key_params(params_expr) - # Optional docstring before body - if len(expr) >= 5 and isinstance(expr[3], str): - doc = expr[3] - body = expr[4] - else: - doc = "" - body = expr[3] - qdef = QueryDef( - name=name_sym.name, params=params, doc=doc, - body=body, closure=dict(env), - ) - env[f"query:{name_sym.name}"] = qdef - return qdef - - -def _sf_defaction(expr: list, env: dict): - """``(defaction name (&key param...) "docstring" body)``""" - from .types import ActionDef - if len(expr) < 4: - raise EvalError("defaction requires name, params, and body") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"defaction name must be symbol, got {type(name_sym).__name__}") - params_expr = expr[2] - if not isinstance(params_expr, list): - raise EvalError("defaction params must be a list") - params = _parse_key_params(params_expr) - if len(expr) >= 5 and isinstance(expr[3], str): - doc = expr[3] - body = expr[4] - else: - doc = "" - body = expr[3] - adef = ActionDef( - name=name_sym.name, params=params, doc=doc, - body=body, closure=dict(env), - ) - env[f"action:{name_sym.name}"] = adef - return adef - - -def _sf_set_bang(expr: list, env: dict) -> Any: - """``(set! name value)`` — mutate existing binding.""" - if len(expr) != 3: - raise EvalError("set! requires name and value") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"set! name must be symbol, got {type(name_sym).__name__}") - value = _trampoline(_eval(expr[2], env)) - # Walk up scope if using Env objects; for plain dicts just overwrite - env[name_sym.name] = value - return value - - -_VALID_CARDINALITIES = {"one-to-one", "one-to-many", "many-to-many"} -_VALID_NAV = {"submenu", "tab", "badge", "inline", "hidden"} - - -def _sf_defrelation(expr: list, env: dict) -> RelationDef: - """``(defrelation :name :from "t" :to "t" :cardinality :card ...)``""" - if len(expr) < 2: - raise EvalError("defrelation requires a name") - - name_kw = expr[1] - if not isinstance(name_kw, Keyword): - raise EvalError(f"defrelation name must be a keyword, got {type(name_kw).__name__}") - rel_name = name_kw.name - - # Parse keyword args from remaining elements - kwargs: dict[str, str | None] = {} - i = 2 - while i < len(expr): - key = expr[i] - if isinstance(key, Keyword): - if i + 1 < len(expr): - val = expr[i + 1] - if isinstance(val, Keyword): - kwargs[key.name] = val.name - else: - kwargs[key.name] = _trampoline(_eval(val, env)) if not isinstance(val, str) else val - i += 2 - else: - kwargs[key.name] = None - i += 1 - else: - i += 1 - - for field in ("from", "to", "cardinality"): - if field not in kwargs: - raise EvalError(f"defrelation {rel_name} missing required :{field}") - - card = kwargs["cardinality"] - if card not in _VALID_CARDINALITIES: - raise EvalError( - f"defrelation {rel_name}: invalid cardinality {card!r}, " - f"expected one of {_VALID_CARDINALITIES}" - ) - - nav = kwargs.get("nav", "hidden") - if nav not in _VALID_NAV: - raise EvalError( - f"defrelation {rel_name}: invalid nav {nav!r}, " - f"expected one of {_VALID_NAV}" - ) - - defn = RelationDef( - name=rel_name, - from_type=kwargs["from"], - to_type=kwargs["to"], - cardinality=card, - inverse=kwargs.get("inverse"), - nav=nav, - nav_icon=kwargs.get("nav-icon"), - nav_label=kwargs.get("nav-label"), - ) - - from .relations import register_relation - register_relation(defn) - - env[f"relation:{rel_name}"] = defn - return defn - - -def _sf_defpage(expr: list, env: dict) -> PageDef: - """``(defpage name :path "/..." :auth :public :content expr ...)`` - - Parses keyword args from the expression. All slot values are stored - as unevaluated AST — they are resolved at request time by execute_page(). - """ - if len(expr) < 2: - raise EvalError("defpage requires a name") - name_sym = expr[1] - if not isinstance(name_sym, Symbol): - raise EvalError(f"defpage name must be symbol, got {type(name_sym).__name__}") - - # Parse keyword args — values are NOT evaluated (stored as AST) - slots: dict[str, Any] = {} - i = 2 - while i < len(expr): - key = expr[i] - if isinstance(key, Keyword) and i + 1 < len(expr): - slots[key.name] = expr[i + 1] - i += 2 - else: - i += 1 - - # Required fields - path = slots.get("path") - if path is None: - raise EvalError(f"defpage {name_sym.name} missing required :path") - if not isinstance(path, str): - raise EvalError(f"defpage {name_sym.name} :path must be a string") - - auth_val = slots.get("auth", "public") - if isinstance(auth_val, Keyword): - auth: str | list = auth_val.name - elif isinstance(auth_val, list): - # (:rights "a" "b") → ["rights", "a", "b"] - auth = [] - for item in auth_val: - if isinstance(item, Keyword): - auth.append(item.name) - elif isinstance(item, str): - auth.append(item) - else: - auth.append(_trampoline(_eval(item, env))) - else: - auth = str(auth_val) if auth_val else "public" - - # Layout — keep unevaluated - layout = slots.get("layout") - if isinstance(layout, Keyword): - layout = layout.name - elif isinstance(layout, list): - # Keep as unevaluated list for execute_page to resolve at request time - pass - - # Cache — evaluate if present (it's a static config dict) - cache_val = slots.get("cache") - cache = None - if cache_val is not None: - cache_result = _trampoline(_eval(cache_val, env)) - if isinstance(cache_result, dict): - cache = cache_result - - # Stream — evaluate (it's a static boolean) - stream_val = slots.get("stream") - stream = False - if stream_val is not None: - stream_result = _trampoline(_eval(stream_val, env)) - stream = bool(stream_result) - - page = PageDef( - name=name_sym.name, - path=path, - auth=auth, - layout=layout, - cache=cache, - data_expr=slots.get("data"), - content_expr=slots.get("content"), - filter_expr=slots.get("filter"), - aside_expr=slots.get("aside"), - menu_expr=slots.get("menu"), - stream=stream, - fallback_expr=slots.get("fallback"), - shell_expr=slots.get("shell"), - closure=dict(env), - ) - env[f"page:{name_sym.name}"] = page - return page - - -# --------------------------------------------------------------------------- -# Delimited continuations — shift / reset -# --------------------------------------------------------------------------- - -_RESET_RESUME = [] # stack of resume values; empty = not resuming - -_RESET_SENTINEL = object() - - -def _sf_reset(expr, env): - """(reset body) — establish a continuation delimiter.""" - body = expr[1] - try: - return _trampoline(_eval(body, env)) - except _ShiftSignal as sig: - def cont_fn(value=NIL): - _RESET_RESUME.append(value) - try: - return _trampoline(_eval(body, env)) - finally: - _RESET_RESUME.pop() - k = Continuation(cont_fn) - sig_env = dict(sig.env) - sig_env[sig.k_name] = k - return _trampoline(_eval(sig.body, sig_env)) - - -def _sf_shift(expr, env): - """(shift k body) — capture continuation to nearest reset.""" - if _RESET_RESUME: - return _RESET_RESUME[-1] - k_name = expr[1].name # symbol - body = expr[2] - raise _ShiftSignal(k_name, body, env) - - -_SPECIAL_FORMS: dict[str, Any] = { - "if": _sf_if, - "when": _sf_when, - "cond": _sf_cond, - "case": _sf_case, - "and": _sf_and, - "or": _sf_or, - "let": _sf_let, - "let*": _sf_let, - "letrec": _sf_letrec, - "lambda": _sf_lambda, - "fn": _sf_lambda, - "define": _sf_define, - "defstyle": _sf_defstyle, - "defcomp": _sf_defcomp, - "defisland": _sf_defisland, - "defrelation": _sf_defrelation, - "begin": _sf_begin, - "do": _sf_begin, - "quote": _sf_quote, - "->": _sf_thread_first, - "set!": _sf_set_bang, - "dynamic-wind": _sf_dynamic_wind, - "defmacro": _sf_defmacro, - "quasiquote": _sf_quasiquote, - "defhandler": _sf_defhandler, - "defpage": _sf_defpage, - "defquery": _sf_defquery, - "defaction": _sf_defaction, - "reset": _sf_reset, - "shift": _sf_shift, -} - - -# --------------------------------------------------------------------------- -# Higher-order forms (need to evaluate the fn arg first) -# --------------------------------------------------------------------------- - -def _ho_map(expr: list, env: dict) -> list: - if len(expr) != 3: - raise EvalError("map requires fn and collection") - fn = _trampoline(_eval(expr[1], env)) - coll = _trampoline(_eval(expr[2], env)) - if isinstance(fn, Lambda): - return [_trampoline(_call_lambda(fn, [item], env)) for item in coll] - if callable(fn): - return [fn(item) for item in coll] - raise EvalError(f"map requires lambda, got {type(fn).__name__}") - - -def _ho_map_indexed(expr: list, env: dict) -> list: - if len(expr) != 3: - raise EvalError("map-indexed requires fn and collection") - fn = _trampoline(_eval(expr[1], env)) - coll = _trampoline(_eval(expr[2], env)) - if not isinstance(fn, Lambda): - raise EvalError(f"map-indexed requires lambda, got {type(fn).__name__}") - if len(fn.params) < 2: - raise EvalError("map-indexed lambda needs (i item) params") - return [_trampoline(_call_lambda(fn, [i, item], env)) for i, item in enumerate(coll)] - - -def _ho_filter(expr: list, env: dict) -> list: - if len(expr) != 3: - raise EvalError("filter requires fn and collection") - fn = _trampoline(_eval(expr[1], env)) - coll = _trampoline(_eval(expr[2], env)) - if not isinstance(fn, Lambda): - raise EvalError(f"filter requires lambda, got {type(fn).__name__}") - return [item for item in coll if _trampoline(_call_lambda(fn, [item], env))] - - -def _ho_reduce(expr: list, env: dict) -> Any: - if len(expr) != 4: - raise EvalError("reduce requires fn, init, and collection") - fn = _trampoline(_eval(expr[1], env)) - acc = _trampoline(_eval(expr[2], env)) - coll = _trampoline(_eval(expr[3], env)) - if not isinstance(fn, Lambda): - raise EvalError(f"reduce requires lambda, got {type(fn).__name__}") - for item in coll: - acc = _trampoline(_call_lambda(fn, [acc, item], env)) - return acc - - -def _ho_some(expr: list, env: dict) -> Any: - if len(expr) != 3: - raise EvalError("some requires fn and collection") - fn = _trampoline(_eval(expr[1], env)) - coll = _trampoline(_eval(expr[2], env)) - if not isinstance(fn, Lambda): - raise EvalError(f"some requires lambda, got {type(fn).__name__}") - for item in coll: - result = _trampoline(_call_lambda(fn, [item], env)) - if result: - return result - return NIL - - -def _ho_every(expr: list, env: dict) -> bool: - if len(expr) != 3: - raise EvalError("every? requires fn and collection") - fn = _trampoline(_eval(expr[1], env)) - coll = _trampoline(_eval(expr[2], env)) - if not isinstance(fn, Lambda): - raise EvalError(f"every? requires lambda, got {type(fn).__name__}") - for item in coll: - if not _trampoline(_call_lambda(fn, [item], env)): - return False - return True - - -def _ho_for_each(expr: list, env: dict) -> Any: - if len(expr) != 3: - raise EvalError("for-each requires fn and collection") - fn = _trampoline(_eval(expr[1], env)) - coll = _trampoline(_eval(expr[2], env)) - if not isinstance(fn, Lambda): - raise EvalError(f"for-each requires lambda, got {type(fn).__name__}") - for item in coll: - _trampoline(_call_lambda(fn, [item], env)) - return NIL - - -_HO_FORMS: dict[str, Any] = { - "map": _ho_map, - "map-indexed": _ho_map_indexed, - "filter": _ho_filter, - "reduce": _ho_reduce, - "some": _ho_some, - "every?": _ho_every, - "for-each": _ho_for_each, -} +def _sf_defaction(expr, env): + return _ref().sf_defaction(expr[1:], env) diff --git a/shared/sx/ref/bootstrap_py.py b/shared/sx/ref/bootstrap_py.py index 31299b1..20c2383 100644 --- a/shared/sx/ref/bootstrap_py.py +++ b/shared/sx/ref/bootstrap_py.py @@ -635,11 +635,9 @@ class PyEmitter: pad = " " * indent name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1]) val_expr = expr[2] - # If value is a lambda/fn, check if body uses set! on let-bound vars - # and emit as def for proper mutation support + # Always emit fn-bodied defines as def statements for flat control flow if (isinstance(val_expr, list) and val_expr and - isinstance(val_expr[0], Symbol) and val_expr[0].name in ("fn", "lambda") - and self._body_uses_set(val_expr)): + isinstance(val_expr[0], Symbol) and val_expr[0].name in ("fn", "lambda")): return self._emit_define_as_def(name, val_expr, indent) val = self.emit(val_expr) return f"{pad}{self._mangle(name)} = {val}" @@ -667,11 +665,23 @@ class PyEmitter: params = fn_expr[1] body = fn_expr[2:] param_names = [] - for p in params: + i = 0 + while i < len(params): + p = params[i] + if isinstance(p, Symbol) and p.name == "&rest": + if i + 1 < len(params): + rest_name = self._mangle(params[i + 1].name if isinstance(params[i + 1], Symbol) else str(params[i + 1])) + param_names.append(f"*{rest_name}") + i += 2 + continue + else: + i += 1 + continue if isinstance(p, Symbol): param_names.append(self._mangle(p.name)) else: param_names.append(str(p)) + i += 1 params_str = ", ".join(param_names) py_name = self._mangle(name) # Find set! target variables that are used from nested lambda scopes @@ -718,7 +728,8 @@ class PyEmitter: """Emit body expressions as statements into lines list. Handles let as local variable declarations, and returns the last - expression. + expression. Control flow in tail position (if, cond, case, when) + is flattened to if/elif statements with returns in each branch. """ pad = " " * indent for i, expr in enumerate(body): @@ -737,10 +748,126 @@ class PyEmitter: lines.append(self.emit_statement(sub, indent)) continue if is_last: - lines.append(f"{pad}return {self.emit(expr)}") + self._emit_return_expr(expr, lines, indent) else: lines.append(self.emit_statement(expr, indent)) + def _emit_return_expr(self, expr, lines: list, indent: int) -> None: + """Emit an expression in return position, flattening control flow.""" + pad = " " * indent + if isinstance(expr, list) and expr and isinstance(expr[0], Symbol): + name = expr[0].name + if name == "if": + self._emit_if_return(expr, lines, indent) + return + if name == "cond": + self._emit_cond_return(expr, lines, indent) + return + if name == "case": + self._emit_case_return(expr, lines, indent) + return + if name == "when": + self._emit_when_return(expr, lines, indent) + return + if name in ("let", "let*"): + self._emit_let_as_stmts(expr, lines, indent, True) + return + if name in ("do", "begin"): + self._emit_body_stmts(expr[1:], lines, indent) + return + lines.append(f"{pad}return {self.emit(expr)}") + + def _emit_if_return(self, expr, lines: list, indent: int) -> None: + """Emit if as statement with returns in each branch.""" + pad = " " * indent + lines.append(f"{pad}if sx_truthy({self.emit(expr[1])}):") + self._emit_return_expr(expr[2], lines, indent + 1) + if len(expr) > 3: + lines.append(f"{pad}else:") + self._emit_return_expr(expr[3], lines, indent + 1) + else: + lines.append(f"{pad}return NIL") + + def _emit_when_return(self, expr, lines: list, indent: int) -> None: + """Emit when as statement with return in body, else return NIL.""" + pad = " " * indent + lines.append(f"{pad}if sx_truthy({self.emit(expr[1])}):") + body_parts = expr[2:] + if len(body_parts) == 1: + self._emit_return_expr(body_parts[0], lines, indent + 1) + else: + for b in body_parts[:-1]: + lines.append(self.emit_statement(b, indent + 1)) + self._emit_return_expr(body_parts[-1], lines, indent + 1) + lines.append(f"{pad}return NIL") + + def _emit_cond_return(self, expr, lines: list, indent: int) -> None: + """Emit cond as if/elif/else with returns in each branch.""" + pad = " " * indent + clauses = expr[1:] + if not clauses: + lines.append(f"{pad}return NIL") + return + is_scheme = ( + all(isinstance(c, list) and len(c) == 2 for c in clauses) + and not any(isinstance(c, Keyword) for c in clauses) + ) + has_else = False + first_clause = True + if is_scheme: + for clause in clauses: + test, body = clause[0], clause[1] + if ((isinstance(test, Symbol) and test.name in ("else", ":else")) or + (isinstance(test, Keyword) and test.name == "else")): + lines.append(f"{pad}else:") + has_else = True + else: + kw = "if" if first_clause else "elif" + lines.append(f"{pad}{kw} sx_truthy({self.emit(test)}):") + first_clause = False + self._emit_return_expr(body, lines, indent + 1) + else: + i = 0 + while i < len(clauses) - 1: + test, body = clauses[i], clauses[i + 1] + if ((isinstance(test, Keyword) and test.name == "else") or + (isinstance(test, Symbol) and test.name in ("else", ":else"))): + lines.append(f"{pad}else:") + has_else = True + else: + kw = "if" if first_clause else "elif" + lines.append(f"{pad}{kw} sx_truthy({self.emit(test)}):") + first_clause = False + self._emit_return_expr(body, lines, indent + 1) + i += 2 + if not has_else: + lines.append(f"{pad}return NIL") + + def _emit_case_return(self, expr, lines: list, indent: int) -> None: + """Emit case as if/elif/else with returns in each branch.""" + pad = " " * indent + match_val = self.emit(expr[1]) + clauses = expr[2:] + lines.append(f"{pad}_match = {match_val}") + has_else = False + first_clause = True + i = 0 + while i < len(clauses) - 1: + test = clauses[i] + body = clauses[i + 1] + if ((isinstance(test, Keyword) and test.name == "else") or + (isinstance(test, Symbol) and test.name in ("else", ":else"))): + lines.append(f"{pad}else:") + has_else = True + else: + kw = "if" if first_clause else "elif" + lines.append(f"{pad}{kw} _match == {self.emit(test)}:") + first_clause = False + self._emit_return_expr(body, lines, indent + 1) + i += 2 + if not has_else: + lines.append(f"{pad}return NIL") + def _emit_let_as_stmts(self, expr, lines: list, indent: int, is_last: bool) -> None: """Emit a let expression as local variable declarations.""" pad = " " * indent diff --git a/shared/sx/ref/platform_py.py b/shared/sx/ref/platform_py.py index fcbb7ba..0d08b78 100644 --- a/shared/sx/ref/platform_py.py +++ b/shared/sx/ref/platform_py.py @@ -578,8 +578,11 @@ def sx_expr_source(x): return x.source if isinstance(x, SxExpr) else str(x) -class EvalError(Exception): - pass +try: + from shared.sx.evaluator import EvalError +except ImportError: + class EvalError(Exception): + pass def _sx_append(lst, item): diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py index f5e59e7..072a625 100644 --- a/shared/sx/ref/sx_ref.py +++ b/shared/sx/ref/sx_ref.py @@ -1,5 +1,3 @@ -# WARNING: special-forms.sx declares forms not in eval.sx: reset, shift -# WARNING: eval.sx dispatches forms not in special-forms.sx: form? """ sx_ref.py -- Generated from reference SX evaluator specification. @@ -539,8 +537,11 @@ def sx_expr_source(x): return x.source if isinstance(x, SxExpr) else str(x) -class EvalError(Exception): - pass +try: + from shared.sx.evaluator import EvalError +except ImportError: + class EvalError(Exception): + pass def _sx_append(lst, item): @@ -947,70 +948,355 @@ def component_set_io_refs(c, refs): # === Transpiled from eval === # trampoline -trampoline = lambda val: (lambda result: (trampoline(eval_expr(thunk_expr(result), thunk_env(result))) if sx_truthy(is_thunk(result)) else result))(val) +def trampoline(val): + result = val + if sx_truthy(is_thunk(result)): + return trampoline(eval_expr(thunk_expr(result), thunk_env(result))) + else: + return result # eval-expr -eval_expr = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('dict', lambda: map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else eval_list(expr, env))), (None, lambda: expr)]) +def eval_expr(expr, env): + _match = type_of(expr) + if _match == 'number': + return expr + elif _match == 'string': + return expr + elif _match == 'boolean': + return expr + elif _match == 'nil': + return NIL + elif _match == 'symbol': + name = symbol_name(expr) + if sx_truthy(env_has(env, name)): + return env_get(env, name) + elif sx_truthy(is_primitive(name)): + return get_primitive(name) + elif sx_truthy((name == 'true')): + return True + elif sx_truthy((name == 'false')): + return False + elif sx_truthy((name == 'nil')): + return NIL + else: + return error(sx_str('Undefined symbol: ', name)) + elif _match == 'keyword': + return keyword_name(expr) + elif _match == 'dict': + return map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr) + elif _match == 'list': + if sx_truthy(empty_p(expr)): + return [] + else: + return eval_list(expr, env) + else: + return expr # eval-list -eval_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: trampoline(eval_expr(x, env)), expr) if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))) else ((lambda name: (sf_if(args, env) if sx_truthy((name == 'if')) else (sf_when(args, env) if sx_truthy((name == 'when')) else (sf_cond(args, env) if sx_truthy((name == 'cond')) else (sf_case(args, env) if sx_truthy((name == 'case')) else (sf_and(args, env) if sx_truthy((name == 'and')) else (sf_or(args, env) if sx_truthy((name == 'or')) else (sf_let(args, env) if sx_truthy((name == 'let')) else (sf_let(args, env) if sx_truthy((name == 'let*')) else (sf_letrec(args, env) if sx_truthy((name == 'letrec')) else (sf_lambda(args, env) if sx_truthy((name == 'lambda')) else (sf_lambda(args, env) if sx_truthy((name == 'fn')) else (sf_define(args, env) if sx_truthy((name == 'define')) else (sf_defcomp(args, env) if sx_truthy((name == 'defcomp')) else (sf_defisland(args, env) if sx_truthy((name == 'defisland')) else (sf_defmacro(args, env) if sx_truthy((name == 'defmacro')) else (sf_defstyle(args, env) if sx_truthy((name == 'defstyle')) else (sf_defhandler(args, env) if sx_truthy((name == 'defhandler')) else (sf_defpage(args, env) if sx_truthy((name == 'defpage')) else (sf_defquery(args, env) if sx_truthy((name == 'defquery')) else (sf_defaction(args, env) if sx_truthy((name == 'defaction')) else (sf_begin(args, env) if sx_truthy((name == 'begin')) else (sf_begin(args, env) if sx_truthy((name == 'do')) else (sf_quote(args, env) if sx_truthy((name == 'quote')) else (sf_quasiquote(args, env) if sx_truthy((name == 'quasiquote')) else (sf_thread_first(args, env) if sx_truthy((name == '->')) else (sf_set_bang(args, env) if sx_truthy((name == 'set!')) else (sf_reset(args, env) if sx_truthy((name == 'reset')) else (sf_shift(args, env) if sx_truthy((name == 'shift')) else (sf_dynamic_wind(args, env) if sx_truthy((name == 'dynamic-wind')) else (ho_map(args, env) if sx_truthy((name == 'map')) else (ho_map_indexed(args, env) if sx_truthy((name == 'map-indexed')) else (ho_filter(args, env) if sx_truthy((name == 'filter')) else (ho_reduce(args, env) if sx_truthy((name == 'reduce')) else (ho_some(args, env) if sx_truthy((name == 'some')) else (ho_every(args, env) if sx_truthy((name == 'every?')) else (ho_for_each(args, env) if sx_truthy((name == 'for-each')) else ((lambda mac: make_thunk(expand_macro(mac, args, env), env))(env_get(env, name)) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (render_expr(expr, env) if sx_truthy((render_active_p() if not sx_truthy(render_active_p()) else is_render_expr(expr))) else eval_call(head, args, env))))))))))))))))))))))))))))))))))))))))(symbol_name(head)) if sx_truthy((type_of(head) == 'symbol')) else eval_call(head, args, env))))(rest(expr)))(first(expr)) +def eval_list(expr, env): + head = first(expr) + args = rest(expr) + if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))): + return map(lambda x: trampoline(eval_expr(x, env)), expr) + else: + if sx_truthy((type_of(head) == 'symbol')): + name = symbol_name(head) + if sx_truthy((name == 'if')): + return sf_if(args, env) + elif sx_truthy((name == 'when')): + return sf_when(args, env) + elif sx_truthy((name == 'cond')): + return sf_cond(args, env) + elif sx_truthy((name == 'case')): + return sf_case(args, env) + elif sx_truthy((name == 'and')): + return sf_and(args, env) + elif sx_truthy((name == 'or')): + return sf_or(args, env) + elif sx_truthy((name == 'let')): + return sf_let(args, env) + elif sx_truthy((name == 'let*')): + return sf_let(args, env) + elif sx_truthy((name == 'letrec')): + return sf_letrec(args, env) + elif sx_truthy((name == 'lambda')): + return sf_lambda(args, env) + elif sx_truthy((name == 'fn')): + return sf_lambda(args, env) + elif sx_truthy((name == 'define')): + return sf_define(args, env) + elif sx_truthy((name == 'defcomp')): + return sf_defcomp(args, env) + elif sx_truthy((name == 'defisland')): + return sf_defisland(args, env) + elif sx_truthy((name == 'defmacro')): + return sf_defmacro(args, env) + elif sx_truthy((name == 'defstyle')): + return sf_defstyle(args, env) + elif sx_truthy((name == 'defhandler')): + return sf_defhandler(args, env) + elif sx_truthy((name == 'defpage')): + return sf_defpage(args, env) + elif sx_truthy((name == 'defquery')): + return sf_defquery(args, env) + elif sx_truthy((name == 'defaction')): + return sf_defaction(args, env) + elif sx_truthy((name == 'begin')): + return sf_begin(args, env) + elif sx_truthy((name == 'do')): + return sf_begin(args, env) + elif sx_truthy((name == 'quote')): + return sf_quote(args, env) + elif sx_truthy((name == 'quasiquote')): + return sf_quasiquote(args, env) + elif sx_truthy((name == '->')): + return sf_thread_first(args, env) + elif sx_truthy((name == 'set!')): + return sf_set_bang(args, env) + elif sx_truthy((name == 'reset')): + return sf_reset(args, env) + elif sx_truthy((name == 'shift')): + return sf_shift(args, env) + elif sx_truthy((name == 'dynamic-wind')): + return sf_dynamic_wind(args, env) + elif sx_truthy((name == 'map')): + return ho_map(args, env) + elif sx_truthy((name == 'map-indexed')): + return ho_map_indexed(args, env) + elif sx_truthy((name == 'filter')): + return ho_filter(args, env) + elif sx_truthy((name == 'reduce')): + return ho_reduce(args, env) + elif sx_truthy((name == 'some')): + return ho_some(args, env) + elif sx_truthy((name == 'every?')): + return ho_every(args, env) + elif sx_truthy((name == 'for-each')): + return ho_for_each(args, env) + elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))): + mac = env_get(env, name) + return make_thunk(expand_macro(mac, args, env), env) + elif sx_truthy((render_active_p() if not sx_truthy(render_active_p()) else is_render_expr(expr))): + return render_expr(expr, env) + else: + return eval_call(head, args, env) + else: + return eval_call(head, args, env) # eval-call -eval_call = lambda head, args, env: (lambda f: (lambda evaluated_args: (apply(f, evaluated_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))) else (call_lambda(f, evaluated_args, env) if sx_truthy(is_lambda(f)) else (call_component(f, args, env) if sx_truthy(is_component(f)) else (call_component(f, args, env) if sx_truthy(is_island(f)) else error(sx_str('Not callable: ', inspect(f))))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env))) +def eval_call(head, args, env): + f = trampoline(eval_expr(head, env)) + evaluated_args = map(lambda a: trampoline(eval_expr(a, env)), args) + if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))): + return apply(f, evaluated_args) + elif sx_truthy(is_lambda(f)): + return call_lambda(f, evaluated_args, env) + elif sx_truthy(is_component(f)): + return call_component(f, args, env) + elif sx_truthy(is_island(f)): + return call_component(f, args, env) + else: + return error(sx_str('Not callable: ', inspect(f))) # call-lambda -call_lambda = lambda f, args, caller_env: (lambda params: (lambda local: (error(sx_str((lambda_name(f) if sx_truthy(lambda_name(f)) else 'lambda'), ' expects ', len(params), ' args, got ', len(args))) if sx_truthy((len(args) > len(params))) else _sx_begin(for_each(lambda pair: _sx_dict_set(local, first(pair), nth(pair, 1)), zip(params, args)), for_each(lambda p: _sx_dict_set(local, p, NIL), slice(params, len(args))), make_thunk(lambda_body(f), local))))(env_merge(lambda_closure(f), caller_env)))(lambda_params(f)) +def call_lambda(f, args, caller_env): + params = lambda_params(f) + local = env_merge(lambda_closure(f), caller_env) + if sx_truthy((len(args) > len(params))): + return error(sx_str((lambda_name(f) if sx_truthy(lambda_name(f)) else 'lambda'), ' expects ', len(params), ' args, got ', len(args))) + else: + for pair in zip(params, args): + local[first(pair)] = nth(pair, 1) + for p in slice(params, len(args)): + local[p] = NIL + return make_thunk(lambda_body(f), local) # call-component -call_component = lambda comp, raw_args, env: (lambda parsed: (lambda kwargs: (lambda children: (lambda local: _sx_begin(for_each(lambda p: _sx_dict_set(local, p, (dict_get(kwargs, p) if sx_truthy(dict_get(kwargs, p)) else NIL)), component_params(comp)), (_sx_dict_set(local, 'children', children) if sx_truthy(component_has_children(comp)) else NIL), make_thunk(component_body(comp), local)))(env_merge(component_closure(comp), env)))(nth(parsed, 1)))(first(parsed)))(parse_keyword_args(raw_args, env)) +def call_component(comp, raw_args, env): + parsed = parse_keyword_args(raw_args, env) + kwargs = first(parsed) + children = nth(parsed, 1) + local = env_merge(component_closure(comp), env) + for p in component_params(comp): + local[p] = (dict_get(kwargs, p) if sx_truthy(dict_get(kwargs, p)) else NIL) + if sx_truthy(component_has_children(comp)): + local['children'] = children + return make_thunk(component_body(comp), local) # parse-keyword-args -parse_keyword_args = lambda raw_args, env: (lambda kwargs: (lambda children: (lambda i: _sx_begin(reduce(lambda state, arg: (lambda idx: (lambda skip: (assoc(state, 'skip', False, 'i', (idx + 1)) if sx_truthy(skip) else (_sx_begin(_sx_dict_set(kwargs, keyword_name(arg), trampoline(eval_expr(nth(raw_args, (idx + 1)), env))), assoc(state, 'skip', True, 'i', (idx + 1))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((idx + 1) < len(raw_args)))) else _sx_begin(_sx_append(children, trampoline(eval_expr(arg, env))), assoc(state, 'i', (idx + 1))))))(get(state, 'skip')))(get(state, 'i')), {'i': 0, 'skip': False}, raw_args), [kwargs, children]))(0))([]))({}) +def parse_keyword_args(raw_args, env): + kwargs = {} + children = [] + i = 0 + reduce(lambda state, arg: (lambda idx: (lambda skip: (assoc(state, 'skip', False, 'i', (idx + 1)) if sx_truthy(skip) else (_sx_begin(_sx_dict_set(kwargs, keyword_name(arg), trampoline(eval_expr(nth(raw_args, (idx + 1)), env))), assoc(state, 'skip', True, 'i', (idx + 1))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((idx + 1) < len(raw_args)))) else _sx_begin(_sx_append(children, trampoline(eval_expr(arg, env))), assoc(state, 'i', (idx + 1))))))(get(state, 'skip')))(get(state, 'i')), {'i': 0, 'skip': False}, raw_args) + return [kwargs, children] # sf-if -sf_if = lambda args, env: (lambda condition: (make_thunk(nth(args, 1), env) if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))) else (make_thunk(nth(args, 2), env) if sx_truthy((len(args) > 2)) else NIL)))(trampoline(eval_expr(first(args), env))) +def sf_if(args, env): + condition = trampoline(eval_expr(first(args), env)) + if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))): + return make_thunk(nth(args, 1), env) + else: + if sx_truthy((len(args) > 2)): + return make_thunk(nth(args, 2), env) + else: + return NIL # sf-when -sf_when = lambda args, env: (lambda condition: (_sx_begin(for_each(lambda e: trampoline(eval_expr(e, env)), slice(args, 1, (len(args) - 1))), make_thunk(last(args), env)) if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))) else NIL))(trampoline(eval_expr(first(args), env))) +def sf_when(args, env): + condition = trampoline(eval_expr(first(args), env)) + if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))): + for e in slice(args, 1, (len(args) - 1)): + trampoline(eval_expr(e, env)) + return make_thunk(last(args), env) + else: + return NIL # sf-cond -sf_cond = lambda args, env: (sf_cond_scheme(args, env) if sx_truthy(((type_of(first(args)) == 'list') if not sx_truthy((type_of(first(args)) == 'list')) else (len(first(args)) == 2))) else sf_cond_clojure(args, env)) +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))): + return sf_cond_scheme(args, env) + else: + return sf_cond_clojure(args, env) # sf-cond-scheme -sf_cond_scheme = lambda clauses, env: (NIL if sx_truthy(empty_p(clauses)) else (lambda clause: (lambda test: (lambda body: (make_thunk(body, env) 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')))) else (make_thunk(body, env) if sx_truthy(trampoline(eval_expr(test, env))) else sf_cond_scheme(rest(clauses), env))))(nth(clause, 1)))(first(clause)))(first(clauses))) +def sf_cond_scheme(clauses, env): + if sx_truthy(empty_p(clauses)): + return NIL + else: + clause = first(clauses) + test = first(clause) + body = nth(clause, 1) + if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))): + return make_thunk(body, env) + else: + if sx_truthy(trampoline(eval_expr(test, env))): + return make_thunk(body, env) + else: + return sf_cond_scheme(rest(clauses), env) # sf-cond-clojure -sf_cond_clojure = lambda clauses, env: (NIL if sx_truthy((len(clauses) < 2)) else (lambda test: (lambda body: (make_thunk(body, env) 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'))))) else (make_thunk(body, env) if sx_truthy(trampoline(eval_expr(test, env))) else sf_cond_clojure(slice(clauses, 2), env))))(nth(clauses, 1)))(first(clauses))) +def sf_cond_clojure(clauses, env): + if sx_truthy((len(clauses) < 2)): + return NIL + else: + test = first(clauses) + body = nth(clauses, 1) + if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))): + return make_thunk(body, env) + else: + if sx_truthy(trampoline(eval_expr(test, env))): + return make_thunk(body, env) + else: + return sf_cond_clojure(slice(clauses, 2), env) # sf-case -sf_case = lambda args, env: (lambda match_val: (lambda clauses: sf_case_loop(match_val, clauses, env))(rest(args)))(trampoline(eval_expr(first(args), env))) +def sf_case(args, env): + match_val = trampoline(eval_expr(first(args), env)) + clauses = rest(args) + return sf_case_loop(match_val, clauses, env) # sf-case-loop -sf_case_loop = lambda match_val, clauses, env: (NIL if sx_truthy((len(clauses) < 2)) else (lambda test: (lambda body: (make_thunk(body, env) 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'))))) else (make_thunk(body, env) if sx_truthy((match_val == trampoline(eval_expr(test, env)))) else sf_case_loop(match_val, slice(clauses, 2), env))))(nth(clauses, 1)))(first(clauses))) +def sf_case_loop(match_val, clauses, env): + if sx_truthy((len(clauses) < 2)): + return NIL + else: + test = first(clauses) + body = nth(clauses, 1) + if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))): + return make_thunk(body, env) + else: + if sx_truthy((match_val == trampoline(eval_expr(test, env)))): + return make_thunk(body, env) + else: + return sf_case_loop(match_val, slice(clauses, 2), env) # sf-and -sf_and = lambda args, env: (True if sx_truthy(empty_p(args)) else (lambda val: (val if sx_truthy((not sx_truthy(val))) else (val if sx_truthy((len(args) == 1)) else sf_and(rest(args), env))))(trampoline(eval_expr(first(args), env)))) +def sf_and(args, env): + if sx_truthy(empty_p(args)): + return True + else: + val = trampoline(eval_expr(first(args), env)) + if sx_truthy((not sx_truthy(val))): + return val + else: + if sx_truthy((len(args) == 1)): + return val + else: + return sf_and(rest(args), env) # sf-or -sf_or = lambda args, env: (False if sx_truthy(empty_p(args)) else (lambda val: (val if sx_truthy(val) else sf_or(rest(args), env)))(trampoline(eval_expr(first(args), env)))) +def sf_or(args, env): + if sx_truthy(empty_p(args)): + return False + else: + val = trampoline(eval_expr(first(args), env)) + if sx_truthy(val): + return val + else: + return sf_or(rest(args), env) # sf-let -sf_let = lambda args, env: (sf_named_let(args, env) if sx_truthy((type_of(first(args)) == 'symbol')) else (lambda bindings: (lambda body: (lambda local: _sx_begin((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)), for_each(lambda e: trampoline(eval_expr(e, local)), slice(body, 0, (len(body) - 1))), make_thunk(last(body), local)))(env_extend(env)))(rest(args)))(first(args))) +def sf_let(args, env): + if sx_truthy((type_of(first(args)) == 'symbol')): + return sf_named_let(args, env) + else: + bindings = first(args) + body = rest(args) + local = env_extend(env) + (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)) + for e in slice(body, 0, (len(body) - 1)): + trampoline(eval_expr(e, local)) + return make_thunk(last(body), local) # sf-named-let -sf_named_let = lambda args, env: (lambda loop_name: (lambda bindings: (lambda body: (lambda params: (lambda inits: _sx_begin((for_each(_sx_fn(lambda binding: ( +def sf_named_let(args, env): + loop_name = symbol_name(first(args)) + bindings = nth(args, 1) + 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)))), (lambda loop_body: (lambda loop_fn: _sx_begin(_sx_set_attr(loop_fn, 'name', loop_name), _sx_dict_set(lambda_closure(loop_fn), loop_name, loop_fn), (lambda init_vals: call_lambda(loop_fn, init_vals, env))(map(lambda e: trampoline(eval_expr(e, env)), inits))))(make_lambda(params, loop_body, env)))((first(body) if sx_truthy((len(body) == 1)) else cons(make_symbol('begin'), body)))))([]))([]))(slice(args, 2)))(nth(args, 1)))(symbol_name(first(args))) +)[-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)))) + loop_body = (first(body) if sx_truthy((len(body) == 1)) else cons(make_symbol('begin'), body)) + loop_fn = make_lambda(params, loop_body, env) + loop_fn.name = loop_name + lambda_closure(loop_fn)[loop_name] = loop_fn + init_vals = map(lambda e: trampoline(eval_expr(e, env)), inits) + return call_lambda(loop_fn, init_vals, env) # sf-lambda -sf_lambda = lambda args, env: (lambda params_expr: (lambda body_exprs: (lambda body: (lambda param_names: make_lambda(param_names, body, env))(map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else p), params_expr)))((first(body_exprs) if sx_truthy((len(body_exprs) == 1)) else cons(make_symbol('begin'), body_exprs))))(rest(args)))(first(args)) +def sf_lambda(args, env): + params_expr = first(args) + body_exprs = rest(args) + body = (first(body_exprs) if sx_truthy((len(body_exprs) == 1)) else cons(make_symbol('begin'), body_exprs)) + param_names = map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else p), params_expr) + return make_lambda(param_names, body, env) # sf-define -sf_define = lambda args, env: (lambda name_sym: (lambda value: _sx_begin((_sx_set_attr(value, 'name', symbol_name(name_sym)) if sx_truthy((is_lambda(value) if not sx_truthy(is_lambda(value)) else is_nil(lambda_name(value)))) else NIL), _sx_dict_set(env, symbol_name(name_sym), value), value))(trampoline(eval_expr(nth(args, 1), env))))(first(args)) +def sf_define(args, env): + name_sym = first(args) + value = trampoline(eval_expr(nth(args, 1), env)) + if sx_truthy((is_lambda(value) if not sx_truthy(is_lambda(value)) else is_nil(lambda_name(value)))): + value.name = symbol_name(name_sym) + env[symbol_name(name_sym)] = value + return value # sf-defcomp -sf_defcomp = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda body: (lambda comp_name: (lambda parsed: (lambda params: (lambda has_children: (lambda affinity: (lambda comp: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), comp), comp))(make_component(comp_name, params, has_children, body, env, affinity)))(defcomp_kwarg(args, 'affinity', 'auto')))(nth(parsed, 1)))(first(parsed)))(parse_comp_params(params_raw)))(strip_prefix(symbol_name(name_sym), '~')))(last(args)))(nth(args, 1)))(first(args)) +def sf_defcomp(args, env): + name_sym = first(args) + params_raw = nth(args, 1) + body = last(args) + comp_name = strip_prefix(symbol_name(name_sym), '~') + parsed = parse_comp_params(params_raw) + params = first(parsed) + has_children = nth(parsed, 1) + affinity = defcomp_kwarg(args, 'affinity', 'auto') + comp = make_component(comp_name, params, has_children, body, env, affinity) + env[symbol_name(name_sym)] = comp + return comp # defcomp-kwarg def defcomp_kwarg(args, key, default_): @@ -1047,10 +1333,29 @@ def parse_comp_params(params_expr): return [params, _cells['has_children']] # sf-defisland -sf_defisland = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda body: (lambda comp_name: (lambda parsed: (lambda params: (lambda has_children: (lambda island: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), island), island))(make_island(comp_name, params, has_children, body, env)))(nth(parsed, 1)))(first(parsed)))(parse_comp_params(params_raw)))(strip_prefix(symbol_name(name_sym), '~')))(last(args)))(nth(args, 1)))(first(args)) +def sf_defisland(args, env): + name_sym = first(args) + params_raw = nth(args, 1) + body = last(args) + comp_name = strip_prefix(symbol_name(name_sym), '~') + parsed = parse_comp_params(params_raw) + params = first(parsed) + has_children = nth(parsed, 1) + island = make_island(comp_name, params, has_children, body, env) + env[symbol_name(name_sym)] = island + return island # sf-defmacro -sf_defmacro = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda body: (lambda parsed: (lambda params: (lambda rest_param: (lambda mac: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), mac), mac))(make_macro(params, rest_param, body, env, symbol_name(name_sym))))(nth(parsed, 1)))(first(parsed)))(parse_macro_params(params_raw)))(nth(args, 2)))(nth(args, 1)))(first(args)) +def sf_defmacro(args, env): + name_sym = first(args) + params_raw = nth(args, 1) + body = nth(args, 2) + parsed = parse_macro_params(params_raw) + params = first(parsed) + rest_param = nth(parsed, 1) + mac = make_macro(params, rest_param, body, env, symbol_name(name_sym)) + env[symbol_name(name_sym)] = mac + return mac # parse-macro-params def parse_macro_params(params_expr): @@ -1061,58 +1366,149 @@ def parse_macro_params(params_expr): return [params, _cells['rest_param']] # sf-defstyle -sf_defstyle = lambda args, env: (lambda name_sym: (lambda value: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), value), value))(trampoline(eval_expr(nth(args, 1), env))))(first(args)) +def sf_defstyle(args, env): + name_sym = first(args) + value = trampoline(eval_expr(nth(args, 1), env)) + env[symbol_name(name_sym)] = value + return value # sf-begin -sf_begin = lambda args, env: (NIL if sx_truthy(empty_p(args)) else _sx_begin(for_each(lambda e: trampoline(eval_expr(e, env)), slice(args, 0, (len(args) - 1))), make_thunk(last(args), env))) +def sf_begin(args, env): + if sx_truthy(empty_p(args)): + return NIL + else: + for e in slice(args, 0, (len(args) - 1)): + trampoline(eval_expr(e, env)) + return make_thunk(last(args), env) # sf-quote -sf_quote = lambda args, env: (NIL if sx_truthy(empty_p(args)) else first(args)) +def sf_quote(args, env): + if sx_truthy(empty_p(args)): + return NIL + else: + return first(args) # sf-quasiquote -sf_quasiquote = lambda args, env: qq_expand(first(args), env) +def sf_quasiquote(args, env): + return qq_expand(first(args), env) # qq-expand -qq_expand = lambda template, env: (template if sx_truthy((not sx_truthy((type_of(template) == 'list')))) else ([] if sx_truthy(empty_p(template)) else (lambda head: (trampoline(eval_expr(nth(template, 1), env)) if sx_truthy(((type_of(head) == 'symbol') if not sx_truthy((type_of(head) == 'symbol')) else (symbol_name(head) == 'unquote'))) else reduce(lambda result, item: ((lambda spliced: (concat(result, spliced) if sx_truthy((type_of(spliced) == 'list')) else (result if sx_truthy(is_nil(spliced)) else append(result, spliced))))(trampoline(eval_expr(nth(item, 1), env))) if sx_truthy(((type_of(item) == 'list') if not sx_truthy((type_of(item) == 'list')) else ((len(item) == 2) if not sx_truthy((len(item) == 2)) else ((type_of(first(item)) == 'symbol') if not sx_truthy((type_of(first(item)) == 'symbol')) else (symbol_name(first(item)) == 'splice-unquote'))))) else append(result, qq_expand(item, env))), [], template)))(first(template)))) +def qq_expand(template, env): + if sx_truthy((not sx_truthy((type_of(template) == 'list')))): + return template + else: + if sx_truthy(empty_p(template)): + return [] + else: + head = first(template) + if sx_truthy(((type_of(head) == 'symbol') if not sx_truthy((type_of(head) == 'symbol')) else (symbol_name(head) == 'unquote'))): + return trampoline(eval_expr(nth(template, 1), env)) + else: + return reduce(lambda result, item: ((lambda spliced: (concat(result, spliced) if sx_truthy((type_of(spliced) == 'list')) else (result if sx_truthy(is_nil(spliced)) else concat(result, [spliced]))))(trampoline(eval_expr(nth(item, 1), env))) if sx_truthy(((type_of(item) == 'list') if not sx_truthy((type_of(item) == 'list')) else ((len(item) == 2) if not sx_truthy((len(item) == 2)) else ((type_of(first(item)) == 'symbol') if not sx_truthy((type_of(first(item)) == 'symbol')) else (symbol_name(first(item)) == 'splice-unquote'))))) else concat(result, [qq_expand(item, env)])), [], template) # sf-thread-first -sf_thread_first = lambda args, env: (lambda val: reduce(lambda result, form: ((lambda f: (lambda rest_args: (lambda all_args: (apply(f, all_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, all_args, env)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(cons(result, rest_args)))(map(lambda a: trampoline(eval_expr(a, env)), rest(form))))(trampoline(eval_expr(first(form), env))) if sx_truthy((type_of(form) == 'list')) else (lambda f: (f(result) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, [result], env)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(trampoline(eval_expr(form, env)))), val, rest(args)))(trampoline(eval_expr(first(args), env))) +def sf_thread_first(args, env): + val = trampoline(eval_expr(first(args), env)) + return reduce(lambda result, form: ((lambda f: (lambda rest_args: (lambda all_args: (apply(f, all_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, all_args, env)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(cons(result, rest_args)))(map(lambda a: trampoline(eval_expr(a, env)), rest(form))))(trampoline(eval_expr(first(form), env))) if sx_truthy((type_of(form) == 'list')) else (lambda f: (f(result) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, [result], env)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(trampoline(eval_expr(form, env)))), val, rest(args)) # sf-set! -sf_set_bang = lambda args, env: (lambda name: (lambda value: _sx_begin(_sx_dict_set(env, name, value), value))(trampoline(eval_expr(nth(args, 1), env))))(symbol_name(first(args))) +def sf_set_bang(args, env): + name = symbol_name(first(args)) + value = trampoline(eval_expr(nth(args, 1), env)) + env[name] = value + return value # sf-letrec -sf_letrec = lambda args, env: (lambda bindings: (lambda body: (lambda local: (lambda names: (lambda val_exprs: _sx_begin((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)))), (lambda values: _sx_begin(for_each(lambda pair: _sx_dict_set(local, first(pair), nth(pair, 1)), zip(names, values)), for_each(lambda val: (for_each(lambda n: _sx_dict_set(lambda_closure(val), n, env_get(local, n)), names) if sx_truthy(is_lambda(val)) else NIL), values)))(map(lambda e: trampoline(eval_expr(e, local)), val_exprs)), for_each(lambda e: trampoline(eval_expr(e, local)), slice(body, 0, (len(body) - 1))), make_thunk(last(body), local)))([]))([]))(env_extend(env)))(rest(args)))(first(args)) +def sf_letrec(args, env): + bindings = first(args) + body = rest(args) + 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)))) + values = map(lambda e: trampoline(eval_expr(e, local)), val_exprs) + for pair in zip(names, values): + local[first(pair)] = nth(pair, 1) + for val in values: + if sx_truthy(is_lambda(val)): + for n in names: + lambda_closure(val)[n] = env_get(local, n) + for e in slice(body, 0, (len(body) - 1)): + trampoline(eval_expr(e, local)) + return make_thunk(last(body), local) # sf-dynamic-wind -sf_dynamic_wind = lambda args, env: (lambda before: (lambda body: (lambda after: _sx_begin(call_thunk(before, env), push_wind_b(before, after), (lambda result: _sx_begin(pop_wind_b(), call_thunk(after, env), result))(call_thunk(body, env))))(trampoline(eval_expr(nth(args, 2), env))))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) +def sf_dynamic_wind(args, env): + before = trampoline(eval_expr(first(args), env)) + body = trampoline(eval_expr(nth(args, 1), env)) + after = trampoline(eval_expr(nth(args, 2), env)) + call_thunk(before, env) + push_wind_b(before, after) + result = call_thunk(body, env) + pop_wind_b() + call_thunk(after, env) + return result # expand-macro -expand_macro = lambda mac, raw_args, env: (lambda local: _sx_begin(for_each(lambda pair: _sx_dict_set(local, first(pair), (nth(raw_args, nth(pair, 1)) if sx_truthy((nth(pair, 1) < len(raw_args))) else NIL)), map_indexed(lambda i, p: [p, i], macro_params(mac))), (_sx_dict_set(local, macro_rest_param(mac), slice(raw_args, len(macro_params(mac)))) if sx_truthy(macro_rest_param(mac)) else NIL), trampoline(eval_expr(macro_body(mac), local))))(env_merge(macro_closure(mac), env)) +def expand_macro(mac, raw_args, env): + local = env_merge(macro_closure(mac), env) + for pair in map_indexed(lambda i, p: [p, i], macro_params(mac)): + local[first(pair)] = (nth(raw_args, nth(pair, 1)) if sx_truthy((nth(pair, 1) < len(raw_args))) else NIL) + if sx_truthy(macro_rest_param(mac)): + local[macro_rest_param(mac)] = slice(raw_args, len(macro_params(mac))) + return trampoline(eval_expr(macro_body(mac), local)) # call-fn -call_fn = lambda f, args, env: (trampoline(call_lambda(f, args, env)) if sx_truthy(is_lambda(f)) else (apply(f, args) if sx_truthy(is_callable(f)) else error(sx_str('Not callable in HO form: ', inspect(f))))) +def call_fn(f, args, env): + if sx_truthy(is_lambda(f)): + return trampoline(call_lambda(f, args, env)) + elif sx_truthy(is_callable(f)): + return apply(f, args) + else: + return error(sx_str('Not callable in HO form: ', inspect(f))) # ho-map -ho_map = lambda args, env: (lambda f: (lambda coll: map(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) +def ho_map(args, env): + f = trampoline(eval_expr(first(args), env)) + coll = trampoline(eval_expr(nth(args, 1), env)) + return map(lambda item: call_fn(f, [item], env), coll) # ho-map-indexed -ho_map_indexed = lambda args, env: (lambda f: (lambda coll: map_indexed(lambda i, item: call_fn(f, [i, item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) +def ho_map_indexed(args, env): + f = trampoline(eval_expr(first(args), env)) + coll = trampoline(eval_expr(nth(args, 1), env)) + return map_indexed(lambda i, item: call_fn(f, [i, item], env), coll) # ho-filter -ho_filter = lambda args, env: (lambda f: (lambda coll: filter(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) +def ho_filter(args, env): + f = trampoline(eval_expr(first(args), env)) + coll = trampoline(eval_expr(nth(args, 1), env)) + return filter(lambda item: call_fn(f, [item], env), coll) # ho-reduce -ho_reduce = lambda args, env: (lambda f: (lambda init: (lambda coll: reduce(lambda acc, item: call_fn(f, [acc, item], env), init, coll))(trampoline(eval_expr(nth(args, 2), env))))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) +def ho_reduce(args, env): + f = trampoline(eval_expr(first(args), env)) + init = trampoline(eval_expr(nth(args, 1), env)) + coll = trampoline(eval_expr(nth(args, 2), env)) + return reduce(lambda acc, item: call_fn(f, [acc, item], env), init, coll) # ho-some -ho_some = lambda args, env: (lambda f: (lambda coll: some(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) +def ho_some(args, env): + f = trampoline(eval_expr(first(args), env)) + coll = trampoline(eval_expr(nth(args, 1), env)) + return some(lambda item: call_fn(f, [item], env), coll) # ho-every -ho_every = lambda args, env: (lambda f: (lambda coll: every_p(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) +def ho_every(args, env): + f = trampoline(eval_expr(first(args), env)) + coll = trampoline(eval_expr(nth(args, 1), env)) + return every_p(lambda item: call_fn(f, [item], env), coll) # ho-for-each -ho_for_each = lambda args, env: (lambda f: (lambda coll: for_each(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(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) # === Transpiled from forms (server definition forms) === @@ -1134,31 +1530,83 @@ def parse_key_params(params_expr): return params # sf-defhandler -sf_defhandler = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda body: (lambda name: (lambda params: (lambda hdef: _sx_begin(_sx_dict_set(env, sx_str('handler:', name), hdef), hdef))(make_handler_def(name, params, body, env)))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 2)))(nth(args, 1)))(first(args)) +def sf_defhandler(args, env): + name_sym = first(args) + params_raw = nth(args, 1) + body = nth(args, 2) + name = symbol_name(name_sym) + params = parse_key_params(params_raw) + hdef = make_handler_def(name, params, body, env) + env[sx_str('handler:', name)] = hdef + return hdef # sf-defquery -sf_defquery = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda name: (lambda params: (lambda has_doc: (lambda doc: (lambda body: (lambda qdef: _sx_begin(_sx_dict_set(env, sx_str('query:', name), qdef), qdef))(make_query_def(name, params, doc, body, env)))((nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))))((nth(args, 2) if sx_truthy(has_doc) else '')))(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 1)))(first(args)) +def sf_defquery(args, env): + name_sym = first(args) + params_raw = nth(args, 1) + name = symbol_name(name_sym) + params = parse_key_params(params_raw) + has_doc = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string')) + doc = (nth(args, 2) if sx_truthy(has_doc) else '') + body = (nth(args, 3) if sx_truthy(has_doc) else nth(args, 2)) + qdef = make_query_def(name, params, doc, body, env) + env[sx_str('query:', name)] = qdef + return qdef # sf-defaction -sf_defaction = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda name: (lambda params: (lambda has_doc: (lambda doc: (lambda body: (lambda adef: _sx_begin(_sx_dict_set(env, sx_str('action:', name), adef), adef))(make_action_def(name, params, doc, body, env)))((nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))))((nth(args, 2) if sx_truthy(has_doc) else '')))(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 1)))(first(args)) +def sf_defaction(args, env): + name_sym = first(args) + params_raw = nth(args, 1) + name = symbol_name(name_sym) + params = parse_key_params(params_raw) + has_doc = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string')) + doc = (nth(args, 2) if sx_truthy(has_doc) else '') + body = (nth(args, 3) if sx_truthy(has_doc) else nth(args, 2)) + adef = make_action_def(name, params, doc, body, env) + env[sx_str('action:', name)] = adef + return adef # sf-defpage -sf_defpage = lambda args, env: (lambda name_sym: (lambda name: (lambda slots: _sx_begin((lambda i: (lambda max_i: for_each(lambda idx: ((_sx_dict_set(slots, keyword_name(nth(args, idx)), nth(args, (idx + 1))) if sx_truthy(((idx + 1) < max_i)) else NIL) if sx_truthy(((idx < max_i) if not sx_truthy((idx < max_i)) else (type_of(nth(args, idx)) == 'keyword'))) else NIL), range(1, max_i, 2)))(len(args)))(1), (lambda pdef: _sx_begin(_sx_dict_set(env, sx_str('page:', name), pdef), pdef))(make_page_def(name, slots, env))))({}))(symbol_name(name_sym)))(first(args)) +def sf_defpage(args, env): + name_sym = first(args) + name = symbol_name(name_sym) + slots = {} + i = 1 + max_i = len(args) + for idx in range(1, max_i, 2): + if sx_truthy(((idx < max_i) if not sx_truthy((idx < max_i)) else (type_of(nth(args, idx)) == 'keyword'))): + if sx_truthy(((idx + 1) < max_i)): + slots[keyword_name(nth(args, idx))] = nth(args, (idx + 1)) + pdef = make_page_def(name, slots, env) + env[sx_str('page:', name)] = pdef + return pdef # stream-chunk-id -stream_chunk_id = lambda chunk: (get(chunk, 'stream-id') if sx_truthy(has_key_p(chunk, 'stream-id')) else 'stream-content') +def stream_chunk_id(chunk): + if sx_truthy(has_key_p(chunk, 'stream-id')): + return get(chunk, 'stream-id') + else: + return 'stream-content' # stream-chunk-bindings -stream_chunk_bindings = lambda chunk: dissoc(chunk, 'stream-id') +def stream_chunk_bindings(chunk): + return dissoc(chunk, 'stream-id') # normalize-binding-key -normalize_binding_key = lambda key: replace(key, '_', '-') +def normalize_binding_key(key): + return replace(key, '_', '-') # bind-stream-chunk -bind_stream_chunk = lambda chunk, base_env: (lambda env: (lambda bindings: _sx_begin(for_each(lambda key: _sx_dict_set(env, normalize_binding_key(key), get(bindings, key)), keys(bindings)), env))(stream_chunk_bindings(chunk)))(merge({}, base_env)) +def bind_stream_chunk(chunk, base_env): + env = merge({}, base_env) + bindings = stream_chunk_bindings(chunk) + for key in keys(bindings): + env[normalize_binding_key(key)] = get(bindings, key) + return env # validate-stream-data -validate_stream_data = lambda data: ((type_of(data) == 'list') if not sx_truthy((type_of(data) == 'list')) else every_p(lambda item: (type_of(item) == 'dict'), data)) +def validate_stream_data(data): + return ((type_of(data) == 'list') if not sx_truthy((type_of(data) == 'list')) else every_p(lambda item: (type_of(item) == 'dict'), data)) # === Transpiled from render (core) === @@ -1173,61 +1621,251 @@ VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'li 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? -is_definition_form = lambda name: ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defisland') if sx_truthy((name == 'defisland')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else (name == 'defhandler')))))) +def is_definition_form(name): + return ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defisland') if sx_truthy((name == 'defisland')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else (name == 'defhandler')))))) # parse-element-args -parse_element_args = lambda args, env: (lambda attrs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(attrs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), [attrs, children]))([]))({}) +def parse_element_args(args, env): + attrs = {} + children = [] + reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(attrs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args) + return [attrs, children] # render-attrs -render_attrs = lambda attrs: join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"')))))(dict_get(attrs, key)), keys(attrs))) +def render_attrs(attrs): + return join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"')))))(dict_get(attrs, key)), keys(attrs))) # eval-cond -eval_cond = lambda clauses, env: (eval_cond_scheme(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)))) else eval_cond_clojure(clauses, env)) +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)))): + return eval_cond_scheme(clauses, env) + else: + return eval_cond_clojure(clauses, env) # eval-cond-scheme -eval_cond_scheme = lambda clauses, env: (NIL if sx_truthy(empty_p(clauses)) else (lambda clause: (lambda test: (lambda body: (body 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')))) else (body if sx_truthy(trampoline(eval_expr(test, env))) else eval_cond_scheme(rest(clauses), env))))(nth(clause, 1)))(first(clause)))(first(clauses))) +def eval_cond_scheme(clauses, env): + if sx_truthy(empty_p(clauses)): + return NIL + else: + clause = first(clauses) + test = first(clause) + body = nth(clause, 1) + if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))): + return body + else: + if sx_truthy(trampoline(eval_expr(test, env))): + return body + else: + return eval_cond_scheme(rest(clauses), env) # eval-cond-clojure -eval_cond_clojure = lambda clauses, env: (NIL if sx_truthy((len(clauses) < 2)) else (lambda test: (lambda body: (body 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'))))) else (body if sx_truthy(trampoline(eval_expr(test, env))) else eval_cond_clojure(slice(clauses, 2), env))))(nth(clauses, 1)))(first(clauses))) +def eval_cond_clojure(clauses, env): + if sx_truthy((len(clauses) < 2)): + return NIL + else: + test = first(clauses) + body = nth(clauses, 1) + if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))): + return body + else: + if sx_truthy(trampoline(eval_expr(test, env))): + return body + else: + return eval_cond_clojure(slice(clauses, 2), env) # process-bindings -process_bindings = lambda bindings, env: (lambda local: _sx_begin(for_each(lambda pair: ((lambda name: _sx_dict_set(local, name, trampoline(eval_expr(nth(pair, 1), local))))((symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair)))) if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))) else NIL), bindings), local))(merge(env)) +def process_bindings(bindings, env): + local = merge(env) + for pair in bindings: + if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))): + name = (symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair))) + local[name] = trampoline(eval_expr(nth(pair, 1), local)) + return local # is-render-expr? -is_render_expr = lambda expr: (False if sx_truthy(((not sx_truthy((type_of(expr) == 'list'))) if sx_truthy((not sx_truthy((type_of(expr) == 'list')))) else empty_p(expr))) else (lambda h: (False if sx_truthy((not sx_truthy((type_of(h) == 'symbol')))) else (lambda n: ((n == '<>') if sx_truthy((n == '<>')) else ((n == 'raw!') if sx_truthy((n == 'raw!')) else (starts_with_p(n, '~') if sx_truthy(starts_with_p(n, '~')) else (starts_with_p(n, 'html:') if sx_truthy(starts_with_p(n, 'html:')) else (contains_p(HTML_TAGS, n) if sx_truthy(contains_p(HTML_TAGS, n)) else ((index_of(n, '-') > 0) if not sx_truthy((index_of(n, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')))))))))(symbol_name(h))))(first(expr))) +def is_render_expr(expr): + if sx_truthy(((not sx_truthy((type_of(expr) == 'list'))) if sx_truthy((not sx_truthy((type_of(expr) == 'list')))) else empty_p(expr))): + return False + else: + h = first(expr) + if sx_truthy((not sx_truthy((type_of(h) == 'symbol')))): + return False + else: + n = symbol_name(h) + return ((n == '<>') if sx_truthy((n == '<>')) else ((n == 'raw!') if sx_truthy((n == 'raw!')) else (starts_with_p(n, '~') if sx_truthy(starts_with_p(n, '~')) else (starts_with_p(n, 'html:') if sx_truthy(starts_with_p(n, 'html:')) else (contains_p(HTML_TAGS, n) if sx_truthy(contains_p(HTML_TAGS, n)) else ((index_of(n, '-') > 0) if not sx_truthy((index_of(n, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')))))))) # === Transpiled from adapter-html === # render-to-html -render_to_html = _sx_fn(lambda expr, env: ( - set_render_active_b(True), - _sx_case(type_of(expr), [('nil', lambda: ''), ('string', lambda: escape_html(expr)), ('number', lambda: sx_str(expr)), ('boolean', lambda: ('true' if sx_truthy(expr) else 'false')), ('list', lambda: ('' if sx_truthy(empty_p(expr)) else render_list_to_html(expr, env))), ('symbol', lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env)), ('keyword', lambda: escape_html(keyword_name(expr))), ('raw-html', lambda: raw_html_content(expr)), (None, lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env))]) -)[-1]) +def render_to_html(expr, env): + set_render_active_b(True) + _match = type_of(expr) + if _match == 'nil': + return '' + elif _match == 'string': + return escape_html(expr) + elif _match == 'number': + return sx_str(expr) + elif _match == 'boolean': + if sx_truthy(expr): + return 'true' + else: + return 'false' + elif _match == 'list': + if sx_truthy(empty_p(expr)): + return '' + else: + return render_list_to_html(expr, env) + elif _match == 'symbol': + return render_value_to_html(trampoline(eval_expr(expr, env)), env) + elif _match == 'keyword': + return escape_html(keyword_name(expr)) + elif _match == 'raw-html': + return raw_html_content(expr) + else: + return render_value_to_html(trampoline(eval_expr(expr, env)), env) # render-value-to-html -render_value_to_html = lambda val, env: _sx_case(type_of(val), [('nil', lambda: ''), ('string', lambda: escape_html(val)), ('number', lambda: sx_str(val)), ('boolean', lambda: ('true' if sx_truthy(val) else 'false')), ('list', lambda: render_list_to_html(val, env)), ('raw-html', lambda: raw_html_content(val)), (None, lambda: escape_html(sx_str(val)))]) +def render_value_to_html(val, env): + _match = type_of(val) + if _match == 'nil': + return '' + elif _match == 'string': + return escape_html(val) + elif _match == 'number': + return sx_str(val) + elif _match == 'boolean': + if sx_truthy(val): + return 'true' + else: + return 'false' + elif _match == 'list': + return render_list_to_html(val, env) + elif _match == 'raw-html': + return raw_html_content(val) + else: + return escape_html(sx_str(val)) # RENDER_HTML_FORMS RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'map', 'map-indexed', 'filter', 'for-each'] # render-html-form? -is_render_html_form = lambda name: contains_p(RENDER_HTML_FORMS, name) +def is_render_html_form(name): + return contains_p(RENDER_HTML_FORMS, name) # render-list-to-html -render_list_to_html = lambda expr, env: ('' if sx_truthy(empty_p(expr)) else (lambda head: (join('', map(lambda x: render_value_to_html(x, env), expr)) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (lambda args: (join('', map(lambda x: render_to_html(x, env), args)) if sx_truthy((name == '<>')) else (join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args)) if sx_truthy((name == 'raw!')) else (render_html_lake(args, env) if sx_truthy((name == 'lake')) else (render_html_marsh(args, env) if sx_truthy((name == 'marsh')) else (render_html_element(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else (render_html_island(env_get(env, name), args, env) if 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))))) else ((lambda val: (render_html_component(val, args, env) if sx_truthy(is_component(val)) else (render_to_html(expand_macro(val, args, env), env) if sx_truthy(is_macro(val)) else error(sx_str('Unknown component: ', name)))))(env_get(env, name)) if sx_truthy(starts_with_p(name, '~')) else (dispatch_html_form(name, expr, env) if sx_truthy(is_render_html_form(name)) else (render_to_html(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else render_value_to_html(trampoline(eval_expr(expr, env)), env)))))))))))(rest(expr)))(symbol_name(head))))(first(expr))) +def render_list_to_html(expr, env): + if sx_truthy(empty_p(expr)): + return '' + else: + head = first(expr) + if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))): + return join('', map(lambda x: render_value_to_html(x, env), expr)) + else: + name = symbol_name(head) + args = rest(expr) + if sx_truthy((name == '<>')): + return join('', map(lambda x: render_to_html(x, env), args)) + elif sx_truthy((name == 'raw!')): + return join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args)) + elif sx_truthy((name == 'lake')): + return render_html_lake(args, env) + elif sx_truthy((name == 'marsh')): + return render_html_marsh(args, env) + elif sx_truthy(contains_p(HTML_TAGS, name)): + return render_html_element(name, args, env) + elif sx_truthy((starts_with_p(name, '~') if not sx_truthy(starts_with_p(name, '~')) else (env_has(env, name) if not sx_truthy(env_has(env, name)) else is_island(env_get(env, name))))): + return render_html_island(env_get(env, name), args, env) + elif sx_truthy(starts_with_p(name, '~')): + val = env_get(env, name) + if sx_truthy(is_component(val)): + return render_html_component(val, args, env) + elif sx_truthy(is_macro(val)): + return render_to_html(expand_macro(val, args, env), env) + else: + return error(sx_str('Unknown component: ', name)) + elif sx_truthy(is_render_html_form(name)): + return dispatch_html_form(name, expr, env) + elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))): + return render_to_html(expand_macro(env_get(env, name), args, env), env) + else: + return render_value_to_html(trampoline(eval_expr(expr, env)), env) # dispatch-html-form -dispatch_html_form = lambda name, expr, env: ((lambda cond_val: (render_to_html(nth(expr, 2), env) if sx_truthy(cond_val) else (render_to_html(nth(expr, 3), env) if sx_truthy((len(expr) > 3)) else '')))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'if')) else (('' if sx_truthy((not sx_truthy(trampoline(eval_expr(nth(expr, 1), env))))) else join('', map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr))))) if sx_truthy((name == 'when')) else ((lambda branch: (render_to_html(branch, env) if sx_truthy(branch) else ''))(eval_cond(rest(expr), env)) if sx_truthy((name == 'cond')) else (render_to_html(trampoline(eval_expr(expr, env)), env) if sx_truthy((name == 'case')) else ((lambda local: join('', map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr)))))(process_bindings(nth(expr, 1), env)) if sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))) else (join('', map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr)))) if sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))) else (_sx_begin(trampoline(eval_expr(expr, env)), '') if sx_truthy(is_definition_form(name)) else ((lambda f: (lambda coll: join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'map')) else ((lambda f: (lambda coll: join('', map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'map-indexed')) else (render_to_html(trampoline(eval_expr(expr, env)), env) if sx_truthy((name == 'filter')) else ((lambda f: (lambda coll: join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'for-each')) else render_value_to_html(trampoline(eval_expr(expr, env)), env)))))))))))) +def dispatch_html_form(name, expr, env): + if sx_truthy((name == 'if')): + cond_val = trampoline(eval_expr(nth(expr, 1), env)) + if sx_truthy(cond_val): + return render_to_html(nth(expr, 2), env) + else: + if sx_truthy((len(expr) > 3)): + return render_to_html(nth(expr, 3), env) + else: + return '' + elif sx_truthy((name == 'when')): + if sx_truthy((not sx_truthy(trampoline(eval_expr(nth(expr, 1), env))))): + return '' + else: + return join('', map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr)))) + elif sx_truthy((name == 'cond')): + branch = eval_cond(rest(expr), env) + if sx_truthy(branch): + return render_to_html(branch, env) + else: + return '' + elif sx_truthy((name == 'case')): + return render_to_html(trampoline(eval_expr(expr, env)), env) + elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))): + local = process_bindings(nth(expr, 1), env) + return join('', map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr)))) + elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))): + return join('', map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr)))) + elif sx_truthy(is_definition_form(name)): + trampoline(eval_expr(expr, env)) + return '' + elif sx_truthy((name == 'map')): + f = trampoline(eval_expr(nth(expr, 1), env)) + coll = trampoline(eval_expr(nth(expr, 2), env)) + return join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)) + elif sx_truthy((name == 'map-indexed')): + f = trampoline(eval_expr(nth(expr, 1), env)) + coll = trampoline(eval_expr(nth(expr, 2), env)) + return join('', map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll)) + elif sx_truthy((name == 'filter')): + return render_to_html(trampoline(eval_expr(expr, env)), env) + elif sx_truthy((name == 'for-each')): + f = trampoline(eval_expr(nth(expr, 1), env)) + coll = trampoline(eval_expr(nth(expr, 2), env)) + return join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)) + else: + return render_value_to_html(trampoline(eval_expr(expr, env)), env) # render-lambda-html -render_lambda_html = lambda f, args, env: (lambda local: _sx_begin(for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f)), render_to_html(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) +def render_lambda_html(f, args, env): + local = env_merge(lambda_closure(f), env) + for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f)) + return render_to_html(lambda_body(f), local) # render-html-component -render_html_component = lambda comp, args, env: (lambda kwargs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(kwargs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), (lambda local: _sx_begin(for_each(lambda p: _sx_dict_set(local, p, (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)), component_params(comp)), (_sx_dict_set(local, 'children', make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))) if sx_truthy(component_has_children(comp)) else NIL), render_to_html(component_body(comp), local)))(env_merge(component_closure(comp), env))))([]))({}) +def render_html_component(comp, args, env): + kwargs = {} + children = [] + reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(kwargs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args) + local = env_merge(component_closure(comp), env) + for p in component_params(comp): + local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL) + if sx_truthy(component_has_children(comp)): + local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children))) + return render_to_html(component_body(comp), local) # render-html-element -render_html_element = lambda tag, args, env: (lambda parsed: (lambda attrs: (lambda children: (lambda is_void: sx_str('<', tag, render_attrs(attrs), (' />' if sx_truthy(is_void) else sx_str('>', join('', map(lambda c: render_to_html(c, env), children)), ''))))(contains_p(VOID_ELEMENTS, tag)))(nth(parsed, 1)))(first(parsed)))(parse_element_args(args, env)) +def render_html_element(tag, args, env): + parsed = parse_element_args(args, env) + attrs = first(parsed) + children = nth(parsed, 1) + is_void = contains_p(VOID_ELEMENTS, tag) + return sx_str('<', tag, render_attrs(attrs), (' />' if sx_truthy(is_void) else sx_str('>', join('', map(lambda c: render_to_html(c, env), children)), ''))) # render-html-lake def render_html_lake(args, env): @@ -1248,31 +1886,123 @@ def render_html_marsh(args, env): return sx_str('<', _cells['marsh_tag'], ' data-sx-marsh="', escape_attr((_cells['marsh_id'] if sx_truthy(_cells['marsh_id']) else '')), '">', join('', map(lambda c: render_to_html(c, env), children)), '') # render-html-island -render_html_island = lambda island, args, env: (lambda kwargs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(kwargs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), (lambda local: (lambda island_name: _sx_begin(for_each(lambda p: _sx_dict_set(local, p, (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)), component_params(island)), (_sx_dict_set(local, 'children', make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))) if sx_truthy(component_has_children(island)) else NIL), (lambda body_html: (lambda state_json: sx_str('', body_html, ''))(serialize_island_state(kwargs)))(render_to_html(component_body(island), local))))(component_name(island)))(env_merge(component_closure(island), env))))([]))({}) +def render_html_island(island, args, env): + kwargs = {} + children = [] + reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(kwargs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args) + local = env_merge(component_closure(island), env) + island_name = component_name(island) + for p in component_params(island): + local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL) + if sx_truthy(component_has_children(island)): + local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children))) + body_html = render_to_html(component_body(island), local) + state_json = serialize_island_state(kwargs) + return sx_str('', body_html, '') # serialize-island-state -serialize_island_state = lambda kwargs: (NIL if sx_truthy(is_empty_dict(kwargs)) else json_serialize(kwargs)) +def serialize_island_state(kwargs): + if sx_truthy(is_empty_dict(kwargs)): + return NIL + else: + return json_serialize(kwargs) # === Transpiled from adapter-sx === # render-to-sx -render_to_sx = lambda expr, env: (lambda result: (result if sx_truthy((type_of(result) == 'string')) else serialize(result)))(aser(expr, env)) +def render_to_sx(expr, env): + result = aser(expr, env) + if sx_truthy((type_of(result) == 'string')): + return result + else: + return serialize(result) # aser -aser = _sx_fn(lambda expr, env: ( - set_render_active_b(True), - _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else aser_list(expr, env))), (None, lambda: expr)]) -)[-1]) +def aser(expr, env): + set_render_active_b(True) + _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 == 'list': + if sx_truthy(empty_p(expr)): + return [] + else: + return aser_list(expr, env) + else: + return expr # aser-list -aser_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: aser(x, env), expr) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (aser_fragment(args, env) if sx_truthy((name == '<>')) else (aser_call(name, args, env) if sx_truthy(starts_with_p(name, '~')) else (aser_call(name, args, env) if sx_truthy((name == 'lake')) else (aser_call(name, args, env) if sx_truthy((name == 'marsh')) else (aser_call(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else (aser_special(name, expr, env) if sx_truthy((is_special_form(name) if sx_truthy(is_special_form(name)) else is_ho_form(name))) else (aser(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (lambda f: (lambda evaled_args: (apply(f, evaled_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))) else (trampoline(call_lambda(f, evaled_args, env)) if sx_truthy(is_lambda(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_component(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_island(f)) else error(sx_str('Not callable: ', inspect(f))))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))))))))))(symbol_name(head))))(rest(expr)))(first(expr)) +def aser_list(expr, env): + head = first(expr) + args = rest(expr) + if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))): + return map(lambda x: aser(x, env), expr) + else: + name = symbol_name(head) + if sx_truthy((name == '<>')): + return aser_fragment(args, env) + elif sx_truthy(starts_with_p(name, '~')): + return aser_call(name, args, env) + elif sx_truthy((name == 'lake')): + return aser_call(name, args, env) + elif sx_truthy((name == 'marsh')): + return aser_call(name, args, env) + elif sx_truthy(contains_p(HTML_TAGS, name)): + return aser_call(name, args, env) + elif sx_truthy((is_special_form(name) if sx_truthy(is_special_form(name)) else is_ho_form(name))): + return aser_special(name, expr, env) + elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))): + return aser(expand_macro(env_get(env, name), args, env), env) + else: + f = trampoline(eval_expr(head, env)) + evaled_args = map(lambda a: trampoline(eval_expr(a, env)), args) + if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))): + return apply(f, evaled_args) + elif sx_truthy(is_lambda(f)): + return trampoline(call_lambda(f, evaled_args, env)) + elif sx_truthy(is_component(f)): + return aser_call(sx_str('~', component_name(f)), args, env) + elif sx_truthy(is_island(f)): + return aser_call(sx_str('~', component_name(f)), args, env) + else: + return error(sx_str('Not callable: ', inspect(f))) # aser-fragment -aser_fragment = lambda children, env: (lambda parts: ('' if sx_truthy(empty_p(parts)) else sx_str('(<> ', join(' ', map(serialize, parts)), ')')))(filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda c: aser(c, env), children))) +def aser_fragment(children, env): + parts = filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda c: aser(c, env), children)) + if sx_truthy(empty_p(parts)): + return '' + else: + return sx_str('(<> ', join(' ', map(serialize, parts)), ')') # aser-call -aser_call = lambda name, args, env: (lambda parts: _sx_begin(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), sx_str('(', join(' ', parts), ')')))([name]) +def aser_call(name, args, env): + 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) + return sx_str('(', join(' ', parts), ')') # SPECIAL_FORM_NAMES SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland'] @@ -1281,88 +2011,312 @@ SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each'] # special-form? -is_special_form = lambda name: contains_p(SPECIAL_FORM_NAMES, name) +def is_special_form(name): + return contains_p(SPECIAL_FORM_NAMES, name) # ho-form? -is_ho_form = lambda name: contains_p(HO_FORM_NAMES, name) +def is_ho_form(name): + return contains_p(HO_FORM_NAMES, name) # aser-special def aser_special(name, expr, env): _cells = {} args = rest(expr) - return ((aser(nth(args, 1), env) if sx_truthy(trampoline(eval_expr(first(args), env))) else (aser(nth(args, 2), env) if sx_truthy((len(args) > 2)) else NIL)) if sx_truthy((name == 'if')) else ((NIL if sx_truthy((not sx_truthy(trampoline(eval_expr(first(args), env))))) else _sx_begin(_sx_cell_set(_cells, 'result', NIL), _sx_begin(for_each(lambda body: _sx_cell_set(_cells, 'result', aser(body, env)), rest(args)), _cells['result']))) if sx_truthy((name == 'when')) else ((lambda branch: (aser(branch, env) if sx_truthy(branch) else NIL))(eval_cond(args, env)) if sx_truthy((name == 'cond')) else ((lambda match_val: (lambda clauses: eval_case_aser(match_val, clauses, env))(rest(args)))(trampoline(eval_expr(first(args), env))) if sx_truthy((name == 'case')) else ((lambda local: _sx_begin(_sx_cell_set(_cells, 'result', NIL), _sx_begin(for_each(lambda body: _sx_cell_set(_cells, 'result', aser(body, local)), rest(args)), _cells['result'])))(process_bindings(first(args), env)) if sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))) else (_sx_begin(_sx_cell_set(_cells, 'result', NIL), _sx_begin(for_each(lambda body: _sx_cell_set(_cells, 'result', aser(body, env)), args), _cells['result'])) if sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))) else (_sx_begin(_sx_cell_set(_cells, 'result', True), _sx_begin(some(_sx_fn(lambda arg: ( + if sx_truthy((name == 'if')): + if sx_truthy(trampoline(eval_expr(first(args), env))): + return aser(nth(args, 1), env) + else: + if sx_truthy((len(args) > 2)): + return aser(nth(args, 2), env) + else: + return NIL + elif sx_truthy((name == 'when')): + if sx_truthy((not sx_truthy(trampoline(eval_expr(first(args), env))))): + return NIL + else: + _cells['result'] = NIL + for body in rest(args): + _cells['result'] = aser(body, env) + return _cells['result'] + elif sx_truthy((name == 'cond')): + branch = eval_cond(args, env) + if sx_truthy(branch): + return aser(branch, env) + else: + return NIL + elif sx_truthy((name == 'case')): + match_val = trampoline(eval_expr(first(args), env)) + clauses = rest(args) + return eval_case_aser(match_val, clauses, env) + elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))): + local = process_bindings(first(args), env) + _cells['result'] = NIL + for body in rest(args): + _cells['result'] = aser(body, local) + return _cells['result'] + elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))): + _cells['result'] = NIL + for body in args: + _cells['result'] = aser(body, env) + return _cells['result'] + elif sx_truthy((name == 'and')): + _cells['result'] = True + some(_sx_fn(lambda arg: ( _sx_cell_set(_cells, 'result', trampoline(eval_expr(arg, env))), (not sx_truthy(_cells['result'])) -)[-1]), args), _cells['result'])) if sx_truthy((name == 'and')) else (_sx_begin(_sx_cell_set(_cells, 'result', False), _sx_begin(some(_sx_fn(lambda arg: ( +)[-1]), args) + return _cells['result'] + elif sx_truthy((name == 'or')): + _cells['result'] = False + some(_sx_fn(lambda arg: ( _sx_cell_set(_cells, 'result', trampoline(eval_expr(arg, env))), _cells['result'] -)[-1]), args), _cells['result'])) if sx_truthy((name == 'or')) else ((lambda f: (lambda coll: map(lambda item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else invoke(f, item)), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) if sx_truthy((name == 'map')) else ((lambda f: (lambda coll: map_indexed(lambda i, item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), i), _sx_dict_set(local, nth(lambda_params(f), 1), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else invoke(f, i, item)), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) if sx_truthy((name == 'map-indexed')) else ((lambda f: (lambda coll: (lambda results: _sx_begin(for_each(lambda item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), item), _sx_append(results, aser(lambda_body(f), local))))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else invoke(f, item)), coll), (NIL if sx_truthy(empty_p(results)) else results)))([]))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) if sx_truthy((name == 'for-each')) else (_sx_begin(trampoline(eval_expr(expr, env)), serialize(expr)) if sx_truthy((name == 'defisland')) else (_sx_begin(trampoline(eval_expr(expr, env)), NIL) if sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else (name == 'defrelation')))))))))) else trampoline(eval_expr(expr, env))))))))))))))) +)[-1]), args) + return _cells['result'] + elif sx_truthy((name == 'map')): + f = trampoline(eval_expr(first(args), env)) + coll = trampoline(eval_expr(nth(args, 1), env)) + return map(lambda item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else invoke(f, item)), coll) + elif sx_truthy((name == 'map-indexed')): + f = trampoline(eval_expr(first(args), env)) + coll = trampoline(eval_expr(nth(args, 1), env)) + return map_indexed(lambda i, item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), i), _sx_dict_set(local, nth(lambda_params(f), 1), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else invoke(f, i, item)), coll) + elif sx_truthy((name == 'for-each')): + f = trampoline(eval_expr(first(args), env)) + coll = trampoline(eval_expr(nth(args, 1), env)) + results = [] + for item in coll: + if sx_truthy(is_lambda(f)): + local = env_merge(lambda_closure(f), env) + local[first(lambda_params(f))] = item + results.append(aser(lambda_body(f), local)) + else: + invoke(f, item) + if sx_truthy(empty_p(results)): + return NIL + else: + return results + elif sx_truthy((name == 'defisland')): + trampoline(eval_expr(expr, env)) + return serialize(expr) + elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else (name == 'defrelation')))))))))): + trampoline(eval_expr(expr, env)) + return NIL + else: + return trampoline(eval_expr(expr, env)) # eval-case-aser -eval_case_aser = lambda match_val, clauses, env: (NIL if sx_truthy((len(clauses) < 2)) else (lambda test: (lambda body: (aser(body, env) 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'))))) else (aser(body, env) if sx_truthy((match_val == trampoline(eval_expr(test, env)))) else eval_case_aser(match_val, slice(clauses, 2), env))))(nth(clauses, 1)))(first(clauses))) +def eval_case_aser(match_val, clauses, env): + if sx_truthy((len(clauses) < 2)): + return NIL + else: + test = first(clauses) + body = nth(clauses, 1) + if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == ':else') if sx_truthy((symbol_name(test) == ':else')) else (symbol_name(test) == 'else'))))): + return aser(body, env) + else: + if sx_truthy((match_val == trampoline(eval_expr(test, env)))): + return aser(body, env) + else: + return eval_case_aser(match_val, slice(clauses, 2), env) # === Transpiled from deps (component dependency analysis) === # scan-refs -scan_refs = lambda node: (lambda refs: _sx_begin(scan_refs_walk(node, refs), refs))([]) +def scan_refs(node): + refs = [] + scan_refs_walk(node, refs) + return refs # scan-refs-walk -scan_refs_walk = lambda node, refs: ((lambda name: ((_sx_append(refs, name) if sx_truthy((not sx_truthy(contains_p(refs, name)))) else NIL) if sx_truthy(starts_with_p(name, '~')) else NIL))(symbol_name(node)) if sx_truthy((type_of(node) == 'symbol')) else (for_each(lambda item: scan_refs_walk(item, refs), node) if sx_truthy((type_of(node) == 'list')) else (for_each(lambda key: scan_refs_walk(dict_get(node, key), refs), keys(node)) if sx_truthy((type_of(node) == 'dict')) else NIL))) +def scan_refs_walk(node, refs): + if sx_truthy((type_of(node) == 'symbol')): + name = symbol_name(node) + if sx_truthy(starts_with_p(name, '~')): + if sx_truthy((not sx_truthy(contains_p(refs, name)))): + return _sx_append(refs, name) + return NIL + return NIL + elif sx_truthy((type_of(node) == 'list')): + return for_each(lambda item: scan_refs_walk(item, refs), node) + elif sx_truthy((type_of(node) == 'dict')): + return for_each(lambda key: scan_refs_walk(dict_get(node, key), refs), keys(node)) + else: + return NIL # transitive-deps-walk -transitive_deps_walk = lambda n, seen, env: (_sx_begin(_sx_append(seen, n), (lambda val: (for_each(lambda ref: transitive_deps_walk(ref, seen, env), scan_refs(component_body(val))) if sx_truthy((type_of(val) == 'component')) else (for_each(lambda ref: transitive_deps_walk(ref, seen, env), scan_refs(macro_body(val))) if sx_truthy((type_of(val) == 'macro')) else NIL)))(env_get(env, n))) if sx_truthy((not sx_truthy(contains_p(seen, n)))) else NIL) +def transitive_deps_walk(n, seen, env): + if sx_truthy((not sx_truthy(contains_p(seen, n)))): + seen.append(n) + val = env_get(env, n) + if sx_truthy((type_of(val) == 'component')): + return for_each(lambda ref: transitive_deps_walk(ref, seen, env), scan_refs(component_body(val))) + elif sx_truthy((type_of(val) == 'macro')): + return for_each(lambda ref: transitive_deps_walk(ref, seen, env), scan_refs(macro_body(val))) + else: + return NIL + return NIL # transitive-deps -transitive_deps = lambda name, env: (lambda seen: (lambda key: _sx_begin(transitive_deps_walk(key, seen, env), filter(lambda x: (not sx_truthy((x == key))), seen)))((name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))))([]) +def transitive_deps(name, env): + seen = [] + key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name)) + transitive_deps_walk(key, seen, env) + return filter(lambda x: (not sx_truthy((x == key))), seen) # compute-all-deps -compute_all_deps = lambda env: 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)) +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)) # scan-components-from-source -scan_components_from_source = lambda source: (lambda matches: map(lambda m: sx_str('~', m), matches))(regex_find_all('\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)', source)) +def scan_components_from_source(source): + matches = regex_find_all('\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)', source) + return map(lambda m: sx_str('~', m), matches) # components-needed -components_needed = lambda page_source, env: (lambda direct: (lambda all_needed: _sx_begin(for_each(_sx_fn(lambda name: ( - (_sx_append(all_needed, name) if sx_truthy((not sx_truthy(contains_p(all_needed, name)))) else NIL), - (lambda val: (lambda deps: for_each(lambda dep: (_sx_append(all_needed, dep) if sx_truthy((not sx_truthy(contains_p(all_needed, dep)))) else NIL), deps))((component_deps(val) if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(empty_p(component_deps(val)))))) else transitive_deps(name, env))))(env_get(env, name)) -)[-1]), direct), all_needed))([]))(scan_components_from_source(page_source)) +def components_needed(page_source, env): + direct = scan_components_from_source(page_source) + all_needed = [] + for name in direct: + if sx_truthy((not sx_truthy(contains_p(all_needed, name)))): + all_needed.append(name) + val = env_get(env, name) + deps = (component_deps(val) if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(empty_p(component_deps(val)))))) else transitive_deps(name, env)) + for dep in deps: + if sx_truthy((not sx_truthy(contains_p(all_needed, dep)))): + all_needed.append(dep) + return all_needed # page-component-bundle -page_component_bundle = lambda page_source, env: components_needed(page_source, env) +def page_component_bundle(page_source, env): + return components_needed(page_source, env) # page-css-classes -page_css_classes = lambda page_source, env: (lambda needed: (lambda classes: _sx_begin(for_each(lambda name: (lambda val: (for_each(lambda cls: (_sx_append(classes, cls) if sx_truthy((not sx_truthy(contains_p(classes, cls)))) else NIL), component_css_classes(val)) if sx_truthy((type_of(val) == 'component')) else NIL))(env_get(env, name)), needed), for_each(lambda cls: (_sx_append(classes, cls) if sx_truthy((not sx_truthy(contains_p(classes, cls)))) else NIL), scan_css_classes(page_source)), classes))([]))(components_needed(page_source, env)) +def page_css_classes(page_source, env): + needed = components_needed(page_source, env) + classes = [] + for name in needed: + val = env_get(env, name) + if sx_truthy((type_of(val) == 'component')): + for cls in component_css_classes(val): + if sx_truthy((not sx_truthy(contains_p(classes, cls)))): + classes.append(cls) + for cls in scan_css_classes(page_source): + if sx_truthy((not sx_truthy(contains_p(classes, cls)))): + classes.append(cls) + return classes # scan-io-refs-walk -scan_io_refs_walk = lambda node, io_names, refs: ((lambda name: ((_sx_append(refs, name) if sx_truthy((not sx_truthy(contains_p(refs, name)))) else NIL) if sx_truthy(contains_p(io_names, name)) else NIL))(symbol_name(node)) if sx_truthy((type_of(node) == 'symbol')) else (for_each(lambda item: scan_io_refs_walk(item, io_names, refs), node) if sx_truthy((type_of(node) == 'list')) else (for_each(lambda key: scan_io_refs_walk(dict_get(node, key), io_names, refs), keys(node)) if sx_truthy((type_of(node) == 'dict')) else NIL))) +def scan_io_refs_walk(node, io_names, refs): + if sx_truthy((type_of(node) == 'symbol')): + name = symbol_name(node) + if sx_truthy(contains_p(io_names, name)): + if sx_truthy((not sx_truthy(contains_p(refs, name)))): + return _sx_append(refs, name) + return NIL + return NIL + elif sx_truthy((type_of(node) == 'list')): + return for_each(lambda item: scan_io_refs_walk(item, io_names, refs), node) + 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)) + else: + return NIL # scan-io-refs -scan_io_refs = lambda node, io_names: (lambda refs: _sx_begin(scan_io_refs_walk(node, io_names, refs), refs))([]) +def scan_io_refs(node, io_names): + refs = [] + scan_io_refs_walk(node, io_names, refs) + return refs # transitive-io-refs-walk -transitive_io_refs_walk = lambda n, seen, all_refs, env, io_names: (_sx_begin(_sx_append(seen, n), (lambda val: (_sx_begin(for_each(lambda ref: (_sx_append(all_refs, ref) if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))) else NIL), scan_io_refs(component_body(val), io_names)), for_each(lambda dep: transitive_io_refs_walk(dep, seen, all_refs, env, io_names), scan_refs(component_body(val)))) if sx_truthy((type_of(val) == 'component')) else (_sx_begin(for_each(lambda ref: (_sx_append(all_refs, ref) if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))) else NIL), scan_io_refs(macro_body(val), io_names)), for_each(lambda dep: transitive_io_refs_walk(dep, seen, all_refs, env, io_names), scan_refs(macro_body(val)))) if sx_truthy((type_of(val) == 'macro')) else NIL)))(env_get(env, n))) if sx_truthy((not sx_truthy(contains_p(seen, n)))) else NIL) +def transitive_io_refs_walk(n, seen, all_refs, env, io_names): + if sx_truthy((not sx_truthy(contains_p(seen, n)))): + seen.append(n) + val = env_get(env, n) + if sx_truthy((type_of(val) == 'component')): + for ref in scan_io_refs(component_body(val), io_names): + if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))): + all_refs.append(ref) + return for_each(lambda dep: transitive_io_refs_walk(dep, seen, all_refs, env, io_names), scan_refs(component_body(val))) + 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))) + else: + return NIL + return NIL # transitive-io-refs -transitive_io_refs = lambda name, env, io_names: (lambda all_refs: (lambda seen: (lambda key: _sx_begin(transitive_io_refs_walk(key, seen, all_refs, env, io_names), all_refs))((name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))))([]))([]) +def transitive_io_refs(name, env, io_names): + all_refs = [] + seen = [] + key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name)) + transitive_io_refs_walk(key, seen, all_refs, env, io_names) + return all_refs # compute-all-io-refs -compute_all_io_refs = lambda env, io_names: 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)) +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)) # component-io-refs-cached -component_io_refs_cached = lambda name, env, io_names: (lambda key: (lambda val: (component_io_refs(val) if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else ((not sx_truthy(is_nil(component_io_refs(val)))) if not sx_truthy((not sx_truthy(is_nil(component_io_refs(val))))) else (not sx_truthy(empty_p(component_io_refs(val))))))) else transitive_io_refs(name, env, io_names)))(env_get(env, key)))((name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))) +def component_io_refs_cached(name, env, io_names): + key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name)) + val = env_get(env, key) + if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else ((not sx_truthy(is_nil(component_io_refs(val)))) if not sx_truthy((not sx_truthy(is_nil(component_io_refs(val))))) else (not sx_truthy(empty_p(component_io_refs(val))))))): + return component_io_refs(val) + else: + return transitive_io_refs(name, env, io_names) # component-pure? -component_pure_p = lambda name, env, io_names: (lambda key: (lambda val: (empty_p(component_io_refs(val)) if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(is_nil(component_io_refs(val)))))) else empty_p(transitive_io_refs(name, env, io_names))))(env_get(env, key)))((name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))) +def component_pure_p(name, env, io_names): + key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name)) + val = env_get(env, key) + if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(is_nil(component_io_refs(val)))))): + return empty_p(component_io_refs(val)) + else: + return empty_p(transitive_io_refs(name, env, io_names)) # render-target -render_target = lambda name, env, io_names: (lambda key: (lambda val: ('server' if sx_truthy((not sx_truthy((type_of(val) == 'component')))) else (lambda affinity: ('server' if sx_truthy((affinity == 'server')) else ('client' if sx_truthy((affinity == 'client')) else ('server' if sx_truthy((not sx_truthy(component_pure_p(name, env, io_names)))) else 'client'))))(component_affinity(val))))(env_get(env, key)))((name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))) +def render_target(name, env, io_names): + key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name)) + val = env_get(env, key) + if sx_truthy((not sx_truthy((type_of(val) == 'component')))): + return 'server' + else: + affinity = component_affinity(val) + if sx_truthy((affinity == 'server')): + return 'server' + elif sx_truthy((affinity == 'client')): + return 'client' + elif sx_truthy((not sx_truthy(component_pure_p(name, env, io_names)))): + return 'server' + else: + return 'client' # page-render-plan -page_render_plan = lambda page_source, env, io_names: (lambda needed: (lambda comp_targets: (lambda server_list: (lambda client_list: (lambda io_deps: _sx_begin(for_each(lambda name: (lambda target: _sx_begin(_sx_dict_set(comp_targets, name, target), (_sx_begin(_sx_append(server_list, name), for_each(lambda io_ref: (_sx_append(io_deps, io_ref) if sx_truthy((not sx_truthy(contains_p(io_deps, io_ref)))) else NIL), component_io_refs_cached(name, env, io_names))) if sx_truthy((target == 'server')) else _sx_append(client_list, name))))(render_target(name, env, io_names)), needed), {'components': comp_targets, 'server': server_list, 'client': client_list, 'io-deps': io_deps}))([]))([]))([]))({}))(components_needed(page_source, env)) +def page_render_plan(page_source, env, io_names): + needed = components_needed(page_source, env) + comp_targets = {} + server_list = [] + client_list = [] + io_deps = [] + for name in needed: + target = render_target(name, env, io_names) + comp_targets[name] = target + if sx_truthy((target == 'server')): + server_list.append(name) + for io_ref in component_io_refs_cached(name, env, io_names): + if sx_truthy((not sx_truthy(contains_p(io_deps, io_ref)))): + io_deps.append(io_ref) + else: + client_list.append(name) + return {'components': comp_targets, 'server': server_list, 'client': client_list, 'io-deps': io_deps} # env-components -env_components = lambda env: filter(lambda k: (lambda v: (is_component(v) if sx_truthy(is_component(v)) else is_macro(v)))(env_get(env, k)), keys(env)) +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) === @@ -1374,22 +2328,58 @@ ENGINE_VERBS = ['get', 'post', 'put', 'delete', 'patch'] DEFAULT_SWAP = 'outerHTML' # parse-time -parse_time = lambda s: (0 if sx_truthy(is_nil(s)) else (parse_int(s, 0) if sx_truthy(ends_with_p(s, 'ms')) else ((parse_int(replace(s, 's', ''), 0) * 1000) if sx_truthy(ends_with_p(s, 's')) else parse_int(s, 0)))) +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) + else: + return parse_int(s, 0) # parse-trigger-spec -parse_trigger_spec = lambda spec: (NIL if sx_truthy(is_nil(spec)) else (lambda raw_parts: 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)))(split(spec, ','))) +def parse_trigger_spec(spec): + if sx_truthy(is_nil(spec)): + return NIL + 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)) # default-trigger -default_trigger = lambda tag_name: ([{'event': 'submit', 'modifiers': {}}] if sx_truthy((tag_name == 'FORM')) else ([{'event': 'change', 'modifiers': {}}] if 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')))) else [{'event': 'click', 'modifiers': {}}])) +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': {}}] + else: + return [{'event': 'click', 'modifiers': {}}] # get-verb-info -get_verb_info = lambda el: 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) +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-request-headers -build_request_headers = lambda el, loaded_components, css_hash: (lambda headers: _sx_begin((lambda target_sel: (_sx_dict_set(headers, 'SX-Target', target_sel) if sx_truthy(target_sel) else NIL))(dom_get_attr(el, 'sx-target')), (_sx_dict_set(headers, 'SX-Components', join(',', loaded_components)) if sx_truthy((not sx_truthy(empty_p(loaded_components)))) else NIL), (_sx_dict_set(headers, 'SX-Css', css_hash) if sx_truthy(css_hash) else NIL), (lambda extra_h: ((lambda parsed: (for_each(lambda key: _sx_dict_set(headers, key, sx_str(get(parsed, key))), keys(parsed)) if sx_truthy(parsed) else NIL))(parse_header_value(extra_h)) if sx_truthy(extra_h) else NIL))(dom_get_attr(el, 'sx-headers')), headers))({'SX-Request': 'true', 'SX-Current-URL': browser_location_href()}) +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 # process-response-headers -process_response_headers = lambda get_header: {'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')} +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): @@ -1405,31 +2395,110 @@ def parse_swap_spec(raw_swap, global_transitions_p): return {'style': style, 'transition': _cells['use_transition']} # parse-retry-spec -parse_retry_spec = lambda retry_attr: (NIL if sx_truthy(is_nil(retry_attr)) else (lambda parts: {'strategy': first(parts), 'start-ms': parse_int(nth(parts, 1), 1000), 'cap-ms': parse_int(nth(parts, 2), 30000)})(split(retry_attr, ':'))) +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 -next_retry_ms = lambda current_ms, cap_ms: min((current_ms * 2), cap_ms) +def next_retry_ms(current_ms, cap_ms): + return min((current_ms * 2), cap_ms) # filter-params -filter_params = lambda params_spec, all_params: (all_params if sx_truthy(is_nil(params_spec)) else ([] if sx_truthy((params_spec == 'none')) else (all_params if sx_truthy((params_spec == '*')) else ((lambda excluded: filter(lambda p: (not sx_truthy(contains_p(excluded, first(p)))), all_params))(map(trim, split(slice(params_spec, 4), ','))) if sx_truthy(starts_with_p(params_spec, 'not ')) else (lambda allowed: filter(lambda p: contains_p(allowed, first(p)), all_params))(map(trim, split(params_spec, ','))))))) +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 -resolve_target = lambda el: (lambda sel: (el if sx_truthy((is_nil(sel) if sx_truthy(is_nil(sel)) else (sel == 'this'))) else (dom_parent(el) if sx_truthy((sel == 'closest')) else dom_query(sel))))(dom_get_attr(el, 'sx-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 -apply_optimistic = lambda el: (lambda directive: (NIL if sx_truthy(is_nil(directive)) else (lambda target: (lambda state: _sx_begin((_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))), state))({'target': target, 'directive': directive}))((resolve_target(el) if sx_truthy(resolve_target(el)) else el))))(dom_get_attr(el, 'sx-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 -revert_optimistic = lambda state: ((lambda target: (lambda directive: (_sx_begin(dom_set_style(target, 'opacity', (get(state, 'opacity') if sx_truthy(get(state, 'opacity')) else '')), dom_set_style(target, 'pointer-events', '')) if sx_truthy((directive == 'remove')) else (dom_set_prop(target, 'disabled', (get(state, 'disabled') if sx_truthy(get(state, 'disabled')) else False)) if sx_truthy((directive == 'disable')) else (dom_remove_class(target, get(state, 'add-class')) if sx_truthy(get(state, 'add-class')) else NIL))))(get(state, 'directive')))(get(state, 'target')) if sx_truthy(state) else NIL) +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 -find_oob_swaps = lambda container: (lambda results: _sx_begin(for_each(lambda attr: (lambda oob_els: for_each(lambda oob: (lambda swap_type: (lambda target_id: _sx_begin(dom_remove_attr(oob, attr), (_sx_append(results, {'element': oob, 'swap-type': swap_type, 'target-id': target_id}) if sx_truthy(target_id) else NIL)))(dom_id(oob)))((dom_get_attr(oob, attr) if sx_truthy(dom_get_attr(oob, attr)) else 'outerHTML')), oob_els))(dom_query_all(container, sx_str('[', attr, ']'))), ['sx-swap-oob', 'hx-swap-oob']), results))([]) +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 -morph_node = lambda old_node, new_node: (NIL 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'))) else (morph_island_children(old_node, new_node) if 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')))))) else (dom_replace_child(dom_parent(old_node), dom_clone(new_node), old_node) if 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)))))) else ((dom_set_text_content(old_node, dom_text_content(new_node)) if sx_truthy((not sx_truthy((dom_text_content(old_node) == dom_text_content(new_node))))) else NIL) if sx_truthy(((dom_node_type(old_node) == 3) if sx_truthy((dom_node_type(old_node) == 3)) else (dom_node_type(old_node) == 8))) else (_sx_begin(sync_attrs(old_node, new_node), (morph_children(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))))) else NIL)) if sx_truthy((dom_node_type(old_node) == 1)) else NIL))))) +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 -sync_attrs = lambda old_el, new_el: (lambda ra_str: (lambda reactive_attrs: _sx_begin(for_each(lambda attr: (lambda name: (lambda val: (dom_set_attr(old_el, name, val) 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))))) else NIL))(nth(attr, 1)))(first(attr)), dom_attr_list(new_el)), 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))))(([] if sx_truthy(empty_p(ra_str)) else split(ra_str, ','))))((dom_get_attr(old_el, 'data-sx-reactive-attrs') if sx_truthy(dom_get_attr(old_el, 'data-sx-reactive-attrs')) else '')) +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): @@ -1458,66 +2527,230 @@ def morph_children(old_parent, new_parent): 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))) # morph-island-children -morph_island_children = lambda old_island, new_island: (lambda old_lakes: (lambda new_lakes: (lambda old_marshes: (lambda new_marshes: (lambda new_lake_map: (lambda new_marsh_map: _sx_begin(for_each(lambda lake: (lambda id_: (_sx_dict_set(new_lake_map, id_, lake) if sx_truthy(id_) else NIL))(dom_get_attr(lake, 'data-sx-lake')), new_lakes), for_each(lambda marsh: (lambda id_: (_sx_dict_set(new_marsh_map, id_, marsh) if sx_truthy(id_) else NIL))(dom_get_attr(marsh, 'data-sx-marsh')), new_marshes), for_each(lambda old_lake: (lambda id_: (lambda new_lake: (_sx_begin(sync_attrs(old_lake, new_lake), morph_children(old_lake, new_lake)) if sx_truthy(new_lake) else NIL))(dict_get(new_lake_map, id_)))(dom_get_attr(old_lake, 'data-sx-lake')), old_lakes), for_each(lambda old_marsh: (lambda id_: (lambda new_marsh: (morph_marsh(old_marsh, new_marsh, old_island) if sx_truthy(new_marsh) else NIL))(dict_get(new_marsh_map, id_)))(dom_get_attr(old_marsh, 'data-sx-marsh')), old_marshes), process_signal_updates(new_island)))({}))({}))(dom_query_all(new_island, '[data-sx-marsh]')))(dom_query_all(old_island, '[data-sx-marsh]')))(dom_query_all(new_island, '[data-sx-lake]')))(dom_query_all(old_island, '[data-sx-lake]')) +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 -morph_marsh = lambda old_marsh, new_marsh, island_el: (lambda transform: (lambda env: (lambda new_html: ((lambda parsed: (lambda sx_content: _sx_begin(dispose_marsh_scope(old_marsh), 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)))))((invoke(transform, parsed) if sx_truthy(transform) else parsed)))(parse(new_html)) 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)))))) else _sx_begin(sync_attrs(old_marsh, new_marsh), morph_children(old_marsh, new_marsh))))(dom_inner_html(new_marsh)))(dom_get_data(old_marsh, 'sx-marsh-env')))(dom_get_data(old_marsh, 'sx-marsh-transform')) +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 -process_signal_updates = lambda root: (lambda signal_els: 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))(dom_query_all(root, '[data-sx-signal]')) +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 -swap_dom_nodes = lambda target, new_nodes, strategy: _sx_case(strategy, [('innerHTML', lambda: (morph_children(target, new_nodes) if sx_truthy(dom_is_fragment_p(new_nodes)) else (lambda wrapper: _sx_begin(dom_append(wrapper, new_nodes), morph_children(target, wrapper)))(dom_create_element('div', NIL)))), ('outerHTML', lambda: (lambda parent: _sx_begin(((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)), parent))(dom_parent(target))), ('afterend', lambda: dom_insert_after(target, new_nodes)), ('beforeend', lambda: dom_append(target, new_nodes)), ('afterbegin', lambda: dom_prepend(target, new_nodes)), ('beforebegin', lambda: dom_insert_before(dom_parent(target), new_nodes, target)), ('delete', lambda: dom_remove_child(dom_parent(target), target)), ('none', lambda: NIL), (None, lambda: (morph_children(target, new_nodes) if sx_truthy(dom_is_fragment_p(new_nodes)) else (lambda wrapper: _sx_begin(dom_append(wrapper, new_nodes), morph_children(target, wrapper)))(dom_create_element('div', NIL))))]) +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 -insert_remaining_siblings = lambda parent, ref_node, sib: ((lambda next: _sx_begin(dom_insert_after(ref_node, sib), insert_remaining_siblings(parent, sib, next)))(dom_next_sibling(sib)) if sx_truthy(sib) else NIL) +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 -swap_html_string = lambda target, html, strategy: _sx_case(strategy, [('innerHTML', lambda: dom_set_inner_html(target, html)), ('outerHTML', lambda: (lambda parent: _sx_begin(dom_insert_adjacent_html(target, 'afterend', html), dom_remove_child(parent, target), parent))(dom_parent(target))), ('afterend', lambda: dom_insert_adjacent_html(target, 'afterend', html)), ('beforeend', lambda: dom_insert_adjacent_html(target, 'beforeend', html)), ('afterbegin', lambda: dom_insert_adjacent_html(target, 'afterbegin', html)), ('beforebegin', lambda: dom_insert_adjacent_html(target, 'beforebegin', html)), ('delete', lambda: dom_remove_child(dom_parent(target), target)), ('none', lambda: NIL), (None, lambda: dom_set_inner_html(target, html))]) +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 -handle_history = lambda el, url, resp_headers: (lambda push_url: (lambda replace_url: (lambda hdr_replace: (browser_replace_state(hdr_replace) if sx_truthy(hdr_replace) else (browser_push_state((url if sx_truthy((push_url == 'true')) else push_url)) if sx_truthy((push_url if not sx_truthy(push_url) else (not sx_truthy((push_url == 'false'))))) else (browser_replace_state((url if sx_truthy((replace_url == 'true')) else replace_url)) if sx_truthy((replace_url if not sx_truthy(replace_url) else (not sx_truthy((replace_url == 'false'))))) else NIL))))(get(resp_headers, 'replace-url')))(dom_get_attr(el, 'sx-replace-url')))(dom_get_attr(el, 'sx-push-url')) +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 -preload_cache_get = lambda cache, url: (lambda entry: (NIL if sx_truthy(is_nil(entry)) else (_sx_begin(dict_delete(cache, url), NIL) if sx_truthy(((now_ms() - get(entry, 'timestamp')) > PRELOAD_TTL)) else _sx_begin(dict_delete(cache, url), entry))))(dict_get(cache, url)) +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 -preload_cache_set = lambda cache, url, text, content_type: _sx_dict_set(cache, url, {'text': text, 'content-type': content_type, 'timestamp': now_ms()}) +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 -classify_trigger = lambda trigger: (lambda event: ('poll' if sx_truthy((event == 'every')) else ('intersect' if sx_truthy((event == 'intersect')) else ('load' if sx_truthy((event == 'load')) else ('revealed' if sx_truthy((event == 'revealed')) else 'event')))))(get(trigger, 'event')) +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? -should_boost_link_p = lambda link: (lambda href: (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')))))))))))(dom_get_attr(link, 'href')) +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? -should_boost_form_p = lambda form: ((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'))))) +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 -parse_sse_swap = lambda el: (dom_get_attr(el, 'sx-sse-swap') if sx_truthy(dom_get_attr(el, 'sx-sse-swap')) else 'message') +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 -split_path_segments = lambda path: (lambda trimmed: (lambda trimmed2: ([] if sx_truthy(empty_p(trimmed2)) else split(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)))((slice(path, 1) if sx_truthy(starts_with_p(path, '/')) else path)) +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 -make_route_segment = lambda seg: ((lambda param_name: (lambda d: _sx_begin(_sx_dict_set(d, 'type', 'param'), _sx_dict_set(d, 'value', param_name), d))({}))(slice(seg, 1, (len(seg) - 1))) if sx_truthy((starts_with_p(seg, '<') if not sx_truthy(starts_with_p(seg, '<')) else ends_with_p(seg, '>'))) else (lambda d: _sx_begin(_sx_dict_set(d, 'type', 'literal'), _sx_dict_set(d, 'value', seg), d))({})) +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 -parse_route_pattern = lambda pattern: (lambda segments: map(make_route_segment, segments))(split_path_segments(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 = {} - return (NIL if sx_truthy((not sx_truthy((len(path_segs) == len(parsed_segs))))) else (lambda params: _sx_begin(_sx_cell_set(_cells, 'matched', True), _sx_begin(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), (params if sx_truthy(_cells['matched']) else NIL))))({})) + 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 -match_route = lambda path, pattern: (lambda path_segs: (lambda parsed_segs: match_route_segments(path_segs, parsed_segs))(parse_route_pattern(pattern)))(split_path_segments(path)) +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): @@ -1537,23 +2770,54 @@ def find_matching_route(path, routes): # === Transpiled from signals (reactive signal runtime) === # signal -signal = lambda initial_value: make_signal(initial_value) +def signal(initial_value): + return make_signal(initial_value) # deref -deref = lambda s: (s if sx_truthy((not sx_truthy(is_signal(s)))) else (lambda ctx: _sx_begin((_sx_begin(tracking_context_add_dep(ctx, s), signal_add_sub(s, tracking_context_notify_fn(ctx))) if sx_truthy(ctx) else NIL), signal_value(s)))(get_tracking_context())) +def deref(s): + if sx_truthy((not sx_truthy(is_signal(s)))): + return s + else: + ctx = get_tracking_context() + if sx_truthy(ctx): + tracking_context_add_dep(ctx, s) + signal_add_sub(s, tracking_context_notify_fn(ctx)) + return signal_value(s) # reset! -reset_b = lambda s, value: ((lambda old: (_sx_begin(signal_set_value(s, value), notify_subscribers(s)) if sx_truthy((not sx_truthy(is_identical(old, value)))) else NIL))(signal_value(s)) if sx_truthy(is_signal(s)) else NIL) +def reset_b(s, value): + if sx_truthy(is_signal(s)): + old = signal_value(s) + if sx_truthy((not sx_truthy(is_identical(old, value)))): + signal_set_value(s, value) + return notify_subscribers(s) + return NIL + return NIL # swap! -swap_b = lambda s, f, *args: ((lambda old: (lambda new_val: (_sx_begin(signal_set_value(s, new_val), notify_subscribers(s)) if sx_truthy((not sx_truthy(is_identical(old, new_val)))) else NIL))(apply(f, cons(old, args))))(signal_value(s)) if sx_truthy(is_signal(s)) else NIL) +def swap_b(s, f, *args): + if sx_truthy(is_signal(s)): + old = signal_value(s) + new_val = apply(f, cons(old, args)) + if sx_truthy((not sx_truthy(is_identical(old, new_val)))): + signal_set_value(s, new_val) + return notify_subscribers(s) + return NIL + return NIL # computed -computed = lambda compute_fn: (lambda s: (lambda deps: (lambda compute_ctx: (lambda recompute: _sx_begin(recompute(), register_in_scope(lambda : dispose_computed(s)), s))(_sx_fn(lambda : ( +def computed(compute_fn): + s = make_signal(NIL) + deps = [] + compute_ctx = NIL + recompute = _sx_fn(lambda : ( for_each(lambda dep: signal_remove_sub(dep, recompute), signal_deps(s)), signal_set_deps(s, []), (lambda ctx: (lambda prev: _sx_begin(set_tracking_context(ctx), (lambda new_val: _sx_begin(set_tracking_context(prev), signal_set_deps(s, tracking_context_deps(ctx)), (lambda old: _sx_begin(signal_set_value(s, new_val), (notify_subscribers(s) if sx_truthy((not sx_truthy(is_identical(old, new_val)))) else NIL)))(signal_value(s))))(invoke(compute_fn))))(get_tracking_context()))(make_tracking_context(recompute)) -)[-1])))(NIL))([]))(make_signal(NIL)) +)[-1]) + recompute() + register_in_scope(lambda : dispose_computed(s)) + return s # effect def effect(effect_fn): @@ -1583,16 +2847,39 @@ def batch(thunk): _batch_depth = (_batch_depth + 1) invoke(thunk) _batch_depth = (_batch_depth - 1) - return ((lambda queue: _sx_begin(_sx_cell_set(_cells, '_batch_queue', []), (lambda seen: (lambda pending: _sx_begin(for_each(lambda s: for_each(lambda sub: (_sx_begin(_sx_append(seen, sub), _sx_append(pending, sub)) if sx_truthy((not sx_truthy(contains_p(seen, sub)))) else NIL), signal_subscribers(s)), queue), for_each(lambda sub: sub(), pending)))([]))([])))(_batch_queue) if sx_truthy((_batch_depth == 0)) else NIL) + if sx_truthy((_batch_depth == 0)): + queue = _batch_queue + _batch_queue = [] + seen = [] + pending = [] + for s in queue: + for sub in signal_subscribers(s): + if sx_truthy((not sx_truthy(contains_p(seen, sub)))): + seen.append(sub) + pending.append(sub) + return for_each(lambda sub: sub(), pending) + return NIL # notify-subscribers -notify_subscribers = lambda s: ((_sx_append(_batch_queue, s) if sx_truthy((not sx_truthy(contains_p(_batch_queue, s)))) else NIL) if sx_truthy((_batch_depth > 0)) else flush_subscribers(s)) +def notify_subscribers(s): + if sx_truthy((_batch_depth > 0)): + if sx_truthy((not sx_truthy(contains_p(_batch_queue, s)))): + return _sx_append(_batch_queue, s) + return NIL + else: + return flush_subscribers(s) # flush-subscribers -flush_subscribers = lambda s: for_each(lambda sub: sub(), signal_subscribers(s)) +def flush_subscribers(s): + return for_each(lambda sub: sub(), signal_subscribers(s)) # dispose-computed -dispose_computed = lambda s: (_sx_begin(for_each(lambda dep: signal_remove_sub(dep, NIL), signal_deps(s)), signal_set_deps(s, [])) if sx_truthy(is_signal(s)) else NIL) +def dispose_computed(s): + if sx_truthy(is_signal(s)): + for dep in signal_deps(s): + signal_remove_sub(dep, NIL) + return signal_set_deps(s, []) + return NIL # *island-scope* _island_scope = NIL @@ -1606,13 +2893,25 @@ def with_island_scope(scope_fn, body_fn): return result # register-in-scope -register_in_scope = lambda disposable: (_island_scope(disposable) if sx_truthy(_island_scope) else NIL) +def register_in_scope(disposable): + if sx_truthy(_island_scope): + return _island_scope(disposable) + return NIL # with-marsh-scope -with_marsh_scope = lambda marsh_el, body_fn: (lambda disposers: _sx_begin(with_island_scope(lambda d: _sx_append(disposers, d), body_fn), dom_set_data(marsh_el, 'sx-marsh-disposers', disposers)))([]) +def with_marsh_scope(marsh_el, body_fn): + disposers = [] + with_island_scope(lambda d: _sx_append(disposers, d), body_fn) + return dom_set_data(marsh_el, 'sx-marsh-disposers', disposers) # dispose-marsh-scope -dispose_marsh_scope = lambda marsh_el: (lambda disposers: (_sx_begin(for_each(lambda d: invoke(d), disposers), dom_set_data(marsh_el, 'sx-marsh-disposers', NIL)) if sx_truthy(disposers) else NIL))(dom_get_data(marsh_el, 'sx-marsh-disposers')) +def dispose_marsh_scope(marsh_el): + disposers = dom_get_data(marsh_el, 'sx-marsh-disposers') + if sx_truthy(disposers): + for d in disposers: + invoke(d) + return dom_set_data(marsh_el, 'sx-marsh-disposers', NIL) + return NIL # *store-registry* _store_registry = {} @@ -1625,23 +2924,33 @@ def def_store(name, init_fn): return get(_store_registry, name) # use-store -use_store = lambda name: (get(_store_registry, name) if sx_truthy(has_key_p(_store_registry, name)) else error(sx_str('Store not found: ', name, '. Call (def-store ...) before (use-store ...).'))) +def use_store(name): + if sx_truthy(has_key_p(_store_registry, name)): + return get(_store_registry, name) + else: + return error(sx_str('Store not found: ', name, '. Call (def-store ...) before (use-store ...).')) # clear-stores def clear_stores(): return _sx_cell_set(_cells, '_store_registry', {}) # emit-event -emit_event = lambda el, event_name, detail: dom_dispatch(el, event_name, detail) +def emit_event(el, event_name, detail): + return dom_dispatch(el, event_name, detail) # on-event -on_event = lambda el, event_name, handler: dom_listen(el, event_name, handler) +def on_event(el, event_name, handler): + return dom_listen(el, event_name, handler) # bridge-event -bridge_event = lambda el, event_name, target_signal, transform_fn: effect(lambda : (lambda remove: remove)(dom_listen(el, event_name, lambda e: (lambda detail: (lambda new_val: reset_b(target_signal, new_val))((invoke(transform_fn, detail) if sx_truthy(transform_fn) else detail)))(event_detail(e))))) +def bridge_event(el, event_name, target_signal, transform_fn): + return effect(lambda : (lambda remove: remove)(dom_listen(el, event_name, lambda e: (lambda detail: (lambda new_val: reset_b(target_signal, new_val))((invoke(transform_fn, detail) if sx_truthy(transform_fn) else detail)))(event_detail(e))))) # resource -resource = lambda fetch_fn: (lambda state: _sx_begin(promise_then(invoke(fetch_fn), lambda data: reset_b(state, {'loading': False, 'data': data, 'error': NIL}), lambda err: reset_b(state, {'loading': False, 'data': NIL, 'error': err})), state))(signal({'loading': True, 'data': NIL, 'error': NIL})) +def resource(fetch_fn): + state = signal({'loading': True, 'data': NIL, 'error': NIL}) + promise_then(invoke(fetch_fn), lambda data: reset_b(state, {'loading': False, 'data': data, 'error': NIL}), lambda err: reset_b(state, {'loading': False, 'data': NIL, 'error': err})) + return state # ========================================================================= diff --git a/shared/sx/tests/run.py b/shared/sx/tests/run.py index fd88522..9e23d49 100644 --- a/shared/sx/tests/run.py +++ b/shared/sx/tests/run.py @@ -771,6 +771,15 @@ def main(): print(f"# --- {spec_name} ---") eval_file(spec["file"], env) + # Reset render state after render tests to avoid leaking + # into subsequent specs (bootstrapped evaluator checks render_active) + if spec_name == "render": + try: + from shared.sx.ref.sx_ref import set_render_active_b + set_render_active_b(False) + except ImportError: + pass + # Summary print() print(f"1..{test_num}")