ludic/examples/library/serialize.ludic
Orkuncakilkaya 9452557f3c
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 17s
ci / build-and-test (push) Successful in 1m15s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 19s
feat(reflect): generic value tree + Reflect.serialize/apply + JSON bridge (#44)
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>
2026-08-31 15:19:21 +03:00

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) }
}
}