diff --git a/packages/ludic.ui/act.ludic b/packages/ludic.ui/act.ludic index 438e0407..165dc365 100644 --- a/packages/ludic.ui/act.ludic +++ b/packages/ludic.ui/act.ludic @@ -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)` diff --git a/packages/ludic.ui/tpl.ludic b/packages/ludic.ui/tpl.ludic index d28376a6..6d06832d 100644 --- a/packages/ludic.ui/tpl.ludic +++ b/packages/ludic.ui/tpl.ludic @@ -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)) } diff --git a/runtime/native/xml.ludic b/runtime/native/xml.ludic index b59400d4..0c5ab7ea 100644 --- a/runtime/native/xml.ludic +++ b/runtime/native/xml.ludic @@ -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] == '/' { # '' { 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 "') { 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).