Summary
data/bg.sor line 146 has a bracket and a closing parenthesis in the wrong
order. Read as a nesting tree, the bracket's ] ends up inside the
call's still-open parentheses, which a strict, tree-based Soros parser
rejects as unparseable - even though the reference implementations handle
this file today without complaint (see "Why the reference implementations
don't notice" below).
Location
data/bg.sor, line 146
Current:
([0-9]{1,3})([0-9]{3}) $(f:|$1) хиляди[ $(and:$(ordinal \2)])
Fixed:
([0-9]{1,3})([0-9]{3}) $(f:|$1) хиляди[ $(and:$(ordinal \2))]
Why this is a typo, not intentional
The current line ends \2)]) - the bracket closes before the call's
second closing parenthesis. Compare it with the singular counterpart one
line above, which already has it right:
1([0-9]{3}) хиляда[ $(and:$(ordinal \1))]
Both rules build the same "N thousand[ and REMAINDER-th]" shape. The
singular rule correctly closes both parentheses before the bracket
())]); the plural rule has those two characters transposed ()])).
That's a plain two-character transposition, verifiable by comparing
against a rule in the same file that already works - not a guess about
intent.
Why the reference implementations don't notice
They never parse [...] as a nesting construct at all. Optional blocks
are rewritten into call syntax by a text substitution, and then every
] is replaced by ), unconditionally, before any paren counting runs:
So a bracket never has to nest correctly relative to the call's own
parentheses: the crossing is absorbed by that substitution, and the
malformed line comes out balanced by accident. No specification says ]
and ) are interchangeable, which is why a parser that builds a real
tree has no principled way to accept the line without either guessing or
replicating that substitution.
The fix changes no output
Verified rather than assumed: running this project's own spellout
against the unpatched bg.sor produces exactly the same values as a
strict parser produces with the fix applied.
| Input |
spellout -l bg -p ordinal (unpatched, upstream) |
With the fix |
| 1023 |
хиляда двадесет и трети |
identical |
| 2023 |
две хиляди двадесет и трети |
identical |
| 5023 |
пет хиляди двадесет и трети |
identical |
The change is therefore backward compatible with the C++/Java/Python
implementations: their pipeline never depended on this line's nesting
being wrong, so the fix only affects a parser that is strict about it.
Context
Found independently by two ports of this project's Soros engine, both of
which build a real syntax tree for templates and so reject the line:
- numbertext-go - Go port,
where it was first found. Write-up, including the engine-side
workarounds considered and rejected before concluding this was a data
typo rather than a parser gap, in its
AGENTS.md.
- numbertext-rs - Rust port
(numbertext and
soros on crates.io). Same
conclusion reached separately; see its
AGENTS.md.
Both carry the one-line change as a local patch, reapplied on every data
sync, and both have a CI check that fails once upstream no longer needs
it - which is how the eventual merge gets noticed.
Suggested fix
- ([0-9]{1,3})([0-9]{3}) $(f:|$1) хиляди[ $(and:$(ordinal \2)])
+ ([0-9]{1,3})([0-9]{3}) $(f:|$1) хиляди[ $(and:$(ordinal \2))]
Happy to open a PR if that is easier.
Summary
data/bg.sorline 146 has a bracket and a closing parenthesis in the wrongorder. Read as a nesting tree, the bracket's
]ends up inside thecall's still-open parentheses, which a strict, tree-based Soros parser
rejects as unparseable - even though the reference implementations handle
this file today without complaint (see "Why the reference implementations
don't notice" below).
Location
data/bg.sor, line 146Current:
Fixed:
Why this is a typo, not intentional
The current line ends
\2)])- the bracket closes before the call'ssecond closing parenthesis. Compare it with the singular counterpart one
line above, which already has it right:
Both rules build the same "N thousand[ and REMAINDER-th]" shape. The
singular rule correctly closes both parentheses before the bracket
(
))]); the plural rule has those two characters transposed ()])).That's a plain two-character transposition, verifiable by comparing
against a rule in the same file that already works - not a guess about
intent.
Why the reference implementations don't notice
They never parse
[...]as a nesting construct at all. Optional blocksare rewritten into call syntax by a text substitution, and then every
]is replaced by), unconditionally, before any paren counting runs:src/Soros.cxx:125:s2 = translate(s2, L"]", L")", L"");src/Soros.py:75:s2 = re.sub(r"]", ")", s2)So a bracket never has to nest correctly relative to the call's own
parentheses: the crossing is absorbed by that substitution, and the
malformed line comes out balanced by accident. No specification says
]and
)are interchangeable, which is why a parser that builds a realtree has no principled way to accept the line without either guessing or
replicating that substitution.
The fix changes no output
Verified rather than assumed: running this project's own
spelloutagainst the unpatched
bg.sorproduces exactly the same values as astrict parser produces with the fix applied.
spellout -l bg -p ordinal(unpatched, upstream)The change is therefore backward compatible with the C++/Java/Python
implementations: their pipeline never depended on this line's nesting
being wrong, so the fix only affects a parser that is strict about it.
Context
Found independently by two ports of this project's Soros engine, both of
which build a real syntax tree for templates and so reject the line:
where it was first found. Write-up, including the engine-side
workarounds considered and rejected before concluding this was a data
typo rather than a parser gap, in its
AGENTS.md.
(
numbertextandsoroson crates.io). Sameconclusion reached separately; see its
AGENTS.md.
Both carry the one-line change as a local patch, reapplied on every data
sync, and both have a CI check that fails once upstream no longer needs
it - which is how the eventual merge gets noticed.
Suggested fix
Happy to open a PR if that is easier.