diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index a9fef9a..b9012d9 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -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) === diff --git a/shared/sx/ref/cek.sx b/shared/sx/ref/cek.sx index cef1070..4343b3a 100644 --- a/shared/sx/ref/cek.sx +++ b/shared/sx/ref/cek.sx @@ -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"))))) diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py index 71934e4..500c330 100644 --- a/shared/sx/ref/sx_ref.py +++ b/shared/sx/ref/sx_ref.py @@ -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) === diff --git a/sx/sx/geography/cek.sx b/sx/sx/geography/cek.sx index fb3c6d3..230e9d7 100644 --- a/sx/sx/geography/cek.sx +++ b/sx/sx/geography/cek.sx @@ -542,79 +542,96 @@ (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)))) - (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))))))) + (let ((bg (signal "violet")) + (size (signal "text-2xl")) + (weight (signal "font-bold")) + (text (signal "the joy of sx")) + (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))) + ;; Preload all dynamic colour variants + (span (~cssx/tw :tokens "hidden bg-violet-50 bg-rose-50 bg-emerald-50 bg-amber-50 bg-sky-50 bg-stone-50 border-violet-200 border-rose-200 border-emerald-200 border-amber-200 border-sky-200 border-stone-200 text-violet-700 text-rose-700 text-emerald-700 text-amber-700 text-sky-700 text-stone-700")) + (div (~cssx/tw :tokens "space-y-4") + ;; Live preview + (div (~cssx/tw :tokens "p-6 rounded-lg text-center transition-all border") + :class (str "bg-" (deref bg) "-50 border-" (deref bg) "-200") + (span :class (str (deref size) " " (deref weight) " text-" (deref bg) "-700") + (deref text))) + ;; Controls + (div (~cssx/tw :tokens "grid grid-cols-2 gap-3") + (div + (label (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Colour") + (div (~cssx/tw :tokens "flex gap-1") + (button :on-click (fn (e) (reset! bg "violet")) + (~cssx/tw :tokens "w-8 h-8 rounded-full") + :class (str "bg-violet-400" (if (= (deref bg) "violet") " ring-2 ring-offset-1 ring-stone-400" "")) + "") + (button :on-click (fn (e) (reset! bg "rose")) + (~cssx/tw :tokens "w-8 h-8 rounded-full") + :class (str "bg-rose-400" (if (= (deref bg) "rose") " ring-2 ring-offset-1 ring-stone-400" "")) + "") + (button :on-click (fn (e) (reset! bg "emerald")) + (~cssx/tw :tokens "w-8 h-8 rounded-full") + :class (str "bg-emerald-400" (if (= (deref bg) "emerald") " ring-2 ring-offset-1 ring-stone-400" "")) + "") + (button :on-click (fn (e) (reset! bg "amber")) + (~cssx/tw :tokens "w-8 h-8 rounded-full") + :class (str "bg-amber-400" (if (= (deref bg) "amber") " ring-2 ring-offset-1 ring-stone-400" "")) + "") + (button :on-click (fn (e) (reset! bg "sky")) + (~cssx/tw :tokens "w-8 h-8 rounded-full") + :class (str "bg-sky-400" (if (= (deref bg) "sky") " ring-2 ring-offset-1 ring-stone-400" "")) + "") + (button :on-click (fn (e) (reset! bg "stone")) + (~cssx/tw :tokens "w-8 h-8 rounded-full") + :class (str "bg-stone-400" (if (= (deref bg) "stone") " ring-2 ring-offset-1 ring-stone-400" "")) + ""))) + (div + (label (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Size") + (div (~cssx/tw :tokens "flex gap-1") + (map (fn (s) + (button :on-click (fn (e) (reset! size s)) + (~cssx/tw :tokens "px-2 py-1 rounded text-xs") + :class (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 (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Weight") + (div (~cssx/tw :tokens "flex gap-1") + (map (fn (w) + (button :on-click (fn (e) (reset! weight w)) + (~cssx/tw :tokens "px-2 py-1 rounded text-xs") + :class (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 (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Text") + (input :type "text" :bind text + (~cssx/tw :tokens "w-full px-2 py-1 rounded border border-stone-300 text-sm")))) + ;; Freeze / Thaw + (div (~cssx/tw :tokens "flex gap-2 items-center") + (button :on-click (fn (e) + (let ((sx (freeze-to-sx "widget"))) + (swap! saved (fn (l) (append l (list sx)))))) + (~cssx/tw :tokens "px-3 py-1.5 rounded bg-amber-500 text-white text-sm hover:bg-amber-600") + "Save config") + (span (~cssx/tw :tokens "text-xs text-stone-400") + (str (len (deref saved)) " saved"))) + ;; Saved configs + (when (not (empty? (deref saved))) + (div (~cssx/tw :tokens "space-y-1") + (label (~cssx/tw :tokens "text-xs text-stone-400 block") "Saved configs") + (map-indexed (fn (i sx) + (button :on-click (fn (e) (thaw-from-sx sx)) + (~cssx/tw :tokens "block w-full text-left px-2 py-1 rounded bg-stone-50 hover:bg-stone-100 font-mono text-xs text-stone-600 truncate") + (str "Config " (+ i 1)))) + (deref saved))))))) ;; ---------------------------------------------------------------------------