ludic/selfhost/frontend/defaults.ludic
Orkuncakilkaya 66a2bc2214 feat(lang): L11 views and templates - the UI is markup, not code
A `view Name { field = x; function q(..); on e(..) }` declaration is the one bridge between a
program and its UI: it writes view_<name>() -> UiView, whose model is a Value of every field and
whose call runs a query or an event by name.

ludic.ui is a template runtime:
- HTML-shaped XML screens and components, loaded at run time;
- {expression} bindings, if/else/each, props, slots, per-instance state;
- on-press / onclick actions (event, set, emit);
- component libraries (export="true", <import src as>).

Styling:
- stylesheets in <style> or importable .lss files (@import);
- CSS selectors (#id, .class, [attr=v], descendant and > combinators, :hover, :disabled,
  :first-child, :last-child, :nth-child, :not) weighed by specificity;
- the box model and flex under CSS's property names.

Also:
- default parameters, and positional-then-named calls;
- Value gains a float kind;
- a function shadowing a runtime one is refused;
- an index is evaluated before the slice is read;
- runtime errors name the right file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:58:47 +03:00

96 lines
2.9 KiB
Text

# defaults.ludic — L11: a parameter may have a default, `pad: float = 8.0`, and a call may leave it
# out - positionally from the end, or by not naming it. That is what makes a component's props
# usable: `button(label: "Buy", on_press: fn buy)` rather than every prop of a button spelt out.
# The default is an expression written where the function is and evaluated for each call.
# `f(a, b, size: 2)`: the leading arguments are the first parameters, by position, and the rest
# are named - the natural call of a component, whose first prop is the one it is about
function call_mixed_to_named(e: Node, labels: []pointer) -> void {
var first_named = -1
var i = 0
while i < len(e.kids) {
if e.kids[i].kind == E_FINIT and first_named < 0 { first_named = i }
if e.kids[i].kind != E_FINIT and first_named >= 0 {
g_err_file = e.file
g_err_line = e.line
perr("a positional argument after a named one")
}
i += 1
}
if first_named <= 0 { return }
let out = new []Node
var k = 0
while k < len(e.kids) {
var a = e.kids[k]
if k < first_named and k < len(labels) {
let fi = node(E_FINIT)
fi.s = labels[k]
fi.a = a
fi.line = a.line
fi.file = a.file
a = fi
}
push(out, a)
k += 1
}
e.kids = out
}
function call_fill_defaults(e: Node, f: Node) -> void {
if f == null { return }
let labels = new []pointer
let defs = new []Node
var any = false
var i = 0
while i < len(f.kids) {
let p = f.kids[i]
if p.kind == N_PARAM {
push(labels, p.s)
push(defs, p.a)
if p.a != null { any = true }
}
i += 1
}
call_mixed_to_named(e, labels)
if not any { return }
if len(e.kids) > 0 and e.kids[0].kind == E_FINIT {
let out = new []Node
var li = 0
while li < len(labels) {
var found: Node = null
var k = 0
while k < len(e.kids) {
if (e.kids[k].s == labels[li]) { found = e.kids[k] }
k += 1
}
if found == null and defs[li] != null {
found = node(E_FINIT)
found.s = labels[li]
found.a = default_copy(defs[li], e)
}
if found != null { push(out, found) }
li += 1
}
# a label the function does not have stays, so the call is refused for it
var k2 = 0
while k2 < len(e.kids) {
if not default_label_in(labels, e.kids[k2].s) { push(out, e.kids[k2]) }
k2 += 1
}
e.kids = out
return
}
while len(e.kids) < len(labels) and defs[len(e.kids)] != null { push(e.kids, default_copy(defs[len(e.kids)], e)) }
}
function default_label_in(labels: []pointer, s: pointer) -> bool {
var i = 0
while i < len(labels) {
if (labels[i] == s) { return true }
i += 1
}
return false
}
# each call gets its own copy of the default, at the call's line for its errors
function default_copy(d: Node, at: Node) -> Node {
let c = gen_clone(d, new []pointer, new []pointer)
c.line = at.line
return c
}