ludic schema: a components section and a natives list (R4)

Each UI component: module, place, doc, xml and lss paths, props and state
(type, default as written, place, doc), states_read (the header's states),
derived fields with their types, functions and events as the template calls
them (states and instance stripped), and the native tags its template uses.
natives: every ui_native / ui_native_input call with a literal tag - tag, via,
handler, place. schema_version stays 1; the lists are additions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 19:36:04 +03:00
parent 28f7b4666b
commit 281ebc23e1
11 changed files with 80436 additions and 71227 deletions

View file

@ -10,6 +10,8 @@
# consts every const: its type and its value as written
# functions every function a `fn` value can name: fn_type is the type a field sees, the states
# 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
#
# 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.
@ -36,6 +38,11 @@ function schema_buf() -> Buf {
sc_consts(b)
buf_puts(b, "\n ],\n \"functions\": [")
sc_functions(b)
sc_natives_find()
buf_puts(b, "\n ],\n \"components\": [")
sc_components(b)
buf_puts(b, "\n ],\n \"natives\": [")
sc_natives(b)
buf_puts(b, "\n ]\n}\n")
return b
}

View file

@ -0,0 +1,296 @@
# emit_schema_ui.ludic — the schema's UI side (emit_schema.ludic): every component, as a UI editor
# needs to know it, and every native tag the program registers with ludic.ui.
#
# components each `component` (component.ludic): its module, place, doc, template and stylesheet
# paths; its props and its state (both fields of the instance, a prop given by the
# parent's attribute, a state kept by the instance and set by the template's `set:`),
# each with its type, its default as written, its doc and place; `states_read`, the
# program's states its header names (supplied by the runtime, never seen by the
# template); `derived`, the fields worked out each frame; `functions` and `events`,
# what the template calls, with the header's states and the instance stripped; and
# `natives`, the registered native tags its template uses
# natives each call of ui_native / ui_native_input whose tag is a string literal: the tag, the
# function called, the handler it names (a `fn` value, else null) and the place. A tag
# worked out at run time cannot be known here and is left out.
# ---- natives ----------------------------------------------------------------------------------
var g_sn_tag: []pointer = new []pointer
var g_sn_via: []pointer = new []pointer
var g_sn_fn: []pointer = new []pointer # the handler's name, or null
var g_sn_call: []Node = new []Node
function sc_natives_find() -> void {
g_sn_tag = new []pointer
g_sn_via = new []pointer
g_sn_fn = new []pointer
g_sn_call = new []Node
var i = 0
while i < len(prog) {
sc_nt_walk(prog[i])
i += 1
}
}
function sc_nt_walk(n: Node) -> void {
if n == null { return }
if n.kind == E_CALL and n.a != null and n.a.kind == E_ID { sc_nt_call(n) }
sc_nt_walk(n.a)
sc_nt_walk(n.b)
sc_nt_walk(n.c)
var i = 0
while i < len(n.kids) {
sc_nt_walk(n.kids[i])
i += 1
}
}
# ui_native(ui_st, tag, measure, draw) / ui_native_input(ui_st, tag, f): the tag counted from the end,
# since the leading state may be written or supplied
function sc_nt_call(n: Node) -> void {
let via = vis_plain(n.a.s)
if not (via == "ui_native") and not (via == "ui_native_input") { return }
if n.file == null or n.pos < 0 or is_runtime_file(n.file) { return }
let f = find_fn(n.a.s)
if f == null { return }
var np = 0
var ti = -1
var k = 0
while k < len(f.kids) {
if f.kids[k].kind == N_PARAM {
if (f.kids[k].s == "tag") { ti = np }
np += 1
}
k += 1
}
if ti < 0 { return }
let ai = len(n.kids) - (np - ti)
if ai < 0 or ai >= len(n.kids) { return }
let t = n.kids[ai]
if t.kind != E_STR or t.s == null { return }
var j = 0
while j < len(g_sn_call) {
let o = g_sn_call[j]
if (g_sn_tag[j] == t.s) and (g_sn_via[j] == via) and (o.file == n.file) and o.a.line == n.a.line and o.a.col == n.a.col { return }
j += 1
}
var h: pointer = null
let last = n.kids[len(n.kids) - 1]
if last.kind == E_FNREF and last.s != null { h = sc_name(last.s) }
push(g_sn_tag, t.s)
push(g_sn_via, via)
push(g_sn_fn, h)
push(g_sn_call, n)
}
function sc_natives(b: Buf) -> void {
let xs = new []int
let keys = new []pointer
var i = 0
while i < len(g_sn_tag) {
let n = g_sn_call[i]
push(xs, i)
push(keys, sc_key(g_sn_tag[i], n.file, n.a.line) + "\t" + g_sn_via[i])
i += 1
}
sc_sort(xs, keys)
i = 0
while i < len(xs) {
let j = xs[i]
let n = g_sn_call[j]
sc_sep(b, i == 0, " ")
buf_puts(b, "{\"tag\": ")
jq_put(b, g_sn_tag[j])
buf_puts(b, ", \"via\": ")
jq_put(b, g_sn_via[j])
buf_puts(b, ", \"handler\": ")
sc_str(b, g_sn_fn[j])
buf_puts(b, ", \"module\": ")
sc_str(b, sc_mod(n.file))
buf_puts(b, ", ")
sc_at(b, n.file, n.a.line, n.a.col) # where the function's name is written
buf_puts(b, "}")
i += 1
}
}
# ---- components -------------------------------------------------------------------------------
function sc_components(b: Buf) -> void {
let xs = new []int
let keys = new []pointer
var c = 0
while c < len(g_cm_name) {
if not is_runtime_file(g_cm_file[c]) {
push(xs, c)
push(keys, sc_key(g_cm_name[c], g_cm_file[c], g_cm_at[c][0]))
} else {
push(keys, "")
}
c += 1
}
sc_sort(xs, keys)
var i = 0
while i < len(xs) {
sc_component(b, xs[i], i == 0)
i += 1
}
}
function sc_component(b: Buf, c: int, first: bool) -> void {
let file = g_cm_file[c]
sc_sep(b, first, " ")
buf_puts(b, "{\"name\": ")
jq_put(b, g_cm_name[c])
buf_puts(b, ", \"module\": ")
sc_str(b, sc_mod(file))
buf_puts(b, ", \"exported\": ")
sc_bool(b, g_cm_head[c].vis == 1)
buf_puts(b, ", ")
sc_at(b, file, g_cm_at[c][0], g_cm_at[c][1])
buf_puts(b, ",\n \"xml\": ")
jq_put(b, g_cm_xml_path[c])
buf_puts(b, ", \"lss\": ")
var lss: pointer = g_cm_lss_path[c]
if len(lss) == 0 { lss = null }
sc_str(b, lss)
buf_puts(b, ",\n \"doc\": ")
sc_str(b, sc_doc(file, g_cm_at[c][2]))
buf_puts(b, ",\n \"props\": [")
sc_cm_keeps(b, c, true)
buf_puts(b, "],\n \"state\": [")
sc_cm_keeps(b, c, false)
buf_puts(b, "],\n \"states_read\": [")
var k = 0
while k < len(g_cm_ps[c]) {
if k > 0 { buf_puts(b, ", ") }
jq_put(b, sc_ty(g_cm_ps[c][k].ty))
k += 1
}
buf_puts(b, "],\n \"derived\": [")
k = 0
while k < len(g_cm_fields[c]) {
sc_sep(b, k == 0, " ")
buf_puts(b, "{\"name\": ")
jq_put(b, g_cm_fnames[c][k])
buf_puts(b, ", \"type\": ")
jq_put(b, sc_ty(g_cm_fields[c][k].ty))
buf_puts(b, ", \"line\": ")
buf_puti(b, g_cm_fline[c][k])
buf_puts(b, "}")
k += 1
}
if len(g_cm_fields[c]) > 0 { buf_puts(b, "\n ") }
buf_puts(b, "],\n \"functions\": [")
sc_cm_calls(b, c, false)
buf_puts(b, "],\n \"events\": [")
sc_cm_calls(b, c, true)
buf_puts(b, "],\n \"natives\": [")
sc_cm_natives(b, c)
buf_puts(b, "]}")
}
# its props (or its state), in the order written: the instance's fields
function sc_cm_keeps(b: Buf, c: int, props: bool) -> void {
let file = g_cm_file[c]
var n = 0
var i = 0
while i < len(g_cm_keep[c]) {
if g_cm_prop[c][i] == props {
let a = i * 5
sc_sep(b, n == 0, " ")
buf_puts(b, "{\"name\": ")
jq_put(b, g_cm_keep[c][i])
buf_puts(b, ", \"type\": ")
jq_put(b, sc_ty(g_cm_keep_ty[c][i]))
buf_puts(b, ", \"default\": ")
sc_str(b, sc_text(file, g_cm_kat[c][a + 3], g_cm_kat[c][a + 4]))
buf_puts(b, ", ")
sc_at(b, file, g_cm_kat[c][a], g_cm_kat[c][a + 1])
if props { buf_puts(b, ", \"attributes\": []") }
buf_puts(b, ", \"doc\": ")
sc_str(b, sc_doc(file, g_cm_kat[c][a + 2]))
buf_puts(b, "}")
n += 1
}
i += 1
}
if n > 0 { buf_puts(b, "\n ") }
}
# its functions (or its `on` events) as the template calls them: past the header's states and the
# instance, which the runtime supplies
function sc_cm_calls(b: Buf, c: int, events: bool) -> void {
var n = 0
var i = 0
while i < len(g_cm_calls[c]) {
if g_cm_isev[c][i] == events {
let f = g_cm_calls[c][i]
sc_sep(b, n == 0, " ")
buf_puts(b, "{\"name\": ")
jq_put(b, g_cm_cnames[c][i])
buf_puts(b, ", \"params\": [")
var k = 0
var np = 0
while k < len(f.kids) {
let p = f.kids[k]
if p.kind == N_PARAM and not (p.s == "self") and not sc_is_state_param(p) {
if np > 0 { buf_puts(b, ", ") }
buf_puts(b, "[")
jq_put(b, p.s)
buf_puts(b, ", ")
jq_put(b, sc_ty(p.ty))
buf_puts(b, "]")
np += 1
}
k += 1
}
buf_puts(b, "]")
if not events {
buf_puts(b, ", \"result\": ")
jq_put(b, sc_ty(f.ty))
}
buf_puts(b, ", \"line\": ")
buf_puti(b, f.line)
buf_puts(b, "}")
n += 1
}
i += 1
}
if n > 0 { buf_puts(b, "\n ") }
}
# the registered native tags its template uses as elements, sorted, each once
function sc_cm_natives(b: Buf, c: int) -> void {
let xml = g_cm_xml[c]
let tags = new []pointer
let xs = new []int
var i = 0
while i < len(g_sn_tag) {
let t = g_sn_tag[i]
var seen = false
var j = 0
while j < len(tags) {
if (tags[j] == t) { seen = true }
j += 1
}
if not seen and sc_xml_uses(xml, t) {
push(xs, len(tags))
push(tags, t)
}
i += 1
}
sc_sort(xs, tags)
i = 0
while i < len(xs) {
if i > 0 { buf_puts(b, ", ") }
jq_put(b, tags[xs[i]])
i += 1
}
}
# `<tag` followed by a space, a '/' or a '>' somewhere in the template
function sc_xml_uses(xml: pointer, tag: pointer) -> bool {
if xml == null { return false }
let n = len(xml)
let m = len(tag)
var i = 0
while i + m + 1 < n {
if xml[i] == '<' and xml[i + 1] == tag[0] and (xml[i + 1 .. i + 1 + m] == tag) {
let e = xml[i + 1 + m]
if e == ' ' or e == '/' or e == '>' or e == '\n' or e == '\t' or e == '\r' { return true }
}
i += 1
}
return false
}