ludic.i18n: inside a bracket an argument's bytes ride raw - trf escapes a 27 or 31 only outside brackets, and the decoder unescapes only there, so a bracketed line carrying a key with holes (Ada's brief: its legend line's escaped arguments) keeps its own escapes whichever way it was put in (a game's txt_key raw, or trf). 81e1f7d8 unescaped inside the bracket and split the legend's arguments out of the brief. nest_test holds a key whose hole holds a key with two holes: bare, bracketed raw, bracketed through trf, and escaped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-30 02:00:20 +03:00
parent 81e1f7d8e6
commit 986fba3586
3 changed files with 35 additions and 6 deletions

View file

@ -1,6 +1,7 @@
# keyed_args.ludic - a key's arguments as the decoder reads them: unescaped, looked up while still English, a count # keyed_args.ludic - a key's arguments as the decoder reads them: unescaped, looked up while still English, a count
# an argument as it was handed in: each 27 before a 27 or a 31 taken off # an argument as it was handed in: each 27 before a 27 or a 31 taken off, outside a bracket only (a
# bracketed line inside it was carried raw, and its own escapes are its own)
@alloc_ok("a key made into text (a cache miss): an argument that held a key with holes") @alloc_ok("a key made into text (a cache miss): an argument that held a key with holes")
function kr_unescape(s: string) -> string { function kr_unescape(s: string) -> string {
var esc = false var esc = false
@ -8,9 +9,11 @@ function kr_unescape(s: string) -> string {
if not esc { return s } if not esc { return s }
let buf = buffer(len(s)) let buf = buffer(len(s))
var n = 0 var n = 0
var d = 0
var i = 0 var i = 0
while i < len(s) { while i < len(s) {
if s[i] == I18N_ESC and i + 1 < len(s) { i += 1 } d = kx_depth(d, s[i])
if d == 0 and s[i] == I18N_ESC and i + 1 < len(s) { i += 1 }
buf[n] = s[i] buf[n] = s[i]
n += 1 n += 1
i += 1 i += 1

View file

@ -63,16 +63,23 @@ function kx_sep(buf: []byte, at: int) -> int {
buf[at] = I18N_SEP buf[at] = I18N_SEP
return at + 1 return at + 1
} }
# an argument's bytes, a 27 before each 27 or 31 of its own, so a key with holes can be one # an argument's bytes, a 27 before each 27 or 31 of its own outside any bracket (a bracketed line's
# bytes are its own and ride raw, 29 .. 30 delimit it), so a key with holes can be one
function kx_len(s: string) -> int { function kx_len(s: string) -> int {
var n = len(s) var n = len(s)
for i in 0 .. len(s) { if s[i] == I18N_SEP or s[i] == I18N_ESC { n += 1 } } var d = 0
for i in 0 .. len(s) {
d = kx_depth(d, s[i])
if d == 0 and (s[i] == I18N_SEP or s[i] == I18N_ESC) { n += 1 }
}
return n return n
} }
function kx_arg(buf: []byte, at: int, s: string) -> int { function kx_arg(buf: []byte, at: int, s: string) -> int {
var j = at var j = at
var d = 0
for i in 0 .. len(s) { for i in 0 .. len(s) {
if s[i] == I18N_SEP or s[i] == I18N_ESC { d = kx_depth(d, s[i])
if d == 0 and (s[i] == I18N_SEP or s[i] == I18N_ESC) {
buf[j] = I18N_ESC buf[j] = I18N_ESC
j += 1 j += 1
} }
@ -81,6 +88,12 @@ function kx_arg(buf: []byte, at: int, s: string) -> int {
} }
return j return j
} }
# how deep in brackets a byte leaves the reader
function kx_depth(d: int, c: int) -> int {
if c == I18N_OPEN { return d + 1 }
if c == I18N_CLOSE and d > 0 { return d - 1 }
return d
}
function kx_put(buf: []byte, at: int, s: string) -> int { function kx_put(buf: []byte, at: int, s: string) -> int {
for i in 0 .. len(s) { buf[at + i] = s[i] } for i in 0 .. len(s) { buf[at + i] = s[i] }
return at + len(s) return at + len(s)

View file

@ -5,7 +5,7 @@ program I18nNestTest {
numbers float numbers float
function q(s: string) -> string { return "\"" + s + "\"" } function q(s: string) -> string { return "\"" + s + "\"" }
function po_line(id: string, tr: string) -> string { return "msgid " + q(id) + "\nmsgstr " + q(tr) + "\n\n" } function po_line(id: string, tr: string) -> string { return "msgid " + q(id) + "\nmsgstr " + q(tr) + "\n\n" }
function en() -> string { return po_line("hud.day", "Day {1}") + po_line("pause.quit", "Quit") + po_line("shop.ada", "Ada: {1}") } function en() -> string { return po_line("hud.day", "Day {1}") + po_line("pause.quit", "Quit") + po_line("shop.ada", "Ada: {1}") + po_line("lore.talk", "{1} is back, {2}.") + po_line("lore.brief", "{1} {2} {3}") }
function tt() -> string { return po_line("hud.day", "{1}. Gun") + po_line("pause.quit", "Cik") + po_line("Settings", "Ayarlar") } function tt() -> string { return po_line("hud.day", "{1}. Gun") + po_line("pause.quit", "Cik") + po_line("Settings", "Ayarlar") }
function b(s: string) -> string { function b(s: string) -> string {
let o = buffer(1) let o = buffer(1)
@ -39,4 +39,17 @@ program I18nNestTest {
expect(L(i18n_st, "Quit" + text_of(c, 1)) == "Quit") expect(L(i18n_st, "Quit" + text_of(c, 1)) == "Quit")
expect(kx_nested("Quit") == false) expect(kx_nested("Quit") == false)
} }
test "a key whose hole holds a key with two holes, bare, bracketed as a game's txt_key puts it, or in trf" (i18n_st: mut I18nState) {
start(i18n_st)
let talk = trf(k"lore.talk", "Old Maroon", "under the far shore", "", "")
let brief = trf(k"lore.brief", "Lightning.", talk, tr(k"pause.quit"), "")
let whole = "Lightning. Old Maroon is back, under the far shore. Quit"
expect(L(i18n_st, brief) == whole)
let sep = buffer(1)
sep[0] = I18N_SEP
expect(L(i18n_st, b(tr(k"shop.ada") + text_of(sep, 1) + b(brief))) == "Ada: " + whole) # txt_key's shape: raw inside
expect(L(i18n_st, trf(k"shop.ada", b(brief), "", "", "")) == "Ada: " + whole)
expect(L(i18n_st, trf(k"shop.ada", brief, "", "", "")) == "Ada: " + whole)
}
} }