compiler: ludic schema gains a "code" section (R7) - the code map from the compiler, so the studio's scan is only a fallback: modules (package, layer, uses and where, friend, files), states, actions, reducers, every dispatch, events (cancellable, net) with every emit and their @On listeners, ports (members with fn types, defaults, required) and binds (port, member, fn, value, at), handlers (phase, hook and target, @Public, @Server / @Predicted, @Queries with filters as written, scene and layer), models (@Owned, @Sync), prefabs, scenes (start, public, shows, lasts / then, loads, enter / exit, layers) and fn_refs - every fn name in code or a resource file with the slot it fills (registry field and row, port member or default, record field, emit field, call argument, assignment, let). Places are "file:line:col"; sorted by name, sites by place; schema_version stays 1 (additive). The parser records emits, fn refs, handlers and scenes' layers / lasts / loads for it (emit_schema_code.ludic, emit_schema_code_game.ludic); LANGUAGE.md and the changeset say so; schema_case lines added (not run); reseeded, bootstrap-cfree fixpoint holds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-30 00:19:24 +03:00
parent 4ccbc12b48
commit bfd99cdda1
12 changed files with 112243 additions and 92761 deletions

View file

@ -304,6 +304,7 @@ const TPL_HOLE: int = 92 # on the callee of e
# non-cancellable event yields 0.
function parse_emit() -> Node {
pi += 1; let n = node(S_EMIT); n.s = eat_id()
cx_emit(n) # the schema's code map (emit_schema_code.ludic)
let r = node(E_REC)
eat_op("("); skipnl()
while not is_op(")") {
@ -344,7 +345,7 @@ function p_primary() -> Node {
if (t.text == "spawn") { return parse_spawn() } # spawn as an expression: the new entity
# fn name — a top-level function as a value, for a worker entry point (Job.parallel_for)
# `fn` stays an ordinary name before an operator word (`p < fn and ...`) or at the end of a line
if (t.text == "fn") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 1].line == t.line) and (toks[pi + 1].text != "and") and (toks[pi + 1].text != "or") and (toks[pi + 1].text != "not") and (toks[pi + 1].text != "in") and (toks[pi + 1].text != "is") { pi += 1; let n = node(E_FNREF); n.line = t.line; n.s = eat_id(); return n }
if (t.text == "fn") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 1].line == t.line) and (toks[pi + 1].text != "and") and (toks[pi + 1].text != "or") and (toks[pi + 1].text != "not") and (toks[pi + 1].text != "in") and (toks[pi + 1].text != "is") { pi += 1; let n = node(E_FNREF); n.line = t.line; n.s = eat_id(); cx_fnref(n); return n }
# try EXPR else { ... } — evaluate a fallible (result-typed) expression; on
# `ok` the whole expression is its payload, on `err` the else block runs (with
# the message bound to `error`) and its trailing expression is the fallback.
@ -954,6 +955,30 @@ function already_loaded(full: pointer) -> bool {
return false
}
# a handler's hook, for the schema's code map: the attribute that made it one, and what it names
function decl_hook(ev: pointer, sp: pointer, dsp: pointer, att: pointer, det: pointer, en: pointer, dis: pointer, ph: pointer) -> pointer {
if ev != null { return "On" }
if sp != null { return "OnSpawn" }
if dsp != null { return "OnDespawn" }
if att != null { return "OnAttach" }
if det != null { return "OnDetach" }
if en != null { return "OnEnable" }
if dis != null { return "OnDisable" }
if ph != null {
if (ph == "Start") { return "OnStart" }
return "OnQuit"
}
return null
}
function decl_hook_target(ev: pointer, sp: pointer, dsp: pointer, att: pointer, det: pointer, en: pointer, dis: pointer) -> pointer {
if ev != null { return ev }
if sp != null { return sp }
if dsp != null { return dsp }
if att != null { return att }
if det != null { return det }
if en != null { return en }
return dis
}
# parse one top-level declaration (or resolve an import) into `prog`.
# Modifiers are `@annotations` in front of the declaration: `@export`, `@edge`,
# `@pure`, `@deterministic`, … — one channel, not a zoo of prefix keywords.
@ -1108,7 +1133,9 @@ function parse_one_decl() -> void {
if is_det { push(g_det_names, h.s) }
if g_aok_pending { push(g_alloc_ok_names, h.s); g_aok_pending = false }
if (role != null) { if (role == "server") { h.ival = 1 } else { h.ival = 2 } } # N5: @Server=1 / @Predicted=2
if (on_event != null) { register_onlisten(on_event, h.a); return } # @On(Event) listener
cx_handler(h, h.s, decl_hook(on_event, onspawn_model, ondespawn_model, onattach_prop, ondetach_prop, onenable_prop, ondisable_prop, hook_phase),
decl_hook_target(on_event, onspawn_model, ondespawn_model, onattach_prop, ondetach_prop, onenable_prop, ondisable_prop), qspec, is_public, null, null) # the schema's code map
if (on_event != null) { register_onlisten(on_event, h.a); cx_listened(); return } # @On(Event) listener
if (onspawn_model != null) {
register_onspawn(onspawn_model, h.a) # spawn hook
if is_public { ensure_event(`model_{onspawn_model}_spawn`, false) } # @Public -> model_<M>_spawn

View file

@ -189,7 +189,7 @@ function parse_queries_anno() -> Node {
let pname = eat_id()
let v = node(E_ID); v.s = pname; push(qn.kids, v) # binding var = property name
let t = node(E_ID); t.s = pname; t.ival = 0; push(terms.kids, t)
if is_op("{") { pi += 1; let ce = expr(); eat_op("}")
if is_op("{") { pi += 1; let f0 = pi; let ce = expr(); t.sp0 = toks[f0].off; t.sp1 = toks[pi - 1].oend; eat_op("}")
let cb = node(E_ID); cb.s = pname; wh = mk_and(wh, qualify_fields(ce, cb)) }
if is_op(",") { pi += 1 }
skipnl()
@ -249,12 +249,13 @@ function parse_scene() -> void {
while is_id("start") or is_id("public") or is_id("shows") or is_id("lasts") or is_id("loads") {
if is_id("start") { pi += 1; g_start_scene = g_scene_count }
else if is_id("shows") { pi += 1; n.ty = eat_id() } # shows Menu: the engine opens, renders and closes it
else if is_id("lasts") { pi += 1; lasts = expr(); if not is_id("then") { perr("lasts N needs `then Scene`") }; pi += 1; then_scene = eat_id() }
else if is_id("lasts") { pi += 1; let l0 = pi; lasts = expr(); n.sp0 = toks[l0].off; n.sp1 = toks[pi - 1].oend; if not is_id("then") { perr("lasts N needs `then Scene`") }; pi += 1; then_scene = eat_id() }
else if is_id("loads") { pi += 1; if not is_id("then") { perr("loads needs `then Scene`") }; pi += 1; loads_then = eat_id(); g_uses_atlas = true }
else { pi += 1; is_pub = true }
}
g_scene_count += 1
if is_pub { ensure_event_empty(`scene_{n.s}_enter`); ensure_event_empty(`scene_{n.s}_exit`) }
var layers = "," # the schema's code map (emit_schema_code.ludic)
skipnl(); eat_op("{")
while true {
skipnl(); if is_op("}") { break }
@ -273,6 +274,7 @@ function parse_scene() -> void {
}
if is_id("layer") { # layer Name [public] { handler .. }
pi += 1; let lname = eat_id()
layers = layers + lname + ","
if is_id("public") { pi += 1; ensure_event_empty(`layer_{lname}_show`); ensure_event_empty(`layer_{lname}_hide`) }
skipnl(); eat_op("{")
let ltag = node(E_ID); ltag.s = lname # the layer name, tagged onto each handler
@ -287,6 +289,7 @@ function parse_scene() -> void {
}
if not is_id("handler") { perr("expected 'handler' in layer") }
let h = parse_system() # N_SYS: .s name, .ty phase, .a body
cx_handler(h, h.s, null, null, qspec, false, n.s, lname)
if (qspec != null) { qspec.a = h.a; let wrap = node(N_BLOCK); push(wrap.kids, qspec); h.a = wrap }
h.s = `{n.s}_{h.s}` # scene-qualified: two scenes may both have a `Draw` handler
h.c = n # tag the owning scene (null = global)
@ -302,6 +305,7 @@ function parse_scene() -> void {
eat_op("}")
if (lasts != null) { scene_add_timer(n, lasts, then_scene) }
if (loads_then != null) { scene_add_loader(n, loads_then) }
cx_scene(layers, then_scene, loads_then, is_pub)
push(g_scenes, n)
}