A self-describing Value node (null/int/fixed/bool/str/list/object) with constructors, builders (Value.add/put) and accessors (get/at/count/kind/ as_int/as_str/…). Reflect.serialize(entity) walks an entity's whole component set into a value tree — one member per component, each a sub-object of its fields — and Reflect.apply(entity, value) writes one back; a fixed field becomes a fixed node, everything else an int node, so the round-trip is bit-exact, with the model id under "@kind". Json.encode/parse bridge the tree to and from compact, stable, diffable text, with fixed written as an exact terminating decimal that parses back bit-for-bit (verified across the raw Q16.16 range). Together: a one-call, bit-exact save/load for entities. Written in Ludic and spliced on demand (runtime/native/value.ludic + reflect_io.ludic, like Query/Light), so a program that doesn't touch Value.*/Json.*/Reflect.serialize compiles byte-identically and the C-free bootstrap fixpoint holds (verified). The general tagged-union/any language type stays tracked in #1; this ships the concrete value tree the serializer needs. Adds 21 namespace-method docs pages + Value/Json sections, examples/library/serialize.ludic, and a regression case. Whole CI set green: x test 72/72, x test-tools 30/30, check-impl/vocabulary/docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
2.6 KiB
Text
56 lines
2.6 KiB
Text
# serialize.ludic — the generic value tree (Value.*), its JSON bridge (Json.*),
|
|
# and reflection-driven save/load (Reflect.serialize / Reflect.apply, issue #44).
|
|
# One call walks an entity's whole component set into a self-describing value
|
|
# tree; Json.encode turns it into text and Json.parse reads it back; Reflect.apply
|
|
# writes a parsed tree into a live entity — a one-call save/load, bit-exact.
|
|
#
|
|
# Each assertion that holds prints its number, so a full run prints:
|
|
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
|
program Serialize {
|
|
property Health { hp: int = 0, max: int = 0 }
|
|
property Velocity { dx: fixed = 0.0, dy: fixed = 0.0 }
|
|
model Unit { Health, Velocity }
|
|
|
|
entry {
|
|
spawn Unit { Health { hp: 7, max: 10 }, Velocity { dx: fixed(2), dy: fixed(3) } }
|
|
let e = 0
|
|
|
|
# --- serialize an entity to a value tree ---
|
|
let tree = Reflect.serialize(e)
|
|
if Value.kind(tree) == 6 { print(1) } # object
|
|
if Value.has(tree, "Health") == 1 { print(2) }
|
|
let h = Value.get(tree, "Health")
|
|
if Value.as_int(Value.get(h, "hp")) == 7 { print(3) }
|
|
if Value.as_int(Value.get(h, "max")) == 10 { print(4) }
|
|
let vv = Value.get(tree, "Velocity")
|
|
if Value.kind(Value.get(vv, "dx")) == 2 { print(5) } # a fixed node
|
|
if Value.as_int(Value.get(vv, "dx")) == 131072 { print(6) } # raw Q16.16 = fixed(2)
|
|
|
|
# --- JSON bridge: encode, then parse straight back ---
|
|
let text = Json.encode(tree)
|
|
if text == "{\"@kind\":1,\"Health\":{\"hp\":7,\"max\":10},\"Velocity\":{\"dx\":2.0,\"dy\":3.0}}" { print(7) }
|
|
let back = Json.parse(text)
|
|
if Value.as_int(Value.get(Value.get(back, "Health"), "hp")) == 7 { print(8) }
|
|
|
|
# --- apply a parsed tree into a second, zeroed entity: a one-call load ---
|
|
spawn Unit { Health { hp: 0, max: 0 }, Velocity { dx: 0.0, dy: 0.0 } }
|
|
let e2 = 1
|
|
let applied = Reflect.apply(e2, back)
|
|
if applied == 4 { print(9) } # four fields written
|
|
let H = Reflect.prop("Health")
|
|
let V = Reflect.prop("Velocity")
|
|
if Reflect.get(e2, H, 0) == 7 { print(10) } # hp round-tripped
|
|
if Reflect.get(e2, H, 1) == 10 { print(11) } # max round-tripped
|
|
if Reflect.get(e2, V, 0) == 131072 { print(12) } # dx (fixed) bit-exact
|
|
|
|
# --- build a value tree by hand + inspect a list ---
|
|
let list = Value.list()
|
|
Value.add(list, Value.int(3))
|
|
Value.add(list, Value.str("hi"))
|
|
Value.add(list, Value.bool(1))
|
|
if Value.count(list) == 3 { print(13) }
|
|
if Value.as_int(Value.at(list, 0)) == 3 { print(14) }
|
|
if Value.as_str(Value.at(list, 1)) == "hi" { print(15) }
|
|
if Json.encode(list) == "[3,\"hi\",true]" { print(16) }
|
|
}
|
|
}
|