Named freeze scopes for serializable reactive state
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m20s

Replace raw CEK state serialization with named freeze scopes.
A freeze scope collects signals registered within it. On freeze,
signal values are serialized to SX. On thaw, values are restored.

- freeze-scope: scoped effect delimiter for signal collection
- freeze-signal: register a signal with a name in the current scope
- cek-freeze-scope / cek-thaw-scope: freeze/thaw by scope name
- freeze-to-sx / thaw-from-sx: full SX text round-trip
- cek-freeze-all / cek-thaw-all: batch operations

Also: register boolean?, symbol?, keyword? predicates in both
Python and JS platforms with proper var aliases.

Demo: counter + name input with Freeze/Thaw buttons.
Frozen SX: {:name "demo" :signals {:count 5 :name "world"}}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 22:42:21 +00:00
parent a759f4da3b
commit cb4f4b85e5
4 changed files with 287 additions and 376 deletions

View File

@@ -4595,109 +4595,69 @@ def trampoline_cek(val):
else:
return val
# primitive-name
def primitive_name(f):
_cells = {}
if sx_truthy(is_lambda(f)):
return lambda_name(f)
else:
_cells['result'] = NIL
names = ['+', '-', '*', '/', '=', '<', '>', '<=', '>=', 'not', 'and', 'or', 'str', 'len', 'first', 'rest', 'nth', 'list', 'cons', 'append', 'map', 'filter', 'reduce', 'for-each', 'some', 'every?', 'get', 'keys', 'dict', 'dict?', 'has-key?', 'assoc', 'empty?', 'nil?', 'number?', 'string?', 'list?', 'type-of', 'identity', 'inc', 'dec', 'mod', 'join', 'split', 'slice', 'contains?', 'starts-with?', 'upper', 'lower', 'trim', 'replace', 'format']
for name in names:
if sx_truthy((is_nil(_cells['result']) if not sx_truthy(is_nil(_cells['result'])) else (is_primitive(name) if not sx_truthy(is_primitive(name)) else is_identical(f, get_primitive(name))))):
_cells['result'] = name
return _cells['result']
# freeze-registry
freeze_registry = {}
# cek-serialize-value
def cek_serialize_value(val):
if sx_truthy(is_nil(val)):
# freeze-signal
def freeze_signal(name, sig):
scope_name = sx_context('sx-freeze-scope', NIL)
if sx_truthy(scope_name):
entries = (get(freeze_registry, scope_name) if sx_truthy(get(freeze_registry, scope_name)) else [])
entries.append({'name': name, 'signal': sig})
return _sx_dict_set(freeze_registry, scope_name, entries)
return NIL
# freeze-scope
def freeze_scope(name, body_fn):
scope_push('sx-freeze-scope', name)
freeze_registry[name] = []
cek_call(body_fn, NIL)
scope_pop('sx-freeze-scope')
return NIL
# cek-freeze-scope
def cek_freeze_scope(name):
entries = (get(freeze_registry, name) if sx_truthy(get(freeze_registry, name)) else [])
signals_dict = {}
for entry in entries:
signals_dict[get(entry, 'name')] = signal_value(get(entry, 'signal'))
return {'name': name, 'signals': signals_dict}
# cek-freeze-all
def cek_freeze_all():
return map(lambda name: cek_freeze_scope(name), keys(freeze_registry))
# cek-thaw-scope
def cek_thaw_scope(name, frozen):
entries = (get(freeze_registry, name) if sx_truthy(get(freeze_registry, name)) else [])
values = get(frozen, 'signals')
if sx_truthy(values):
for entry in entries:
sig_name = get(entry, 'name')
sig = get(entry, 'signal')
val = get(values, sig_name)
if sx_truthy((not sx_truthy(is_nil(val)))):
reset_b(sig, val)
return NIL
elif sx_truthy(number_p(val)):
return val
elif sx_truthy(string_p(val)):
return val
elif sx_truthy(boolean_p(val)):
return val
elif sx_truthy(symbol_p(val)):
return val
elif sx_truthy(keyword_p(val)):
return val
elif sx_truthy(list_p(val)):
return map(cek_serialize_value, val)
elif sx_truthy(is_lambda(val)):
return [make_symbol('lambda'), lambda_params(val), lambda_body(val)]
elif sx_truthy(is_callable(val)):
return [make_symbol('primitive'), (primitive_name(val) if sx_truthy(primitive_name(val)) else '?')]
elif sx_truthy(dict_p(val)):
return cek_serialize_env(val)
else:
return sx_str(val)
return NIL
# cek-serialize-env
def cek_serialize_env(env):
result = {}
ks = keys(env)
for k in ks:
result[k] = cek_serialize_value(get(env, k))
return result
# cek-thaw-all
def cek_thaw_all(frozen_list):
for frozen in frozen_list:
cek_thaw_scope(get(frozen, 'name'), frozen)
return NIL
# cek-serialize-frame
def cek_serialize_frame(frame):
result = {}
ks = keys(frame)
for k in ks:
v = get(frame, k)
result[k] = (v if sx_truthy((k == 'type')) else (v if sx_truthy((k == 'tag')) else (cek_serialize_value(v) if sx_truthy((k == 'f')) else (cek_serialize_env(v) if sx_truthy((k == 'env')) else (map(cek_serialize_value, v) if sx_truthy((k == 'evaled')) else (v if sx_truthy((k == 'remaining')) else (map(cek_serialize_value, v) if sx_truthy((k == 'results')) else (v if sx_truthy((k == 'raw-args')) else (cek_serialize_value(v) if sx_truthy((k == 'current-item')) else (v if sx_truthy((k == 'name')) else (cek_serialize_value(v) if sx_truthy((k == 'update-fn')) else (v if sx_truthy((k == 'first-render')) else cek_serialize_value(v)))))))))))))
return result
# freeze-to-sx
def freeze_to_sx(name):
return sx_serialize(cek_freeze_scope(name))
# cek-freeze
def cek_freeze(state):
return {'phase': get(state, 'phase'), 'control': get(state, 'control'), 'value': cek_serialize_value(get(state, 'value')), 'env': cek_serialize_env(get(state, 'env')), 'kont': map(cek_serialize_frame, get(state, 'kont'))}
# cek-thaw-value
def cek_thaw_value(val):
if sx_truthy(is_nil(val)):
return NIL
elif sx_truthy(number_p(val)):
return val
elif sx_truthy(string_p(val)):
return val
elif sx_truthy(boolean_p(val)):
return val
elif sx_truthy(symbol_p(val)):
return val
elif sx_truthy(keyword_p(val)):
return val
elif sx_truthy((list_p(val) if not sx_truthy(list_p(val)) else ((not sx_truthy(empty_p(val))) if not sx_truthy((not sx_truthy(empty_p(val)))) else (symbol_p(first(val)) if not sx_truthy(symbol_p(first(val))) else (symbol_name(first(val)) == 'primitive'))))):
return get_primitive(nth(val, 1))
elif sx_truthy((list_p(val) if not sx_truthy(list_p(val)) else ((not sx_truthy(empty_p(val))) if not sx_truthy((not sx_truthy(empty_p(val)))) else (symbol_p(first(val)) if not sx_truthy(symbol_p(first(val))) else (symbol_name(first(val)) == 'lambda'))))):
return make_lambda(nth(val, 1), nth(val, 2), {})
elif sx_truthy(list_p(val)):
return map(cek_thaw_value, val)
elif sx_truthy(dict_p(val)):
return cek_thaw_env(val)
else:
return val
# cek-thaw-env
def cek_thaw_env(frozen_env):
result = make_env()
for k in keys(frozen_env):
result[k] = cek_thaw_value(get(frozen_env, k))
return result
# cek-thaw-frame
def cek_thaw_frame(frozen_frame):
result = {}
ks = keys(frozen_frame)
for k in ks:
v = get(frozen_frame, k)
result[k] = (v if sx_truthy((k == 'type')) else (v if sx_truthy((k == 'tag')) else (cek_thaw_value(v) if sx_truthy((k == 'f')) else (cek_thaw_env(v) if sx_truthy((k == 'env')) else (map(cek_thaw_value, v) if sx_truthy((k == 'evaled')) else (v if sx_truthy((k == 'remaining')) else (map(cek_thaw_value, v) if sx_truthy((k == 'results')) else (v if sx_truthy((k == 'raw-args')) else (cek_thaw_value(v) if sx_truthy((k == 'current-item')) else (v if sx_truthy((k == 'name')) else (cek_thaw_value(v) if sx_truthy((k == 'update-fn')) else (v if sx_truthy((k == 'first-render')) else cek_thaw_value(v)))))))))))))
return result
# cek-thaw
def cek_thaw(frozen):
return {'phase': get(frozen, 'phase'), 'control': get(frozen, 'control'), 'value': cek_thaw_value(get(frozen, 'value')), 'env': cek_thaw_env(get(frozen, 'env')), 'kont': map(cek_thaw_frame, get(frozen, 'kont'))}
# thaw-from-sx
def thaw_from_sx(sx_text):
parsed = sx_parse(sx_text)
if sx_truthy((not sx_truthy(empty_p(parsed)))):
frozen = first(parsed)
return cek_thaw_scope(get(frozen, 'name'), frozen)
return NIL
# === Transpiled from signals (reactive signal runtime) ===