diff --git a/oxifmt.typ b/oxifmt.typ index cd774a8..66d86f5 100644 --- a/oxifmt.typ +++ b/oxifmt.typ @@ -89,62 +89,50 @@ 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 - } else { + if current-fmt-span == none { + if last-was-rbracket { + last-was-rbracket = false // escape }} result.push((escape: (escaped: "}", span: (last-i, i + 1)))) + } else { + // 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 +155,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 ---