From 8478bc1513c03cc48b2c4406b1147e1361a6e907 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Wed, 28 May 2025 02:37:09 -0300 Subject: [PATCH 1/3] close format specifiers eagerly Plus error on stray } at the end --- oxifmt.typ | 69 ++++++++++++++++++++---------------------- tests/strfmt-tests.typ | 10 +++++- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/oxifmt.typ b/oxifmt.typ index cd774a8..947c908 100644 --- a/oxifmt.typ +++ b/oxifmt.typ @@ -89,62 +89,54 @@ last-was-lbracket = false // escape {{ last-was-rbracket = false if current-fmt-span.at(0) == last-i { + // outside a span ({...} {{ <-) => emit an 'escaped' token current-fmt-span = none // cancel this span current-fmt-name = none - } - if current-fmt-name != none { - // if in the middle of a larger span ({ ... {{ <-): - // add the escaped character to the format name - current-fmt-name += character - } else { - // outside a span ({...} {{ <-) => emit an 'escaped' token result.push((escape: (escaped: "{", span: (last-i, i + 1)))) + } else { + panic("String formatter error: internal error: invalid left bracket state") } - - last-i = i - i += 1 // '{' is ASCII, so 1 byte - continue - } - if last-was-rbracket { - // { ... }{ <--- ok, close the previous span - (result, current-fmt-span, current-fmt-name) = write-format-span(last-i, result, current-fmt-span, current-fmt-name) - last-was-rbracket = false - } - if current-fmt-span == none { + } else if current-fmt-span == none { // begin span current-fmt-span = (i, none) current-fmt-name = "" + + // indicate we just started a span + // in case it is escaped right afterwards + last-was-lbracket = true + } else { + // if in the middle of a larger span ({ ... { <-): + // error + excessive-lbracket() } - last-was-lbracket = true } else if character == "}" { last-was-lbracket = false - if last-was-rbracket { - last-was-rbracket = false // escape }} - if current-fmt-name != none { - current-fmt-name += character + if current-fmt-name == none { + if last-was-rbracket { + last-was-rbracket = false // escape }} + if current-fmt-name != none { + current-fmt-name += character + } else { + result.push((escape: (escaped: "}", span: (last-i, i + 1)))) + } } else { - result.push((escape: (escaped: "}", span: (last-i, i + 1)))) + // delay erroring on unmatched } to the next iteration + // in case this is an escaped } + last-was-rbracket = true } - - last-i = i - i += 1 // '}' is ASCII, so 1 byte - continue + } else { + // { ... } <--- ok, close the previous span + // Do this eagerly, escaping } inside { ... } is invalid + (result, current-fmt-span, current-fmt-name) = write-format-span(i, result, current-fmt-span, current-fmt-name) } - // delay closing the span to the next iteration - // in case this is an escaped } - last-was-rbracket = true } else { - // { ... {A <--- non-escaped { inside larger {} - if last-was-lbracket and (current-fmt-span != none and current-fmt-span.at(0) != last-i) { - excessive-lbracket() - } if last-was-rbracket { if current-fmt-span == none { // {...} }A <--- non-escaped } with no matching { excessive-rbracket() } else { - // { ... }A <--- ok, close the previous span - (result, current-fmt-span, current-fmt-name) = write-format-span(last-i, result, current-fmt-span, current-fmt-name) + // { ... }A <--- span should have been eagerly closed already + panic("String formatter error: internal error: invalid right bracket state") } } // {abc <--- add character to the format name @@ -167,6 +159,9 @@ // {abcd| <--- string ended with unclosed span missing-rbracket() } + } else if last-was-rbracket { + // } <--- unmatched and unescaped } at the very end + excessive-rbracket() } result diff --git a/tests/strfmt-tests.typ b/tests/strfmt-tests.typ index a2065fb..fefcc33 100644 --- a/tests/strfmt-tests.typ +++ b/tests/strfmt-tests.typ @@ -11,7 +11,9 @@ assert.eq(strfmt("a{{}}b ={}{}= c{0}d", false, (a: "55", b: 20.3)), "a{}b =false(a: \"55\", b: 20.3)= cfalsed") // test escaping {{ }} from inside { } formats - assert.eq(strfmt("a{b{{b}}b}", ..("b{b}b": 5)), "a5") + // (this is now invalid and should error) + // assert.eq(strfmt("a{b{{b}}b}", ..("b{b}b": 5)), "a5") + // assert.eq(strfmt("a{b}}b}", ..("b}b": 5)), "a5") // test 0 prefix with numbers, but also using 0 as a non-numeric affix assert.eq(strfmt("{:08}|{0:0<8}|{0:0>8}|{0:0^8}", 120), "00000120|12000000|00000120|000120000") @@ -153,6 +155,12 @@ assert.eq(strfmt("{0:e}",1.7976931348623157e+308), "1.7976931348623146e308") assert.eq(strfmt("{0:e}",-1.7976931348623157e+308), "-1.7976931348623146e308") } +// Issue #17: should not escape bracket in format var name +#{ + assert.eq(strfmt("{{{}}}", 1), "{1}") + assert.eq(strfmt("{{"), "{") + assert.eq(strfmt("}}"), "}") +} // DOC TESTS #{ // --- Quick examples --- From 36eecca8b67dccb292e35e1c912dc98c0f788bd9 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 3 Jun 2025 18:55:16 -0300 Subject: [PATCH 2/3] remove unnecessary branch --- oxifmt.typ | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/oxifmt.typ b/oxifmt.typ index 947c908..7481d77 100644 --- a/oxifmt.typ +++ b/oxifmt.typ @@ -114,11 +114,7 @@ if current-fmt-name == none { if last-was-rbracket { last-was-rbracket = false // escape }} - if current-fmt-name != none { - current-fmt-name += character - } else { - result.push((escape: (escaped: "}", span: (last-i, i + 1)))) - } + result.push((escape: (escaped: "}", span: (last-i, i + 1)))) } else { // delay erroring on unmatched } to the next iteration // in case this is an escaped } From dacd75ba34fb5ee06a021bcaefbb4999649436e6 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 3 Jun 2025 19:10:36 -0300 Subject: [PATCH 3/3] change rbracket check --- oxifmt.typ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oxifmt.typ b/oxifmt.typ index 7481d77..66d86f5 100644 --- a/oxifmt.typ +++ b/oxifmt.typ @@ -111,7 +111,7 @@ } } else if character == "}" { last-was-lbracket = false - if current-fmt-name == none { + if current-fmt-span == none { if last-was-rbracket { last-was-rbracket = false // escape }} result.push((escape: (escaped: "}", span: (last-i, i + 1))))