templates: {s} with one string hole and nothing else is a new string, not s itself - it was the one template that passed its string through (string() does), and code that wrote it as a copy kept what it then freed; the mark rides the callee, since escape analysis writes the call's uns; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a436cfbe35
commit
dd25267502
6 changed files with 29922 additions and 29729 deletions
|
|
@ -2078,6 +2078,10 @@ let msg = `hello {name}, you have {count + 1} messages`
|
||||||
so a string, a char or another template literal inside it is taken whole - `` `a {wrap(`b {n}`)} c` ``
|
so a string, a char or another template literal inside it is taken whole - `` `a {wrap(`b {n}`)} c` ``
|
||||||
is one literal, and a brace or a backtick inside a string in a hole is text.
|
is one literal, and a brace or a backtick inside a string in a hole is text.
|
||||||
|
|
||||||
|
A template is always a new string. `` `{s}` `` - one string hole and nothing else - is a copy of
|
||||||
|
`s`, not `s` itself (where `str(s)` passes a string through), so it can be kept after `s` is given
|
||||||
|
back.
|
||||||
|
|
||||||
**Slicing.** `s[a..b]` is a fresh substring of the bytes `[a, b)`, and `len(s)`
|
**Slicing.** `s[a..b]` is a fresh substring of the bytes `[a, b)`, and `len(s)`
|
||||||
is a string's byte length — so `path[0..len(path) - 6]` trims an extension and
|
is a string's byte length — so `path[0..len(path) - 6]` trims an extension and
|
||||||
`s[i]` still indexes a single byte. `expr with { field: … }`
|
`s[i]` still indexes a single byte. `expr with { field: … }`
|
||||||
|
|
|
||||||
6
changes/template-copy.md
Normal file
6
changes/template-copy.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
**A template of one string hole is a new string.** `` `{s}` `` was `s` itself (`str()` passes a string
|
||||||
|
through), so code that wrote it as a copy kept a pointer it then freed - ludic.ui's rule text named
|
||||||
|
whatever reused the bytes. It now copies, where a template with any other part already made a new
|
||||||
|
string; `str(s)` still passes through. No template in the game or the packages had a string as its lone hole.
|
||||||
|
|
@ -190,6 +190,12 @@ function first_arg_is_text(e: Node) -> bool {
|
||||||
|
|
||||||
# a namespace that computes inline also takes the methods an `alias` gives it (L6): Time.now_us
|
# a namespace that computes inline also takes the methods an `alias` gives it (L6): Time.now_us
|
||||||
# is declared in runtime/native/namespaces.ludic beside Time.now, which the compiler computes
|
# is declared in runtime/native/namespaces.ludic beside Time.now, which the compiler computes
|
||||||
|
# "" - the other half of a template's lone string hole, copied by concatenation
|
||||||
|
function tpl_empty() -> Node {
|
||||||
|
let n = node(E_STR)
|
||||||
|
n.s = ""
|
||||||
|
return n
|
||||||
|
}
|
||||||
function emit_alias_or_fail(ns: pointer, meth: pointer, e: Node) -> Val {
|
function emit_alias_or_fail(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
let al = ns_alias_find(ns, meth)
|
let al = ns_alias_find(ns, meth)
|
||||||
if al < 0 { perr(`unknown builtin {ns}.{meth}`) }
|
if al < 0 { perr(`unknown builtin {ns}.{meth}`) }
|
||||||
|
|
@ -706,6 +712,9 @@ function emit_call(e: Node) -> Val {
|
||||||
}
|
}
|
||||||
if (name == "string") { # string(x): int/bool/fixed/long -> text, a string passes through
|
if (name == "string") { # string(x): int/bool/fixed/long -> text, a string passes through
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
|
# but a template that is one string hole (`{s}`) makes a copy: written as one, it was kept as
|
||||||
|
# one, and the original freed under it (ludic.ui's rule text)
|
||||||
|
if (llty(a.ty) == "ptr") and e.a.uns == TPL_COPY and not a.fresh { return emit_str_op("+", a, emit_expr(tpl_empty())) }
|
||||||
if (llty(a.ty) == "ptr") { return a }
|
if (llty(a.ty) == "ptr") { return a }
|
||||||
if is_fp(a.ty) { return emit_fp_str(a) }
|
if is_fp(a.ty) { return emit_fp_str(a) }
|
||||||
if (llty(a.ty) == "i64") { g_uses_longstr = true; return fresh_val(emit_bind(`call ptr @lp_long_str(i64 {a.code})`), "string") }
|
if (llty(a.ty) == "i64") { g_uses_longstr = true; return fresh_val(emit_bind(`call ptr @lp_long_str(i64 {a.code})`), "string") }
|
||||||
|
|
|
||||||
|
|
@ -293,8 +293,10 @@ function parse_interp(raw: pointer) -> Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lj > 0 or (acc == null) { acc = interp_add(acc, interp_lit(lit, lj)) }
|
if lj > 0 or (acc == null) { acc = interp_add(acc, interp_lit(lit, lj)) }
|
||||||
|
if acc.kind == E_CALL { acc.a.uns = TPL_COPY } # `{s}` alone is a new string, never s itself (emit_call.ludic)
|
||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
|
const TPL_COPY: int = 91 # on the callee of a template's lone string(hole)
|
||||||
|
|
||||||
# emit E(field: v, ...) — shared by the statement form and the expression form.
|
# emit E(field: v, ...) — shared by the statement form and the expression form.
|
||||||
# As an expression it yields a cancellable event's cancelled flag (0/1); a
|
# As an expression it yields a cancellable event's cancelled flag (0/1); a
|
||||||
|
|
|
||||||
29656
selfhost/ludicc.seed.ll
29656
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue