ludic/selfhost/frontend/component_rw.ludic
Orkuncakilkaya 20d011f8e9 feat(lang): UI components as files - component Name { ... } beside Name.xml and Name.lss
The compiler turns a component declaration into:
- a record of its props and state;
- a constructor, a props setter, a model and a call;
- a class, registered with ludic.ui at start.

Its template and styles are read from beside it and compiled in, with @import inlined, and a
missing template fails the build.

In the runtime:
- each mounted instance keeps its own props and state, and `set` in a template writes the state;
- styles are scoped to the component;
- class, style and id on a component's tag land on its root, styled by the parent's sheet too;
- ui_reload re-reads a component's files from disk and keeps instances.

The package's own module is now ludic_ui, so a program may call a directory of its own ui.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:50:12 +03:00

99 lines
2.6 KiB
Text

# component_rw.ludic — L11: a component's code names its props, state, fields and functions plainly;
# each becomes the instance's: `taps += 1` is `self.taps += 1`, `label()` is cmp_x_label(self), a
# field is its getter called on self. Every function of the component takes `self` first.
function cm_rewrite(c: int, f: Node, self_ty: pointer) -> void {
var i = 0
while i < len(f.kids) {
let p = f.kids[i]
if cm_is_keep(c, p.s) or cm_is_field(c, p.s) {
g_err_file = f.file
g_err_line = f.line
g_parsing = false
perr(`component {g_cm_name[c]}: the parameter {p.s} has the name of its prop, state or field; call it something else`)
}
i += 1
}
let me = node(N_PARAM)
me.s = "self"
me.ty = self_ty
let kids = new []Node
push(kids, me)
i = 0
while i < len(f.kids) {
push(kids, f.kids[i])
i += 1
}
f.kids = kids
cm_rw(c, f.a)
}
function cm_is_keep(c: int, s: pointer) -> bool {
var i = 0
while i < len(g_cm_keep[c]) {
if (g_cm_keep[c][i] == s) { return true }
i += 1
}
return false
}
function cm_is_field(c: int, s: pointer) -> bool {
var i = 0
while i < len(g_cm_fnames[c]) {
if (g_cm_fnames[c][i] == s) { return true }
i += 1
}
return false
}
function cm_is_call(c: int, s: pointer) -> bool {
var i = 0
while i < len(g_cm_cnames[c]) {
if (g_cm_cnames[c][i] == s) { return true }
i += 1
}
return false
}
function cm_self() -> Node {
let e = node(E_ID)
e.s = "self"
return e
}
function cm_rw(c: int, n: Node) -> void {
if n == null { return }
if (n.kind == S_LET or n.kind == S_FOR) and n.s != null and (cm_is_keep(c, n.s) or cm_is_field(c, n.s)) {
g_err_file = n.file
g_err_line = n.line
g_parsing = false
perr(`component {g_cm_name[c]}: {n.s} is its own prop, state or field; name the local something else`)
}
if n.kind == E_ID and cm_is_keep(c, n.s) {
n.kind = E_MEMBER
n.a = cm_self()
return
}
if n.kind == E_ID and cm_is_field(c, n.s) {
n.kind = E_CALL
let callee = node(E_ID)
callee.s = cm_prefix(g_cm_name[c]) + "get_" + n.s
n.a = callee
n.kids = new []Node
push(n.kids, cm_self())
return
}
if n.kind == E_CALL and n.a != null and n.a.kind == E_ID and cm_is_call(c, n.a.s) and not cm_is_keep(c, n.a.s) {
n.a.s = cm_prefix(g_cm_name[c]) + n.a.s
let args = new []Node
push(args, cm_self())
var i = 0
while i < len(n.kids) {
push(args, n.kids[i])
i += 1
}
n.kids = args
}
cm_rw(c, n.a)
cm_rw(c, n.b)
cm_rw(c, n.c)
var i = 0
while i < len(n.kids) {
cm_rw(c, n.kids[i])
i += 1
}
}