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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user