;; ========================================================================== ;; freeze.sx — Serializable state boundaries ;; ;; Freeze scopes collect signals registered within them. On freeze, ;; their current values are serialized to SX. On thaw, values are ;; restored. Multiple named scopes can coexist independently. ;; ;; This is a library built on top of the evaluator's scoped effects ;; (scope-push!/scope-pop!/context) and signal system. It is NOT ;; part of the core evaluator — it loads after evaluator.sx. ;; ;; 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 ;; ========================================================================== ;; Registry of freeze scopes: name → list of {name signal} entries (define freeze-registry (dict)) ;; 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)))))) ;; 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)) ;; 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)))) ;; Freeze all scopes (define cek-freeze-all :effects [] (fn () (map (fn (name) (cek-freeze-scope name)) (keys freeze-registry)))) ;; 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))))) ;; 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))) ;; 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))))))