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>
This commit is contained in:
parent
334469ef61
commit
66a2bc2214
64 changed files with 74806 additions and 63975 deletions
52
packages/ludic.ui/eval.ludic
Normal file
52
packages/ludic.ui/eval.ludic
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# eval.ludic - an expression's value, here
|
||||
function ui_eval(x: UiExpr, e: UiEnv) -> Val {
|
||||
if x.op == X_LIT { return x.v }
|
||||
if x.op == X_NAME { return env_get(e, x.s) }
|
||||
if x.op == X_MEMBER {
|
||||
let o = ui_eval(x.a, e)
|
||||
if Value.kind(o) == 6 and Value.has(o, x.s) != 0 { return Value.get(o, x.s) }
|
||||
if Value.kind(o) == 5 and x.s == "length" { return Value.int(Value.count(o)) }
|
||||
ui_err(`.{x.s}: there is no such field`)
|
||||
return Value.null()
|
||||
}
|
||||
if x.op == X_INDEX { return Value.at(ui_eval(x.a, e), Value.as_int(ui_eval(x.b, e))) }
|
||||
if x.op == X_CALL { return ev_call(x, e) }
|
||||
if x.op == X_NOT { return vl_bool(not vl_truthy(ui_eval(x.a, e))) }
|
||||
if x.op == X_NEG { return vl_arith("-", Value.int(0), ui_eval(x.a, e)) }
|
||||
if x.op == X_AND {
|
||||
if not vl_truthy(ui_eval(x.a, e)) { return vl_bool(false) }
|
||||
return vl_bool(vl_truthy(ui_eval(x.b, e)))
|
||||
}
|
||||
if x.op == X_OR {
|
||||
if vl_truthy(ui_eval(x.a, e)) { return vl_bool(true) }
|
||||
return vl_bool(vl_truthy(ui_eval(x.b, e)))
|
||||
}
|
||||
if x.op == X_COND {
|
||||
if vl_truthy(ui_eval(x.a, e)) { return ui_eval(x.b, e) }
|
||||
return ui_eval(x.c, e)
|
||||
}
|
||||
if x.op == X_TEXT {
|
||||
var out = ""
|
||||
for i in 0 .. len(x.args) { out = out + vl_text(ui_eval(x.args[i], e)) }
|
||||
return Value.str(out)
|
||||
}
|
||||
return vl_binary(x.s, ui_eval(x.a, e), ui_eval(x.b, e))
|
||||
}
|
||||
# a call: len() and range() are the template's own, anything else is the view's
|
||||
function ev_call(x: UiExpr, e: UiEnv) -> Val {
|
||||
let args = Value.list()
|
||||
for i in 0 .. len(x.args) { Value.add(args, ui_eval(x.args[i], e)) }
|
||||
if x.s == "len" and len(x.args) == 1 { return Value.int(Value.count(Value.at(args, 0))) }
|
||||
if x.s == "range" and len(x.args) == 1 {
|
||||
let out = Value.list()
|
||||
for i in 0 .. Value.as_int(Value.at(args, 0)) { Value.add(out, Value.int(i)) }
|
||||
return out
|
||||
}
|
||||
if ui_view == null or ui_view.call == null {
|
||||
ui_err(`{x.s}(): no view is showing to ask`)
|
||||
return Value.null()
|
||||
}
|
||||
return ui_view.call(x.s, args)
|
||||
}
|
||||
function ui_eval_text(x: UiExpr, e: UiEnv) -> string { return vl_text(ui_eval(x, e)) }
|
||||
function ui_eval_bool(x: UiExpr, e: UiEnv) -> bool { return vl_truthy(ui_eval(x, e)) }
|
||||
Loading…
Add table
Add a link
Reference in a new issue