Replace invoke with cek-call in reactive island primitives

All signal operations (computed, effect, batch, etc.) now dispatch
function calls through cek-call, which routes SX lambdas via cek-run
and native callables via apply. This replaces the invoke shim.

Key changes:
- cek.sx: add cek-call (defined before reactive-shift-deref), replace
  invoke in subscriber disposal and ReactiveResetFrame handler
- signals.sx: replace all 11 invoke calls with cek-call
- js.sx: fix octal escape in js-quote-string (char-from-code 0)
- platform_js.py: fix JS append to match Python (list concat semantics),
  add Continuation type guard in PLATFORM_CEK_JS, add scheduleIdle
  safety check, module ordering (cek before signals)
- platform_py.py: fix ident-char regex (remove [ ] from valid chars),
  module ordering (cek before signals)
- run_js_sx.py: emit PLATFORM_CEK_JS before transpiled spec files
- page-functions.sx: add cek and provide page functions for SX URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 10:11:48 +00:00
parent 30d9d4aa4c
commit 455e48df07
20 changed files with 911 additions and 600 deletions

View File

@@ -952,7 +952,7 @@ char_from_code = PRIMITIVES["char-from-code"]
import re as _re_parser
_IDENT_START_RE = _re_parser.compile(r"[a-zA-Z_~*+\-><=/!?&]")
_IDENT_CHAR_RE = _re_parser.compile(r"[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]")
_IDENT_CHAR_RE = _re_parser.compile(r"[a-zA-Z0-9_~*+\-><=/!?.:&/#,]")
def ident_start_p(ch):
@@ -2179,36 +2179,39 @@ def sx_parse(source):
_cells['pos'] = 0
len_src = len(source)
def skip_comment():
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (not sx_truthy((nth(source, _cells['pos']) == '\n'))))):
_cells['pos'] = (_cells['pos'] + 1)
return skip_comment()
return NIL
while True:
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (not sx_truthy((nth(source, _cells['pos']) == '\n'))))):
_cells['pos'] = (_cells['pos'] + 1)
continue
break
def skip_ws():
if sx_truthy((_cells['pos'] < len_src)):
ch = nth(source, _cells['pos'])
if sx_truthy(((ch == ' ') if sx_truthy((ch == ' ')) else ((ch == '\t') if sx_truthy((ch == '\t')) else ((ch == '\n') if sx_truthy((ch == '\n')) else (ch == '\r'))))):
_cells['pos'] = (_cells['pos'] + 1)
return skip_ws()
elif sx_truthy((ch == ';')):
_cells['pos'] = (_cells['pos'] + 1)
skip_comment()
return skip_ws()
else:
return NIL
return NIL
while True:
if sx_truthy((_cells['pos'] < len_src)):
ch = nth(source, _cells['pos'])
if sx_truthy(((ch == ' ') if sx_truthy((ch == ' ')) else ((ch == '\t') if sx_truthy((ch == '\t')) else ((ch == '\n') if sx_truthy((ch == '\n')) else (ch == '\r'))))):
_cells['pos'] = (_cells['pos'] + 1)
continue
elif sx_truthy((ch == ';')):
_cells['pos'] = (_cells['pos'] + 1)
skip_comment()
continue
else:
break
break
def hex_digit_value(ch):
return index_of('0123456789abcdef', lower(ch))
def read_string():
_cells['pos'] = (_cells['pos'] + 1)
_cells['buf'] = ''
def read_str_loop():
while True:
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unterminated string')
error('Unterminated string')
break
else:
ch = nth(source, _cells['pos'])
if sx_truthy((ch == '"')):
_cells['pos'] = (_cells['pos'] + 1)
return NIL
break
elif sx_truthy((ch == '\\')):
_cells['pos'] = (_cells['pos'] + 1)
esc = nth(source, _cells['pos'])
@@ -2223,25 +2226,23 @@ def sx_parse(source):
d3 = hex_digit_value(nth(source, _cells['pos']))
_ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
_cells['buf'] = sx_str(_cells['buf'], char_from_code(((d0 * 4096) + (d1 * 256))))
return read_str_loop()
continue
else:
_cells['buf'] = sx_str(_cells['buf'], ('\n' if sx_truthy((esc == 'n')) else ('\t' if sx_truthy((esc == 't')) else ('\r' if sx_truthy((esc == 'r')) else esc))))
_cells['pos'] = (_cells['pos'] + 1)
return read_str_loop()
continue
else:
_cells['buf'] = sx_str(_cells['buf'], ch)
_cells['pos'] = (_cells['pos'] + 1)
return read_str_loop()
read_str_loop()
continue
return _cells['buf']
def read_ident():
start = _cells['pos']
def read_ident_loop():
while True:
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else ident_char_p(nth(source, _cells['pos'])))):
_cells['pos'] = (_cells['pos'] + 1)
return read_ident_loop()
return NIL
read_ident_loop()
continue
break
return slice(source, start, _cells['pos'])
def read_keyword():
_cells['pos'] = (_cells['pos'] + 1)
@@ -2251,10 +2252,11 @@ def sx_parse(source):
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (nth(source, _cells['pos']) == '-'))):
_cells['pos'] = (_cells['pos'] + 1)
def read_digits():
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (lambda c: ((c >= '0') if not sx_truthy((c >= '0')) else (c <= '9')))(nth(source, _cells['pos'])))):
_cells['pos'] = (_cells['pos'] + 1)
return read_digits()
return NIL
while True:
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (lambda c: ((c >= '0') if not sx_truthy((c >= '0')) else (c <= '9')))(nth(source, _cells['pos'])))):
_cells['pos'] = (_cells['pos'] + 1)
continue
break
read_digits()
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (nth(source, _cells['pos']) == '.'))):
_cells['pos'] = (_cells['pos'] + 1)
@@ -2277,52 +2279,52 @@ def sx_parse(source):
return make_symbol(name)
def read_list(close_ch):
items = []
def read_list_loop():
while True:
skip_ws()
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unterminated list')
error('Unterminated list')
break
else:
if sx_truthy((nth(source, _cells['pos']) == close_ch)):
_cells['pos'] = (_cells['pos'] + 1)
return NIL
break
else:
items.append(read_expr())
return read_list_loop()
read_list_loop()
continue
return items
def read_map():
result = {}
def read_map_loop():
while True:
skip_ws()
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unterminated map')
error('Unterminated map')
break
else:
if sx_truthy((nth(source, _cells['pos']) == '}')):
_cells['pos'] = (_cells['pos'] + 1)
return NIL
break
else:
key_expr = read_expr()
key_str = (keyword_name(key_expr) if sx_truthy((type_of(key_expr) == 'keyword')) else sx_str(key_expr))
val_expr = read_expr()
result[key_str] = val_expr
return read_map_loop()
read_map_loop()
continue
return result
def read_raw_string():
_cells['buf'] = ''
def raw_loop():
while True:
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unterminated raw string')
error('Unterminated raw string')
break
else:
ch = nth(source, _cells['pos'])
if sx_truthy((ch == '|')):
_cells['pos'] = (_cells['pos'] + 1)
return NIL
break
else:
_cells['buf'] = sx_str(_cells['buf'], ch)
_cells['pos'] = (_cells['pos'] + 1)
return raw_loop()
raw_loop()
continue
return _cells['buf']
def read_expr():
skip_ws()
@@ -2343,6 +2345,9 @@ def sx_parse(source):
return read_string()
elif sx_truthy((ch == ':')):
return read_keyword()
elif sx_truthy((ch == "'")):
_cells['pos'] = (_cells['pos'] + 1)
return [make_symbol('quote'), read_expr()]
elif sx_truthy((ch == '`')):
_cells['pos'] = (_cells['pos'] + 1)
return [make_symbol('quasiquote'), read_expr()]
@@ -2388,13 +2393,12 @@ def sx_parse(source):
else:
return error(sx_str('Unexpected character: ', ch))
exprs = []
def parse_loop():
while True:
skip_ws()
if sx_truthy((_cells['pos'] < len_src)):
exprs.append(read_expr())
return parse_loop()
return NIL
parse_loop()
continue
break
return exprs
# sx-serialize
@@ -3862,234 +3866,6 @@ def prepare_url_expr(url_path, env):
return auto_quote_unknowns(expr, env)
# === Transpiled from signals (reactive signal runtime) ===
# make-signal
def make_signal(value):
return {'__signal': True, 'value': value, 'subscribers': [], 'deps': []}
# signal?
def is_signal(x):
return (dict_p(x) if not sx_truthy(dict_p(x)) else has_key_p(x, '__signal'))
# signal-value
def signal_value(s):
return get(s, 'value')
# signal-set-value!
def signal_set_value(s, v):
return _sx_dict_set(s, 'value', v)
# signal-subscribers
def signal_subscribers(s):
return get(s, 'subscribers')
# signal-add-sub!
def signal_add_sub(s, f):
if sx_truthy((not sx_truthy(contains_p(get(s, 'subscribers'), f)))):
return _sx_append(get(s, 'subscribers'), f)
return NIL
# signal-remove-sub!
def signal_remove_sub(s, f):
return _sx_dict_set(s, 'subscribers', filter(lambda sub: (not sx_truthy(is_identical(sub, f))), get(s, 'subscribers')))
# signal-deps
def signal_deps(s):
return get(s, 'deps')
# signal-set-deps!
def signal_set_deps(s, deps):
return _sx_dict_set(s, 'deps', deps)
# signal
def signal(initial_value):
return make_signal(initial_value)
# deref
def deref(s):
if sx_truthy((not sx_truthy(is_signal(s)))):
return s
else:
ctx = sx_context('sx-reactive', NIL)
if sx_truthy(ctx):
dep_list = get(ctx, 'deps')
notify_fn = get(ctx, 'notify')
if sx_truthy((not sx_truthy(contains_p(dep_list, s)))):
dep_list.append(s)
signal_add_sub(s, notify_fn)
return signal_value(s)
# reset!
def reset_b(s, value):
if sx_truthy(is_signal(s)):
old = signal_value(s)
if sx_truthy((not sx_truthy(is_identical(old, value)))):
signal_set_value(s, value)
return notify_subscribers(s)
return NIL
return NIL
# swap!
def swap_b(s, f, *args):
if sx_truthy(is_signal(s)):
old = signal_value(s)
new_val = apply(f, cons(old, args))
if sx_truthy((not sx_truthy(is_identical(old, new_val)))):
signal_set_value(s, new_val)
return notify_subscribers(s)
return NIL
return NIL
# computed
def computed(compute_fn):
s = make_signal(NIL)
deps = []
compute_ctx = NIL
recompute = _sx_fn(lambda : (
for_each(lambda dep: signal_remove_sub(dep, recompute), signal_deps(s)),
signal_set_deps(s, []),
(lambda ctx: _sx_begin(scope_push('sx-reactive', ctx), (lambda new_val: _sx_begin(scope_pop('sx-reactive'), signal_set_deps(s, get(ctx, 'deps')), (lambda old: _sx_begin(signal_set_value(s, new_val), (notify_subscribers(s) if sx_truthy((not sx_truthy(is_identical(old, new_val)))) else NIL)))(signal_value(s))))(invoke(compute_fn))))({'deps': [], 'notify': recompute})
)[-1])
recompute()
register_in_scope(lambda : dispose_computed(s))
return s
# effect
def effect(effect_fn):
_cells = {}
_cells['deps'] = []
_cells['disposed'] = False
_cells['cleanup_fn'] = NIL
run_effect = lambda : (_sx_begin((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: _sx_begin(scope_push('sx-reactive', ctx), (lambda result: _sx_begin(scope_pop('sx-reactive'), _sx_cell_set(_cells, 'deps', get(ctx, 'deps')), (_sx_cell_set(_cells, 'cleanup_fn', result) if sx_truthy(is_callable(result)) else NIL)))(invoke(effect_fn))))({'deps': [], 'notify': run_effect})) if sx_truthy((not sx_truthy(_cells['disposed']))) else NIL)
run_effect()
dispose_fn = _sx_fn(lambda : (
_sx_cell_set(_cells, 'disposed', True),
(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])
register_in_scope(dispose_fn)
return dispose_fn
# *batch-depth*
_batch_depth = 0
# *batch-queue*
_batch_queue = []
# batch
def batch(thunk):
_batch_depth = (_batch_depth + 1)
invoke(thunk)
_batch_depth = (_batch_depth - 1)
if sx_truthy((_batch_depth == 0)):
queue = _batch_queue
_batch_queue = []
seen = []
pending = []
for s in queue:
for sub in signal_subscribers(s):
if sx_truthy((not sx_truthy(contains_p(seen, sub)))):
seen.append(sub)
pending.append(sub)
for sub in pending:
sub()
return NIL
return NIL
# notify-subscribers
def notify_subscribers(s):
if sx_truthy((_batch_depth > 0)):
if sx_truthy((not sx_truthy(contains_p(_batch_queue, s)))):
return _sx_append(_batch_queue, s)
return NIL
else:
return flush_subscribers(s)
# flush-subscribers
def flush_subscribers(s):
for sub in signal_subscribers(s):
sub()
return NIL
# dispose-computed
def dispose_computed(s):
if sx_truthy(is_signal(s)):
for dep in signal_deps(s):
signal_remove_sub(dep, NIL)
return signal_set_deps(s, [])
return NIL
# with-island-scope
def with_island_scope(scope_fn, body_fn):
scope_push('sx-island-scope', scope_fn)
result = body_fn()
scope_pop('sx-island-scope')
return result
# register-in-scope
def register_in_scope(disposable):
collector = sx_context('sx-island-scope', NIL)
if sx_truthy(collector):
return invoke(collector, disposable)
return NIL
# with-marsh-scope
def with_marsh_scope(marsh_el, body_fn):
disposers = []
with_island_scope(lambda d: _sx_append(disposers, d), body_fn)
return dom_set_data(marsh_el, 'sx-marsh-disposers', disposers)
# dispose-marsh-scope
def dispose_marsh_scope(marsh_el):
disposers = dom_get_data(marsh_el, 'sx-marsh-disposers')
if sx_truthy(disposers):
for d in disposers:
invoke(d)
return dom_set_data(marsh_el, 'sx-marsh-disposers', NIL)
return NIL
# *store-registry*
_store_registry = {}
# def-store
def def_store(name, init_fn):
registry = _store_registry
if sx_truthy((not sx_truthy(has_key_p(registry, name)))):
_store_registry = assoc(registry, name, invoke(init_fn))
return get(_store_registry, name)
# use-store
def use_store(name):
if sx_truthy(has_key_p(_store_registry, name)):
return get(_store_registry, name)
else:
return error(sx_str('Store not found: ', name, '. Call (def-store ...) before (use-store ...).'))
# clear-stores
def clear_stores():
return _sx_cell_set(_cells, '_store_registry', {})
# emit-event
def emit_event(el, event_name, detail):
return dom_dispatch(el, event_name, detail)
# on-event
def on_event(el, event_name, handler):
return dom_listen(el, event_name, handler)
# bridge-event
def bridge_event(el, event_name, target_signal, transform_fn):
return effect(lambda : (lambda remove: remove)(dom_listen(el, event_name, lambda e: (lambda detail: (lambda new_val: reset_b(target_signal, new_val))((invoke(transform_fn, detail) if sx_truthy(transform_fn) else detail)))(event_detail(e)))))
# resource
def resource(fetch_fn):
state = signal({'loading': True, 'data': NIL, 'error': NIL})
promise_then(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
# === Transpiled from cek (explicit CEK machine evaluator) ===
# cek-run
@@ -4371,6 +4147,18 @@ def step_sf_shift(args, env, kont):
def step_sf_deref(args, env, kont):
return make_cek_state(first(args), env, kont_push(make_deref_frame(env), kont))
# cek-call
def cek_call(f, args):
a = ([] if sx_truthy(is_nil(args)) else args)
if sx_truthy(is_nil(f)):
return NIL
elif sx_truthy(is_lambda(f)):
return cek_run(continue_with_call(f, a, {}, a, []))
elif sx_truthy(is_callable(f)):
return apply(f, a)
else:
return NIL
# reactive-shift-deref
def reactive_shift_deref(sig, env, kont):
_cells = {}
@@ -4381,14 +4169,14 @@ def reactive_shift_deref(sig, env, kont):
update_fn = get(reset_frame, 'update-fn')
_cells['sub_disposers'] = []
subscriber = _sx_fn(lambda : (
for_each(lambda d: invoke(d), _cells['sub_disposers']),
for_each(lambda d: cek_call(d, NIL), _cells['sub_disposers']),
_sx_cell_set(_cells, 'sub_disposers', []),
(lambda new_reset: (lambda new_kont: with_island_scope(lambda d: _sx_append(_cells['sub_disposers'], d), lambda : cek_run(make_cek_value(signal_value(sig), env, new_kont))))(concat(captured_frames, [new_reset], remaining_kont)))(make_reactive_reset_frame(env, update_fn, False))
)[-1])
signal_add_sub(sig, subscriber)
register_in_scope(_sx_fn(lambda : (
signal_remove_sub(sig, subscriber),
for_each(lambda d: invoke(d), _cells['sub_disposers'])
for_each(lambda d: cek_call(d, NIL), _cells['sub_disposers'])
)[-1]))
initial_kont = concat(captured_frames, [reset_frame], remaining_kont)
return make_cek_value(signal_value(sig), env, initial_kont)
@@ -4610,7 +4398,7 @@ def step_continue(state):
update_fn = get(frame, 'update-fn')
first_p = get(frame, 'first-render')
if sx_truthy((update_fn if not sx_truthy(update_fn) else (not sx_truthy(first_p)))):
invoke(update_fn, value)
cek_call(update_fn, [value])
return make_cek_value(value, env, rest_k)
elif sx_truthy((ft == 'scope')):
name = get(frame, 'name')
@@ -4686,6 +4474,234 @@ def trampoline_cek(val):
return val
# === Transpiled from signals (reactive signal runtime) ===
# make-signal
def make_signal(value):
return {'__signal': True, 'value': value, 'subscribers': [], 'deps': []}
# signal?
def is_signal(x):
return (dict_p(x) if not sx_truthy(dict_p(x)) else has_key_p(x, '__signal'))
# signal-value
def signal_value(s):
return get(s, 'value')
# signal-set-value!
def signal_set_value(s, v):
return _sx_dict_set(s, 'value', v)
# signal-subscribers
def signal_subscribers(s):
return get(s, 'subscribers')
# signal-add-sub!
def signal_add_sub(s, f):
if sx_truthy((not sx_truthy(contains_p(get(s, 'subscribers'), f)))):
return _sx_append(get(s, 'subscribers'), f)
return NIL
# signal-remove-sub!
def signal_remove_sub(s, f):
return _sx_dict_set(s, 'subscribers', filter(lambda sub: (not sx_truthy(is_identical(sub, f))), get(s, 'subscribers')))
# signal-deps
def signal_deps(s):
return get(s, 'deps')
# signal-set-deps!
def signal_set_deps(s, deps):
return _sx_dict_set(s, 'deps', deps)
# signal
def signal(initial_value):
return make_signal(initial_value)
# deref
def deref(s):
if sx_truthy((not sx_truthy(is_signal(s)))):
return s
else:
ctx = sx_context('sx-reactive', NIL)
if sx_truthy(ctx):
dep_list = get(ctx, 'deps')
notify_fn = get(ctx, 'notify')
if sx_truthy((not sx_truthy(contains_p(dep_list, s)))):
dep_list.append(s)
signal_add_sub(s, notify_fn)
return signal_value(s)
# reset!
def reset_b(s, value):
if sx_truthy(is_signal(s)):
old = signal_value(s)
if sx_truthy((not sx_truthy(is_identical(old, value)))):
signal_set_value(s, value)
return notify_subscribers(s)
return NIL
return NIL
# swap!
def swap_b(s, f, *args):
if sx_truthy(is_signal(s)):
old = signal_value(s)
new_val = apply(f, cons(old, args))
if sx_truthy((not sx_truthy(is_identical(old, new_val)))):
signal_set_value(s, new_val)
return notify_subscribers(s)
return NIL
return NIL
# computed
def computed(compute_fn):
s = make_signal(NIL)
deps = []
compute_ctx = NIL
recompute = _sx_fn(lambda : (
for_each(lambda dep: signal_remove_sub(dep, recompute), signal_deps(s)),
signal_set_deps(s, []),
(lambda ctx: _sx_begin(scope_push('sx-reactive', ctx), (lambda new_val: _sx_begin(scope_pop('sx-reactive'), signal_set_deps(s, get(ctx, 'deps')), (lambda old: _sx_begin(signal_set_value(s, new_val), (notify_subscribers(s) if sx_truthy((not sx_truthy(is_identical(old, new_val)))) else NIL)))(signal_value(s))))(cek_call(compute_fn, NIL))))({'deps': [], 'notify': recompute})
)[-1])
recompute()
register_in_scope(lambda : dispose_computed(s))
return s
# effect
def effect(effect_fn):
_cells = {}
_cells['deps'] = []
_cells['disposed'] = False
_cells['cleanup_fn'] = NIL
run_effect = lambda : (_sx_begin((cek_call(_cells['cleanup_fn'], NIL) if sx_truthy(_cells['cleanup_fn']) else NIL), for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']), _sx_cell_set(_cells, 'deps', []), (lambda ctx: _sx_begin(scope_push('sx-reactive', ctx), (lambda result: _sx_begin(scope_pop('sx-reactive'), _sx_cell_set(_cells, 'deps', get(ctx, 'deps')), (_sx_cell_set(_cells, 'cleanup_fn', result) if sx_truthy(is_callable(result)) else NIL)))(cek_call(effect_fn, NIL))))({'deps': [], 'notify': run_effect})) if sx_truthy((not sx_truthy(_cells['disposed']))) else NIL)
run_effect()
dispose_fn = _sx_fn(lambda : (
_sx_cell_set(_cells, 'disposed', True),
(cek_call(_cells['cleanup_fn'], NIL) if sx_truthy(_cells['cleanup_fn']) else NIL),
for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']),
_sx_cell_set(_cells, 'deps', [])
)[-1])
register_in_scope(dispose_fn)
return dispose_fn
# *batch-depth*
_batch_depth = 0
# *batch-queue*
_batch_queue = []
# batch
def batch(thunk):
_batch_depth = (_batch_depth + 1)
cek_call(thunk, NIL)
_batch_depth = (_batch_depth - 1)
if sx_truthy((_batch_depth == 0)):
queue = _batch_queue
_batch_queue = []
seen = []
pending = []
for s in queue:
for sub in signal_subscribers(s):
if sx_truthy((not sx_truthy(contains_p(seen, sub)))):
seen.append(sub)
pending.append(sub)
for sub in pending:
sub()
return NIL
return NIL
# notify-subscribers
def notify_subscribers(s):
if sx_truthy((_batch_depth > 0)):
if sx_truthy((not sx_truthy(contains_p(_batch_queue, s)))):
return _sx_append(_batch_queue, s)
return NIL
else:
return flush_subscribers(s)
# flush-subscribers
def flush_subscribers(s):
for sub in signal_subscribers(s):
sub()
return NIL
# dispose-computed
def dispose_computed(s):
if sx_truthy(is_signal(s)):
for dep in signal_deps(s):
signal_remove_sub(dep, NIL)
return signal_set_deps(s, [])
return NIL
# with-island-scope
def with_island_scope(scope_fn, body_fn):
scope_push('sx-island-scope', scope_fn)
result = body_fn()
scope_pop('sx-island-scope')
return result
# register-in-scope
def register_in_scope(disposable):
collector = sx_context('sx-island-scope', NIL)
if sx_truthy(collector):
return cek_call(collector, [disposable])
return NIL
# with-marsh-scope
def with_marsh_scope(marsh_el, body_fn):
disposers = []
with_island_scope(lambda d: _sx_append(disposers, d), body_fn)
return dom_set_data(marsh_el, 'sx-marsh-disposers', disposers)
# dispose-marsh-scope
def dispose_marsh_scope(marsh_el):
disposers = dom_get_data(marsh_el, 'sx-marsh-disposers')
if sx_truthy(disposers):
for d in disposers:
cek_call(d, NIL)
return dom_set_data(marsh_el, 'sx-marsh-disposers', NIL)
return NIL
# *store-registry*
_store_registry = {}
# def-store
def def_store(name, init_fn):
registry = _store_registry
if sx_truthy((not sx_truthy(has_key_p(registry, name)))):
_store_registry = assoc(registry, name, cek_call(init_fn, NIL))
return get(_store_registry, name)
# use-store
def use_store(name):
if sx_truthy(has_key_p(_store_registry, name)):
return get(_store_registry, name)
else:
return error(sx_str('Store not found: ', name, '. Call (def-store ...) before (use-store ...).'))
# clear-stores
def clear_stores():
return _sx_cell_set(_cells, '_store_registry', {})
# emit-event
def emit_event(el, event_name, detail):
return dom_dispatch(el, event_name, detail)
# on-event
def on_event(el, event_name, handler):
return dom_listen(el, event_name, handler)
# bridge-event
def bridge_event(el, event_name, target_signal, transform_fn):
return effect(lambda : (lambda remove: remove)(dom_listen(el, event_name, lambda e: (lambda detail: (lambda new_val: reset_b(target_signal, new_val))((cek_call(transform_fn, [detail]) if sx_truthy(transform_fn) else detail)))(event_detail(e)))))
# resource
def resource(fetch_fn):
state = signal({'loading': True, 'data': NIL, 'error': NIL})
promise_then(cek_call(fetch_fn, NIL), lambda data: reset_b(state, {'loading': False, 'data': data, 'error': NIL}), lambda err: reset_b(state, {'loading': False, 'data': NIL, 'error': err}))
return state
# === Transpiled from adapter-async ===
# async-render