Xml parse gives back what no node keeps: a closing tag's name, and the text runs and joins an element's text leaves behind (a run a #text node shares is kept); ludic.ui frees an attribute's value once parsed (every reader copies what it keeps) and act_parse's reader

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 00:30:13 +03:00
parent 7b20f305ff
commit 765d800842
3 changed files with 27 additions and 5 deletions

View file

@ -38,6 +38,7 @@ function act_parse(src: string, err: []string) -> UiAct {
rd_ws(r)
if r.err == "" and r.i < len(r.s) { rd_fail(r, "';' or the end") }
if r.err != "" { push(err, r.err) }
unsafe { free(r) } # the reader only: names and expressions are copied out
return a
}
# `set` or `emit` as the action's first word - not a function of that name, as in `set(4)`

View file

@ -40,6 +40,9 @@ function tpl_read(ui_st: mut UiState, x: Xml, file: string) -> UiTpl {
push(t.keys, k)
push(t.vals, tpl_value(v, err))
}
# the value is parsed (an action, a style, an expression), each copying what it keeps; an
# empty one is the reader's own literal
if len(v) > 0 { unsafe { free(v) } }
}
if tp_mixed(x) { tp_read_mixed(ui_st, t, x, file, err) } else {
for i in 0 .. Xml.child_count(x) { push(t.kids, tpl_read(ui_st, Xml.child(x, i), file)) }

View file

@ -23,6 +23,7 @@ property Xml {
kids: []Xml # child elements, in document order
at: int = 0 # where its '<' is in the text, for an error that names a line
mixed: []Xml # its elements and its runs of text ("#text"), in document order
joined: bool = false # `text` is a join made here (not a run a #text node shares): free to replace
}
function xml_new(tag: pointer) -> Xml {
@ -267,6 +268,7 @@ function xp_element(rt_xml_st: mut RtXmlState, p: XP) -> Xml {
if p.i + 1 < p.n and p.s[p.i + 1] == '/' { # '</' close
p.i += 2
let cn = xp_name(p)
free(cn) # the closing name is only read past
while p.i < p.n and p.s[p.i] != '>' { p.i += 1 }
p.i += 1 # skip '>'
return node
@ -276,7 +278,7 @@ function xp_element(rt_xml_st: mut RtXmlState, p: XP) -> Xml {
p.i += 9 # past "<![CDATA["
let start = p.i
while p.i + 2 < p.n and not (p.s[p.i] == ']' and p.s[p.i + 1] == ']' and p.s[p.i + 2] == '>') { p.i += 1 }
node.text += p.s[start..p.i]
xp_text_add(node, p.s[start..p.i], true)
p.i += 3 # past "]]>"
} else {
if xp_skip_misc(p) { } # comment / PI inside content
@ -291,15 +293,15 @@ function xp_element(rt_xml_st: mut RtXmlState, p: XP) -> Xml {
let start = p.i
while p.i < p.n and p.s[p.i] != '<' { p.i += 1 }
let run = xml_unescape(rt_xml_st, p.s[start..p.i])
node.text += run
xp_text_run(node, run)
let kept = xp_text_run(node, run)
xp_text_add(node, run, not kept)
}
}
return node
}
# a run of text among an element's children, kept in order unless it is only white space
function xp_text_run(node: Xml, run: string) -> void {
function xp_text_run(node: Xml, run: string) -> bool {
var blank = true
var i = 0
while i < len(run) {
@ -307,10 +309,26 @@ function xp_text_run(node: Xml, run: string) -> void {
if not (c == ' ' or c == '\t' or c == '\n' or c == '\r') { blank = false }
i += 1
}
if blank { return }
if blank { return false }
let t = xml_new("#text")
t.text = run
push(node.mixed, t)
return true
}
# a run joined onto the element's text. What the join leaves behind is given back: the text it
# replaces when that was a join of its own, and the run when nothing else keeps it (`mine`)
function xp_text_add(node: Xml, run: string, mine: bool) -> void {
if len(node.text) == 0 {
node.text = run # its first text is the run itself
node.joined = mine
return
}
let old = node.text
node.text = old + run
if node.joined { free(old) }
if mine { free(run) }
node.joined = true
}
# parse a whole document -> its root element (or the synthetic empty node).