ludic.ui: string matching compares in place - at_alias, lk_in, colours and every s[a..b] == p made a string per position tried, per rule, per element, per frame (a benchmark page: ~3 MB -> 256 KB a frame)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8808dda605
commit
83200a453b
17 changed files with 84 additions and 50 deletions
|
|
@ -1,6 +1,5 @@
|
|||
# attr.ludic - what a property sets, by its CSS name or the short one (width or w, background or
|
||||
# bg, font-size or size, ...). An attribute on an element is the same property, applied last.
|
||||
const AT_ALIAS: string = " width=w height=h min-width=min-w max-width=max-w min-height=min-h max-height=max-h padding=pad background=bg background-color=bg font-size=size opacity=alpha flex-grow=grow flex-shrink=shrink flex-direction=dir flex-wrap=wrap justify-content=justify align-items=align align-self=self row-gap=gap column-gap=gap text-align=talign "
|
||||
const AT_UNDRAWN: string = " font-weight font-family font-style box-shadow cursor transition animation letter-spacing text-decoration "
|
||||
# a stylesheet's property: one that means nothing is said
|
||||
function el_attr(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void {
|
||||
|
|
@ -12,7 +11,7 @@ function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool {
|
|||
let k = at_alias(key)
|
||||
ui_st.el_cur = n
|
||||
ui_st.el_key = k
|
||||
if len(k) > 2 and k[0..2] == "--" {
|
||||
if len(k) > 2 and sat(k, 0, "--") {
|
||||
cs_var_set(n, k, vl_text(v0))
|
||||
return true
|
||||
}
|
||||
|
|
@ -20,15 +19,28 @@ function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool {
|
|||
return el_size(ui_st, n, k, v) or el_box(ui_st, n, k, v) or el_flex(n, k, v) or el_look(ui_st, n, k, v) or el_place_prop(ui_st, n, k, v) or el_edge(ui_st, n, k, v) or el_more(ui_st, n, k, v)
|
||||
}
|
||||
function at_alias(key: string) -> string {
|
||||
let pat = ` {key}=`
|
||||
let n = len(pat)
|
||||
for i in 0 .. len(AT_ALIAS) - n + 1 {
|
||||
if AT_ALIAS[i..i + n] == pat {
|
||||
var j = i + n
|
||||
while AT_ALIAS[j] != 32 { j += 1 }
|
||||
return AT_ALIAS[i + n..j]
|
||||
}
|
||||
}
|
||||
# the CSS name to the short one; literals, so asking allocates nothing (asked per rule per element)
|
||||
if key == "width" { return "w" }
|
||||
if key == "height" { return "h" }
|
||||
if key == "min-width" { return "min-w" }
|
||||
if key == "max-width" { return "max-w" }
|
||||
if key == "min-height" { return "min-h" }
|
||||
if key == "max-height" { return "max-h" }
|
||||
if key == "padding" { return "pad" }
|
||||
if key == "background" { return "bg" }
|
||||
if key == "background-color" { return "bg" }
|
||||
if key == "font-size" { return "size" }
|
||||
if key == "opacity" { return "alpha" }
|
||||
if key == "flex-grow" { return "grow" }
|
||||
if key == "flex-shrink" { return "shrink" }
|
||||
if key == "flex-direction" { return "dir" }
|
||||
if key == "flex-wrap" { return "wrap" }
|
||||
if key == "justify-content" { return "justify" }
|
||||
if key == "align-items" { return "align" }
|
||||
if key == "align-self" { return "self" }
|
||||
if key == "row-gap" { return "gap" }
|
||||
if key == "column-gap" { return "gap" }
|
||||
if key == "text-align" { return "talign" }
|
||||
return key
|
||||
}
|
||||
function el_size(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
||||
|
|
@ -49,7 +61,7 @@ function el_look(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
|||
n.fg = ui_color(ui_st, s)
|
||||
n.fg_a = ui_st.cl_alpha
|
||||
} else if key == "bg" {
|
||||
if gr_is(tpl_words(s)) { nx(n).grad = gr_parse(ui_st, s) } else if len(s) > 4 and s[0..4] == "url(" { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" {
|
||||
if gr_is(tpl_words(s)) { nx(n).grad = gr_parse(ui_st, s) } else if len(s) > 4 and sat(s, 0, "url(") { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" {
|
||||
n.bg = 0
|
||||
n.bg_a = 0.0
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ function el_num(s: string) -> float {
|
|||
# start, center, end, stretch; for justify also between, around, evenly - with CSS's spellings
|
||||
function el_place(v: Val) -> int {
|
||||
var s = vl_text(v)
|
||||
if len(s) > 6 and s[0..6] == "space-" { s = s[6..len(s)] }
|
||||
if len(s) > 5 and s[0..5] == "flex-" { s = s[5..len(s)] }
|
||||
if len(s) > 6 and sat(s, 0, "space-") { s = s[6..len(s)] }
|
||||
if len(s) > 5 and sat(s, 0, "flex-") { s = s[5..len(s)] }
|
||||
if s == "center" { return UI_CENTER }
|
||||
if s == "end" or s == "right" { return UI_END }
|
||||
if s == "stretch" or s == "between" { return 3 }
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
|||
# url(path) or url("path") -> path
|
||||
function el_url(s: string) -> string {
|
||||
var t = tpl_words(s)
|
||||
if len(t) > 5 and t[0..4] == "url(" { t = tpl_words(t[4..len(t) - 1]) }
|
||||
if len(t) > 5 and sat(t, 0, "url(") { t = tpl_words(t[4..len(t) - 1]) }
|
||||
if len(t) >= 2 and (t[0] == 34 or t[0] == 39) { t = t[1..len(t) - 1] }
|
||||
if t == "none" { return "" }
|
||||
return t
|
||||
|
|
@ -78,7 +78,7 @@ function el_secs(s: string) -> float {
|
|||
for i in 0 .. len(parts) {
|
||||
let p = parts[i]
|
||||
if len(p) > 1 and p[0] >= 48 and p[0] <= 57 or len(p) > 1 and p[0] == 46 {
|
||||
if len(p) > 2 and p[len(p) - 2..len(p)] == "ms" { return el_num(p) / 1000.0 }
|
||||
if len(p) > 2 and sat(p, len(p) - 2, "ms") { return el_num(p) / 1000.0 }
|
||||
return el_num(p)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function ca_fn_at(s: string, at: int) -> string {
|
|||
if ca_word(s, at, "clamp(") { return "clamp" }
|
||||
return ""
|
||||
}
|
||||
function ca_word(s: string, at: int, w: string) -> bool { return at + len(w) <= len(s) and s[at..at + len(w)] == w }
|
||||
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
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@ function cs_rule(ui_st: mut UiState, n: UiNode, r: UiRule) -> void { cs_rule_in(
|
|||
# a rule's custom properties first, so its other declarations can read them; values read in `e`
|
||||
function cs_rule_in(ui_st: mut UiState, n: UiNode, r: UiRule, e: UiEnv) -> void {
|
||||
for j in 0 .. len(r.keys) {
|
||||
if len(r.keys[j]) > 2 and r.keys[j][0..2] == "--" { el_attr(ui_st, n, r.keys[j], ui_eval(ui_st, r.vals[j], e)) }
|
||||
if len(r.keys[j]) > 2 and sat(r.keys[j], 0, "--") { el_attr(ui_st, n, r.keys[j], ui_eval(ui_st, r.vals[j], e)) }
|
||||
}
|
||||
for j in 0 .. len(r.keys) {
|
||||
if not (len(r.keys[j]) > 2 and r.keys[j][0..2] == "--") and cs_pass_ok(ui_st, r.keys[j]) { el_attr(ui_st, n, r.keys[j], ui_eval(ui_st, r.vals[j], e)) }
|
||||
if not (len(r.keys[j]) > 2 and sat(r.keys[j], 0, "--")) and cs_pass_ok(ui_st, r.keys[j]) { el_attr(ui_st, n, r.keys[j], ui_eval(ui_st, r.vals[j], e)) }
|
||||
}
|
||||
}
|
||||
function ua_sheet(ui_st: mut UiState) -> UiSheet {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ function ui_color(ui_st: mut UiState, name0: string) -> int {
|
|||
let name = tpl_words(name0)
|
||||
ui_st.cl_alpha = 1.0
|
||||
if name == "none" or name == "" or name == "transparent" { return UI_NONE }
|
||||
if name[0] == 35 { return cl_hex(ui_st, name[1..len(name)]) }
|
||||
if len(name) > 4 and (name[0..4] == "rgb(" or name[0..5] == "rgba(") { return cl_rgb(ui_st, name) }
|
||||
let pat = ` {name}=`
|
||||
let n = len(pat)
|
||||
for i in 0 .. len(CL_NAMES) - n + 1 {
|
||||
if CL_NAMES[i..i + n] == pat { return cl_hex(ui_st, CL_NAMES[i + n..i + n + 6]) }
|
||||
if name[0] == 35 { return cl_hex_at(ui_st, name, 1, len(name) - 1) }
|
||||
if len(name) > 4 and (sat(name, 0, "rgb(") or sat(name, 0, "rgba(")) { return cl_rgb(ui_st, name) }
|
||||
let n = len(name)
|
||||
for i in 0 .. len(CL_NAMES) - n - 1 {
|
||||
if CL_NAMES[i] == 32 and CL_NAMES[i + n + 1] == 61 and sat(CL_NAMES, i + 1, name) { return cl_hex_at(ui_st, CL_NAMES, i + n + 2, 6) }
|
||||
}
|
||||
ui_err(ui_st, `{name}: not a colour - #rrggbb, rgb(), rgba(), a name, or var(--a-colour)`)
|
||||
return 15658734
|
||||
|
|
@ -20,16 +19,17 @@ function cl_digit(c: int) -> int {
|
|||
if c >= 65 { return c - 55 }
|
||||
return c - 48
|
||||
}
|
||||
function cl_hex(ui_st: mut UiState, h: string) -> int {
|
||||
function cl_hex(ui_st: mut UiState, h: string) -> int { return cl_hex_at(ui_st, h, 0, len(h)) }
|
||||
# the n hex digits of `s` from `at`, read where they stand
|
||||
function cl_hex_at(ui_st: mut UiState, s: string, at: int, n: int) -> int {
|
||||
var c = 0
|
||||
let n = len(h)
|
||||
if n == 3 or n == 4 {
|
||||
for i in 0 .. 3 { c = c * 256 + cl_digit(h[i]) * 17 }
|
||||
if n == 4 { ui_st.cl_alpha = float(cl_digit(h[3]) * 17) / 255.0 }
|
||||
for i in 0 .. 3 { c = c * 256 + cl_digit(s[at + i]) * 17 }
|
||||
if n == 4 { ui_st.cl_alpha = float(cl_digit(s[at + 3]) * 17) / 255.0 }
|
||||
return c
|
||||
}
|
||||
for i in 0 .. 3 { c = c * 256 + cl_digit(h[i * 2]) * 16 + cl_digit(h[i * 2 + 1]) }
|
||||
if n == 8 { ui_st.cl_alpha = float(cl_digit(h[6]) * 16 + cl_digit(h[7])) / 255.0 }
|
||||
for i in 0 .. 3 { c = c * 256 + cl_digit(s[at + i * 2]) * 16 + cl_digit(s[at + i * 2 + 1]) }
|
||||
if n == 8 { ui_st.cl_alpha = float(cl_digit(s[at + 6]) * 16 + cl_digit(s[at + 7])) / 255.0 }
|
||||
return c
|
||||
}
|
||||
# rgb(217, 165, 74) or rgba(0, 0, 0, 0.55)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export property UiGradient {
|
|||
alphas: []float = null
|
||||
at: []float = null # each stop's place, 0 to 1
|
||||
}
|
||||
function gr_is(s: string) -> bool { return len(s) > 16 and s[0..16] == "linear-gradient(" }
|
||||
function gr_is(s: string) -> bool { return len(s) > 16 and sat(s, 0, "linear-gradient(") }
|
||||
function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
|
||||
let s = tpl_words(s0)
|
||||
let parts = el_commas(s[16..len(s) - 1])
|
||||
|
|
@ -36,7 +36,7 @@ function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
|
|||
return g
|
||||
}
|
||||
function gr_dir(g: UiGradient, w: string) -> bool {
|
||||
if w == "to bottom" { g.dir = 0 } else if w == "to top" { g.dir = 1 } else if w == "to right" { g.dir = 2 } else if w == "to left" { g.dir = 3 } else if len(w) > 3 and w[len(w) - 3..len(w)] == "deg" {
|
||||
if w == "to bottom" { g.dir = 0 } else if w == "to top" { g.dir = 1 } else if w == "to right" { g.dir = 2 } else if w == "to left" { g.dir = 3 } else if len(w) > 3 and sat(w, len(w) - 3, "deg") {
|
||||
let d = int(Math.round(el_num(w) / 90.0)) % 4
|
||||
g.dir = 1
|
||||
if d == 1 or d == -3 { g.dir = 2 }
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export function ui_err(ui_st: mut UiState, msg0: string) -> void {
|
|||
function ui_has(s: string, part: string) -> bool {
|
||||
let n = len(part)
|
||||
for i in 0 .. len(s) - n + 1 {
|
||||
if s[i..i + n] == part { return true }
|
||||
if sat(s, i, part) { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ function rd_op(r: UiRd, op: string) -> bool {
|
|||
rd_ws(r)
|
||||
let n = len(op)
|
||||
if r.i + n > len(r.s) { return false }
|
||||
if r.s[r.i..r.i + n] != op { return false }
|
||||
if not sat(r.s, r.i, op) { return false }
|
||||
r.i += n
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,3 +39,13 @@ function lib_beside(path: string, src: string) -> string {
|
|||
if cut < 0 { return src }
|
||||
return path[0..cut + 1] + src
|
||||
}
|
||||
# `s` has `p` starting at `at`: compared in place, where s[at..at + len(p)] == p made a new string
|
||||
# for every place it looked (Ludic frees nothing, and the cascade asks this per element per frame)
|
||||
function sat(s: string, at: int, p: string) -> bool {
|
||||
let n = len(p)
|
||||
if at < 0 or at + n > len(s) { return false }
|
||||
for i in 0 .. n {
|
||||
if s[at + i] != p[i] { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function lc_note(ui_st: mut UiState, key: string, names: []string, acts: []UiAct
|
|||
}
|
||||
function lc_mine(key: string, screen: string) -> bool {
|
||||
let n = len(screen)
|
||||
return key == screen or (len(key) > n and key[0..n] == screen and key[n] == 47)
|
||||
return key == screen or (len(key) > n and sat(key, 0, screen) and key[n] == 47)
|
||||
}
|
||||
# the screen's mounts and unmounts since its last frame; other screens' records are kept
|
||||
function lc_run(ui_st: mut UiState, screen: string) -> void {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,9 @@ function lk_kids(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void {
|
|||
function lk_element(ui_st: UiState, tag: string) -> bool { return lk_in(LK_OWN, tag) or lk_in(EL_BOXES, tag) or lk_in(EL_TEXTS, tag) or nt_find(ui_st, tag) != null }
|
||||
# a word in a list of words written " a b c "
|
||||
function lk_in(words: string, w: string) -> bool {
|
||||
let pat = ` {w} `
|
||||
let n = len(pat)
|
||||
for i in 0 .. len(words) - n + 1 {
|
||||
if words[i..i + n] == pat { return true }
|
||||
let n = len(w)
|
||||
for i in 0 .. len(words) - n - 1 {
|
||||
if words[i] == 32 and words[i + n + 1] == 32 and sat(words, i + 1, w) { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ function r3u_image_h(render3d_st: mut Render3dState, ui_st: mut UiState, src: st
|
|||
function r3u_is_cell(ui_st: UiState, src: string) -> bool {
|
||||
for k in 0 .. len(ui_st.r3u_atlas) {
|
||||
let p = ui_st.r3u_atlas[k]
|
||||
if len(src) > len(p) and src[0..len(p)] == p and src[len(p)] == 58 { return true }
|
||||
if len(src) > len(p) and sat(src, 0, p) and src[len(p)] == 58 { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function ui_forget(ui_st: mut UiState, screen: string) -> void {
|
|||
let vals = new []Val
|
||||
for i in 0 .. len(ui_st.st_keys) {
|
||||
let k = ui_st.st_keys[i]
|
||||
let mine = len(k) >= len(screen) and k[0..len(screen)] == screen and (len(k) == len(screen) or k[len(screen)] == 47)
|
||||
let mine = len(k) >= len(screen) and sat(k, 0, screen) and (len(k) == len(screen) or k[len(screen)] == 47)
|
||||
if not mine {
|
||||
push(keys, k)
|
||||
push(vals, ui_st.st_vals[i])
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function sheet_read(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void {
|
|||
if from != "" { src = lib_text(ui_st, lib_beside(f.path, from)) }
|
||||
lss_text(ui_st, f, src)
|
||||
}
|
||||
function lss_is(path: string) -> bool { return len(path) > 4 and path[len(path) - 4..len(path)] == ".lss" }
|
||||
function lss_is(path: string) -> bool { return len(path) > 4 and sat(path, len(path) - 4, ".lss") }
|
||||
# a stylesheet file: a file with rules and imports and nothing else
|
||||
function lss_file(ui_st: mut UiState, src: string, path: string) -> UiFile {
|
||||
let f = lib_new(ui_st, path)
|
||||
|
|
@ -64,7 +64,7 @@ function lss_block(ui_st: mut UiState, f: UiFile, src: string, md: UiMedia, err:
|
|||
# @import "base.lss"; - its rules come before this file's own
|
||||
function lss_import(ui_st: mut UiState, f: UiFile, what: string) -> void {
|
||||
var p = tpl_words(what)
|
||||
if len(p) > 5 and p[0..4] == "url(" { p = p[4..len(p) - 1] }
|
||||
if len(p) > 5 and sat(p, 0, "url(") { p = p[4..len(p) - 1] }
|
||||
if len(p) >= 2 and (p[0] == 34 or p[0] == 39) { p = p[1..len(p) - 1] }
|
||||
let other = lib_file(ui_st, lib_beside(f.path, p))
|
||||
if other != null {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function tpl_read(ui_st: mut UiState, x: Xml, file: string) -> UiTpl {
|
|||
for i in 0 .. len(x.akeys) {
|
||||
let k: string = x.akeys[i]
|
||||
let v: string = x.avals[i]
|
||||
if len(k) > 3 and k[0..3] == "on-" {
|
||||
if len(k) > 3 and sat(k, 0, "on-") {
|
||||
push(t.on_keys, tpl_event(k[3..len(k)]))
|
||||
push(t.on_acts, act_parse(v, err))
|
||||
} else if k == "onclick" {
|
||||
|
|
@ -68,6 +68,7 @@ function tpl_value(v: string, err: []string) -> UiExpr {
|
|||
}
|
||||
# text as a reader sees it: the ends trimmed and every run of white space one space
|
||||
function tpl_words(s: string) -> string {
|
||||
if tpl_clean(s) { return s }
|
||||
var out = ""
|
||||
var gap = false
|
||||
for i in 0 .. len(s) {
|
||||
|
|
@ -86,3 +87,15 @@ function tpl_attr(t: UiTpl, key: string) -> UiExpr {
|
|||
}
|
||||
return null
|
||||
}
|
||||
# no space at either end, no two together, no tab or line break: tpl_words would give it back as is
|
||||
function tpl_clean(s: string) -> bool {
|
||||
let n = len(s)
|
||||
if n == 0 { return true }
|
||||
if s[0] == 32 or s[n - 1] == 32 { return false }
|
||||
for i in 0 .. n {
|
||||
let c = s[i]
|
||||
if c == 9 or c == 10 or c == 13 { return false }
|
||||
if c == 32 and s[i + 1] == 32 { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
function el_unit(ui_st: UiState, s: string) -> float {
|
||||
let f = el_num(s)
|
||||
let n = len(s)
|
||||
if n > 3 and s[n - 3..n] == "rem" { return f * el_px(ui_st, 16.0) }
|
||||
if n > 2 and s[n - 2..n] == "em" {
|
||||
if n > 3 and sat(s, n - 3, "rem") { return f * el_px(ui_st, 16.0) }
|
||||
if n > 2 and sat(s, n - 2, "em") {
|
||||
if ui_st.el_cur == null { return f * el_px(ui_st, 16.0) }
|
||||
if ui_st.el_key == "size" and ui_st.el_cur.parent != null { return f * cs_fs(ui_st, ui_st.el_cur.parent) }
|
||||
return f * cs_fs(ui_st, ui_st.el_cur)
|
||||
}
|
||||
if n > 2 and s[n - 2..n] == "vw" { return f * ui_st.ly_vp_w / 100.0 }
|
||||
if n > 2 and s[n - 2..n] == "vh" { return f * ui_st.ly_vp_h / 100.0 }
|
||||
if n > 2 and sat(s, n - 2, "vw") { return f * ui_st.ly_vp_w / 100.0 }
|
||||
if n > 2 and sat(s, n - 2, "vh") { return f * ui_st.ly_vp_h / 100.0 }
|
||||
return el_px(ui_st, f)
|
||||
}
|
||||
# the font size an element has so far in its cascade (its own, else its parent's)
|
||||
|
|
@ -55,7 +55,7 @@ function cs_vars_in(ui_st: mut UiState, n: UiNode, v: Val) -> Val {
|
|||
var found = false
|
||||
var i = 0
|
||||
while i + 4 <= len(s) {
|
||||
if s[i..i + 4] == "var(" {
|
||||
if sat(s, i, "var(") {
|
||||
found = true
|
||||
var j = i + 4
|
||||
var depth = 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue