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>
116 lines
4.7 KiB
Text
116 lines
4.7 KiB
Text
# component_class.ludic — L11: a component's class: its name, its template and styles as read from
|
|
# beside it (and where they are, for a developer's build to read again), and its functions; the
|
|
# registration is a global, so every component is known before the program's first frame
|
|
function cm_class(c: int, px: pointer, head: Node) -> void {
|
|
let name = g_cm_name[c]
|
|
let dir = dir_of(g_cm_file[c])
|
|
let xml_path = join_path(dir, name + ".xml")
|
|
let lss_path = join_path(dir, name + ".lss")
|
|
let xml = read_file(xml_path)
|
|
if xml == null { cm_err(c, `there is no {name}.xml beside it - a component's template is NAME.xml in its directory`) }
|
|
var lss = read_file(lss_path)
|
|
var lss_at = lss_path
|
|
if lss == null {
|
|
lss = ""
|
|
lss_at = ""
|
|
} else { lss = cm_lss_inline(c, lss, dir, 0) }
|
|
g_cm_xml_path[c] = xml_path
|
|
g_cm_lss_path[c] = lss_at
|
|
g_cm_xml[c] = xml
|
|
var src = `function {px}class() -> UiClass {{\n let k = new UiClass\n k.name = "{name}"\n`
|
|
src = src + ` k.xml_path = "{xml_path}"\n k.xml = {px}xml_text()\n k.lss_path = "{lss_at}"\n k.lss = {px}lss_text()\n`
|
|
src = src + ` k.make = fn {px}new\n k.renew = fn {px}renew\n k.props = fn {px}props\n k.model = fn {px}model\n k.call = fn {px}call\n return k\n}}\n`
|
|
vw_parse(src, g_cm_file[c], g_cm_line[c], head.vis)
|
|
vw_parse(cm_renew_src(c, px, "Cmp" + name), g_cm_file[c], g_cm_line[c], head.vis)
|
|
cm_text_fn(c, px + "xml_text", xml, head)
|
|
cm_text_fn(c, px + "lss_text", lss, head)
|
|
let reg = node(E_CALL)
|
|
let callee = node(E_ID)
|
|
callee.s = "ui_register"
|
|
reg.a = callee
|
|
let arg = node(E_CALL)
|
|
let make = node(E_ID)
|
|
make.s = px + "class"
|
|
arg.a = make
|
|
arg.kids = new []Node
|
|
reg.kids = new []Node
|
|
push(reg.kids, arg)
|
|
head.a = reg
|
|
}
|
|
# a function answering a file's text, carried in the program as it was read
|
|
function cm_text_fn(c: int, name: pointer, text: pointer, head: Node) -> void {
|
|
let at = len(prog)
|
|
vw_parse(`function {name}() -> string {{\n return ""\n}}\n`, g_cm_file[c], g_cm_line[c], head.vis)
|
|
prog[at].a.kids[0].a.s = text
|
|
}
|
|
# a stylesheet with each `@import "x.lss";` replaced by that file (beside it, or from the project's
|
|
# root when it starts with /), so the program carries its whole look
|
|
function cm_lss_inline(c: int, text: pointer, dir: pointer, depth: int) -> pointer {
|
|
if depth > 8 { cm_err(c, "its stylesheets @import each other in a circle") }
|
|
var out = ""
|
|
var i = 0
|
|
let n = len(text)
|
|
while i < n {
|
|
var j = i
|
|
while j < n and text[j] != '\n' { j += 1 }
|
|
let line: pointer = text[i..j]
|
|
let path = cm_import_of(line)
|
|
if path == null { out = out + line + "\n" } else {
|
|
var at = join_path(dir, path)
|
|
if path[0] == '/' { at = path[1..len(path)] }
|
|
let inner = read_file(at)
|
|
if inner == null { cm_err(c, `its stylesheet imports {path}, which is not there`) }
|
|
out = out + cm_lss_inline(c, inner, dir_of(at), depth + 1) + "\n"
|
|
}
|
|
i = j + 1
|
|
}
|
|
return out
|
|
}
|
|
# the path in `@import "x";` (or with single quotes), or null for any other line
|
|
function cm_import_of(line: pointer) -> pointer {
|
|
var i = 0
|
|
while i < len(line) and (line[i] == ' ' or line[i] == '\t') { i += 1 }
|
|
if i + 7 > len(line) { return null }
|
|
if not (line[i..i + 7] == "@import") { return null }
|
|
var a = i + 7
|
|
while a < len(line) and line[a] != '"' and line[a] != '\'' { a += 1 }
|
|
if a >= len(line) { return null }
|
|
let q = line[a]
|
|
var b = a + 1
|
|
while b < len(line) and line[b] != q { b += 1 }
|
|
return line[a + 1..b]
|
|
}
|
|
# a component is used by its name as a tag, so two of one name (or of names that differ only in case,
|
|
# which would share their functions' names) are an error naming both files
|
|
function cm_unique(name: pointer) -> void {
|
|
var i = 0
|
|
while i < len(g_cm_name) {
|
|
if (cm_prefix(g_cm_name[i]) == cm_prefix(name)) {
|
|
perr(`component {name} is declared twice: here and in {g_cm_file[i]}:{g_cm_line[i]} - a component is used by its name as a tag, so each needs a name of its own`)
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
# an instance's record put back as `new` made it, for ludic.ui to mount again rather than make another:
|
|
# each field its default, or its type's zero
|
|
function cm_renew_src(c: int, px: pointer, rec: pointer) -> pointer {
|
|
var src = `function {px}renew(p: pointer) -> void {{\n let s: {rec} = p\n`
|
|
var i = 0
|
|
while i < len(g_cm_keep[c]) {
|
|
let f = g_cm_keep[c][i]
|
|
if g_cm_defs[c][i] != null {
|
|
src = src + ` s.{f} = {px}def_{f}(s)\n`
|
|
} else {
|
|
src = src + ` s.{f} = {cm_zero(g_cm_keep_ty[c][i])}\n`
|
|
}
|
|
i += 1
|
|
}
|
|
return src + "}\n"
|
|
}
|
|
function cm_zero(ty: pointer) -> pointer {
|
|
if ty == "int" or ty == "long" or ty == "byte" { return "0" }
|
|
if ty == "float" { return "0.0" }
|
|
if ty == "fixed" { return "fixed(0)" }
|
|
if ty == "bool" { return "false" }
|
|
return "null"
|
|
}
|