Fix reactive islands client-side navigation and hydration
Three bugs prevented islands from working during SX wire navigation: 1. components_for_request() only bundled Component and Macro defs, not Island defs — client never received defisland definitions during navigation (components_for_page for initial HTML shell was correct). 2. hydrate-island used morph-children which can't transfer addEventListener event handlers from freshly rendered DOM to existing nodes. Changed to clear+append so reactive DOM with live signal subscriptions is inserted directly. 3. asyncRenderToDom (client-side async page eval) checked _component but not _island on ~-prefixed names — islands fell through to generic eval which failed. Now delegates to renderDomIsland. 4. setInterval_/setTimeout_ passed SX Lambda objects directly to native timers. JS coerced them to "[object Object]" and tried to eval as code, causing "missing ] after element list". Added _wrapSxFn to convert SX lambdas to JS functions before passing to timers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
# 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.
|
||||
|
||||
@@ -402,6 +403,16 @@ def tracking_context_notify_fn(ctx):
|
||||
return ctx.notify_fn if isinstance(ctx, _TrackingContext) else NIL
|
||||
|
||||
|
||||
def invoke(f, *args):
|
||||
"""Call f with args — handles both native callables and SX lambdas.
|
||||
|
||||
In Python, all transpiled lambdas are natively callable, so this is
|
||||
just a direct call. The JS host needs dispatch logic here because
|
||||
SX lambdas from runtime-evaluated code are objects, not functions.
|
||||
"""
|
||||
return f(*args)
|
||||
|
||||
|
||||
def json_serialize(obj):
|
||||
import json
|
||||
try:
|
||||
@@ -467,17 +478,8 @@ def dict_delete(d, k):
|
||||
|
||||
|
||||
def is_render_expr(expr):
|
||||
"""Check if expression is an HTML element, component, or fragment."""
|
||||
if not isinstance(expr, list) or not expr:
|
||||
return False
|
||||
h = expr[0]
|
||||
if not isinstance(h, Symbol):
|
||||
return False
|
||||
n = h.name
|
||||
return (n == "<>" or n == "raw!" or
|
||||
n.startswith("~") or n.startswith("html:") or
|
||||
n in HTML_TAGS or
|
||||
("-" in n and len(expr) > 1 and isinstance(expr[1], Keyword)))
|
||||
"""Placeholder — overridden by transpiled version from render.sx."""
|
||||
return False
|
||||
|
||||
|
||||
# Render dispatch -- set by adapter
|
||||
@@ -519,6 +521,10 @@ def make_raw_html(s):
|
||||
return _RawHTML(s)
|
||||
|
||||
|
||||
def sx_expr_source(x):
|
||||
return x.source if isinstance(x, SxExpr) else str(x)
|
||||
|
||||
|
||||
class EvalError(Exception):
|
||||
pass
|
||||
|
||||
@@ -558,7 +564,12 @@ def escape_string(s):
|
||||
|
||||
|
||||
def serialize(val):
|
||||
"""Serialize an SX value to SX source text."""
|
||||
"""Serialize an SX value to SX source text.
|
||||
|
||||
Note: parser.sx defines sx-serialize with a serialize alias, but parser.sx
|
||||
is only included in JS builds (for client-side parsing). Python builds
|
||||
provide this as a platform function.
|
||||
"""
|
||||
t = type_of(val)
|
||||
if t == "sx-expr":
|
||||
return val.source
|
||||
@@ -592,179 +603,26 @@ def serialize(val):
|
||||
return "nil"
|
||||
return str(val)
|
||||
|
||||
# Aliases for transpiled code — parser.sx defines sx-serialize/sx-serialize-dict
|
||||
# but parser.sx is JS-only. Provide aliases so transpiled render.sx works.
|
||||
sx_serialize = serialize
|
||||
sx_serialize_dict = lambda d: serialize(d)
|
||||
|
||||
_SPECIAL_FORM_NAMES = frozenset([
|
||||
"if", "when", "cond", "case", "and", "or",
|
||||
"let", "let*", "lambda", "fn",
|
||||
"define", "defcomp", "defmacro", "defstyle",
|
||||
"defhandler", "defpage", "defquery", "defaction", "defrelation",
|
||||
"begin", "do", "quote", "quasiquote",
|
||||
"->", "set!",
|
||||
])
|
||||
|
||||
_HO_FORM_NAMES = frozenset([
|
||||
"map", "map-indexed", "filter", "reduce",
|
||||
"some", "every?", "for-each",
|
||||
])
|
||||
_SPECIAL_FORM_NAMES = frozenset() # Placeholder — overridden by transpiled adapter-sx.sx
|
||||
_HO_FORM_NAMES = frozenset()
|
||||
|
||||
def is_special_form(name):
|
||||
return name in _SPECIAL_FORM_NAMES
|
||||
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||
return False
|
||||
|
||||
def is_ho_form(name):
|
||||
return name in _HO_FORM_NAMES
|
||||
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||
return False
|
||||
|
||||
|
||||
def aser_special(name, expr, env):
|
||||
"""Evaluate a special/HO form in aser mode.
|
||||
|
||||
Control flow forms evaluate conditions normally but render branches
|
||||
through aser (serializing tags/components instead of rendering HTML).
|
||||
Definition forms evaluate for side effects and return nil.
|
||||
"""
|
||||
# Control flow — evaluate conditions, aser branches
|
||||
args = expr[1:]
|
||||
if name == "if":
|
||||
cond_val = trampoline(eval_expr(args[0], env))
|
||||
if sx_truthy(cond_val):
|
||||
return aser(args[1], env)
|
||||
return aser(args[2], env) if _b_len(args) > 2 else NIL
|
||||
if name == "when":
|
||||
cond_val = trampoline(eval_expr(args[0], env))
|
||||
if sx_truthy(cond_val):
|
||||
result = NIL
|
||||
for body in args[1:]:
|
||||
result = aser(body, env)
|
||||
return result
|
||||
return NIL
|
||||
if name == "cond":
|
||||
clauses = args
|
||||
if clauses and isinstance(clauses[0], _b_list) and _b_len(clauses[0]) == 2:
|
||||
for clause in clauses:
|
||||
test = clause[0]
|
||||
if isinstance(test, Symbol) and test.name in ("else", ":else"):
|
||||
return aser(clause[1], env)
|
||||
if isinstance(test, Keyword) and test.name == "else":
|
||||
return aser(clause[1], env)
|
||||
if sx_truthy(trampoline(eval_expr(test, env))):
|
||||
return aser(clause[1], env)
|
||||
else:
|
||||
i = 0
|
||||
while i < _b_len(clauses) - 1:
|
||||
test = clauses[i]
|
||||
result = clauses[i + 1]
|
||||
if isinstance(test, Keyword) and test.name == "else":
|
||||
return aser(result, env)
|
||||
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
||||
return aser(result, env)
|
||||
if sx_truthy(trampoline(eval_expr(test, env))):
|
||||
return aser(result, env)
|
||||
i += 2
|
||||
return NIL
|
||||
if name == "case":
|
||||
match_val = trampoline(eval_expr(args[0], env))
|
||||
clauses = args[1:]
|
||||
i = 0
|
||||
while i < _b_len(clauses) - 1:
|
||||
test = clauses[i]
|
||||
result = clauses[i + 1]
|
||||
if isinstance(test, Keyword) and test.name == "else":
|
||||
return aser(result, env)
|
||||
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
||||
return aser(result, env)
|
||||
if match_val == trampoline(eval_expr(test, env)):
|
||||
return aser(result, env)
|
||||
i += 2
|
||||
return NIL
|
||||
if name in ("let", "let*"):
|
||||
bindings = args[0]
|
||||
local = _b_dict(env)
|
||||
if isinstance(bindings, _b_list):
|
||||
if bindings and isinstance(bindings[0], _b_list):
|
||||
for b in bindings:
|
||||
var = b[0]
|
||||
vname = var.name if isinstance(var, Symbol) else var
|
||||
local[vname] = trampoline(eval_expr(b[1], local))
|
||||
else:
|
||||
for i in _b_range(0, _b_len(bindings), 2):
|
||||
var = bindings[i]
|
||||
vname = var.name if isinstance(var, Symbol) else var
|
||||
local[vname] = trampoline(eval_expr(bindings[i + 1], local))
|
||||
result = NIL
|
||||
for body in args[1:]:
|
||||
result = aser(body, local)
|
||||
return result
|
||||
if name in ("begin", "do"):
|
||||
result = NIL
|
||||
for body in args:
|
||||
result = aser(body, env)
|
||||
return result
|
||||
if name == "and":
|
||||
result = True
|
||||
for arg in args:
|
||||
result = trampoline(eval_expr(arg, env))
|
||||
if not sx_truthy(result):
|
||||
return result
|
||||
return result
|
||||
if name == "or":
|
||||
result = False
|
||||
for arg in args:
|
||||
result = trampoline(eval_expr(arg, env))
|
||||
if sx_truthy(result):
|
||||
return result
|
||||
return result
|
||||
# HO forms in aser mode — map/for-each render through aser
|
||||
if name == "map":
|
||||
fn = trampoline(eval_expr(args[0], env))
|
||||
coll = trampoline(eval_expr(args[1], env))
|
||||
results = []
|
||||
for item in coll:
|
||||
if isinstance(fn, Lambda):
|
||||
local = _b_dict(fn.closure)
|
||||
local.update(env)
|
||||
local[fn.params[0]] = item
|
||||
results.append(aser(fn.body, local))
|
||||
elif callable(fn):
|
||||
results.append(fn(item))
|
||||
else:
|
||||
raise EvalError("map requires callable")
|
||||
return results
|
||||
if name == "map-indexed":
|
||||
fn = trampoline(eval_expr(args[0], env))
|
||||
coll = trampoline(eval_expr(args[1], env))
|
||||
results = []
|
||||
for i, item in enumerate(coll):
|
||||
if isinstance(fn, Lambda):
|
||||
local = _b_dict(fn.closure)
|
||||
local.update(env)
|
||||
local[fn.params[0]] = i
|
||||
local[fn.params[1]] = item
|
||||
results.append(aser(fn.body, local))
|
||||
elif callable(fn):
|
||||
results.append(fn(i, item))
|
||||
else:
|
||||
raise EvalError("map-indexed requires callable")
|
||||
return results
|
||||
if name == "for-each":
|
||||
fn = trampoline(eval_expr(args[0], env))
|
||||
coll = trampoline(eval_expr(args[1], env))
|
||||
results = []
|
||||
for item in coll:
|
||||
if isinstance(fn, Lambda):
|
||||
local = _b_dict(fn.closure)
|
||||
local.update(env)
|
||||
local[fn.params[0]] = item
|
||||
results.append(aser(fn.body, local))
|
||||
elif callable(fn):
|
||||
fn(item)
|
||||
return results if results else NIL
|
||||
# Definition forms — evaluate for side effects
|
||||
if name in ("define", "defcomp", "defmacro", "defstyle",
|
||||
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
|
||||
trampoline(eval_expr(expr, env))
|
||||
return NIL
|
||||
# Lambda/fn, quote, quasiquote, set!, -> : evaluate normally
|
||||
result = eval_expr(expr, env)
|
||||
return trampoline(result)
|
||||
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||
return trampoline(eval_expr(expr, env))
|
||||
|
||||
|
||||
# =========================================================================
|
||||
@@ -1021,6 +879,7 @@ parse_int = PRIMITIVES["parse-int"]
|
||||
upper = PRIMITIVES["upper"]
|
||||
has_key_p = PRIMITIVES["has-key?"]
|
||||
dissoc = PRIMITIVES["dissoc"]
|
||||
index_of = PRIMITIVES["index-of"]
|
||||
|
||||
|
||||
# =========================================================================
|
||||
@@ -1042,7 +901,7 @@ def component_css_classes(c):
|
||||
return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else []
|
||||
|
||||
def env_components(env):
|
||||
"""Return list of component/macro names in an environment."""
|
||||
"""Placeholder — overridden by transpiled version from deps.sx."""
|
||||
return [k for k, v in env.items()
|
||||
if isinstance(v, (Component, Macro))]
|
||||
|
||||
@@ -1320,6 +1179,9 @@ eval_cond_clojure = lambda clauses, env: (NIL if sx_truthy((len(clauses) < 2)) e
|
||||
# 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))
|
||||
|
||||
# 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)))
|
||||
|
||||
|
||||
# === Transpiled from adapter-html ===
|
||||
|
||||
@@ -1357,6 +1219,51 @@ render_html_island = lambda island, args, env: (lambda kwargs: (lambda children:
|
||||
serialize_island_state = lambda kwargs: (NIL if sx_truthy(is_empty_dict(kwargs)) else 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))
|
||||
|
||||
# aser
|
||||
aser = 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)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else aser_list(expr, env))), (None, lambda: 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(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))
|
||||
|
||||
# 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)))
|
||||
|
||||
# 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])
|
||||
|
||||
# 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']
|
||||
|
||||
# HO_FORM_NAMES
|
||||
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)
|
||||
|
||||
# ho-form?
|
||||
is_ho_form = lambda name: 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: (
|
||||
_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: (
|
||||
_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)))))))))))))))
|
||||
|
||||
# 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)))
|
||||
|
||||
|
||||
# === Transpiled from deps (component dependency analysis) ===
|
||||
|
||||
# scan-refs
|
||||
@@ -1413,6 +1320,9 @@ render_target = lambda name, env, io_names: (lambda key: (lambda val: ('server'
|
||||
# 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), transitive_io_refs(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))
|
||||
|
||||
# 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))
|
||||
|
||||
|
||||
# === Transpiled from signals (reactive signal runtime) ===
|
||||
|
||||
@@ -1432,7 +1342,7 @@ swap_b = lambda s, f, *args: ((lambda old: (lambda new_val: (_sx_begin(signal_se
|
||||
computed = lambda compute_fn: (lambda s: (lambda deps: (lambda compute_ctx: (lambda recompute: _sx_begin(recompute(), s))(_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))))(compute_fn())))(get_tracking_context()))(make_tracking_context(recompute))
|
||||
(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))
|
||||
|
||||
# effect
|
||||
@@ -1441,11 +1351,11 @@ def effect(effect_fn):
|
||||
_cells['deps'] = []
|
||||
_cells['disposed'] = False
|
||||
_cells['cleanup_fn'] = NIL
|
||||
run_effect = lambda : (_sx_begin((cleanup_fn() if sx_truthy(_cells['cleanup_fn']) else NIL), for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']), _sx_cell_set(_cells, 'deps', []), (lambda ctx: (lambda prev: _sx_begin(set_tracking_context(ctx), (lambda result: _sx_begin(set_tracking_context(prev), _sx_cell_set(_cells, 'deps', tracking_context_deps(ctx)), (_sx_cell_set(_cells, 'cleanup_fn', result) if sx_truthy(is_callable(result)) else NIL)))(effect_fn())))(get_tracking_context()))(make_tracking_context(run_effect))) if sx_truthy((not sx_truthy(_cells['disposed']))) else NIL)
|
||||
run_effect = lambda : (_sx_begin((invoke(_cells['cleanup_fn']) if sx_truthy(_cells['cleanup_fn']) else NIL), for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']), _sx_cell_set(_cells, 'deps', []), (lambda ctx: (lambda prev: _sx_begin(set_tracking_context(ctx), (lambda result: _sx_begin(set_tracking_context(prev), _sx_cell_set(_cells, 'deps', tracking_context_deps(ctx)), (_sx_cell_set(_cells, 'cleanup_fn', result) if sx_truthy(is_callable(result)) else NIL)))(invoke(effect_fn))))(get_tracking_context()))(make_tracking_context(run_effect))) if sx_truthy((not sx_truthy(_cells['disposed']))) else NIL)
|
||||
run_effect()
|
||||
return _sx_fn(lambda : (
|
||||
_sx_cell_set(_cells, 'disposed', True),
|
||||
(cleanup_fn() if sx_truthy(_cells['cleanup_fn']) else NIL),
|
||||
(invoke(_cells['cleanup_fn']) if sx_truthy(_cells['cleanup_fn']) else NIL),
|
||||
for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']),
|
||||
_sx_cell_set(_cells, 'deps', [])
|
||||
)[-1])
|
||||
@@ -1459,7 +1369,7 @@ _batch_queue = []
|
||||
# batch
|
||||
def batch(thunk):
|
||||
_batch_depth = (_batch_depth + 1)
|
||||
thunk()
|
||||
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)
|
||||
|
||||
@@ -1493,7 +1403,7 @@ _store_registry = {}
|
||||
def def_store(name, init_fn):
|
||||
registry = _store_registry
|
||||
if sx_truthy((not sx_truthy(has_key_p(registry, name)))):
|
||||
_store_registry = assoc(registry, name, init_fn())
|
||||
_store_registry = assoc(registry, name, invoke(init_fn))
|
||||
return get(_store_registry, name)
|
||||
|
||||
# use-store
|
||||
@@ -1510,7 +1420,7 @@ emit_event = lambda el, event_name, detail: dom_dispatch(el, event_name, detail)
|
||||
on_event = lambda el, event_name, handler: 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))((transform_fn(detail) if sx_truthy(transform_fn) else detail)))(event_detail(e)))))
|
||||
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)))))
|
||||
|
||||
|
||||
# =========================================================================
|
||||
@@ -1549,6 +1459,9 @@ def _wrap_aser_outputs():
|
||||
# Public API
|
||||
# =========================================================================
|
||||
|
||||
# Wrap aser outputs to return SxExpr
|
||||
_wrap_aser_outputs()
|
||||
|
||||
# Set HTML as default adapter
|
||||
_setup_html_adapter()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user