lua: a numeral ends where its digits do, so 1then is two tokens

`AdvancedTradeSkillWindow2.lua:3878` is `if Item.ReplaceWords == 1then`, with no space, and it
compiles on the reference. Its `read_numeral` (`0x6ffee0`) is 5.0's: digits, at most one `.` and its
digits, then — given an `e`/`E` — the marker, an optional sign (the `0x2b`/`0x2d` test at `0x6fff04`,
reached with the marker already saved) and its own digit loop. The only character-class test in the
whole routine is the ctype digit mask at `0x6fff47`; past it the buffer is terminated and handed to
`luaO_str2d` (`0x6f5900`). 5.1 instead ends with
`while (isalnum(ls->current) || ls->current == '_') save_and_next(ls);`, which swallowed `1then`
into the numeral and failed it — so we raised `malformed number` and lost a 4000-line file and every
global it defines, and its own XML then raised four more times calling them. The addon was dead.

The ninth hunk of the `lua-src` fork, which is where a grammar difference belongs: `BENILLA.md` is
explicit that rewriting the chunk source means parsing Lua with a regex. A replacement, not a
deletion — 5.1 had no exponent-digit loop of its own, so deleting the slurp alone would have cost
`1e2`, which is what the new test caught.

The same boundary is the deliberate half: 1.12 has **no hex literals**. The digit loop stops at the
`x`, so the reference reads `0xFF` as `0` and the name `xFF` and cannot parse the statement around
it. A comment- and string-stripping scan of the installed corpus finds hex in code in exactly two
files, `pfQuest/toolbox/pngLua/deflate.lua` and `numberlua.lua`, which no `.toc` row reaches — zero
loaded sites, the same standard the fork's three other deletions were held to. Our own shipped Lua
and his saved variables have none either. Two of our own test chunks did, and are now decimal:
no chunk of benilla's Lua may use a construct the client's parser rejects.

Verified: the whole chain still loads with no failure, the `WOW_DATA` failure set is unchanged, and
`AdvancedTradeSkillWindow2` now publishes `ATSW_OnLoad` and `ATSWTypeColor`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-18 08:20:02 -06:00
co-authored by Claude Opus 5
parent 4764e4fb2d
commit 8e0d1bae8d
4 changed files with 105 additions and 14 deletions
+15 -3
View File
@@ -196,10 +196,22 @@ static void read_numeral (LexState *ls, SemInfo *seminfo) {
do {
save_and_next(ls);
} while (isdigit(ls->current) || ls->current == '.');
if (check_next(ls, "Ee")) /* `E'? */
/* benilla: 5.1's trailing `while (isalnum(ls->current) || ls->current == '_')
** save_and_next(ls);' is replaced by 5.0's own exponent-digit loop, which is what the client's
** read_numeral (0x6ffee0) has: it saves the `E', takes an optional sign (the 0x2b/0x2d test at
** 0x6fff04, which runs with the marker already in the buffer) and then runs its digit loop --
** the ctype digit mask 0x4 at 0x6fff47, the only character-class test in the whole routine --
** and stops. Nothing slurps the following word, so the client reads `1then' as the number 1 and
** the name `then', which is why `if Item.ReplaceWords == 1then' compiles there; and nothing
** slurps an `x' either, so 1.12 has no hex literals, reading `0xFF' as 0 and the name `xFF'.
** 5.1 gave both runs to the numeral and then failed it. Its slurp also carried the exponent's
** own digits, so deleting it alone would have cost us `1e2' -- hence the loop, not a deletion.
*/
if (check_next(ls, "Ee")) { /* `E'? */
check_next(ls, "+-"); /* optional exponent sign */
while (isalnum(ls->current) || ls->current == '_')
save_and_next(ls);
while (isdigit(ls->current))
save_and_next(ls);
}
save(ls, '\0');
buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */
if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) /* format error? */