Named freeze scopes for serializable reactive state
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled

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 a8a24e20d2
4 changed files with 271 additions and 375 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-14T22:31:34Z";
var SX_VERSION = "2026-03-14T22:48:39Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -5292,73 +5292,72 @@ PRIMITIVES["eval-expr-cek"] = evalExprCek;
var trampolineCek = function(val) { return (isSxTruthy(isThunk(val)) ? evalExprCek(thunkExpr(val), thunkEnv(val)) : val); };
PRIMITIVES["trampoline-cek"] = trampolineCek;
// primitive-name
var primitiveName = function(f) { return (isSxTruthy(isLambda(f)) ? lambdaName(f) : (function() {
var result = NIL;
var 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"];
{ var _c = names; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; if (isSxTruthy((isSxTruthy(isNil(result)) && isSxTruthy(isPrimitive(name)) && isIdentical(f, getPrimitive(name))))) {
result = name;
} } }
return result;
})()); };
PRIMITIVES["primitive-name"] = primitiveName;
// freeze-registry
var freezeRegistry = {};
PRIMITIVES["freeze-registry"] = freezeRegistry;
// cek-serialize-value
var cekSerializeValue = function(val) { return (isSxTruthy(isNil(val)) ? NIL : (isSxTruthy(isNumber(val)) ? val : (isSxTruthy(isString(val)) ? val : (isSxTruthy(boolean_p(val)) ? val : (isSxTruthy(symbol_p(val)) ? val : (isSxTruthy(keyword_p(val)) ? val : (isSxTruthy(isList(val)) ? map(cekSerializeValue, val) : (isSxTruthy(isLambda(val)) ? [makeSymbol("lambda"), lambdaParams(val), lambdaBody(val)] : (isSxTruthy(isCallable(val)) ? [makeSymbol("primitive"), sxOr(primitiveName(val), "?")] : (isSxTruthy(isDict(val)) ? cekSerializeEnv(val) : (String(val)))))))))))); };
PRIMITIVES["cek-serialize-value"] = cekSerializeValue;
// cek-serialize-env
var cekSerializeEnv = function(env) { return (function() {
var result = {};
var ks = keys(env);
{ var _c = ks; for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; result[k] = cekSerializeValue(get(env, k)); } }
return result;
// freeze-signal
var freezeSignal = function(name, sig) { return (function() {
var scopeName = sxContext("sx-freeze-scope", NIL);
return (isSxTruthy(scopeName) ? (function() {
var entries = sxOr(get(freezeRegistry, scopeName), []);
entries.push({["name"]: name, ["signal"]: sig});
return dictSet(freezeRegistry, scopeName, entries);
})() : NIL);
})(); };
PRIMITIVES["cek-serialize-env"] = cekSerializeEnv;
PRIMITIVES["freeze-signal"] = freezeSignal;
// cek-serialize-frame
var cekSerializeFrame = function(frame) { return (function() {
var result = {};
var ks = keys(frame);
{ var _c = ks; for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; (function() {
var v = get(frame, k);
return dictSet(result, k, (isSxTruthy((k == "type")) ? v : (isSxTruthy((k == "tag")) ? v : (isSxTruthy((k == "f")) ? cekSerializeValue(v) : (isSxTruthy((k == "env")) ? cekSerializeEnv(v) : (isSxTruthy((k == "evaled")) ? map(cekSerializeValue, v) : (isSxTruthy((k == "remaining")) ? v : (isSxTruthy((k == "results")) ? map(cekSerializeValue, v) : (isSxTruthy((k == "raw-args")) ? v : (isSxTruthy((k == "current-item")) ? cekSerializeValue(v) : (isSxTruthy((k == "name")) ? v : (isSxTruthy((k == "update-fn")) ? cekSerializeValue(v) : (isSxTruthy((k == "first-render")) ? v : cekSerializeValue(v))))))))))))));
})(); } }
return result;
// freeze-scope
var freezeScope = function(name, bodyFn) { scopePush("sx-freeze-scope", name);
freezeRegistry[name] = [];
cekCall(bodyFn, NIL);
scopePop("sx-freeze-scope");
return NIL; };
PRIMITIVES["freeze-scope"] = freezeScope;
// cek-freeze-scope
var cekFreezeScope = function(name) { return (function() {
var entries = sxOr(get(freezeRegistry, name), []);
var signalsDict = {};
{ var _c = entries; for (var _i = 0; _i < _c.length; _i++) { var entry = _c[_i]; signalsDict[get(entry, "name")] = signalValue(get(entry, "signal")); } }
return {["name"]: name, ["signals"]: signalsDict};
})(); };
PRIMITIVES["cek-serialize-frame"] = cekSerializeFrame;
PRIMITIVES["cek-freeze-scope"] = cekFreezeScope;
// cek-freeze
var cekFreeze = function(state) { return {["phase"]: get(state, "phase"), ["control"]: get(state, "control"), ["value"]: cekSerializeValue(get(state, "value")), ["env"]: cekSerializeEnv(get(state, "env")), ["kont"]: map(cekSerializeFrame, get(state, "kont"))}; };
PRIMITIVES["cek-freeze"] = cekFreeze;
// cek-freeze-all
var cekFreezeAll = function() { return map(function(name) { return cekFreezeScope(name); }, keys(freezeRegistry)); };
PRIMITIVES["cek-freeze-all"] = cekFreezeAll;
// cek-thaw-value
var cekThawValue = function(val) { return (isSxTruthy(isNil(val)) ? NIL : (isSxTruthy(isNumber(val)) ? val : (isSxTruthy(isString(val)) ? val : (isSxTruthy(boolean_p(val)) ? val : (isSxTruthy(symbol_p(val)) ? val : (isSxTruthy(keyword_p(val)) ? val : (isSxTruthy((isSxTruthy(isList(val)) && isSxTruthy(!isSxTruthy(isEmpty(val))) && isSxTruthy(symbol_p(first(val))) && (symbolName(first(val)) == "primitive"))) ? getPrimitive(nth(val, 1)) : (isSxTruthy((isSxTruthy(isList(val)) && isSxTruthy(!isSxTruthy(isEmpty(val))) && isSxTruthy(symbol_p(first(val))) && (symbolName(first(val)) == "lambda"))) ? makeLambda(nth(val, 1), nth(val, 2), {}) : (isSxTruthy(isList(val)) ? map(cekThawValue, val) : (isSxTruthy(isDict(val)) ? cekThawEnv(val) : val)))))))))); };
PRIMITIVES["cek-thaw-value"] = cekThawValue;
// cek-thaw-env
var cekThawEnv = function(frozenEnv) { return (function() {
var result = makeEnv();
{ var _c = keys(frozenEnv); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; envSet(result, k, cekThawValue(get(frozenEnv, k))); } }
return result;
// cek-thaw-scope
var cekThawScope = function(name, frozen) { return (function() {
var entries = sxOr(get(freezeRegistry, name), []);
var values = get(frozen, "signals");
return (isSxTruthy(values) ? forEach(function(entry) { return (function() {
var sigName = get(entry, "name");
var sig = get(entry, "signal");
var val = get(values, sigName);
return (isSxTruthy(!isSxTruthy(isNil(val))) ? reset_b(sig, val) : NIL);
})(); }, entries) : NIL);
})(); };
PRIMITIVES["cek-thaw-env"] = cekThawEnv;
PRIMITIVES["cek-thaw-scope"] = cekThawScope;
// cek-thaw-frame
var cekThawFrame = function(frozenFrame) { return (function() {
var result = {};
var ks = keys(frozenFrame);
{ var _c = ks; for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; (function() {
var v = get(frozenFrame, k);
return dictSet(result, k, (isSxTruthy((k == "type")) ? v : (isSxTruthy((k == "tag")) ? v : (isSxTruthy((k == "f")) ? cekThawValue(v) : (isSxTruthy((k == "env")) ? cekThawEnv(v) : (isSxTruthy((k == "evaled")) ? map(cekThawValue, v) : (isSxTruthy((k == "remaining")) ? v : (isSxTruthy((k == "results")) ? map(cekThawValue, v) : (isSxTruthy((k == "raw-args")) ? v : (isSxTruthy((k == "current-item")) ? cekThawValue(v) : (isSxTruthy((k == "name")) ? v : (isSxTruthy((k == "update-fn")) ? cekThawValue(v) : (isSxTruthy((k == "first-render")) ? v : cekThawValue(v))))))))))))));
})(); } }
return result;
// cek-thaw-all
var cekThawAll = function(frozenList) { return forEach(function(frozen) { return cekThawScope(get(frozen, "name"), frozen); }, frozenList); };
PRIMITIVES["cek-thaw-all"] = cekThawAll;
// freeze-to-sx
var freezeToSx = function(name) { return sxSerialize(cekFreezeScope(name)); };
PRIMITIVES["freeze-to-sx"] = freezeToSx;
// thaw-from-sx
var thawFromSx = function(sxText) { return (function() {
var parsed = sxParse(sxText);
return (isSxTruthy(!isSxTruthy(isEmpty(parsed))) ? (function() {
var frozen = first(parsed);
return cekThawScope(get(frozen, "name"), frozen);
})() : NIL);
})(); };
PRIMITIVES["cek-thaw-frame"] = cekThawFrame;
// cek-thaw
var cekThaw = function(frozen) { return {["phase"]: get(frozen, "phase"), ["control"]: get(frozen, "control"), ["value"]: cekThawValue(get(frozen, "value")), ["env"]: cekThawEnv(get(frozen, "env")), ["kont"]: map(cekThawFrame, get(frozen, "kont"))}; };
PRIMITIVES["cek-thaw"] = cekThaw;
PRIMITIVES["thaw-from-sx"] = thawFromSx;
// === Transpiled from signals (reactive signal runtime) ===

View File

@@ -1034,163 +1034,98 @@
val)))
;; --------------------------------------------------------------------------
;; 13. CEK state serialization — freeze and resume computation
;; 13. Freeze scopes — named serializable state boundaries
;; --------------------------------------------------------------------------
;;
;; Serialize a CEK state to an s-expression. The result can be:
;; - Printed as text (sx-serialize)
;; - Stored, transmitted, content-addressed
;; - Parsed back (sx-parse) and resumed (cek-run)
;; A freeze scope collects signals registered within it. On freeze,
;; their current values are serialized to SX. On thaw, values are
;; restored. Multiple named scopes can coexist independently.
;;
;; Native functions serialize as (primitive "name") — looked up on resume.
;; Lambdas serialize as (lambda (params) body closure-env).
;; Environments serialize as dicts of their visible bindings.
;; Uses the scoped effects system: scope-push!/scope-pop!/context.
;;
;; Usage:
;; (freeze-scope "editor"
;; (let ((doc (signal "hello")))
;; (freeze-signal "doc" doc)
;; ...))
;;
;; (cek-freeze-scope "editor") → {:name "editor" :signals {:doc "hello"}}
;; (cek-thaw-scope "editor" frozen-data) → restores signal values
(define primitive-name :effects []
(fn (f)
;; For lambdas, use lambda-name. For native callables, check common names.
(if (lambda? f)
(lambda-name f)
;; Native function — try common primitive names
(let ((result nil)
(names (list "+" "-" "*" "/" "=" "<" ">" "<=" ">=" "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-each (fn (name)
(when (and (nil? result) (primitive? name) (identical? f (get-primitive name)))
(set! result name)))
names)
result))))
;; Registry of freeze scopes: name → list of {name signal} entries
(define freeze-registry (dict))
(define cek-serialize-value :effects []
(fn (val)
(cond
(nil? val) nil
(number? val) val
(string? val) val
(boolean? val) val
(symbol? val) val
(keyword? val) val
(list? val) (map cek-serialize-value val)
(lambda? val) (list (make-symbol "lambda")
(lambda-params val)
(lambda-body val))
(callable? val) (list (make-symbol "primitive")
(or (primitive-name val) "?"))
(dict? val) (cek-serialize-env val)
:else (str val))))
;; Register a signal in the current freeze scope
(define freeze-signal :effects [mutation]
(fn (name sig)
(let ((scope-name (context "sx-freeze-scope" nil)))
(when scope-name
(let ((entries (or (get freeze-registry scope-name) (list))))
(append! entries (dict "name" name "signal" sig))
(dict-set! freeze-registry scope-name entries))))))
(define cek-serialize-env :effects []
(fn (env)
(let ((result (dict))
(ks (keys env)))
(for-each (fn (k)
(dict-set! result k (cek-serialize-value (get env k))))
ks)
result)))
;; Freeze scope delimiter — collects signals registered within body
(define freeze-scope :effects [mutation]
(fn (name body-fn)
(scope-push! "sx-freeze-scope" name)
;; Initialize empty entry list for this scope
(dict-set! freeze-registry name (list))
(cek-call body-fn nil)
(scope-pop! "sx-freeze-scope")
nil))
(define cek-serialize-frame :effects []
(fn (frame)
(let ((result (dict))
(ks (keys frame)))
(for-each (fn (k)
(let ((v (get frame k)))
(dict-set! result k
(cond
(= k "type") v
(= k "tag") v
(= k "f") (cek-serialize-value v)
(= k "env") (cek-serialize-env v)
(= k "evaled") (map cek-serialize-value v)
(= k "remaining") v ;; unevaluated exprs stay as-is
(= k "results") (map cek-serialize-value v)
(= k "raw-args") v
(= k "current-item") (cek-serialize-value v)
(= k "name") v
(= k "update-fn") (cek-serialize-value v)
(= k "first-render") v
:else (cek-serialize-value v)))))
ks)
result)))
;; Freeze a named scope → SX dict of signal values
(define cek-freeze-scope :effects []
(fn (name)
(let ((entries (or (get freeze-registry name) (list)))
(signals-dict (dict)))
(for-each (fn (entry)
(dict-set! signals-dict
(get entry "name")
(signal-value (get entry "signal"))))
entries)
(dict "name" name "signals" signals-dict))))
(define cek-freeze :effects []
(fn (state)
(dict
"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")))))
;; Freeze all scopes
(define cek-freeze-all :effects []
(fn ()
(map (fn (name) (cek-freeze-scope name))
(keys freeze-registry))))
;; Deserialize: reconstruct a runnable CEK state from frozen SX.
;; Native functions are looked up by name in the current PRIMITIVES.
;; Thaw a named scope — restore signal values from frozen data
(define cek-thaw-scope :effects [mutation]
(fn (name frozen)
(let ((entries (or (get freeze-registry name) (list)))
(values (get frozen "signals")))
(when values
(for-each (fn (entry)
(let ((sig-name (get entry "name"))
(sig (get entry "signal"))
(val (get values sig-name)))
(when (not (nil? val))
(reset! sig val))))
entries)))))
(define cek-thaw-value :effects []
(fn (val)
(cond
(nil? val) nil
(number? val) val
(string? val) val
(boolean? val) val
(symbol? val) val
(keyword? val) val
;; (primitive "name") → look up native function
(and (list? val) (not (empty? val))
(symbol? (first val))
(= (symbol-name (first val)) "primitive"))
(get-primitive (nth val 1))
;; (lambda (params) body) → reconstruct Lambda
(and (list? val) (not (empty? val))
(symbol? (first val))
(= (symbol-name (first val)) "lambda"))
(make-lambda (nth val 1) (nth val 2) (dict))
(list? val) (map cek-thaw-value val)
(dict? val) (cek-thaw-env val)
:else val)))
;; Thaw all scopes from a list of frozen scope dicts
(define cek-thaw-all :effects [mutation]
(fn (frozen-list)
(for-each (fn (frozen)
(cek-thaw-scope (get frozen "name") frozen))
frozen-list)))
(define cek-thaw-env :effects []
(fn (frozen-env)
(let ((result (make-env)))
(for-each (fn (k)
(env-set! result k (cek-thaw-value (get frozen-env k))))
(keys frozen-env))
result)))
;; Serialize a frozen scope to SX text
(define freeze-to-sx :effects []
(fn (name)
(sx-serialize (cek-freeze-scope name))))
;; Restore from SX text
(define thaw-from-sx :effects [mutation]
(fn (sx-text)
(let ((parsed (sx-parse sx-text)))
(when (not (empty? parsed))
(let ((frozen (first parsed)))
(cek-thaw-scope (get frozen "name") frozen))))))
(define cek-thaw-frame :effects []
(fn (frozen-frame)
(let ((result (dict))
(ks (keys frozen-frame)))
(for-each (fn (k)
(let ((v (get frozen-frame k)))
(dict-set! result k
(cond
(= k "type") v
(= k "tag") v
(= k "f") (cek-thaw-value v)
(= k "env") (cek-thaw-env v)
(= k "evaled") (map cek-thaw-value v)
(= k "remaining") v
(= k "results") (map cek-thaw-value v)
(= k "raw-args") v
(= k "current-item") (cek-thaw-value v)
(= k "name") v
(= k "update-fn") (cek-thaw-value v)
(= k "first-render") v
:else (cek-thaw-value v)))))
ks)
result)))
(define cek-thaw :effects []
(fn (frozen)
(dict
"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")))))

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) ===

View File

@@ -542,79 +542,81 @@
(defisland ~geography/cek/freeze-demo ()
(let ((source (signal "(+ 1 (* 2 3))"))
(state-sig (signal nil))
(step-count (signal 0))
(frozen-sx (signal ""))
(thaw-result (signal "")))
(letrec
((do-parse (fn ()
(reset! frozen-sx "")
(reset! thaw-result "")
(reset! step-count 0)
(let ((parsed (sx-parse (deref source))))
(when (not (empty? parsed))
(reset! state-sig (make-cek-state (first parsed) (make-env) (list)))))))
(do-step (fn ()
(when (and (deref state-sig) (not (cek-terminal? (deref state-sig))))
(reset! state-sig (cek-step (deref state-sig)))
(swap! step-count inc))))
(do-freeze (fn ()
(when (deref state-sig)
(let ((frozen (cek-freeze (deref state-sig))))
(reset! frozen-sx (sx-serialize frozen))))))
(do-thaw (fn ()
(when (not (empty? (deref frozen-sx)))
(let ((parsed (sx-parse (deref frozen-sx))))
(when (not (empty? parsed))
(let ((thawed (cek-thaw (first parsed))))
(reset! thaw-result
(str "Resumed from step " (deref step-count) ": "
(cek-run thawed)))))))))
(do-run (fn ()
(when (deref state-sig)
(let run-loop ()
(when (not (cek-terminal? (deref state-sig)))
(do-step)
(run-loop)))))))
;; Auto-parse
(effect (fn () (do-parse)))
(div :class "space-y-4"
;; Input
(div :class "flex gap-2 items-end"
(div :class "flex-1"
(label :class "text-xs text-stone-400 block mb-1" "Expression")
(input :type "text" :bind source
:class "w-full px-3 py-1.5 rounded border border-stone-300 font-mono text-sm focus:outline-none focus:border-violet-400"
:on-change (fn (e) (do-parse))))
(let ((bg (signal "violet"))
(size (signal "text-2xl"))
(weight (signal "font-bold"))
(text (signal "the joy of sx"))
(frozen-text (signal ""))
(saved (signal (list))))
;; Register in freeze scope
(freeze-scope "widget" (fn ()
(freeze-signal "bg" bg)
(freeze-signal "size" size)
(freeze-signal "weight" weight)
(freeze-signal "text" text)))
(div :class "space-y-4"
;; Live preview
(div :class (str "p-6 rounded-lg text-center transition-all "
"bg-" (deref bg) "-50 border border-" (deref bg) "-200")
(span :class (str (deref size) " " (deref weight) " text-" (deref bg) "-700")
(deref text)))
;; Controls
(div :class "grid grid-cols-2 gap-3"
(div
(label :class "text-xs text-stone-400 block mb-1" "Colour")
(div :class "flex gap-1"
(button :on-click (fn (e) (do-step))
:class "px-3 py-1.5 rounded bg-violet-500 text-white text-sm hover:bg-violet-600"
"Step")
(button :on-click (fn (e) (do-run))
:class "px-3 py-1.5 rounded bg-violet-700 text-white text-sm hover:bg-violet-800"
"Run")))
;; Step count + freeze button
(div :class "flex items-center gap-3"
(span :class "text-sm text-stone-500 font-mono"
"Step " (deref step-count))
(when (deref state-sig)
(button :on-click (fn (e) (do-freeze))
:class "px-3 py-1.5 rounded bg-amber-500 text-white text-sm hover:bg-amber-600"
"Freeze")))
;; Frozen SX output
(when (not (empty? (deref frozen-sx)))
(div :class "space-y-2"
(label :class "text-xs text-stone-400 block" "Frozen CEK state")
(pre :class "text-xs font-mono bg-stone-50 rounded p-3 overflow-x-auto whitespace-pre-wrap text-stone-700"
(deref frozen-sx))
(button :on-click (fn (e) (do-thaw))
:class "px-3 py-1.5 rounded bg-emerald-600 text-white text-sm hover:bg-emerald-700"
"Thaw \u2192 Resume")))
;; Thaw result
(when (not (empty? (deref thaw-result)))
(div :class "rounded bg-emerald-50 border border-emerald-200 p-3 text-emerald-800 font-mono text-sm"
(deref thaw-result)))))))
(for-each (fn (c)
(button :on-click (fn (e) (reset! bg c))
:class (str "w-8 h-8 rounded-full bg-" c "-400"
(if (= (deref bg) c) " ring-2 ring-offset-1 ring-stone-400" ""))
""))
(list "violet" "rose" "emerald" "amber" "sky" "stone"))))
(div
(label :class "text-xs text-stone-400 block mb-1" "Size")
(div :class "flex gap-1"
(for-each (fn (s)
(button :on-click (fn (e) (reset! size s))
:class (str "px-2 py-1 rounded text-xs "
(if (= (deref size) s)
"bg-stone-700 text-white" "bg-stone-100 text-stone-600"))
s))
(list "text-sm" "text-lg" "text-2xl" "text-4xl"))))
(div
(label :class "text-xs text-stone-400 block mb-1" "Weight")
(div :class "flex gap-1"
(for-each (fn (w)
(button :on-click (fn (e) (reset! weight w))
:class (str "px-2 py-1 rounded text-xs "
(if (= (deref weight) w)
"bg-stone-700 text-white" "bg-stone-100 text-stone-600"))
w))
(list "font-normal" "font-bold" "font-semibold"))))
(div
(label :class "text-xs text-stone-400 block mb-1" "Text")
(input :type "text" :bind text
:class "w-full px-2 py-1 rounded border border-stone-300 text-sm")))
;; Freeze / Thaw
(div :class "flex gap-2 items-center"
(button :on-click (fn (e)
(let ((sx (freeze-to-sx "widget")))
(reset! frozen-text sx)
(swap! saved (fn (l) (append l (list sx))))))
:class "px-3 py-1.5 rounded bg-amber-500 text-white text-sm hover:bg-amber-600"
"Save config")
(span :class "text-xs text-stone-400"
(str (len (deref saved)) " saved")))
;; Saved configs
(when (not (empty? (deref saved)))
(div :class "space-y-1"
(label :class "text-xs text-stone-400 block" "Saved configs")
(for-each (fn (sx)
(div :class "flex gap-2 items-center"
(button :on-click (fn (e) (thaw-from-sx sx))
:class "flex-1 text-left px-2 py-1 rounded bg-stone-50 hover:bg-stone-100 font-mono text-xs text-stone-600 truncate"
sx)
))
(deref saved))))
)))
;; ---------------------------------------------------------------------------