Serializable CEK state: cek-freeze and cek-thaw
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled

Freeze a CEK state to pure s-expressions. Thaw it back to a live
state and resume with cek-run. Full round-trip through SX text works:
freeze → sx-serialize → sx-parse → thaw → resume → same result.

- cek-freeze: serialize control/env/kont/value to SX dicts
- cek-thaw: reconstruct live state from frozen SX
- Native functions serialize as (primitive "name"), looked up on resume
- Lambdas serialize as (lambda (params) body)
- Environments serialize as flat dicts of visible bindings
- Continuation frames serialize as typed dicts

Enables: localStorage persistence, content-addressed computation,
cross-machine migration, time-travel debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 22:11:05 +00:00
parent 4dd9968264
commit b03c84b962
3 changed files with 335 additions and 1 deletions

View File

@@ -1032,3 +1032,165 @@
(if (thunk? val)
(eval-expr-cek (thunk-expr val) (thunk-env val))
val)))
;; --------------------------------------------------------------------------
;; 13. CEK state serialization — freeze and resume computation
;; --------------------------------------------------------------------------
;;
;; 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)
;;
;; 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.
(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))))
(define cek-serialize-value :effects []
(fn (val)
(cond
(nil? val) nil
(number? val) val
(string? val) val
(= (type-of val) "boolean") val
(= (type-of val) "symbol") val
(= (type-of val) "keyword") 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))))
(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)))
(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)))
(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")))))
;; Deserialize: reconstruct a runnable CEK state from frozen SX.
;; Native functions are looked up by name in the current PRIMITIVES.
(define cek-thaw-value :effects []
(fn (val)
(cond
(nil? val) nil
(number? val) val
(string? val) val
(= (type-of val) "boolean") val
(= (type-of val) "symbol") val
(= (type-of val) "keyword") val
;; (primitive "name") → look up native function
(and (list? val) (not (empty? val))
(= (type-of (first val)) "symbol")
(= (symbol-name (first val)) "primitive"))
(get-primitive (nth val 1))
;; (lambda (params) body) → reconstruct Lambda
(and (list? val) (not (empty? val))
(= (type-of (first val)) "symbol")
(= (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)))
(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)))
(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")))))