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>
This commit is contained in:
parent
0d1b09e4f0
commit
9452557f3c
31 changed files with 16753 additions and 14898 deletions
|
|
@ -295,6 +295,36 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if (meth == "has") { bare = "world_has"; push(labels, "entity"); push(labels, "prop") }
|
||||
if (meth == "kind") { bare = "world_kind"; push(labels, "entity") }
|
||||
if (meth == "model") { bare = "world_model_id"; push(labels, "name") }
|
||||
# #44 — serialize an entity to a value tree and apply one back (reflect_io.ludic)
|
||||
if (meth == "serialize") { bare = "reflect_serialize"; push(labels, "entity") }
|
||||
if (meth == "apply") { bare = "reflect_apply"; push(labels, "entity"); push(labels, "value") }
|
||||
}
|
||||
# Value.* — the generic value tree (runtime/native/value.ludic, spliced on
|
||||
# demand). Nodes are int/fixed/bool/str/list/object; the methods map straight
|
||||
# to the spliced value_* functions, whose signatures carry the return types.
|
||||
if (ns == "Value") {
|
||||
if (meth == "null") { bare = "value_null" }
|
||||
if (meth == "int") { bare = "value_int"; push(labels, "n") }
|
||||
if (meth == "fixed") { bare = "value_fixed"; push(labels, "f") }
|
||||
if (meth == "bool") { bare = "value_bool"; push(labels, "b") }
|
||||
if (meth == "str") { bare = "value_str"; push(labels, "s") }
|
||||
if (meth == "list") { bare = "value_list" }
|
||||
if (meth == "object") { bare = "value_object" }
|
||||
if (meth == "add") { bare = "value_add"; push(labels, "list"); push(labels, "item") }
|
||||
if (meth == "put") { bare = "value_put"; push(labels, "obj"); push(labels, "key"); push(labels, "item") }
|
||||
if (meth == "get") { bare = "value_get"; push(labels, "obj"); push(labels, "key") }
|
||||
if (meth == "has") { bare = "value_has"; push(labels, "obj"); push(labels, "key") }
|
||||
if (meth == "at") { bare = "value_at"; push(labels, "list"); push(labels, "index") }
|
||||
if (meth == "key_at") { bare = "value_key_at"; push(labels, "obj"); push(labels, "index") }
|
||||
if (meth == "count") { bare = "value_count"; push(labels, "value") }
|
||||
if (meth == "kind") { bare = "value_kind"; push(labels, "value") }
|
||||
if (meth == "as_int") { bare = "value_as_int"; push(labels, "value") }
|
||||
if (meth == "as_str") { bare = "value_as_str"; push(labels, "value") }
|
||||
}
|
||||
# Json.* — the text bridge over the value tree (runtime/native/value.ludic).
|
||||
if (ns == "Json") {
|
||||
if (meth == "encode") { bare = "json_encode"; push(labels, "value") }
|
||||
if (meth == "parse") { bare = "json_parse"; push(labels, "text") }
|
||||
}
|
||||
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
|
||||
reorder_named(e, labels)
|
||||
|
|
|
|||
|
|
@ -178,6 +178,8 @@ function p_postfix() -> Node {
|
|||
if e.a.kind == E_ID and e.a.s == "Query" { g_uses_query = true } # splice the ECS spatial-query runtime on demand
|
||||
if e.a.kind == E_ID and e.a.s == "Reflect" { g_uses_reflect = true } # force-emit the reflection ABI (Reflect.* reads the world schema)
|
||||
if e.a.kind == E_ID and e.a.s == "Light" { g_uses_light = true } # splice the 2D light-accumulation pass on demand
|
||||
if e.a.kind == E_ID and (e.a.s == "Value" or e.a.s == "Json") { g_uses_value = true } # splice the value tree + JSON on demand (#44)
|
||||
if e.a.kind == E_ID and e.a.s == "Reflect" and (e.s == "serialize" or e.s == "apply") { g_uses_value = true; g_uses_reflect_io = true } # Reflect.serialize/apply -> value tree + world table
|
||||
}
|
||||
else { if is_op("[") { pi = pi + 1; let lo = expr()
|
||||
if is_op("..") { pi = pi + 1; let sl = node(E_SLICE); sl.a = e; sl.b = lo; sl.c = expr(); eat_op("]"); e = sl } # s[a..b] substring
|
||||
|
|
@ -403,6 +405,8 @@ var g_uses_regex: bool = false # a program mentioned Regex.* -> splice the re
|
|||
var g_uses_query: bool = false # a program mentioned Query.* -> splice the query runtime + reflection ABI
|
||||
var g_uses_reflect: bool = false # a program mentioned Reflect.* -> force-emit the reflection ABI
|
||||
var g_uses_light: bool = false # a program mentioned Light.* -> splice the 2D light pass
|
||||
var g_uses_value: bool = false # Value.*/Json.*/Reflect.serialize -> splice the value tree + JSON (#44)
|
||||
var g_uses_reflect_io: bool = false # Reflect.serialize/apply -> splice the reflection serializer
|
||||
|
||||
function already_loaded(full: pointer) -> bool {
|
||||
var i = 0
|
||||
|
|
@ -585,6 +589,20 @@ function maybe_splice_runtime() -> void {
|
|||
do_import("runtime/native/light.ludic")
|
||||
cur_dir = saved
|
||||
}
|
||||
# Value.*/Json.* (#44) get the generic value tree + JSON bridge spliced in; it
|
||||
# is self-contained (only string/slice ops), so it works in a plain tool too.
|
||||
if g_uses_value {
|
||||
cur_dir = ""
|
||||
do_import("runtime/native/value.ludic")
|
||||
cur_dir = saved
|
||||
}
|
||||
# Reflect.serialize/apply add the reflection serializer, which stands on both
|
||||
# the value tree (above) and the world table (force-emitted for Reflect.*).
|
||||
if g_uses_reflect_io {
|
||||
cur_dir = ""
|
||||
do_import("runtime/native/reflect_io.ludic")
|
||||
cur_dir = saved
|
||||
}
|
||||
}
|
||||
|
||||
function parse_program() -> void {
|
||||
|
|
@ -606,6 +624,8 @@ function parse_program() -> void {
|
|||
g_uses_query = false
|
||||
g_uses_reflect = false
|
||||
g_uses_light = false
|
||||
g_uses_value = false
|
||||
g_uses_reflect_io = false
|
||||
g_tests = new []Node
|
||||
loaded_paths = new []pointer
|
||||
skipnl()
|
||||
|
|
|
|||
30896
selfhost/ludicc.seed.ll
30896
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue