feat(types): add Reflect.* — runtime reflection over the world schema (#20)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 17s
ci / build-and-test (push) Successful in 1m12s
commit-lint / conventional-commits (push) Successful in 4s
docs / build-and-deploy (push) Successful in 18s

Runtime type reflection: enumerate properties and fields by index, resolve ids
by name, read a field's type, and get/set/has an entity's fields generically —
the foundation the issue calls out for auto-serialization, data-driven tools,
and debug/inspector overlays. Built on the existing EV2 reflection ABI plus a
new EV8 metadata-enumeration layer, all generated at compile time (a table walk,
no heavy runtime introspection), so a binary that never reflects pays nothing.

Surface (Reflect.*, aliased in emit_call.ludic over the world_* reflection ABI):
  - Reflect.prop(name) / field(prop,name)        resolve ids by name (-1 = none)
  - Reflect.prop_count() / prop_name(i)          enumerate properties
  - Reflect.field_count(prop) / field_name(prop,i) / field_type(prop,i)
                                                 enumerate a component's fields
  - Reflect.get / set / has (entity, prop, ...)  read/write/test a field by id
  - Reflect.kind(entity) / model(name)           an entity's model, by id/name

New codegen (emit_world.ludic, EV8): ludic_prop_count / prop_name /
field_count / field_name / field_type, generated the same way as ludic_prop_id
— a switch over the compile-time property/field metadata, falling through to the
mod-registered (dynamic) registries. Field names/types come straight from the
AST, so field_type reports the declared type ("int"/"fixed"/…). A program that
uses Reflect.* force-emits the reflection ABI (g_uses_reflect) so it needs no
@events of its own, exactly like Query.* (#42).

examples/library/reflect.ludic asserts 20 cases including a generic inspector
that sums every field of every component an entity has while naming none of them
— the auto-save / debug-overlay pattern end to end. Wired into x test (now 64
passed). Docs: a Reflect section + 12 per-symbol pages (positioned as an
advanced/tooling surface), inventory/coverage green. Seed reseeded; the C-free
bootstrap fixpoint holds.

Scope: this lands the reflection core and a real consumer (the generic
inspector). The generic value-tree `serialize` the proposal also sketches wants
a tagged-union/any value type from the #1 type-system work, so it is tracked as
a follow-up rather than forced in here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-31 13:28:51 +03:00
parent b25dc328a2
commit 12f2dbe958
20 changed files with 16376 additions and 14274 deletions

View file

@ -0,0 +1,7 @@
---
id: reflect
title: Reflect
order: 31
---
Runtime reflection over the world schema — inspect properties, fields, and entity state by name and by index. It powers the conveniences game devs consume without writing reflection code: automatic save/load, data-driven tools, and debug/inspector overlays. Enumerate with <a href="reflect-prop_count"><code>Reflect.prop_count</code></a>/<a href="reflect-prop_name"><code>Reflect.prop_name</code></a> and <a href="reflect-field_count"><code>Reflect.field_count</code></a>/<a href="reflect-field_name"><code>Reflect.field_name</code></a>/<a href="reflect-field_type"><code>Reflect.field_type</code></a>; resolve ids with <a href="reflect-prop"><code>Reflect.prop</code></a>/<a href="reflect-field"><code>Reflect.field</code></a>; read and write an entity's fields with <a href="reflect-get"><code>Reflect.get</code></a>/<a href="reflect-set"><code>Reflect.set</code></a>/<a href="reflect-has"><code>Reflect.has</code></a>; and identify its model with <a href="reflect-kind"><code>Reflect.kind</code></a>/<a href="reflect-model"><code>Reflect.model</code></a>. The metadata tables are generated at compile time (the same reflection ABI a foreign mod binds), so introspection is a table walk, not heavy runtime machinery. This is an advanced/tooling surface — most developers get its benefits through built-in features. Related: <a href="world"><code>World</code></a>, <a href="query"><code>Query</code></a>.

View file

@ -0,0 +1,30 @@
---
id: reflect-field
name: Reflect.field
category: reflect
kind: namespace-method
tokens: Reflect.field
sig: Reflect.field(prop, name) -> int
tip: The field id of a named field within a property, or -1.
order: 2
ns: Reflect
member: field
---
Resolves a field name within property <code>prop</code> to its field id (index), or <code>-1</code> if the property has no such field. Pair it with <a href="reflect-get"><code>Reflect.get</code></a>/<a href="reflect-set"><code>Reflect.set</code></a> to read or write that field.
Parameters:
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
- `name` — the field name
```ludic
program Demo {
property Health { hp: int = 0, max: int = 0 }
model Unit { Health }
entry {
let H = Reflect.prop("Health")
let mx = Reflect.field(H, "max")
print(mx)
}
}
```

View file

@ -0,0 +1,27 @@
---
id: reflect-field_count
name: Reflect.field_count
category: reflect
kind: namespace-method
tokens: Reflect.field_count
sig: Reflect.field_count(prop) -> int
tip: How many fields a property has.
order: 5
ns: Reflect
member: field_count
---
Returns the number of fields in property <code>prop</code>. Walk <code>0 .. Reflect.field_count(prop)</code> with <a href="reflect-field_name"><code>Reflect.field_name</code></a> and <a href="reflect-field_type"><code>Reflect.field_type</code></a> to enumerate a component's shape.
Parameters:
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
```ludic
program Demo {
property Health { hp: int = 0, max: int = 0 }
model Unit { Health }
entry {
print(Reflect.field_count(Reflect.prop("Health"))) # 2
}
}
```

View file

@ -0,0 +1,29 @@
---
id: reflect-field_name
name: Reflect.field_name
category: reflect
kind: namespace-method
tokens: Reflect.field_name
sig: Reflect.field_name(prop, index) -> string
tip: The name of the field at an index within a property.
order: 6
ns: Reflect
member: field_name
---
Returns the name of the field at <code>index</code> within property <code>prop</code> (<code>0 .. Reflect.field_count(prop)</code>), or the empty string if out of range. The key half of a generic serializer or inspector row.
Parameters:
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
- `index` — the field index
```ludic
program Demo {
property Health { hp: int = 0, max: int = 0 }
model Unit { Health }
entry {
let H = Reflect.prop("Health")
print(Reflect.field_name(H, 0)) # hp
}
}
```

View file

@ -0,0 +1,29 @@
---
id: reflect-field_type
name: Reflect.field_type
category: reflect
kind: namespace-method
tokens: Reflect.field_type
sig: Reflect.field_type(prop, index) -> string
tip: The type name of the field at an index within a property.
order: 7
ns: Reflect
member: field_type
---
Returns the declared type name of the field at <code>index</code> within property <code>prop</code> — <code>"int"</code>, <code>"fixed"</code>, <code>"bool"</code>, and so on. A serializer uses it to format each value correctly, and an inspector uses it to pick the right editor widget.
Parameters:
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
- `index` — the field index
```ludic
program Demo {
property Velocity { dx: fixed = 0.0, dy: fixed = 0.0 }
model Mover { Velocity }
entry {
let V = Reflect.prop("Velocity")
print(Reflect.field_type(V, 0)) # fixed
}
}
```

View file

@ -0,0 +1,31 @@
---
id: reflect-get
name: Reflect.get
category: reflect
kind: namespace-method
tokens: Reflect.get
sig: Reflect.get(entity, prop, field) -> int
tip: Read one field of an entity by (prop, field) id.
order: 8
ns: Reflect
member: get
---
Reads the value of field <code>field</code> of property <code>prop</code> on <code>entity</code>, as an <code>int</code>. A <code>fixed</code> field comes back as its raw Q16.16 bits (the same integer <code>fixed</code> stores), so a generic reader can move it without interpreting it. Same as <a href="world-get"><code>World.get</code></a>, under the reflection namespace.
Parameters:
- `entity` — the entity to read
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
- `field` — a field id (from <a href="reflect-field"><code>Reflect.field</code></a>)
```ludic
program Demo {
property Health { hp: int = 0 }
model Unit { Health }
entry {
spawn Unit { Health { hp: 7 } }
let H = Reflect.prop("Health")
print(Reflect.get(0, H, Reflect.field(H, "hp"))) # 7
}
}
```

View file

@ -0,0 +1,29 @@
---
id: reflect-has
name: Reflect.has
category: reflect
kind: namespace-method
tokens: Reflect.has
sig: Reflect.has(entity, prop) -> bool
tip: Does an entity carry a property?
order: 10
ns: Reflect
member: has
---
Returns whether <code>entity</code> carries property <code>prop</code>. A generic walker checks it before reading a component's fields, so it visits only the components an entity actually has.
Parameters:
- `entity` — the entity to test
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
```ludic
program Demo {
property Health { hp: int = 0 }
model Unit { Health }
entry {
spawn Unit { Health { hp: 7 } }
if Reflect.has(0, Reflect.prop("Health")) { print(1) }
}
}
```

View file

@ -0,0 +1,28 @@
---
id: reflect-kind
name: Reflect.kind
category: reflect
kind: namespace-method
tokens: Reflect.kind
sig: Reflect.kind(entity) -> int
tip: The model id of an entity.
order: 11
ns: Reflect
member: kind
---
Returns the model (archetype) id of <code>entity</code> — which kind of thing it is. Compare it against <a href="reflect-model"><code>Reflect.model</code></a> to branch on an entity's type, or use it to label a save record. Same as <a href="world-kind"><code>World.kind</code></a>, under the reflection namespace.
Parameters:
- `entity` — the entity to identify
```ludic
program Demo {
property Health { hp: int = 0 }
model Unit { Health }
entry {
spawn Unit { Health { hp: 7 } }
if Reflect.kind(0) == Reflect.model("Unit") { print(1) }
}
}
```

View file

@ -0,0 +1,27 @@
---
id: reflect-model
name: Reflect.model
category: reflect
kind: namespace-method
tokens: Reflect.model
sig: Reflect.model(name) -> int
tip: The model id for a model name, or -1 if unknown.
order: 12
ns: Reflect
member: model
---
Resolves a model (archetype) name to its id, or <code>-1</code> if no such model exists. The counterpart to <a href="reflect-kind"><code>Reflect.kind</code></a>: resolve the name once, then compare entities' kinds against it.
Parameters:
- `name` — the model name
```ludic
program Demo {
property Health { hp: int = 0 }
model Unit { Health }
entry {
print(Reflect.model("Unit"))
}
}
```

View file

@ -0,0 +1,28 @@
---
id: reflect-prop
name: Reflect.prop
category: reflect
kind: namespace-method
tokens: Reflect.prop
sig: Reflect.prop(name) -> int
tip: The property id for a property name, or -1 if unknown.
order: 1
ns: Reflect
member: prop
---
Resolves a property name to its stable id, or <code>-1</code> if no such property exists. The id is what every other <code>Reflect.*</code> (and <a href="query"><code>Query</code></a>) call takes. Same as <a href="world-prop_id"><code>World.prop_id</code></a>, under the reflection namespace.
Parameters:
- `name` — the property name
```ludic
program Demo {
property Health { hp: int = 0 }
model Unit { Health }
entry {
let H = Reflect.prop("Health")
print(H)
}
}
```

View file

@ -0,0 +1,24 @@
---
id: reflect-prop_count
name: Reflect.prop_count
category: reflect
kind: namespace-method
tokens: Reflect.prop_count
sig: Reflect.prop_count() -> int
tip: How many properties the world schema defines.
order: 3
ns: Reflect
member: prop_count
---
Returns the number of properties in the world — every declared <code>property</code>, plus any the runtime or a mod registered. Walk <code>0 .. Reflect.prop_count()</code> with <a href="reflect-prop_name"><code>Reflect.prop_name</code></a> to enumerate the whole schema.
```ludic
program Demo {
property Health { hp: int = 0 }
model Unit { Health }
entry {
print(Reflect.prop_count())
}
}
```

View file

@ -0,0 +1,32 @@
---
id: reflect-prop_name
name: Reflect.prop_name
category: reflect
kind: namespace-method
tokens: Reflect.prop_name
sig: Reflect.prop_name(index) -> string
tip: The name of the property at an index (or "").
order: 4
ns: Reflect
member: prop_name
---
Returns the name of the property at <code>index</code> (<code>0 .. Reflect.prop_count()</code>), or the empty string if out of range. The reverse of <a href="reflect-prop"><code>Reflect.prop</code></a> — together they let a generic tool list every component by name.
Parameters:
- `index` — the property index
```ludic
program Demo {
property Health { hp: int = 0 }
model Unit { Health }
entry {
var p = 0
let n = Reflect.prop_count()
while p < n {
print(Reflect.prop_name(p))
p = p + 1
}
}
}
```

View file

@ -0,0 +1,33 @@
---
id: reflect-set
name: Reflect.set
category: reflect
kind: namespace-method
tokens: Reflect.set
sig: Reflect.set(entity, prop, field, value)
tip: Write one field of an entity by (prop, field) id.
order: 9
ns: Reflect
member: set
---
Writes <code>value</code> into field <code>field</code> of property <code>prop</code> on <code>entity</code>. For a <code>fixed</code> field, pass the raw Q16.16 bits. This is how a generic loader restores saved state and how a debug overlay applies an edit. Same as <a href="world-set"><code>World.set</code></a>, under the reflection namespace.
Parameters:
- `entity` — the entity to modify
- `prop` — a property id (from <a href="reflect-prop"><code>Reflect.prop</code></a>)
- `field` — a field id (from <a href="reflect-field"><code>Reflect.field</code></a>)
- `value` — the integer value to store
```ludic
program Demo {
property Health { hp: int = 0 }
model Unit { Health }
entry {
spawn Unit { Health { hp: 7 } }
let H = Reflect.prop("Health")
Reflect.set(0, H, Reflect.field(H, "hp"), 99)
print(Reflect.get(0, H, 0)) # 99
}
}
```

View file

@ -0,0 +1,69 @@
# reflect.ludic — Reflect.* runtime reflection over the world schema: enumerate
# properties and fields by index, resolve ids by name, read the type of a field,
# and get/set an entity's fields generically. 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 17 18 19 20
# The generic-inspector case (17) walks every component + field of an entity
# without naming any of them — the pattern auto-save and debug overlays use.
program Reflect {
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 # the first (only) spawn is entity 0
# --- enumerate properties by index. The count is >= the two declared here:
# the runtime splices its own components (e.g. Cell, from the grid library
# core imports), and reflection honestly reports every one. ---
if Reflect.prop_count() >= 2 { print(1) }
if Reflect.prop_name(0) == "Health" { print(2) }
if Reflect.prop_name(1) == "Velocity" { print(3) }
# --- resolve property ids by name ---
let H = Reflect.prop("Health")
let V = Reflect.prop("Velocity")
if H == 0 { print(4) }
if V == 1 { print(5) }
if Reflect.prop("Nope") < 0 { print(6) } # unknown -> -1
# --- enumerate fields by index, with names and types ---
if Reflect.field_count(H) == 2 { print(7) }
if Reflect.field_name(H, 0) == "hp" { print(8) }
if Reflect.field_name(H, 1) == "max" { print(9) }
if Reflect.field_type(H, 0) == "int" { print(10) }
if Reflect.field_type(V, 0) == "fixed" { print(11) }
if Reflect.field(H, "max") == 1 { print(12) }
# --- entity membership + identity ---
if Reflect.has(e, H) { print(13) }
if Reflect.has(e, V) { print(14) }
if Reflect.kind(e) == Reflect.model("Unit") { print(15) }
if Reflect.model("Unit") >= 0 { print(16) }
# --- generic inspector: sum every field of every component the entity has,
# naming nothing. Health(7+10) + Velocity(fixed(2)+fixed(3) raw) ---
var total = 0
let pc = Reflect.prop_count()
var p = 0
while p < pc {
if Reflect.has(e, p) {
let fc = Reflect.field_count(p)
var f = 0
while f < fc {
total = total + Reflect.get(e, p, f)
f = f + 1
}
}
p = p + 1
}
if total == 327697 { print(17) } # 7 + 10 + 131072 + 196608
# --- get / set a field by (prop, field) id ---
if Reflect.get(e, H, 0) == 7 { print(18) } # hp
Reflect.set(e, H, 0, 99)
if Reflect.get(e, H, 0) == 99 { print(19) } # write took effect
if Reflect.get(e, V, 0) == 131072 { print(20) } # dx stored as raw Q16.16 = fixed(2)
}
}

View file

@ -234,6 +234,24 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
if (meth == "nearest") { bare = "query_nearest"; push(labels, "prop"); push(labels, "pos"); push(labels, "x_field"); push(labels, "y_field"); push(labels, "x"); push(labels, "y") }
if (meth == "within") { bare = "query_within"; push(labels, "prop"); push(labels, "pos"); push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "x_field"); push(labels, "y_field") }
}
# Reflect.* — runtime type reflection over the world schema (the EV2/EV8 ABI).
# Enumerate properties and fields by index, resolve ids by name, and read/write
# a field by (prop, field) id — the foundation for auto-serialization and debug
# inspectors. Reads the same generated metadata a foreign mod binds.
if (ns == "Reflect") {
if (meth == "prop") { bare = "world_prop_id"; push(labels, "name") }
if (meth == "field") { bare = "world_field_id"; push(labels, "prop"); push(labels, "name") }
if (meth == "prop_count") { bare = "world_prop_count" }
if (meth == "prop_name") { bare = "world_prop_name"; push(labels, "index") }
if (meth == "field_count") { bare = "world_field_count"; push(labels, "prop") }
if (meth == "field_name") { bare = "world_field_name"; push(labels, "prop"); push(labels, "index") }
if (meth == "field_type") { bare = "world_field_type"; push(labels, "prop"); push(labels, "index") }
if (meth == "get") { bare = "world_get"; push(labels, "entity"); push(labels, "prop"); push(labels, "field") }
if (meth == "set") { bare = "world_set"; push(labels, "entity"); push(labels, "prop"); push(labels, "field"); push(labels, "value") }
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") }
}
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
reorder_named(e, labels)
let id = node(E_ID); id.s = bare; e.a = id
@ -378,6 +396,12 @@ function emit_call(e: Node) -> Val {
if (name == "world_attach_dyn") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); emit(" call void @ludic_attach_dyn(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n"); return val("0", "void") }
if (name == "world_detach_dyn") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); emit(" call void @ludic_detach_dyn(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n"); return val("0", "void") }
if (name == "world_spawn") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_spawn(i32 {a.code})`), "int") }
# EV8 — schema enumeration, walking property/field metadata by index (Reflect.*).
if (name == "world_prop_count") { return val(emit_bind("call i32 @ludic_prop_count()"), "int") }
if (name == "world_prop_name") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call ptr @ludic_prop_name(i32 {a.code})`), "string") }
if (name == "world_field_count") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_field_count(i32 {a.code})`), "int") }
if (name == "world_field_name") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call ptr @ludic_field_name(i32 {a.code}, i32 {b.code})`), "string") }
if (name == "world_field_type") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call ptr @ludic_field_type(i32 {a.code}, i32 {b.code})`), "string") }
if is_intrinsic(name) { return emit_intrinsic(name, e) }
if is_intrinsic2(name) { return emit_intrinsic2(name, e) }
if is_math_builtin(name) { return emit_math_builtin(name, e) }

View file

@ -81,7 +81,7 @@ function emit_program() -> void {
var i = 0
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) }; i = i + 1 }
if len(g_events) > 0 { emit_event_fns() } # EV0: @ev_<E> event-dispatch functions
if has_ecs() and (len(g_events) > 0 or g_uses_query) { emit_world_table() } # EV2: the mod reflection ABI (also powers Query.*)
if has_ecs() and (len(g_events) > 0 or g_uses_query or g_uses_reflect) { emit_world_table() } # EV2/EV8: the mod reflection ABI (also powers Query.* / Reflect.*)
if has_ecs() { emit_ecs_allocator(); emit_snapshot() }
if has_ecs() { emit_net() } # N2/N3: @Sync serializers + @Owned storage (gated internally)
if has_ui() { emit_ui_build() }

View file

@ -294,6 +294,118 @@ function emit_world_table() -> void {
emit("hitb:\n ret i32 %e\n")
emit("cont:\n %e1 = add i32 %e, 1\n br label %loop\n")
emit("none:\n ret i32 -1\n}\n\n")
emit_world_reflect_enum()
}
# EV8 — schema enumeration: walk the property/field metadata by index, not just
# by name. Powers the Reflect.* namespace (auto-serialization, debug inspectors)
# so a mod can list every component and field without knowing them up front.
# Compile-time props take ids 0..NC-1; ids >= NC are mod-registered (dynamic),
# whose field names are unknown (i32 fields) so they report "" / "int".
function emit_world_reflect_enum() -> void {
let empty = emit_str_const("")
let tint = emit_str_const("int")
# count the compile-time components (== the first dynamic prop id)
var ncomp = 0
var i = 0
while i < len(prog) { if prog[i].kind == N_COMP { ncomp = ncomp + 1 }; i = i + 1 }
let NC = itoa(ncomp)
# ludic_prop_count() -> total property count (compile-time + mod-registered)
emit("define i32 @ludic_prop_count() {\nentry:\n")
emit(" %d = load i32, ptr @dyn_count\n %n = add i32 %d, "); emit(NC); emit("\n ret i32 %n\n}\n\n")
# ludic_prop_name(i) -> the i-th property's name (or "" if out of range)
emit("define ptr @ludic_prop_name(i32 %i) {\nentry:\n")
var k = 0; i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let sc = emit_str_const(prog[i].s); let sk = itoa(k)
emit(" %pe"); emit(sk); emit(" = icmp eq i32 %i, "); emit(sk); emit("\n")
emit(" br i1 %pe"); emit(sk); emit(", label %ph"); emit(sk); emit(", label %pn"); emit(sk); emit("\n")
emit("ph"); emit(sk); emit(":\n ret ptr "); emit(sc); emit("\n")
emit("pn"); emit(sk); emit(":\n")
k = k + 1
}
i = i + 1
}
emit(" %dyn = sub i32 %i, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n")
emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %pbad, label %pdyn\n")
emit("pdyn:\n %np = getelementptr inbounds [32 x ptr], ptr @dynName, i32 0, i32 %dyn\n %nm = load ptr, ptr %np\n ret ptr %nm\n")
emit("pbad:\n ret ptr "); emit(empty); emit("\n}\n\n")
# ludic_field_count(prop) -> the number of fields of a property
emit("define i32 @ludic_field_count(i32 %p) {\nentry:\n")
k = 0; i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let c = prog[i]; let sk = itoa(k)
emit(" %fce"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
emit(" br i1 %fce"); emit(sk); emit(", label %fch"); emit(sk); emit(", label %fcn"); emit(sk); emit("\n")
emit("fch"); emit(sk); emit(":\n ret i32 "); emit(itoa(len(c.kids))); emit("\n")
emit("fcn"); emit(sk); emit(":\n")
k = k + 1
}
i = i + 1
}
emit(" %dyn = sub i32 %p, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n")
emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %fcbad, label %fcdyn\n")
emit("fcdyn:\n %fp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %dyn\n %fn = load i32, ptr %fp\n ret i32 %fn\n")
emit("fcbad:\n ret i32 0\n}\n\n")
# ludic_field_name(prop, f) -> the f-th field's name of a property (or "")
emit("define ptr @ludic_field_name(i32 %p, i32 %f) {\nentry:\n")
k = 0; i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let c = prog[i]; let sk = itoa(k)
emit(" %fne"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
emit(" br i1 %fne"); emit(sk); emit(", label %fnk"); emit(sk); emit(", label %fnn"); emit(sk); emit("\n")
emit("fnk"); emit(sk); emit(":\n")
var f = 0
while f < len(c.kids) {
let fc = emit_str_const(c.kids[f].s); let fk = `{sk}_{itoa(f)}`
emit(" %fnfe"); emit(fk); emit(" = icmp eq i32 %f, "); emit(itoa(f)); emit("\n")
emit(" br i1 %fnfe"); emit(fk); emit(", label %fnfh"); emit(fk); emit(", label %fnfn"); emit(fk); emit("\n")
emit("fnfh"); emit(fk); emit(":\n ret ptr "); emit(fc); emit("\n")
emit("fnfn"); emit(fk); emit(":\n")
f = f + 1
}
emit(" ret ptr "); emit(empty); emit("\n")
emit("fnn"); emit(sk); emit(":\n")
k = k + 1
}
i = i + 1
}
emit(" ret ptr "); emit(empty); emit("\n}\n\n")
# ludic_field_type(prop, f) -> the f-th field's type name (or "int" fallback)
emit("define ptr @ludic_field_type(i32 %p, i32 %f) {\nentry:\n")
k = 0; i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let c = prog[i]; let sk = itoa(k)
emit(" %fte"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
emit(" br i1 %fte"); emit(sk); emit(", label %ftk"); emit(sk); emit(", label %ftn"); emit(sk); emit("\n")
emit("ftk"); emit(sk); emit(":\n")
var f = 0
while f < len(c.kids) {
let tc = emit_str_const(c.kids[f].ty); let fk = `{sk}_{itoa(f)}`
emit(" %ftfe"); emit(fk); emit(" = icmp eq i32 %f, "); emit(itoa(f)); emit("\n")
emit(" br i1 %ftfe"); emit(fk); emit(", label %ftfh"); emit(fk); emit(", label %ftfn"); emit(fk); emit("\n")
emit("ftfh"); emit(fk); emit(":\n ret ptr "); emit(tc); emit("\n")
emit("ftfn"); emit(fk); emit(":\n")
f = f + 1
}
emit(" ret ptr "); emit(tint); emit("\n")
emit("ftn"); emit(sk); emit(":\n")
k = k + 1
}
i = i + 1
}
emit(" ret ptr "); emit(tint); emit("\n}\n\n")
}
# N5 — the drivable sim (NETWORKING-DESIGN §5). The per-frame phases the auto-loop

View file

@ -163,6 +163,7 @@ function p_postfix() -> Node {
if is_op(".") { pi = pi + 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
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)
}
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
@ -376,6 +377,7 @@ var loaded_paths: []pointer
var cur_dir: pointer
var g_uses_regex: bool = false # a program mentioned Regex.* -> splice the regex runtime
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
function already_loaded(full: pointer) -> bool {
var i = 0
@ -554,6 +556,7 @@ function parse_program() -> void {
g_toggled_layers = new []pointer
g_uses_regex = false
g_uses_query = false
g_uses_reflect = false
loaded_paths = new []pointer
skipnl()
g_game_name = "Ludic"

File diff suppressed because it is too large Load diff

View file

@ -106,6 +106,7 @@ function cmd_test() -> int {
feat_case("library/grid", "", "1 2 3 4 5 6 7 8 9 10 11 12 13", "grid.ludic (Grid line/flood/line_of_sight + A* pathfinding over the tilemap)")
feat_case("library/anim", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34", "anim.ludic (Anim frame/once/pingpong/cell + Tween progress/loop/yoyo/ease/number/round/point/tint)")
feat_case("library/query", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "query.ludic (Query count/first/nearest/within — ECS spatial queries over the reflection ABI)")
feat_case("library/reflect", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", "reflect.ludic (Reflect prop/field enumeration + type + get/set/has/kind — runtime reflection over the world schema)")
feat_case("library/logging", "", "0 5 2 1", "logging.ludic (Log levels, set_level/level threshold, structured fields)")
# Os known-folders/arch and Fs.list read the BSD utsname/dirent layout, so
# their asserted values are macOS-specific; skip off Darwin (see is_darwin).