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:
parent
4ccbc12b48
commit
bfd99cdda1
12 changed files with 112243 additions and 92761 deletions
39
LANGUAGE.md
39
LANGUAGE.md
|
|
@ -801,6 +801,43 @@ object with `"schema_version": 1`:
|
|||
and is left out.
|
||||
- `units` - `@Unit`'s canonical spellings.
|
||||
- `lang` - the text keys and the languages (see Text keys), or null without a `lang` line.
|
||||
- `code` - the program's code map, so an editor needs no scan of its own. Every place is
|
||||
`"file:line:col"` (`"at"`); what the runtime declares and what the compiler writes itself are left
|
||||
out. It holds:
|
||||
- `modules` - `{"name", "package", "layer", "uses", "uses_at", "friend", "files"}`: `uses` null
|
||||
without a `uses` line, `friend` null, or `{"of": null}` for a friend of every module and
|
||||
`{"of": [...]}` for `friend module lab of a, b`; `files` its source files and the resource files
|
||||
read into its registries;
|
||||
- `states`, `actions` (`{"name", "module", "at"}`), `reducers` (`{"state", "action", "module",
|
||||
"at"}`) and `dispatch` (every `dispatch`: `{"action", "module", "at"}`);
|
||||
- `events` - `{"name", "module", "at", "cancellable", "net"}` (`net` `"toserver"` for
|
||||
`@ToServer`, `"toclients"` for `@ToClients`, else null), its `emits` (every `emit`'s place) and
|
||||
its `listeners` (the `@On` handlers: `{"handler", "module", "at"}`);
|
||||
- `ports` - `{"name", "module", "at", "bound", "members"}`, each member `{"name", "type", "default",
|
||||
"required"}` (its `fn` type as a field's is spelled, its default as written); and `binds` - one
|
||||
row per member a `bind` gives: `{"port", "member", "fn", "value", "module", "at"}`, `fn` the
|
||||
function a `fn name` names (null for a variable bound to a member that takes nothing) and
|
||||
`value` as written;
|
||||
- `handlers` - `{"name", "module", "at", "phase", "hook", "target", "public", "net", "queries",
|
||||
"scene", "layer"}`: `phase` null for a hook, `hook` null or `"On"`, `"OnSpawn"`, `"OnDespawn"`,
|
||||
`"OnAttach"`, `"OnDetach"`, `"OnEnable"`, `"OnDisable"`, `"OnStart"`, `"OnQuit"` with `target`
|
||||
the event, model or property it names; `net` `"server"` (`@Server`), `"predicted"`
|
||||
(`@Predicted`) or null; `queries` null or `{"these": [{"property", "filter"}], "on"}` (a
|
||||
filter as written, or null); a scene's handler has its name as written and its `scene` and
|
||||
`layer`;
|
||||
- `models` (`{"name", "module", "at", "owned", "properties": [{"name", "sync"}]}`), `prefabs`
|
||||
(`{"name", "model", "module", "at", "components": [{"property", "fields": [{"name",
|
||||
"value"}]}]}`) and `scenes` (`{"name", "module", "at", "start", "public", "shows", "lasts",
|
||||
"then", "loads", "enter", "exit", "layers"}`: `lasts` as written, `then` the scene it goes on to,
|
||||
`loads` the scene a `loads then` one goes on to, `enter` / `exit` whether it has the block);
|
||||
- `fn_refs` - every `fn name` written, in code or in a resource file: `{"fn", "module", "at",
|
||||
"slot_kind", "slot", "entry"}`, the slot it fills: `"registry"` (`"Registry.field"`, `entry` the
|
||||
row's key), `"port"` (a bind's `"Port.member"`), `"port_default"`, `"default"` (a record
|
||||
field's default, `"Type.field"`), `"record"` (a `new` or a spawn's `"Type.field"`), `"event"`
|
||||
(an `emit`'s `"Event.field"`), `"arg"` (`"callee(i)"`, the i-th argument written), `"assign"`
|
||||
(`s.tick = fn f`: `"s.tick"`), `"let"` (the name), or `"value"` with a null slot.
|
||||
|
||||
Named lists are sorted by name (then file and line), sites by place.
|
||||
|
||||
Each list is sorted by name (then file and line), a registry's entries are in index order, and the
|
||||
paths are the ones the compiler read, so two runs over the same source write the same file. The
|
||||
|
|
@ -2323,7 +2360,7 @@ ludic test -j 4 # four tests at once (default: one per
|
|||
ludic test packages/ludic.base # the test programs under a directory (a package's)
|
||||
ludic build --check # every check a build makes, nothing written
|
||||
ludic build --check --diagnostics=json # the same, every error as a JSON array on stdout
|
||||
ludic schema -o build/schema.json # records, registries, entries, consts, components and natives, for editors
|
||||
ludic schema -o build/schema.json # records, registries, entries, consts, components, natives, lang and the code map, for editors
|
||||
ludic deps # how tangled the modules are, as the compiler resolved them
|
||||
ludic deps --check tests/deps-baseline.txt # fail when a number rose (--baseline FILE writes them)
|
||||
|
||||
|
|
|
|||
12
changes/schema-code.md
Normal file
12
changes/schema-code.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
bump: minor
|
||||
type: feature
|
||||
**`ludic schema` carries the code map: a `"code"` section.** Beside the records, registries and
|
||||
`lang`, the schema now lists what an editor's code map needs, from the compiler's own reading:
|
||||
`modules` (uses, layer, friend, files), `states`, `actions`, `reducers` and every `dispatch`, `events`
|
||||
(cancellable, net) with every `emit` and their `@On` listeners, `ports` (members with their `fn`
|
||||
types and defaults) and `binds` (port, member, the function named, where), `handlers` (phase, hook
|
||||
and what it names, `@Public`, `@Server` / `@Predicted`, `@Queries` with their filters, the scene and
|
||||
layer), `models` (`@Owned`, `@Sync`), `prefabs`, `scenes` (start, shows, lasts / loads and what
|
||||
follows, layers) and `fn_refs` - every `fn name` in code or in a resource file, with the slot it
|
||||
fills (a registry's field and row, a port's member, a record field, an argument, ...). Every place
|
||||
is `"file:line:col"`; the rest of the schema is unchanged, so `schema_version` stays 1.
|
||||
|
|
@ -12,6 +12,9 @@
|
|||
# stripped (the runtime supplies them), written as a field's type is ("fn(A,B)->R")
|
||||
# components every UI component: props, state, derived fields, functions, events (emit_schema_ui)
|
||||
# natives every native tag registered with ui_native / ui_native_input by a literal
|
||||
# units, lang @Unit's spellings; the text keys and the languages (emit_schema_lang)
|
||||
# code the code map: modules, states, actions, reducers, dispatch, events, ports, binds,
|
||||
# handlers, models, prefabs, scenes and fn_refs (emit_schema_code)
|
||||
#
|
||||
# Deterministic: each list sorted by name (then file and line), an entry list in index order, the
|
||||
# paths as the compiler read them, and nothing about the run. The engine's runtime is left out.
|
||||
|
|
@ -47,6 +50,8 @@ function schema_buf() -> Buf {
|
|||
sc_units(b)
|
||||
buf_puts(b, "],\n \"lang\": ")
|
||||
sc_lang(b) # emit_schema_lang.ludic: the keys and the languages
|
||||
buf_puts(b, ",\n \"code\": ")
|
||||
sc_code(b) # emit_schema_code.ludic: the code map
|
||||
buf_puts(b, "\n}\n")
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
459
selfhost/backend/emit_schema_code.ludic
Normal file
459
selfhost/backend/emit_schema_code.ludic
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
# emit_schema_code.ludic — the schema's "code" (emit_schema.ludic): the program's code map as the
|
||||
# compiler read it, so an editor needs no scan of its own. Every place is "file:line:col".
|
||||
#
|
||||
# modules name, package, layer, uses (null without a `uses` line) and where, friend, files
|
||||
# states name, module, at actions name, module, at
|
||||
# reducers state, action, module, at dispatch action, module, at
|
||||
# events name, module, at, cancellable, net (@ToServer / @ToClients), emits (sites) and
|
||||
# listeners (the @On handlers: handler, module, at)
|
||||
# ports name, module, at, bound, members (name, type, default as written, required)
|
||||
# binds port, member, fn (the function a `fn name` names, else null), value as written, at
|
||||
# handlers (emit_schema_code_game.ludic) name, module, at, phase, hook, target, public, net,
|
||||
# queries, scene, layer
|
||||
# models, prefabs, scenes, fn_refs (emit_schema_code_game.ludic)
|
||||
#
|
||||
# Each list is sorted by name (then file and line) or, for sites, by place. What the runtime
|
||||
# declares, and what the compiler writes itself, is left out.
|
||||
|
||||
# ---- what the parser records for it ------------------------------------------------------------
|
||||
var g_cx_emits: []Node = new []Node # every `emit E(...)` read
|
||||
var g_cx_fnrefs: []Node = new []Node # every `fn name` read, in code or data
|
||||
var g_cx_h: []Node = new []Node # every handler written: its node (name, phase, net role)
|
||||
var g_cx_hname: []pointer = new []pointer # its name as written (a scene's are qualified later)
|
||||
var g_cx_hhook: []pointer = new []pointer # null, "On", "OnSpawn", "OnDespawn", ..., "OnStart", "OnQuit"
|
||||
var g_cx_htarget: []pointer = new []pointer # the event, model or property the hook names
|
||||
var g_cx_hq: []Node = new []Node # its @Queries (an S_QUERY), or null
|
||||
var g_cx_hpub: []bool = new []bool # @Public
|
||||
var g_cx_hscene: []pointer = new []pointer # the scene and layer it is written in, or null
|
||||
var g_cx_hlayer: []pointer = new []pointer
|
||||
var g_cx_hlisten: []Node = new []Node # an @On handler's listener (g_onlisten), whose event is resolved
|
||||
var g_cx_slayers: []pointer = new []pointer # per g_scenes entry: ",a,b," its layers
|
||||
var g_cx_sthen: []pointer = new []pointer # `lasts N then T`'s T, or null (N is the scene's sp0..sp1)
|
||||
var g_cx_sloads: []pointer = new []pointer # `loads then T`'s T, or null
|
||||
var g_cx_spub: []bool = new []bool
|
||||
|
||||
function cx_emit(n: Node) -> void { push(g_cx_emits, n) }
|
||||
function cx_fnref(n: Node) -> void { push(g_cx_fnrefs, n) }
|
||||
function cx_handler(h: Node, name: pointer, hook: pointer, target: pointer, q: Node, pub: bool, scene: pointer, layer: pointer) -> void {
|
||||
push(g_cx_h, h)
|
||||
push(g_cx_hname, name)
|
||||
push(g_cx_hhook, hook)
|
||||
push(g_cx_htarget, target)
|
||||
push(g_cx_hq, q)
|
||||
push(g_cx_hpub, pub)
|
||||
push(g_cx_hscene, scene)
|
||||
push(g_cx_hlayer, layer)
|
||||
push(g_cx_hlisten, null)
|
||||
}
|
||||
# the handler just recorded is an @On listener, the one just registered
|
||||
function cx_listened() -> void {
|
||||
let k = len(g_cx_hlisten) - 1
|
||||
if k >= 0 and len(g_onlisten) > 0 { g_cx_hlisten[k] = g_onlisten[len(g_onlisten) - 1] }
|
||||
}
|
||||
function cx_scene(layers: pointer, then_scene: pointer, loads: pointer, pub: bool) -> void {
|
||||
push(g_cx_slayers, layers)
|
||||
push(g_cx_sthen, then_scene)
|
||||
push(g_cx_sloads, loads)
|
||||
push(g_cx_spub, pub)
|
||||
}
|
||||
|
||||
# ---- the section ----------------------------------------------------------------------------------
|
||||
function sc_code(b: Buf) -> void {
|
||||
buf_puts(b, "{\n \"modules\": [")
|
||||
cx_modules(b)
|
||||
buf_puts(b, "\n ],\n \"states\": [")
|
||||
cx_decls(b, 2)
|
||||
buf_puts(b, "\n ],\n \"actions\": [")
|
||||
cx_decls(b, 3)
|
||||
buf_puts(b, "\n ],\n \"reducers\": [")
|
||||
cx_reducers(b)
|
||||
buf_puts(b, "\n ],\n \"dispatch\": [")
|
||||
cx_dispatch(b)
|
||||
buf_puts(b, "\n ],\n \"events\": [")
|
||||
cx_events(b)
|
||||
buf_puts(b, "\n ],\n \"ports\": [")
|
||||
cx_ports(b)
|
||||
buf_puts(b, "\n ],\n \"binds\": [")
|
||||
cx_binds(b)
|
||||
buf_puts(b, "\n ],\n \"handlers\": [")
|
||||
cx_handlers(b)
|
||||
buf_puts(b, "\n ],\n \"models\": [")
|
||||
cx_models(b)
|
||||
buf_puts(b, "\n ],\n \"prefabs\": [")
|
||||
cx_prefabs(b)
|
||||
buf_puts(b, "\n ],\n \"scenes\": [")
|
||||
cx_scenes(b)
|
||||
buf_puts(b, "\n ],\n \"fn_refs\": [")
|
||||
cx_fn_refs(b)
|
||||
buf_puts(b, "\n ]\n }")
|
||||
}
|
||||
|
||||
# ---- places ---------------------------------------------------------------------------------------
|
||||
function cx_at_text(n: Node) -> pointer {
|
||||
return `{n.file}:{itoa(n.line)}:{itoa(n.col)}`
|
||||
}
|
||||
function cx_at(b: Buf, n: Node) -> void {
|
||||
buf_puts(b, "\"at\": ")
|
||||
if n == null or n.file == null { buf_puts(b, "null") } else { jq_put(b, cx_at_text(n)) }
|
||||
}
|
||||
# a key that sorts by place
|
||||
function cx_at_key(n: Node) -> pointer {
|
||||
var f = n.file
|
||||
if f == null { f = "" }
|
||||
return `{f}\t{itoa(1000000 + n.line)}\t{itoa(1000000 + n.col)}`
|
||||
}
|
||||
# written by the program, in a file it read (not the runtime's, not the compiler's own)
|
||||
function cx_written(n: Node) -> bool {
|
||||
return n != null and n.file != null and n.pos >= 0 and not is_runtime_file(n.file)
|
||||
}
|
||||
# or read from a resource file, whose tokens carry no offset (lex_at)
|
||||
function cx_written_or_data(n: Node) -> bool {
|
||||
if cx_written(n) { return true }
|
||||
if n == null or n.file == null { return false }
|
||||
let k = len(n.file)
|
||||
return k > 5 and (n.file[k - 5 .. k] == ".lres")
|
||||
}
|
||||
function cx_module(b: Buf, file: pointer) -> void {
|
||||
buf_puts(b, "\"module\": ")
|
||||
sc_str(b, sc_mod(file))
|
||||
}
|
||||
# ",a,b," as a JSON array, each name once
|
||||
function cx_list(b: Buf, list: pointer) -> void {
|
||||
buf_puts(b, "[")
|
||||
let seen = ss_new(64)
|
||||
var n = 0
|
||||
var i = 1
|
||||
while i < len(list) {
|
||||
var j = i
|
||||
while j < len(list) and list[j] != ',' { j += 1 }
|
||||
if j > i {
|
||||
let name = list[i .. j]
|
||||
if ss_add(seen, name) {
|
||||
if n > 0 { buf_puts(b, ", ") }
|
||||
jq_put(b, name)
|
||||
n += 1
|
||||
}
|
||||
}
|
||||
i = j + 1
|
||||
}
|
||||
buf_puts(b, "]")
|
||||
}
|
||||
# the indices 0..n-1 sorted by keys
|
||||
function cx_order(keys: []pointer) -> []int {
|
||||
let xs = new []int
|
||||
var i = 0
|
||||
while i < len(keys) {
|
||||
if keys[i] != null { push(xs, i) }
|
||||
i += 1
|
||||
}
|
||||
sc_sort(xs, keys)
|
||||
return xs
|
||||
}
|
||||
|
||||
# ---- modules --------------------------------------------------------------------------------------
|
||||
function cx_mod_names(names: []pointer, seen: StrSet, from: []pointer) -> void {
|
||||
var i = 0
|
||||
while i < len(from) {
|
||||
if from[i] != null and len(from[i]) > 0 and ss_add(seen, from[i]) { push(names, from[i]) }
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
function cx_modules(b: Buf) -> void {
|
||||
let names = new []pointer
|
||||
let seen = ss_new(256)
|
||||
cx_mod_names(names, seen, g_mod_name)
|
||||
cx_mod_names(names, seen, g_mu_mod)
|
||||
cx_mod_names(names, seen, g_mod_friends)
|
||||
let xs = cx_order(names)
|
||||
var a = 0
|
||||
while a < len(xs) {
|
||||
let m = names[xs[a]]
|
||||
sc_sep(b, a == 0, " ")
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, m)
|
||||
let files = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_mod_file) {
|
||||
if (g_mod_name[i] == m) and not is_runtime_file(g_mod_file[i]) { push(files, g_mod_file[i]) }
|
||||
i += 1
|
||||
}
|
||||
var pkg: pointer = null
|
||||
if len(files) > 0 and len(pkg_of_file(files[0])) > 0 { pkg = pkg_of_file(files[0]) }
|
||||
buf_puts(b, ", \"package\": ")
|
||||
sc_str(b, pkg)
|
||||
let ly = mod_layer(m)
|
||||
buf_puts(b, ", \"layer\": ")
|
||||
if len(ly) == 0 { buf_puts(b, "null") } else { jq_put(b, ly) }
|
||||
let u = mod_find_uses(m)
|
||||
buf_puts(b, ", \"uses\": ")
|
||||
if u < 0 { buf_puts(b, "null") } else { cx_list(b, g_mu_list[u]) }
|
||||
buf_puts(b, ", \"uses_at\": ")
|
||||
if u < 0 { buf_puts(b, "null") } else { jq_put(b, `{g_mu_file[u]}:{itoa(g_mu_line[u])}`) }
|
||||
buf_puts(b, ", \"friend\": ")
|
||||
cx_friend(b, m)
|
||||
buf_puts(b, ", \"files\": [")
|
||||
let fx = cx_order(files)
|
||||
i = 0
|
||||
while i < len(fx) {
|
||||
if i > 0 { buf_puts(b, ", ") }
|
||||
jq_put(b, files[fx[i]])
|
||||
i += 1
|
||||
}
|
||||
buf_puts(b, "]}")
|
||||
a += 1
|
||||
}
|
||||
}
|
||||
# null, or {"of": null} for a friend of every module, else {"of": [the modules]}
|
||||
function cx_friend(b: Buf, m: pointer) -> void {
|
||||
var all = false
|
||||
var list: pointer = null
|
||||
var i = 0
|
||||
while i < len(g_mod_friends) {
|
||||
if (g_mod_friends[i] == m) {
|
||||
if len(g_fr_scope[i]) == 0 { all = true } else if list == null { list = g_fr_scope[i] } else { list = list + g_fr_scope[i] }
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
if not all and list == null {
|
||||
buf_puts(b, "null")
|
||||
return
|
||||
}
|
||||
buf_puts(b, "{\"of\": ")
|
||||
if all { buf_puts(b, "null") } else { cx_list(b, list) }
|
||||
buf_puts(b, "}")
|
||||
}
|
||||
|
||||
# ---- states, actions, reducers, dispatch ----------------------------------------------------------
|
||||
# the records of one kind (2 a state, 3 an action) in prog
|
||||
function cx_decls(b: Buf, uns: int) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_COMP and d.uns == uns and d.s != null and cx_written(d) { push(keys, sc_key(sc_name(d.s), d.file, d.line)) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let d = prog[xs[i]]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, sc_name(d.s))
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, d.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, d)
|
||||
buf_puts(b, "}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
function cx_reducers(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_red_nodes) {
|
||||
let f = g_red_nodes[i]
|
||||
if cx_written(f) { push(keys, `{g_red_state[i]}\t{g_red_action[i]}\t{cx_at_key(f)}`) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let k = xs[i]
|
||||
let f = g_red_nodes[k]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"state\": ")
|
||||
jq_put(b, g_red_state[k])
|
||||
buf_puts(b, ", \"action\": ")
|
||||
jq_put(b, g_red_action[k])
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, f.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, f)
|
||||
buf_puts(b, "}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
function cx_dispatch(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_dsp_at) {
|
||||
let n = g_dsp_at[i]
|
||||
if cx_written(n) { push(keys, cx_at_key(n)) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let n = g_dsp_at[xs[i]]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"action\": ")
|
||||
jq_put(b, sc_name(g_dsp_names[xs[i]]))
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, n.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, n)
|
||||
buf_puts(b, "}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- events ---------------------------------------------------------------------------------------
|
||||
function cx_events(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_events) {
|
||||
let e = g_events[i]
|
||||
if e.s != null and cx_written(e) { push(keys, sc_key(sc_name(e.s), e.file, e.line)) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let ekeys = new []pointer # the emits, by place
|
||||
i = 0
|
||||
while i < len(g_cx_emits) {
|
||||
if cx_written(g_cx_emits[i]) { push(ekeys, cx_at_key(g_cx_emits[i])) } else { push(ekeys, null) }
|
||||
i += 1
|
||||
}
|
||||
let ex = cx_order(ekeys)
|
||||
let hkeys = new []pointer # the listeners, by place
|
||||
i = 0
|
||||
while i < len(g_cx_h) {
|
||||
if g_cx_hlisten[i] != null and cx_written(g_cx_h[i]) { push(hkeys, cx_at_key(g_cx_h[i])) } else { push(hkeys, null) }
|
||||
i += 1
|
||||
}
|
||||
let hx = cx_order(hkeys)
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let e = g_events[xs[i]]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, sc_name(e.s))
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, e.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, e)
|
||||
buf_puts(b, ", \"cancellable\": ")
|
||||
sc_bool(b, e.ival == 1)
|
||||
buf_puts(b, ", \"net\": ")
|
||||
sc_str(b, e.ty)
|
||||
buf_puts(b, ", \"emits\": [")
|
||||
var n = 0
|
||||
var k = 0
|
||||
while k < len(ex) {
|
||||
let s = g_cx_emits[ex[k]]
|
||||
if (s.s == e.s) {
|
||||
if n > 0 { buf_puts(b, ", ") }
|
||||
jq_put(b, cx_at_text(s))
|
||||
n += 1
|
||||
}
|
||||
k += 1
|
||||
}
|
||||
buf_puts(b, "], \"listeners\": [")
|
||||
n = 0
|
||||
k = 0
|
||||
while k < len(hx) {
|
||||
let h = hx[k]
|
||||
if (g_cx_hlisten[h].s == e.s) {
|
||||
if n > 0 { buf_puts(b, ", ") }
|
||||
buf_puts(b, "{\"handler\": ")
|
||||
jq_put(b, g_cx_hname[h])
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, g_cx_h[h].file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, g_cx_h[h])
|
||||
buf_puts(b, "}")
|
||||
n += 1
|
||||
}
|
||||
k += 1
|
||||
}
|
||||
buf_puts(b, "]}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- ports and binds ------------------------------------------------------------------------------
|
||||
function cx_ports(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_pt_name) {
|
||||
let c = g_pt_comp[i]
|
||||
if cx_written(c) { push(keys, sc_key(g_pt_name[i], c.file, c.line)) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let k = xs[i]
|
||||
let c = g_pt_comp[k]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, g_pt_name[k])
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, c.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, c)
|
||||
buf_puts(b, ", \"bound\": ")
|
||||
sc_bool(b, g_pt_bind[k] != null)
|
||||
buf_puts(b, ", \"members\": [")
|
||||
var f = 0
|
||||
while f < len(c.kids) {
|
||||
let fi = c.kids[f]
|
||||
if f > 0 { buf_puts(b, ", ") }
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, fi.s)
|
||||
buf_puts(b, ", \"type\": ")
|
||||
jq_put(b, sc_ty(fi.ty))
|
||||
buf_puts(b, ", \"default\": ")
|
||||
sc_str(b, sc_text(fi.file, fi.sp0, fi.sp1))
|
||||
buf_puts(b, ", \"required\": ")
|
||||
sc_bool(b, fi.a == null)
|
||||
buf_puts(b, "}")
|
||||
f += 1
|
||||
}
|
||||
buf_puts(b, "]}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
# one row per member a bind gives
|
||||
function cx_binds(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
let rows = new []Node
|
||||
let ports = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_bd) {
|
||||
let bd = g_bd[i]
|
||||
if cx_written(bd) and bd.a != null {
|
||||
var k = 0
|
||||
while k < len(bd.a.kids) {
|
||||
let fi = bd.a.kids[k]
|
||||
if fi.kind == E_FINIT {
|
||||
push(keys, cx_at_key(fi))
|
||||
push(rows, fi)
|
||||
push(ports, bd.s)
|
||||
}
|
||||
k += 1
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let fi = rows[xs[i]]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"port\": ")
|
||||
jq_put(b, ports[xs[i]])
|
||||
buf_puts(b, ", \"member\": ")
|
||||
jq_put(b, fi.s)
|
||||
buf_puts(b, ", \"fn\": ")
|
||||
if fi.a != null and fi.a.kind == E_FNREF and fi.a.pos >= 0 { jq_put(b, sc_name(fi.a.s)) } else { buf_puts(b, "null") }
|
||||
buf_puts(b, ", \"value\": ")
|
||||
sc_str(b, sc_text(fi.file, fi.sp0, fi.sp1))
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, fi.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, fi)
|
||||
buf_puts(b, "}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
415
selfhost/backend/emit_schema_code_game.ludic
Normal file
415
selfhost/backend/emit_schema_code_game.ludic
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
# emit_schema_code_game.ludic — the "code" section's handlers, models, prefabs, scenes and fn_refs
|
||||
# (emit_schema_code.ludic says the shape of the rest).
|
||||
#
|
||||
# handlers name (as written: a scene's without its scene), module, at, phase (null for a hook),
|
||||
# hook (null, "On", "OnSpawn", "OnDespawn", "OnAttach", "OnDetach", "OnEnable",
|
||||
# "OnDisable", "OnStart", "OnQuit"), target (the hook's event, model or property),
|
||||
# public (@Public), net ("server" for @Server, "predicted" for @Predicted, else null),
|
||||
# queries (null, or {"these": [{"property", "filter"}], "on": model or null}), scene, layer
|
||||
# models name, module, at, owned (@Owned), properties [{"name", "sync"}]
|
||||
# prefabs name, model, module, at, components [{"property", "fields": [{"name", "value"}]}]
|
||||
# scenes name, module, at, start, public, shows, lasts (as written) and then, loads (the scene it
|
||||
# goes on to), enter, exit (has one), layers
|
||||
# fn_refs every `fn name` written, in code or data: fn, module, at, and the slot it fills -
|
||||
# slot_kind "registry" (slot "Registry.field", entry its key), "port" (a bind's
|
||||
# "Port.member"), "port_default", "default" (a record field's), "record" (a `new` or a
|
||||
# spawn's "Type.field"), "event" (an emit's "Event.field"), "arg" ("callee(i)", the i-th
|
||||
# argument written), "assign" (`s.tick = fn f`: "s.tick"), "let" (the name) or "value"
|
||||
# (anywhere else, slot null)
|
||||
|
||||
# ---- handlers -------------------------------------------------------------------------------------
|
||||
function cx_handlers(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_cx_h) {
|
||||
let h = g_cx_h[i]
|
||||
if cx_written(h) { push(keys, sc_key(g_cx_hname[i], h.file, h.line)) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let k = xs[i]
|
||||
let h = g_cx_h[k]
|
||||
let hook = g_cx_hhook[k]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, g_cx_hname[k])
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, h.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, h)
|
||||
buf_puts(b, ", \"phase\": ")
|
||||
if hook == null or (hook == "OnStart") or (hook == "OnQuit") { sc_str(b, h.ty) } else { buf_puts(b, "null") }
|
||||
buf_puts(b, ", \"hook\": ")
|
||||
sc_str(b, hook)
|
||||
buf_puts(b, ", \"target\": ")
|
||||
sc_str(b, g_cx_htarget[k])
|
||||
buf_puts(b, ", \"public\": ")
|
||||
sc_bool(b, g_cx_hpub[k])
|
||||
buf_puts(b, ", \"net\": ")
|
||||
if h.ival == 1 { jq_put(b, "server") } else if h.ival == 2 { jq_put(b, "predicted") } else { buf_puts(b, "null") }
|
||||
buf_puts(b, ", \"queries\": ")
|
||||
cx_queries(b, g_cx_hq[k])
|
||||
buf_puts(b, ", \"scene\": ")
|
||||
sc_str(b, g_cx_hscene[k])
|
||||
buf_puts(b, ", \"layer\": ")
|
||||
sc_str(b, g_cx_hlayer[k])
|
||||
buf_puts(b, "}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
# @Queries(these: [P {filter}, ...], on: M): its terms, a filter as written (parse_queries_anno)
|
||||
function cx_queries(b: Buf, q: Node) -> void {
|
||||
if q == null or q.c == null {
|
||||
buf_puts(b, "null")
|
||||
return
|
||||
}
|
||||
buf_puts(b, "{\"these\": [")
|
||||
var on: pointer = null
|
||||
var n = 0
|
||||
var i = 0
|
||||
while i < len(q.c.kids) {
|
||||
let t = q.c.kids[i]
|
||||
if t.ival == 1 {
|
||||
on = t.s
|
||||
} else {
|
||||
if n > 0 { buf_puts(b, ", ") }
|
||||
buf_puts(b, "{\"property\": ")
|
||||
jq_put(b, t.s)
|
||||
buf_puts(b, ", \"filter\": ")
|
||||
sc_str(b, sc_text(t.file, t.sp0, t.sp1))
|
||||
buf_puts(b, "}")
|
||||
n += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
buf_puts(b, "], \"on\": ")
|
||||
sc_str(b, on)
|
||||
buf_puts(b, "}")
|
||||
}
|
||||
|
||||
# ---- models and prefabs ---------------------------------------------------------------------------
|
||||
function cx_models(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_ARCH and d.s != null and cx_written(d) { push(keys, sc_key(sc_name(d.s), d.file, d.line)) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let d = prog[xs[i]]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, sc_name(d.s))
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, d.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, d)
|
||||
buf_puts(b, ", \"owned\": ")
|
||||
sc_bool(b, d.ival == 1)
|
||||
buf_puts(b, ", \"properties\": [")
|
||||
var k = 0
|
||||
while k < len(d.kids) {
|
||||
if k > 0 { buf_puts(b, ", ") }
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, sc_name(d.kids[k].s))
|
||||
buf_puts(b, ", \"sync\": ")
|
||||
sc_bool(b, d.kids[k].ival == 1)
|
||||
buf_puts(b, "}")
|
||||
k += 1
|
||||
}
|
||||
buf_puts(b, "]}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
function cx_prefabs(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_PREFAB and d.s != null and cx_written(d) { push(keys, sc_key(sc_name(d.s), d.file, d.line)) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let d = prog[xs[i]]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, sc_name(d.s))
|
||||
buf_puts(b, ", \"model\": ")
|
||||
sc_str(b, sc_name(d.ty))
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, d.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, d)
|
||||
buf_puts(b, ", \"components\": [")
|
||||
var k = 0
|
||||
while k < len(d.kids) {
|
||||
let ci = d.kids[k]
|
||||
if k > 0 { buf_puts(b, ", ") }
|
||||
buf_puts(b, "{\"property\": ")
|
||||
jq_put(b, sc_name(ci.s))
|
||||
buf_puts(b, ", \"fields\": [")
|
||||
var f = 0
|
||||
while ci.a != null and f < len(ci.a.kids) {
|
||||
let fi = ci.a.kids[f]
|
||||
if f > 0 { buf_puts(b, ", ") }
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, fi.s)
|
||||
buf_puts(b, ", \"value\": ")
|
||||
sc_str(b, sc_text(fi.file, fi.sp0, fi.sp1))
|
||||
buf_puts(b, "}")
|
||||
f += 1
|
||||
}
|
||||
buf_puts(b, "]}")
|
||||
k += 1
|
||||
}
|
||||
buf_puts(b, "]}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- scenes ---------------------------------------------------------------------------------------
|
||||
function cx_scenes(b: Buf) -> void {
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_scenes) {
|
||||
let n = g_scenes[i]
|
||||
if cx_written(n) and i < len(g_cx_slayers) { push(keys, sc_key(sc_name(n.s), n.file, n.line)) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
while i < len(xs) {
|
||||
let k = xs[i]
|
||||
let n = g_scenes[k]
|
||||
sc_sep(b, i == 0, " ")
|
||||
buf_puts(b, "{\"name\": ")
|
||||
jq_put(b, sc_name(n.s))
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, n.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, n)
|
||||
buf_puts(b, ", \"start\": ")
|
||||
sc_bool(b, n.ival == g_start_scene)
|
||||
buf_puts(b, ", \"public\": ")
|
||||
sc_bool(b, g_cx_spub[k])
|
||||
buf_puts(b, ", \"shows\": ")
|
||||
sc_str(b, n.ty)
|
||||
buf_puts(b, ", \"lasts\": ")
|
||||
sc_str(b, sc_text(n.file, n.sp0, n.sp1))
|
||||
buf_puts(b, ", \"then\": ")
|
||||
sc_str(b, g_cx_sthen[k])
|
||||
buf_puts(b, ", \"loads\": ")
|
||||
sc_str(b, g_cx_sloads[k])
|
||||
buf_puts(b, ", \"enter\": ")
|
||||
sc_bool(b, n.a != null)
|
||||
buf_puts(b, ", \"exit\": ")
|
||||
sc_bool(b, n.b != null)
|
||||
buf_puts(b, ", \"layers\": ")
|
||||
cx_list(b, g_cx_slayers[k])
|
||||
buf_puts(b, "}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- fn refs --------------------------------------------------------------------------------------
|
||||
# the walk's findings: each `fn name` node met, with the slot it was met in (the first meeting wins)
|
||||
var g_cx_wn: []Node = new []Node
|
||||
var g_cx_wkind: []pointer = new []pointer
|
||||
var g_cx_wslot: []pointer = new []pointer
|
||||
var g_cx_wentry: []pointer = new []pointer
|
||||
|
||||
function cx_fn_refs(b: Buf) -> void {
|
||||
cx_walk_all()
|
||||
let keys = new []pointer
|
||||
var i = 0
|
||||
while i < len(g_cx_fnrefs) {
|
||||
let n = g_cx_fnrefs[i]
|
||||
if cx_written_or_data(n) and n.s != null { push(keys, `{cx_at_key(n)}\t{n.s}`) } else { push(keys, null) }
|
||||
i += 1
|
||||
}
|
||||
let xs = cx_order(keys)
|
||||
i = 0
|
||||
var first = true
|
||||
var last: pointer = null
|
||||
while i < len(xs) {
|
||||
let n = g_cx_fnrefs[xs[i]]
|
||||
let key = keys[xs[i]]
|
||||
if last == null or not (key == last) { # a node read twice is one site
|
||||
last = key
|
||||
var w = 0
|
||||
while w < len(g_cx_wn) and g_cx_wn[w] != n { w += 1 }
|
||||
sc_sep(b, first, " ")
|
||||
first = false
|
||||
buf_puts(b, "{\"fn\": ")
|
||||
jq_put(b, sc_name(n.s))
|
||||
buf_puts(b, ", ")
|
||||
cx_module(b, n.file)
|
||||
buf_puts(b, ", ")
|
||||
cx_at(b, n)
|
||||
buf_puts(b, ", \"slot_kind\": ")
|
||||
if w < len(g_cx_wn) { jq_put(b, g_cx_wkind[w]) } else { jq_put(b, "value") }
|
||||
buf_puts(b, ", \"slot\": ")
|
||||
if w < len(g_cx_wn) { sc_str(b, g_cx_wslot[w]) } else { buf_puts(b, "null") }
|
||||
buf_puts(b, ", \"entry\": ")
|
||||
if w < len(g_cx_wn) { sc_str(b, g_cx_wentry[w]) } else { buf_puts(b, "null") }
|
||||
buf_puts(b, "}")
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
# every place a `fn name` can be written, the narrowest slots first: a registry's rows, the binds, the
|
||||
# ports' defaults, then the program's declarations and the bodies kept outside them
|
||||
function cx_walk_all() -> void {
|
||||
var i = 0
|
||||
while i < len(g_df_rec) {
|
||||
cx_walk_rec(g_df_rec[i], "registry", g_df_reg[i], g_df_key[i], 0)
|
||||
i += 1
|
||||
}
|
||||
i = 0
|
||||
while i < len(g_bd) {
|
||||
if g_bd[i].a != null { cx_walk_rec(g_bd[i].a, "port", g_bd[i].s, null, 0) }
|
||||
i += 1
|
||||
}
|
||||
i = 0
|
||||
while i < len(g_pt_comp) {
|
||||
let c = g_pt_comp[i]
|
||||
var f = 0
|
||||
while f < len(c.kids) {
|
||||
cx_walk(c.kids[f].a, "port_default", `{g_pt_name[i]}.{c.kids[f].s}`, null, 0)
|
||||
f += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].file == null or not is_runtime_file(prog[i].file) { cx_walk(prog[i], "value", null, null, 0) }
|
||||
i += 1
|
||||
}
|
||||
cx_walk_list(g_tests)
|
||||
cx_walk_list(g_onlisten)
|
||||
cx_walk_list(g_onspawn)
|
||||
cx_walk_list(g_ondespawn)
|
||||
cx_walk_list(g_onattach)
|
||||
cx_walk_list(g_ondetach)
|
||||
cx_walk_list(g_onenable)
|
||||
cx_walk_list(g_ondisable)
|
||||
cx_walk_list(g_scenes)
|
||||
}
|
||||
function cx_walk_list(xs: []Node) -> void {
|
||||
var i = 0
|
||||
while i < len(xs) {
|
||||
cx_walk(xs[i], "value", null, null, 0)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
# a record literal's values, each in the slot owner.field
|
||||
function cx_walk_rec(rec: Node, kind: pointer, owner: pointer, entry: pointer, depth: int) -> void {
|
||||
if rec == null or depth > 400 { return }
|
||||
var i = 0
|
||||
while i < len(rec.kids) {
|
||||
let fi = rec.kids[i]
|
||||
if fi.kind == E_FINIT { cx_walk(fi.a, kind, `{owner}.{fi.s}`, entry, depth + 1) } else { cx_walk(fi, "value", null, null, depth + 1) }
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
function cx_callee(c: Node) -> pointer {
|
||||
if c == null { return null }
|
||||
if c.kind == E_ID and c.s != null { return sc_name(c.s) }
|
||||
if c.kind == E_MEMBER and c.a != null and c.a.kind == E_ID and c.a.s != null and c.s != null { return `{c.a.s}.{c.s}` }
|
||||
return null
|
||||
}
|
||||
function cx_walk(n: Node, kind: pointer, slot: pointer, entry: pointer, depth: int) -> void {
|
||||
if n == null or depth > 400 { return }
|
||||
let k = n.kind
|
||||
let d = depth + 1
|
||||
if k == E_FNREF {
|
||||
push(g_cx_wn, n)
|
||||
push(g_cx_wkind, kind)
|
||||
push(g_cx_wslot, slot)
|
||||
push(g_cx_wentry, entry)
|
||||
return
|
||||
}
|
||||
if k == E_NEW and n.a != null and n.a.kind == E_REC and n.s != null {
|
||||
cx_walk_rec(n.a, "record", sc_name(n.s), null, d)
|
||||
cx_walk(n.b, "value", null, null, d)
|
||||
return
|
||||
}
|
||||
if k == S_EMIT and n.a != null and n.s != null {
|
||||
cx_walk_rec(n.a, "event", sc_name(n.s), null, d)
|
||||
return
|
||||
}
|
||||
if (k == S_SPAWN or k == N_PREFAB) {
|
||||
var i = 0
|
||||
while i < len(n.kids) {
|
||||
let ci = n.kids[i]
|
||||
if ci.kind == E_FINIT and ci.a != null and ci.a.kind == E_REC { cx_walk_rec(ci.a, "record", sc_name(ci.s), null, d) } else { cx_walk(ci, "value", null, null, d) }
|
||||
i += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
if k == E_CALL {
|
||||
let callee = cx_callee(n.a)
|
||||
cx_walk(n.a, "value", null, null, d)
|
||||
var i = 0
|
||||
var arg = 0
|
||||
while i < len(n.kids) {
|
||||
let a = n.kids[i]
|
||||
if a != null and a.pos >= 0 {
|
||||
if callee != null { cx_walk(a, "arg", `{callee}({itoa(arg)})`, null, d) } else { cx_walk(a, "value", null, null, d) }
|
||||
arg += 1
|
||||
} else {
|
||||
cx_walk(a, "value", null, null, d)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
if k == N_COMP {
|
||||
var i = 0
|
||||
while i < len(n.kids) {
|
||||
if n.s != null and n.kids[i].s != null { cx_walk(n.kids[i].a, "default", `{sc_name(n.s)}.{n.kids[i].s}`, null, d) }
|
||||
i += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
if k == S_ASSIGN and n.b != null and n.b.kind == E_FNREF { # `s.tick = fn f`: the slot as written
|
||||
cx_walk(n.b, "assign", cx_callee(n.a), null, d)
|
||||
return
|
||||
}
|
||||
if k == S_LET and n.a != null and n.a.kind == E_FNREF and n.s != null {
|
||||
cx_walk(n.a, "let", n.s, null, d)
|
||||
return
|
||||
}
|
||||
if k == E_LIST { # a list of fn values fills the slot it is in
|
||||
var i = 0
|
||||
while i < len(n.kids) {
|
||||
cx_walk(n.kids[i], kind, slot, entry, d)
|
||||
i += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
if k == N_SYS { # its .b and .c are its layer's and scene's tags
|
||||
cx_walk(n.a, "value", null, null, d)
|
||||
return
|
||||
}
|
||||
if k == N_SCENE {
|
||||
cx_walk(n.a, "value", null, null, d)
|
||||
cx_walk(n.b, "value", null, null, d)
|
||||
return
|
||||
}
|
||||
cx_walk(n.a, "value", null, null, d)
|
||||
cx_walk(n.b, "value", null, null, d)
|
||||
cx_walk(n.c, "value", null, null, d)
|
||||
var i = 0
|
||||
while i < len(n.kids) {
|
||||
cx_walk(n.kids[i], "value", null, null, d)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
99267
selfhost/ludicc.seed.ll
99267
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
104748
selfhost/ludicc.win.seed.ll
104748
selfhost/ludicc.win.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,8 @@
|
|||
# What an editor of a program's data needs, from the compiler's own view: every record with its
|
||||
# fields, types, defaults, docs and attributes (@Ref, @Range, ...), every registry with its entries
|
||||
# in their final order and where each is written, every const, and the functions a `fn` value can
|
||||
# name. The compiler writes it (`ludicc --emit-schema`, selfhost/backend/emit_schema.ludic) once the
|
||||
# name, the text keys, and the code map (modules, actions, events, ports, handlers, scenes, fn refs).
|
||||
# The compiler writes it (`ludicc --emit-schema`, selfhost/backend/emit_schema.ludic) once the
|
||||
# program has type-checked.
|
||||
#
|
||||
# ludic schema [file] the JSON on stdout
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/backend/emit_schema_ui.ludic")
|
||||
push(f, "selfhost/backend/emit_schema_hash.ludic")
|
||||
push(f, "selfhost/backend/emit_schema_lang.ludic")
|
||||
push(f, "selfhost/backend/emit_schema_code.ludic")
|
||||
push(f, "selfhost/backend/emit_schema_code_game.ludic")
|
||||
push(f, "selfhost/check/check_state.ludic")
|
||||
push(f, "selfhost/check/check_index.ludic")
|
||||
push(f, "selfhost/check/check_types.ludic")
|
||||
|
|
|
|||
|
|
@ -1303,6 +1303,21 @@ function cmd_dev_test() -> int {
|
|||
schema_case("lang/i18n/i18n", "\"unused\": [\"map.close\"]", "--emit-schema: lang.unused - en.po's keys the program uses nowhere")
|
||||
schema_case("lang/i18n/i18n", "\"missing\": [\"hud.day\"], \"fuzzy\": [\"pause.close\"], \"extra\": [\"old.title\"]", "--emit-schema: lang.languages - another .po's missing, fuzzy and extra keys against en.po")
|
||||
schema_case("lang/i18n/i18n", "{\"english\": \"Close\", \"keys\": [\"map.close\", \"pause.close\"]}", "--emit-schema: lang.split - one English under several keys")
|
||||
schema_case("actions/modules", "{\"state\": \"Bag\", \"action\": \"PickUp\", \"module\": \"bag\", \"at\": \"examples/actions/mods/bag/index.ludic:4:16\"}", "--emit-schema: code.reducers - a reducer's state, action, module and place")
|
||||
schema_case("actions/modules", "{\"action\": \"PickUp\", \"module\": null, \"at\": \"examples/actions/modules.ludic:8:21\"}", "--emit-schema: code.dispatch - every dispatch site")
|
||||
schema_case("modules/ports", "{\"name\": \"Clock\", \"module\": \"clock\", \"at\": \"examples/modules/station/clock/index.ludic:4:13\", \"bound\": true, \"members\": [{\"name\": \"now\", \"type\": \"fn()->int\", \"default\": null, \"required\": true}, {\"name\": \"day\", \"type\": \"fn()->int\", \"default\": \"fn first_day\", \"required\": false}]}", "--emit-schema: code.ports - a port's members, their fn types and defaults")
|
||||
schema_case("modules/ports", "{\"port\": \"Clock\", \"member\": \"now\", \"fn\": \"game_hours\", \"value\": \"fn game_hours\", \"module\": \"app\", \"at\": \"examples/modules/station/app/index.ludic:3:14\"}", "--emit-schema: code.binds - a bind's member, the function it names and where")
|
||||
schema_case("modules/ports", "{\"name\": \"clock\", \"package\": null, \"layer\": null, \"uses\": [\"units\"], \"uses_at\": \"examples/modules/station/clock/index.ludic:3\", \"friend\": null, \"files\": [\"examples/modules/station/clock/index.ludic\"]}", "--emit-schema: code.modules - a module's uses and files")
|
||||
schema_case("modules/friend_of", "{\"name\": \"lab\", \"package\": null, \"layer\": null, \"uses\": null, \"uses_at\": null, \"friend\": {\"of\": [\"bank\"]}", "--emit-schema: code.modules - a friend module and whose names it sees")
|
||||
schema_case("networking/net_demo", "{\"name\": \"Move\", \"module\": null, \"at\": \"examples/networking/net_demo.ludic:24:19\", \"cancellable\": false, \"net\": \"toserver\", \"emits\": [\"examples/networking/net_demo.ludic:32:10\"], \"listeners\": [{\"handler\": \"DoMove\", \"module\": null, \"at\": \"examples/networking/net_demo.ludic:25:21\"}]}", "--emit-schema: code.events - a remote event, its emit sites and @On listeners")
|
||||
schema_case("networking/net_demo", "{\"name\": \"Ship\", \"module\": null, \"at\": \"examples/networking/net_demo.ludic:22:16\", \"owned\": true, \"properties\": [{\"name\": \"Pos\", \"sync\": true}]}", "--emit-schema: code.models - an @Owned model and its @Sync property")
|
||||
schema_case("networking/net_roles", "\"phase\": \"Update\", \"hook\": null, \"target\": null, \"public\": false, \"net\": \"server\"", "--emit-schema: code.handlers - an @Server handler's phase and net role")
|
||||
schema_case("lang/annotations", "\"queries\": {\"these\": [{\"property\": \"Transform\", \"filter\": \"scale > 0\"}, {\"property\": \"Velocity\", \"filter\": \"dx > 0 or dy > 0\"}], \"on\": \"Actor\"}", "--emit-schema: code.handlers - @Queries' terms, filters and model")
|
||||
schema_case("lang/annotations", "\"phase\": null, \"hook\": \"OnSpawn\", \"target\": \"Actor\"", "--emit-schema: code.handlers - a lifecycle hook and the model it names")
|
||||
schema_case("lang/scenes", "{\"name\": \"Title\", \"module\": null, \"at\": \"examples/lang/scenes.ludic:24:9\", \"start\": true, \"public\": false, \"shows\": null, \"lasts\": null, \"then\": null, \"loads\": null, \"enter\": true, \"exit\": true, \"layers\": [\"Main\"]}", "--emit-schema: code.scenes - the start scene, its blocks and layers")
|
||||
schema_case("lang/scenes", "\"scene\": \"Play\", \"layer\": \"Hud\"", "--emit-schema: code.handlers - a scene's handler, its scene and layer")
|
||||
schema_case("state/write_reach", "{\"fn\": \"log_note\", \"module\": null, \"at\": \"examples/state/write_reach.ludic:21:37\", \"slot_kind\": \"registry\", \"slot\": \"Kinds.note\", \"entry\": \"box\"}", "--emit-schema: code.fn_refs - a fn in a registry row, the field and row it fills")
|
||||
schema_case("modules/ports", "\"slot_kind\": \"port_default\", \"slot\": \"Clock.day\"", "--emit-schema: code.fn_refs - a port member's default")
|
||||
diag_json_case("rejected/i18n_keys/key_missing", 2, "the key pause.gone is not in examples/rejected/i18n_keys/lang/en.po", "a key literal en.po does not have is refused, at its line and column")
|
||||
diag_json_case("rejected/i18n_keys/key_missing", 2, "is a plural key, and examples/rejected/i18n_keys/lang/en.po:7 gives pause.resume no msgid_plural", "kn\"...\" is a plural key: en.po's entry has a msgid_plural")
|
||||
diag_json_case("rejected/i18n_keys/key_missing", 2, "trf: hud.day is given 2 value(s), and its English in examples/rejected/i18n_keys/lang/en.po has 1 hole(s)", "trf given more values than the key's English has holes: a warning")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue