Step 15: bytecode + CEK state serialization — 16 tests

bytecode-serialize/deserialize: sxbc v2 format wrapping compiled code
dicts. cek-serialize/deserialize: cek-state v1 format wrapping suspended
CEK state (phase, request, env, kont). Both use SX s-expression
round-trip via inspect/parse. lib/serialize.sx has pure SX versions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 08:19:16 +00:00
parent 99c5c44cc1
commit bca0d8e4e5
3 changed files with 213 additions and 0 deletions

View File

@@ -182,6 +182,35 @@ let make_test_env () =
Dict ld Dict ld
| _ -> Nil); | _ -> Nil);
(* Step 15: bytecode + CEK state serialization *)
bind "bytecode-serialize" (fun args ->
match args with
| [v] -> String ("(sxbc 2 " ^ Sx_types.inspect v ^ ")")
| _ -> raise (Eval_error "bytecode-serialize: expected 1 arg"));
bind "bytecode-deserialize" (fun args ->
match args with
| [String s] ->
let parsed = Sx_parser.parse_all s in
(match parsed with
| [List (Symbol "sxbc" :: Number _ :: payload :: _)] -> payload
| _ -> raise (Eval_error "bytecode-deserialize: invalid sxbc format"))
| _ -> raise (Eval_error "bytecode-deserialize: expected string"));
bind "cek-serialize" (fun args ->
match args with
| [v] -> String ("(cek-state 1 " ^ Sx_types.inspect v ^ ")")
| _ -> raise (Eval_error "cek-serialize: expected 1 arg"));
bind "cek-deserialize" (fun args ->
match args with
| [String s] ->
let parsed = Sx_parser.parse_all s in
(match parsed with
| [List (Symbol "cek-state" :: Number _ :: payload :: _)] -> payload
| _ -> raise (Eval_error "cek-deserialize: invalid cek-state format"))
| _ -> raise (Eval_error "cek-deserialize: expected string"));
bind "sx-parse-one" (fun args -> bind "sx-parse-one" (fun args ->
match args with match args with
| [String s] -> | [String s] ->

39
lib/serialize.sx Normal file
View File

@@ -0,0 +1,39 @@
;; Bytecode + CEK state serialization
;; Uses SX s-expression format for portability across all hosts.
;; ── Bytecode serialization ────────────────────────────────────────
(define
bytecode-serialize
(fn (code) (str "(sxbc 2 " (serialize code) ")")))
(define
bytecode-deserialize
(fn
(s)
(let
((parsed (first (sx-parse s))))
(if
(and (list? parsed) (>= (len parsed) 3) (= (first parsed) "sxbc"))
(nth parsed 2)
(error "bytecode-deserialize: invalid sxbc format")))))
;; ── CEK state serialization ───────────────────────────────────────
(define
cek-serialize
(fn (state) (str "(cek-state 1 " (serialize state) ")")))
(define
cek-deserialize
(fn
(s)
(let
((parsed (first (sx-parse s))))
(if
(and
(list? parsed)
(>= (len parsed) 3)
(= (first parsed) "cek-state"))
(nth parsed 2)
(error "cek-deserialize: invalid cek-state format")))))

View File

@@ -0,0 +1,145 @@
;; Step 15: Bytecode + continuation serialization
;; Round-trip compiled code and suspended CEK state through strings.
;; ── Bytecode serialization ────────────────────────────────────────
(defsuite
"bytecode-serialize-basic"
(deftest
"compile and serialize round-trips"
(let
((code (compile "(+ 1 2)")))
(assert (not (nil? code)))
(let
((serialized (bytecode-serialize code)))
(assert (string? serialized))
(assert (> (string-length serialized) 0)))))
(deftest
"serialized format has sxbc header"
(let
((code (compile "(+ 1 2)")))
(let
((serialized (bytecode-serialize code)))
(assert (starts-with? serialized "(sxbc 2")))))
(deftest
"deserialized code has same structure"
(let
((code (compile "(+ 1 2)")))
(let
((serialized (bytecode-serialize code))
(restored (bytecode-deserialize serialized)))
(assert (not (nil? restored)))
(assert (dict? restored)))))
(deftest
"deserialized bytecode array matches"
(let
((code (compile "(+ 1 2)")))
(let
((serialized (bytecode-serialize code))
(restored (bytecode-deserialize serialized)))
(assert= (get restored "bytecode") (get code "bytecode")))))
(deftest
"deserialized constants match"
(let
((code (compile "(str \"hello\" \" \" \"world\")")))
(let
((serialized (bytecode-serialize code))
(restored (bytecode-deserialize serialized)))
(assert= (get restored "constants") (get code "constants"))))))
(defsuite
"bytecode-serialize-types"
(deftest
"number bytecodes round-trip"
(let
((code (compile "(+ 42 3)")))
(let
((restored (bytecode-deserialize (bytecode-serialize code))))
(assert= (get restored "bytecode") (get code "bytecode")))))
(deftest
"multiple expressions round-trip"
(let
((code (compile "(define x 1)")))
(let
((restored (bytecode-deserialize (bytecode-serialize code))))
(assert= (get restored "bytecode") (get code "bytecode"))
(assert= (get restored "constants") (get code "constants")))))
(deftest
"dict expression round-trips"
(let
((code (compile "(dict :a 1 :b 2)")))
(let
((restored (bytecode-deserialize (bytecode-serialize code))))
(assert= (get restored "constants") (get code "constants")))))
(deftest
"lambda expression round-trips"
(let
((code (compile "(fn (x) (+ x 1))")))
(let
((restored (bytecode-deserialize (bytecode-serialize code))))
(assert= (get restored "bytecode") (get code "bytecode")))))
(deftest
"invalid format raises error"
(let
((raised false))
(guard
(e (true (set! raised true)))
(bytecode-deserialize "not valid"))
(assert raised "should raise for invalid format"))))
;; ── CEK state serialization ───────────────────────────────────────
(defsuite
"cek-serialize-basic"
(deftest
"suspended state serializes"
(let
((state (make-cek-suspended {:op "test"} (dict) (list))))
(let
((serialized (cek-serialize state)))
(assert (string? serialized))
(assert (starts-with? serialized "(cek-state 1")))))
(deftest
"deserialized state preserves phase"
(let
((state (make-cek-suspended {:op "test"} (dict) (list))))
(let
((restored (cek-deserialize (cek-serialize state))))
(assert (cek-suspended? restored)))))
(deftest
"deserialized state preserves request"
(let
((state (make-cek-suspended {:url "/api" :op "fetch"} (dict) (list))))
(let
((restored (cek-deserialize (cek-serialize state))))
(let
((req (cek-io-request restored)))
(assert= (get req :op) "fetch")
(assert= (get req :url) "/api"))))))
(defsuite
"cek-serialize-values"
(deftest
"request with nested data round-trips"
(let
((req {:params {:ids (list 1 2 3) :name "test"} :op "query"}) (state (make-cek-suspended req (dict) (list))))
(let
((restored (cek-deserialize (cek-serialize state))))
(let
((rreq (cek-io-request restored)))
(assert= (get rreq :op) "query")
(assert= (get (get rreq :params) :name) "test")
(assert= (get (get rreq :params) :ids) (list 1 2 3))))))
(deftest
"string with special chars round-trips"
(let
((req {:op "test" :data "line1\nline2\ttab"}) (state (make-cek-suspended req (dict) (list))))
(let
((restored (cek-deserialize (cek-serialize state))))
(assert= (get (cek-io-request restored) :data) "line1\nline2\ttab"))))
(deftest
"invalid cek format raises error"
(let
((raised false))
(guard (e (true (set! raised true))) (cek-deserialize "garbage"))
(assert raised "should raise for invalid format"))))