ludic.ui, runtime, compiler: an expression's Value comes from the screen's pool while it is built (never a state's start or an action's), true and false shared, calc() terms pooled and read in place, a model's list fields filled in place (value_set_ints/strs/floats/bools); reseeded

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 01:05:35 +03:00
parent 25f4d49e43
commit fa119d234b
18 changed files with 44838 additions and 44532 deletions

View file

@ -55,13 +55,15 @@ function el_pct(v: Val) -> float {
return 0.0
}
# the number at the front of `12.5px`
function el_num(s: string) -> float {
function el_num(s: string) -> float { return el_num_in(s, 0, len(s)) }
# the number in s from..to, read where it stands
function el_num_in(s: string, from: int, to: int) -> float {
var f = 0.0
var unit = 0.0
var neg = false
for i in 0 .. len(s) {
for i in from .. to {
let c = s[i]
if c == 45 and i == 0 { neg = true } else if c == 46 { unit = 0.1 } else if c >= 48 and c <= 57 {
if c == 45 and i == from { neg = true } else if c == 46 { unit = 0.1 } else if c >= 48 and c <= 57 {
if unit > 0.0 {
f = f + float(c - 48) * unit
unit = unit * 0.1

View file

@ -34,12 +34,19 @@ function ui_default_backend() -> UiBackend {
export state UiState {
pl_pools: []UiPool = new []UiPool # pool.ludic: each screen's nodes and envs, used again
pl_cur: UiPool = null
uv_on: bool = false # pool.ludic: a build is under way, so expressions' Values are pooled
uv_true: Val = null
uv_false: Val = null
mm: UiMemo = null # memo.ludic: splits, gradients and keys by their text
mm_ints: []string = null
mm_ivals: []Val = null
ev_depth: int = 0 # eval.ludic: a call's arguments, in lists kept one a depth
ev_args: []Val = new []Val
cv_texts: []string = new []string # units_pieces.ludic: a value's var() pieces, kept
ca_pool: []CaVal = new []CaVal # calc.ludic: an evaluation's terms, used again by the next
ca_used: int = 0
ca_args: [][]CaVal = new [][]CaVal
ca_depth: int = 0
fit_out: []float = new []float # draw_content.ludic: an image's box, kept
r3n_cut: []float = new []float # render3d_nine.ludic: a nine-slice's edges, kept
r3n_xs: []float = new []float

View file

@ -7,6 +7,8 @@ function ca_eval(ui_st: mut UiState, s: string) -> bool {
ui_st.ca_s = s
ui_st.ca_i = 0
ui_st.ca_bad = ""
ui_st.ca_used = 0 # this evaluation's terms come from the pool, from the start
ui_st.ca_depth = 0
let v = ca_factor(ui_st)
ca_ws(ui_st)
if ui_st.ca_bad == "" and ui_st.ca_i < len(ui_st.ca_s) { ui_st.ca_bad = "nothing after its closing )" }
@ -42,10 +44,10 @@ function ca_sum(ui_st: mut UiState) -> CaVal {
}
return a
}
function ca_add(ui_st: UiState, a: CaVal, b: CaVal, minus: bool) -> CaVal {
function ca_add(ui_st: mut UiState, a: CaVal, b: CaVal, minus: bool) -> CaVal {
var sign = 1.0
if minus { sign = -1.0 }
let r = new CaVal
let r = ca_new(ui_st)
if a.bare and b.bare {
r.n = a.n + sign * b.n
return r
@ -56,10 +58,22 @@ function ca_add(ui_st: UiState, a: CaVal, b: CaVal, minus: bool) -> CaVal {
return r
}
# a bare number beside a length is a length in px
function ca_len(ui_st: UiState, a: CaVal) -> CaVal {
function ca_len(ui_st: mut UiState, a: CaVal) -> CaVal {
if not a.bare { return a }
let r = new CaVal
let r = ca_new(ui_st)
r.bare = false
r.px = el_px(ui_st, a.n)
return r
}
# a term, blank, from the pool: an evaluation is over before the next begins, and what it works out is
# copied into ca_px / ca_pct, so its records are used again by the next one (never made a frame)
function ca_new(ui_st: mut UiState) -> CaVal {
if ui_st.ca_used >= len(ui_st.ca_pool) { push(ui_st.ca_pool, new CaVal) }
let r = ui_st.ca_pool[ui_st.ca_used]
ui_st.ca_used += 1
r.px = 0.0
r.pct = 0.0
r.n = 0.0
r.bare = true
return r
}

View file

@ -11,23 +11,28 @@ function ca_fn_at(s: string, at: int) -> string {
function ca_word(s: string, at: int, w: string) -> bool { return at + len(w) <= len(s) and sat(s, at, w) }
function ca_fn(ui_st: mut UiState, f: string) -> CaVal {
ui_st.ca_i = ui_st.ca_i + len(f) + 1
let args = new []CaVal
let depth = ui_st.ca_depth # min(calc(...)) nests: a kept list of arguments a depth
while len(ui_st.ca_args) <= depth { push(ui_st.ca_args, new []CaVal) }
let args = ui_st.ca_args[depth]
List.clear(args)
ui_st.ca_depth = depth + 1
while ui_st.ca_bad == "" {
push(args, ca_sum(ui_st))
ca_ws(ui_st)
if ui_st.ca_i < len(ui_st.ca_s) and ui_st.ca_s[ui_st.ca_i] == 44 { ui_st.ca_i += 1 } else { break }
}
ui_st.ca_depth = depth
if ui_st.ca_bad == "" and (ui_st.ca_i >= len(ui_st.ca_s) or ui_st.ca_s[ui_st.ca_i] != 41) { ui_st.ca_bad = "a closing )" }
ui_st.ca_i += 1
if ui_st.ca_bad != "" { return new CaVal }
if ui_st.ca_bad != "" { return ca_new(ui_st) }
if f == "clamp" and len(args) != 3 {
ui_st.ca_bad = "clamp(least, want, most)"
return new CaVal
return ca_new(ui_st)
}
for i in 0 .. len(args) {
if args[i].pct != 0.0 {
ui_st.ca_bad = `lengths in {f}(), not a percentage`
return new CaVal
return ca_new(ui_st)
}
}
if f == "clamp" { return ca_pick(ui_st, ca_pick(ui_st, args[1], args[0], false), args[2], true) }
@ -36,7 +41,7 @@ function ca_fn(ui_st: mut UiState, f: string) -> CaVal {
return r
}
# the smaller (or the larger) of two, a bare number beside a length being px
function ca_pick(ui_st: UiState, a: CaVal, b: CaVal, smaller: bool) -> CaVal {
function ca_pick(ui_st: mut UiState, a: CaVal, b: CaVal, smaller: bool) -> CaVal {
if a.bare and b.bare {
if (b.n < a.n) == smaller and b.n != a.n { return b }
return a

View file

@ -10,7 +10,7 @@ function ca_product(ui_st: mut UiState) -> CaVal {
ui_st.ca_i += 1
let b = ca_factor(ui_st)
if div and not b.bare and not a.bare and a.pct == 0.0 and b.pct == 0.0 and b.px != 0.0 {
a = ca_ratio(a.px / b.px)
a = ca_ratio(ui_st, a.px / b.px)
continue
}
if div and not b.bare {
@ -21,7 +21,7 @@ function ca_product(ui_st: mut UiState) -> CaVal {
ui_st.ca_bad = "a number that is not 0 after /"
return a
}
if div { a = ca_scale(a, 1.0 / b.n) } else if a.bare { a = ca_scale(b, a.n) } else if b.bare { a = ca_scale(a, b.n) } else {
if div { a = ca_scale(ui_st, a, 1.0 / b.n) } else if a.bare { a = ca_scale(ui_st, b, a.n) } else if b.bare { a = ca_scale(ui_st, a, b.n) } else {
ui_st.ca_bad = "a plain number on one side of *"
return a
}
@ -29,13 +29,13 @@ function ca_product(ui_st: mut UiState) -> CaVal {
return a
}
# a length over a length is a plain number (100vw / 1800px)
function ca_ratio(k: float) -> CaVal {
let r = new CaVal
function ca_ratio(ui_st: mut UiState, k: float) -> CaVal {
let r = ca_new(ui_st)
r.n = k
return r
}
function ca_scale(a: CaVal, k: float) -> CaVal {
let r = new CaVal
function ca_scale(ui_st: mut UiState, a: CaVal, k: float) -> CaVal {
let r = ca_new(ui_st)
r.bare = a.bare
r.n = a.n * k
r.px = a.px * k
@ -46,12 +46,12 @@ function ca_factor(ui_st: mut UiState) -> CaVal {
ca_ws(ui_st)
if ui_st.ca_i >= len(ui_st.ca_s) {
ui_st.ca_bad = "a value"
return new CaVal
return ca_new(ui_st)
}
let c = ui_st.ca_s[ui_st.ca_i]
if c == 45 {
ui_st.ca_i += 1
return ca_scale(ca_factor(ui_st), -1.0)
return ca_scale(ui_st, ca_factor(ui_st), -1.0)
}
let f = ca_fn_at(ui_st.ca_s, ui_st.ca_i)
if f == "min" or f == "max" or f == "clamp" { return ca_fn(ui_st, f) }
@ -71,18 +71,21 @@ function ca_number(ui_st: mut UiState) -> CaVal {
while ui_st.ca_i < len(ui_st.ca_s) and ((ui_st.ca_s[ui_st.ca_i] >= 48 and ui_st.ca_s[ui_st.ca_i] <= 57) or ui_st.ca_s[ui_st.ca_i] == 46) { ui_st.ca_i += 1 }
let num_end = ui_st.ca_i
while ui_st.ca_i < len(ui_st.ca_s) and ((ui_st.ca_s[ui_st.ca_i] >= 97 and ui_st.ca_s[ui_st.ca_i] <= 122) or ui_st.ca_s[ui_st.ca_i] == 37) { ui_st.ca_i += 1 }
let r = new CaVal
let r = ca_new(ui_st)
if num_end == at {
ui_st.ca_bad = "a number"
return r
}
let word: string = ui_st.ca_s[at..ui_st.ca_i]
let unit: string = ui_st.ca_s[num_end..ui_st.ca_i]
if unit == "" {
r.n = el_num(word)
let s = ui_st.ca_s
let end = ui_st.ca_i
let f = el_num_in(s, at, num_end)
if end == num_end {
r.n = f
return r
}
r.bare = false
if unit == "%" { r.pct = el_num(word) } else if unit == "px" or unit == "em" or unit == "rem" or unit == "vw" or unit == "vh" { r.px = el_unit(ui_st, word) } else { ui_st.ca_bad = `a unit calc() knows, not {unit}` }
if ca_unit(s, num_end, end, "%") { r.pct = f } else if ca_unit(s, num_end, end, "px") { r.px = el_px(ui_st, f) } else if ca_unit(s, num_end, end, "rem") { r.px = f * el_px(ui_st, 16.0) } else if ca_unit(s, num_end, end, "em") { r.px = f * el_em(ui_st) } else if ca_unit(s, num_end, end, "vw") { r.px = f * ui_st.ly_vp_w / 100.0 } else if ca_unit(s, num_end, end, "vh") { r.px = f * ui_st.ly_vp_h / 100.0 } else { ui_st.ca_bad = `a unit calc() knows, not {s[num_end..end]}` }
return r
}
# the unit between `from` and `to` is `u`, compared in place
function ca_unit(s: string, from: int, to: int, u: string) -> bool { return to - from == len(u) and sat(s, from, u) }

View file

@ -23,7 +23,7 @@ function ct_sound(n: UiNode) -> string {
function ct_activate(ui_st: mut UiState, n: UiNode) -> void {
if not n.enabled { return }
let t = ct_type(n)
if t == "checkbox" { ct_change(ui_st, n, vl_bool(not ui_attr_on(n, "checked"))) } else if t == "radio" { ct_change(ui_st, n, ui_attr(n, "value")) } else if t == "select" { ct_step(ui_st, n, 1) } else if t == "key" { ct_key_start(ui_st, n) } else if t == "number" { ct_num_commit(ui_st, n) } else if t == "text" { ct_submit(ui_st, n) } else if t == "" {
if t == "checkbox" { ct_change(ui_st, n, vl_bool(ui_st, not ui_attr_on(n, "checked"))) } else if t == "radio" { ct_change(ui_st, n, ui_attr(n, "value")) } else if t == "select" { ct_step(ui_st, n, 1) } else if t == "key" { ct_key_start(ui_st, n) } else if t == "number" { ct_num_commit(ui_st, n) } else if t == "text" { ct_submit(ui_st, n) } else if t == "" {
ui_sound(ui_st, ct_sound(n))
ui_fire(ui_st, n, "press", Value.null())
}

View file

@ -5,21 +5,21 @@ function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
if x.op == X_MEMBER {
let o = ui_eval(ui_st, 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)) }
if Value.kind(o) == 5 and x.s == "length" { return uv_int(ui_st, Value.count(o)) }
ui_err(ui_st, `.{x.s}: there is no such field`)
return Value.null()
}
if x.op == X_INDEX { return Value.at(ui_eval(ui_st, x.a, e), Value.as_int(ui_eval(ui_st, x.b, e))) }
if x.op == X_CALL { return ev_call(ui_st, x, e) }
if x.op == X_NOT { return vl_bool(not vl_truthy(ui_eval(ui_st, x.a, e))) }
if x.op == X_NEG { return vl_arith("-", Value.int(0), ui_eval(ui_st, x.a, e)) }
if x.op == X_NOT { return vl_bool(ui_st, not vl_truthy(ui_eval(ui_st, x.a, e))) }
if x.op == X_NEG { return vl_arith(ui_st, "-", mm_ival(ui_st, 0), ui_eval(ui_st, x.a, e)) }
if x.op == X_AND {
if not vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(false) }
return vl_bool(vl_truthy(ui_eval(ui_st, x.b, e)))
if not vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(ui_st, false) }
return vl_bool(ui_st, vl_truthy(ui_eval(ui_st, x.b, e)))
}
if x.op == X_OR {
if vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(true) }
return vl_bool(vl_truthy(ui_eval(ui_st, x.b, e)))
if vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(ui_st, true) }
return vl_bool(ui_st, vl_truthy(ui_eval(ui_st, x.b, e)))
}
if x.op == X_COND {
if vl_truthy(ui_eval(ui_st, x.a, e)) { return ui_eval(ui_st, x.b, e) }
@ -27,10 +27,10 @@ function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
}
if x.op == X_TEXT {
var out = ""
for i in 0 .. len(x.args) { out = out + vl_text(ui_eval(ui_st, x.args[i], e)) }
return Value.str(out)
for i in 0 .. len(x.args) { out = out + vl_text_u(ui_st, ui_eval(ui_st, x.args[i], e)) }
return uv_str(ui_st, out)
}
return vl_binary(x.s, ui_eval(ui_st, x.a, e), ui_eval(ui_st, x.b, e))
return vl_binary(ui_st, x.s, ui_eval(ui_st, x.a, e), ui_eval(ui_st, x.b, e))
}
# a call: len() and range() are the template's own, anything else is the view's
function ev_call(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
@ -46,10 +46,10 @@ function ev_call(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
}
# the call itself, its arguments evaluated; a callee reads them and keeps none
function ev_call_with(ui_st: mut UiState, x: UiExpr, e: UiEnv, args: Val) -> Val {
if x.s == "len" and len(x.args) == 1 { return Value.int(Value.count(Value.at(args, 0))) }
if x.s == "len" and len(x.args) == 1 { return uv_int(ui_st, 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)) }
let out = uv_list(ui_st)
for i in 0 .. Value.as_int(Value.at(args, 0)) { Value.add(out, uv_int(ui_st, i)) }
return out
}
var at = e

View file

@ -40,6 +40,7 @@ export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> Ui
}
let t = ui_st.lib_screens[si]
pl_begin(ui_st, screen)
ui_st.uv_on = true
lc_begin(ui_st)
ui_st.ui_view = view
let e = env_new(ui_st, null)
@ -55,6 +56,7 @@ export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> Ui
}
bd_kids(ui_st, t.kids, e, screen, root.children)
cs_tree(ui_st, root)
ui_st.uv_on = false
return root
}
# the room vw, vh and @media measure against; ui_show sets it to the screen's rectangle

View file

@ -12,6 +12,9 @@ export property UiPool {
envs1: []UiEnv = null
un: int = 0
ue: int = 0
vals0: []Val = null # what expressions work out while the screen is built (uv_new)
vals1: []Val = null
uv: int = 0
}
# a build of `screen` begins: its other generation is handed out again from the start
function pl_begin(ui_st: mut UiState, screen: string) -> void {
@ -26,11 +29,14 @@ function pl_begin(ui_st: mut UiState, screen: string) -> void {
p.nodes1 = new []UiNode
p.envs0 = new []UiEnv
p.envs1 = new []UiEnv
p.vals0 = new []Val
p.vals1 = new []Val
push(ui_st.pl_pools, p)
}
p.gen = 1 - p.gen
p.un = 0
p.ue = 0
p.uv = 0
ui_st.pl_cur = p
}
# a node, blank, with its own lists (empty) - from the pool when a build is under way
@ -82,3 +88,37 @@ function env_fresh() -> UiEnv {
e.on_envs = new []UiEnv
return e
}
# a Value an expression works out while a screen is built: from the pool while the build runs (none
# outlives the screen's next build but one, like its nodes), made as ever outside one - an action's
# `set`, a <state>'s start, what a handler is given are kept, so they are never pooled
function uv_new(ui_st: mut UiState, tag: int) -> Val {
let p = ui_st.pl_cur
if not ui_st.uv_on or p == null { return Value.become(Value.null(), tag) }
var list = p.vals0
if p.gen == 1 { list = p.vals1 }
if p.uv < len(list) {
let v = list[p.uv]
p.uv += 1
return Value.become(v, tag)
}
let v = Value.become(Value.null(), tag)
push(list, v)
p.uv += 1
return v
}
function uv_int(ui_st: mut UiState, n: int) -> Val {
if n >= 0 and n < 1024 { return mm_ival(ui_st, n) }
return Value.be_int(uv_new(ui_st, 1), n)
}
function uv_float(ui_st: mut UiState, x: float) -> Val { return Value.be_float(uv_new(ui_st, 7), x) }
function uv_str(ui_st: mut UiState, s: string) -> Val { return Value.be_str(uv_new(ui_st, 4), s) }
function uv_list(ui_st: mut UiState) -> Val { return uv_new(ui_st, 5) }
# true and false, one of each for good: read them, never change them
function uv_bool(ui_st: mut UiState, b: bool) -> Val {
if ui_st.uv_true == null {
ui_st.uv_true = Value.bool(1)
ui_st.uv_false = Value.bool(0)
}
if b { return ui_st.uv_true }
return ui_st.uv_false
}

View file

@ -2,6 +2,7 @@
# a Ludic component shown as a screen: an instance at the top, keyed by its name
function fr_class_nodes(ui_st: mut UiState, cls: UiClass) -> UiNode {
pl_begin(ui_st, cls.name)
ui_st.uv_on = true
lc_begin(ui_st)
ui_st.in_build += 1
ui_st.ui_view = null
@ -26,6 +27,7 @@ function fr_class_nodes(ui_st: mut UiState, cls: UiClass) -> UiNode {
bd_class(ui_st, use, e, cls.name, root.children)
in_sweep(ui_st, cls.name)
cs_tree(ui_st, root)
ui_st.uv_on = false
return root
}
function fr_root(ui_st: mut UiState, key: string, sheet: UiSheet) -> UiNode {

View file

@ -10,7 +10,10 @@ function st_for(ui_st: mut UiState, key: string, t: UiTpl, e: UiEnv) -> Val {
if ui_st.st_keys[i] == key { return ui_st.st_vals[i] }
}
let o = Value.object()
let was = ui_st.uv_on # a state's start is kept for good: its values are never pooled
ui_st.uv_on = false
for i in 0 .. len(decl.keys) { Value.put(o, decl.keys[i], ui_eval(ui_st, decl.vals[i], e)) }
ui_st.uv_on = was
push(ui_st.st_keys, key)
push(ui_st.st_vals, o)
return o

View file

@ -1,6 +1,12 @@
# units.ludic - lengths in px (the renderer's scale), em (the font size here), rem (the root's),
# vw and vh (the screen's), and custom properties: --name set on an element, read by var(--name)
# in it and everything inside it
# the px of one em for the element being styled (a font size's em is its parent's)
function el_em(ui_st: UiState) -> float {
if ui_st.el_cur == null { return el_px(ui_st, 16.0) }
if ui_st.el_key == "size" and ui_st.el_cur.parent != null { return cs_fs(ui_st, ui_st.el_cur.parent) }
return cs_fs(ui_st, ui_st.el_cur)
}
function el_unit(ui_st: UiState, s: string) -> float {
let f = el_num(s)
let n = len(s)

View file

@ -1,9 +1,6 @@
# values.ludic - arithmetic, comparison, truth and text over a template's values. A sum of two
# whole numbers stays whole; anything with a fraction in it is a float; a string in a sum joins.
function vl_bool(b: bool) -> Val {
if b { return Value.bool(1) }
return Value.bool(0)
}
function vl_bool(ui_st: mut UiState, b: bool) -> Val { return uv_bool(ui_st, b) }
function vl_num(v: Val) -> bool { return Value.kind(v) == 1 or Value.kind(v) == 2 or Value.kind(v) == 7 }
function vl_truthy(v: Val) -> bool {
let k = Value.kind(v)
@ -29,25 +26,25 @@ function vl_text(v: Val) -> string {
}
return Json.encode(v)
}
function vl_arith(op: string, a: Val, b: Val) -> Val {
function vl_arith(ui_st: mut UiState, op: string, a: Val, b: Val) -> Val {
if Value.kind(a) == 1 and Value.kind(b) == 1 {
let x = Value.as_int(a)
let y = Value.as_int(b)
if op == "+" { return Value.int(x + y) }
if op == "-" { return Value.int(x - y) }
if op == "*" { return Value.int(x * y) }
if y == 0 { return Value.int(0) }
if op == "/" { return Value.int(x / y) }
return Value.int(x % y)
if op == "+" { return uv_int(ui_st, x + y) }
if op == "-" { return uv_int(ui_st, x - y) }
if op == "*" { return uv_int(ui_st, x * y) }
if y == 0 { return uv_int(ui_st, 0) }
if op == "/" { return uv_int(ui_st, x / y) }
return uv_int(ui_st, x % y)
}
let x = Value.as_float(a)
let y = Value.as_float(b)
if op == "+" { return Value.float(x + y) }
if op == "-" { return Value.float(x - y) }
if op == "*" { return Value.float(x * y) }
if y == 0.0 { return Value.float(0.0) }
if op == "/" { return Value.float(x / y) }
return Value.float(x - y * float(int(x / y)))
if op == "+" { return uv_float(ui_st, x + y) }
if op == "-" { return uv_float(ui_st, x - y) }
if op == "*" { return uv_float(ui_st, x * y) }
if y == 0.0 { return uv_float(ui_st, 0.0) }
if op == "/" { return uv_float(ui_st, x / y) }
return uv_float(ui_st, x - y * float(int(x / y)))
}
function vl_equal(a: Val, b: Val) -> bool {
if vl_num(a) and vl_num(b) { return Value.as_float(a) == Value.as_float(b) }
@ -56,17 +53,17 @@ function vl_equal(a: Val, b: Val) -> bool {
if Value.kind(a) == 3 { return Value.as_int(a) == Value.as_int(b) }
return Value.kind(a) == 0
}
function vl_binary(op: string, a: Val, b: Val) -> Val {
if op == "==" { return vl_bool(vl_equal(a, b)) }
if op == "!=" { return vl_bool(not vl_equal(a, b)) }
if op == "+" and (Value.kind(a) == 4 or Value.kind(b) == 4) { return Value.str(vl_text(a) + vl_text(b)) }
if op == "+" or op == "-" or op == "*" or op == "/" or op == "%" { return vl_arith(op, a, b) }
function vl_binary(ui_st: mut UiState, op: string, a: Val, b: Val) -> Val {
if op == "==" { return vl_bool(ui_st, vl_equal(a, b)) }
if op == "!=" { return vl_bool(ui_st, not vl_equal(a, b)) }
if op == "+" and (Value.kind(a) == 4 or Value.kind(b) == 4) { return uv_str(ui_st, vl_text_u(ui_st, a) + vl_text_u(ui_st, b)) }
if op == "+" or op == "-" or op == "*" or op == "/" or op == "%" { return vl_arith(ui_st, op, a, b) }
let x = Value.as_float(a)
let y = Value.as_float(b)
if op == "<" { return vl_bool(x < y) }
if op == "<=" { return vl_bool(x <= y) }
if op == ">" { return vl_bool(x > y) }
return vl_bool(x >= y)
if op == "<" { return vl_bool(ui_st, x < y) }
if op == "<=" { return vl_bool(ui_st, x <= y) }
if op == ">" { return vl_bool(ui_st, x > y) }
return vl_bool(ui_st, x >= y)
}
# a value as text, whatever kind it is: a component's string prop given a number (label="{3}") reads
# "3", not nothing

View file

@ -480,6 +480,10 @@ namespace Value {
alias as_str(value) = value_as_str
alias free(value) = value_free
alias clear(value) = value_clear
alias become(value, tag) = value_become
alias be_int(value, n) = value_be_int
alias be_float(value, x) = value_be_float
alias be_str(value, s) = value_be_str
}
namespace Json {
alias encode(value) = json_encode

View file

@ -105,6 +105,79 @@ function value_set_bool(o: Val, key: pointer, b: bool) -> void {
v.num = 0
if b { v.num = 1 }
}
# a list field of an object set in place: the list under `key` is kept, and item i keeps its Value when it
# is already that kind (a component's model, filled into the same object every frame)
function value_list_fit(o: Val, key: pointer, n: int) -> Val {
let l = value_slot(o, key, 5)
value_lists(l)
while len(l.kids) > n { List.pop(l.kids) }
return l
}
function value_item(l: Val, i: int, tag: int) -> Val {
if i < len(l.kids) {
if l.kids[i].tag == tag { return l.kids[i] }
let v = value_new(tag)
l.kids[i] = v
return v
}
let v = value_new(tag)
push(l.kids, v)
return v
}
function value_set_ints(o: Val, key: pointer, xs: []int) -> void {
let l = value_list_fit(o, key, len(xs))
for i in 0 .. len(xs) {
let v = value_item(l, i, 1)
v.num = xs[i]
}
}
function value_set_floats(o: Val, key: pointer, xs: []float) -> void {
let l = value_list_fit(o, key, len(xs))
for i in 0 .. len(xs) {
let v = value_item(l, i, 7)
v.num = float_bits(xs[i])
}
}
function value_set_strs(o: Val, key: pointer, xs: []string) -> void {
let l = value_list_fit(o, key, len(xs))
for i in 0 .. len(xs) {
let v = value_item(l, i, 4)
v.txt = xs[i]
}
}
function value_set_bools(o: Val, key: pointer, xs: []bool) -> void {
let l = value_list_fit(o, key, len(xs))
for i in 0 .. len(xs) {
let v = value_item(l, i, 3)
v.num = 0
if xs[i] { v.num = 1 }
}
}
# a Value turned into a blank one of kind `tag`, its own lists (if it has them) kept and emptied - for
# a pool that hands the same records out again (ludic.ui's, frame after frame)
function value_become(v: Val, tag: int) -> Val {
v.tag = tag
v.num = 0
v.txt = null
if tag == 5 or tag == 6 { value_lists(v) }
if v.kids != null { value_clear(v) }
return v
}
function value_be_int(v: Val, n: int) -> Val {
value_become(v, 1)
v.num = n
return v
}
function value_be_float(v: Val, x: float) -> Val {
value_become(v, 7)
v.num = float_bits(x)
return v
}
function value_be_str(v: Val, s: pointer) -> Val {
value_become(v, 4)
v.txt = s
return v
}
# a list or an object emptied in place, its room kept - for one filled again every frame
function value_clear(v: Val) -> void {
if v.kids == null { return }

View file

@ -46,6 +46,10 @@ function cm_set(c: int, ty: pointer, k: pointer, x: pointer, head: Node) -> poin
if (ty == "float") { return ` value_set_float(o, "{k}", {x})\n` }
if (ty == "string") { return ` value_set_str(o, "{k}", {x})\n` }
if (ty == "bool") { return ` value_set_bool(o, "{k}", {x})\n` }
if (ty == "[]int") { return ` value_set_ints(o, "{k}", {x})\n` }
if (ty == "[]float") { return ` value_set_floats(o, "{k}", {x})\n` }
if (ty == "[]string") { return ` value_set_strs(o, "{k}", {x})\n` }
if (ty == "[]bool") { return ` value_set_bools(o, "{k}", {x})\n` }
return ` value_put(o, "{k}", {cm_to(c, ty, x, head)})\n`
}
function cm_to(c: int, ty: pointer, x: pointer, head: Node) -> pointer {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff