From bca0d8e4e571fc9f09f3e98fc4a058105993f36f Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 11 Apr 2026 08:19:16 +0000 Subject: [PATCH] =?UTF-8?q?Step=2015:=20bytecode=20+=20CEK=20state=20seria?= =?UTF-8?q?lization=20=E2=80=94=2016=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- hosts/ocaml/bin/run_tests.ml | 29 ++++++ lib/serialize.sx | 39 ++++++++ spec/tests/test-bytecode-serial.sx | 145 +++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 lib/serialize.sx create mode 100644 spec/tests/test-bytecode-serial.sx diff --git a/hosts/ocaml/bin/run_tests.ml b/hosts/ocaml/bin/run_tests.ml index 48cc0570..aea71b3d 100644 --- a/hosts/ocaml/bin/run_tests.ml +++ b/hosts/ocaml/bin/run_tests.ml @@ -182,6 +182,35 @@ let make_test_env () = Dict ld | _ -> 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 -> match args with | [String s] -> diff --git a/lib/serialize.sx b/lib/serialize.sx new file mode 100644 index 00000000..0f35ec1f --- /dev/null +++ b/lib/serialize.sx @@ -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"))))) diff --git a/spec/tests/test-bytecode-serial.sx b/spec/tests/test-bytecode-serial.sx new file mode 100644 index 00000000..9c843939 --- /dev/null +++ b/spec/tests/test-bytecode-serial.sx @@ -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"))))