Skip to content

Commit 5b3040b

Browse files
committed
feat(lua): backport 5.1 leveled long brackets; fix top-level vararg fallback
Adversarial testing of the transpiler surfaced two gaps beyond PR 28: - Leveled long brackets ([=[ ]=], [==[ ]==]) are the one 5.1 syntax construct still missing. The 5.0 engine lexer has no =-level handling (verified in FUN_006ff610), so they fail to compile. A new source-walking pre-pass rewrites a leveled long string to [[body]] when the body is safe (byte-exact, keeps the leading-newline strip) or to a quoted literal with \-newline continuations otherwise, and blanks leveled long comments to spaces. Line numbers preserved. Gated by the new LongBrackets toggle. - Top-level ... outside the addon file funnel (RunScript, XML <OnLoad>, loadstring) expanded to unpack(arg) and threw on nil arg, where 5.1 yields nothing. Non-addon vararg chunks now get a local arg={n=0} fallback. Adds a /capitranspile self-test suite (19 cases, all passing in-game) and updates docs/API.md (also fixing a stale limits note that described plain long strings as non-nesting: they nest by depth, matching the engine) and README.
1 parent c642356 commit 5b3040b

4 files changed

Lines changed: 349 additions & 39 deletions

File tree

AddOns/!!!ClassicAPI/Util/AddOnCompat.lua

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,95 @@ if C_AddOns.DoesAddOnExist("Puppeteer") then
8484
CAPI_MouseoverClearedCompat(PTEnemyUpdater)
8585
end)
8686
end
87+
88+
-- ============================================================================
89+
-- Transpiler self-test suite. Run in-game with /capitranspile (alias /captr).
90+
--
91+
-- Exercises the Lua 5.1 syntax backport in the DLL (luasyntax/Transpile.cpp):
92+
-- leveled long brackets `[=[`, the `#` and `%` operators, `...` expansion, and
93+
-- `0x` hex literals. Each case checks EITHER the exact rewrite text (via
94+
-- _classicapi_TranspileLength) OR end-to-end runtime behavior (via loadstring,
95+
-- which funnels through the same luaL_loadbuffer hook every chunk uses).
96+
--
97+
-- Every `[=[` / `#` / `%` / `...` / `0x` below is inside a double-quoted string,
98+
-- so this file itself transpiles to a no-op on load — the constructs reach the
99+
-- transpiler only at runtime, when the test feeds them in.
100+
-- ============================================================================
101+
local function CAPI_RunTranspileTests()
102+
if type(_classicapi_TranspileLength) ~= "function" then
103+
print("|cffff5555[transpile]|r ClassicAPI DLL not loaded — nothing to test.")
104+
return
105+
end
106+
107+
local pass, fail = 0, 0
108+
local function ok(name, cond, detail)
109+
if cond then
110+
pass = pass + 1
111+
else
112+
fail = fail + 1
113+
print("|cffff5555[transpile] FAIL|r " .. name ..
114+
(detail and (" " .. detail) or ""))
115+
end
116+
end
117+
118+
-- The transpiler rewrites `input` to exactly `expected`.
119+
local function textEq(name, input, expected)
120+
local got = _classicapi_TranspileLength(input)
121+
ok(name, got == expected,
122+
"got=[" .. tostring(got) .. "] want=[" .. expected .. "]")
123+
end
124+
125+
-- Compile+run a full chunk through loadstring; compare its return value.
126+
local function runChunk(name, src, expected)
127+
local f, err = loadstring(src)
128+
if not f then ok(name, false, "compile: " .. tostring(err)); return end
129+
local good, val = pcall(f)
130+
ok(name, good and val == expected,
131+
good and ("got=[" .. tostring(val) .. "] want=[" .. tostring(expected) .. "]")
132+
or ("error: " .. tostring(val)))
133+
end
134+
local function evalEq(name, expr, expected)
135+
runChunk(name, "return " .. expr, expected)
136+
end
137+
138+
-- leveled long brackets (new pass) --------------------------------------
139+
textEq("longbracket tier1 lvl2", "s=[==[hello]==]", "s=[[hello]]")
140+
textEq("longbracket tier1 lvl1", "s=[=[hi]=]", "s=[[hi]]")
141+
textEq("longbracket tier2 has-]]", "s=[==[a]]b]==]", "s=\"a]]b\"")
142+
textEq("longbracket tier2 trail-]", "s=[=[end]]=]", "s=\"end]\"")
143+
textEq("longbracket lvl0 kept", "s=[[plain]]", "s=[[plain]]")
144+
evalEq("longbracket runtime has-]]","[==[a]]b]==]", "a]]b")
145+
evalEq("longbracket runtime tier1", "[=[plain]=]", "plain")
146+
runChunk("longcomment blanked", "--[==[ hidden ]==] return 7", 7)
147+
148+
-- # length --------------------------------------------------------------
149+
textEq("len rewrite", "x=#t", "x=__len(t)")
150+
evalEq("len table", "#({1,2,3})", 3)
151+
evalEq("len string", "#('abcd')", 4)
152+
153+
-- % modulo (real 5.1 floor-mod, not C fmod) -----------------------------
154+
textEq("mod rewrite", "x=a%b", "x=__mod(a,b)")
155+
evalEq("mod positive", "5 % 3", 2)
156+
evalEq("mod negative", "-1 % 3", 2) -- C fmod would give -1
157+
evalEq("mod precedence", "2 + 8 % 3", 4) -- 2 + (8 % 3)
158+
159+
-- 0x hex literals -------------------------------------------------------
160+
textEq("hex rewrite", "x=0xFF", "x=255")
161+
evalEq("hex value", "0x10", 16)
162+
163+
-- ... vararg ------------------------------------------------------------
164+
runChunk("vararg fallback empty", "local t = {...} return #t", 0)
165+
runChunk("vararg expand in fn",
166+
"local function g(...) return #({...}) end return g(1,2,3,4)", 4)
167+
168+
-- summary ---------------------------------------------------------------
169+
if fail == 0 then
170+
print("|cff40ff40[transpile] all " .. pass .. " tests passed.|r")
171+
else
172+
print("|cffff5555[transpile] " .. fail .. " FAILED|r, " .. pass .. " passed.")
173+
end
174+
end
175+
176+
SLASH_CAPITRANSPILE1 = "/capitranspile"
177+
SLASH_CAPITRANSPILE2 = "/captr"
178+
SlashCmdList["CAPITRANSPILE"] = CAPI_RunTranspileTests

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ Transparent engine tweaks — no API to call, they just fix a vanilla limitation
234234
|-------|--------|
235235
| Tooltip line cap | Lifts `GameTooltip`'s hard 30-line limit to 60 for every `GameTooltipTemplate` frame (`GameTooltip`, `ShoppingTooltip1/2`, `ItemRefTooltip`, AtlasLoot, …). Stat-heavy tooltips and comparison blocks (e.g. pfUI's eqcompare) no longer have their extra lines silently dropped. Done in pure C++ by growing the engine's FontString pool at tooltip-creation time. |
236236
| Inline textures | Draws inline texture markup (`\|T…\|t`) as icons in FontStrings, chat, and tooltips, the way 4.3.4+ clients do. Vanilla 1.12 shows the raw escape as literal text instead. This covers item and spell icons, raid-target markers, and the coin icons in money strings. `GetStringWidth` and `GetStringHeight` count the icons, so measured width and text wrapping stay correct. Done in pure C++ by hooking the engine's text pipeline — no addon. |
237-
| Lua 5.1 syntax | Compiles the Lua 5.1 length (`#`), modulo (`%`), `...`-expression, and `0x` hex-literal syntax that vanilla's Lua 5.0 rejects, by rewriting addon source before it compiles. Each addon file also receives its `(name, table)` through `...` (`local name, tbl = ...`). See [Lua 5.1 syntax](docs/API.md#lua-51-syntax). |
237+
| Lua 5.1 syntax | Compiles the Lua 5.1 length (`#`), modulo (`%`), `...`-expression, `0x` hex-literal, and leveled long bracket (`[=[ ]=]`) syntax that vanilla's Lua 5.0 rejects, by rewriting addon source before it compiles. Each addon file also receives its `(name, table)` through `...` (`local name, tbl = ...`). See [Lua 5.1 syntax](docs/API.md#lua-51-syntax). |
238238
| Lua 5.1 environment protection | `getfenv` / `setfenv` honor a `__environment` metatable field — the Lua 5.1 sandbox form — in addition to vanilla's raw `__fenv`. See [getfenv / setfenv environment protection](docs/API.md#getfenv--setfenv-environment-protection). |
239239
| Multi-flavor & conditional TOC loading | Loads modern multi-flavor addons that ship one folder. Selects a version-specific TOC (`<Name>_ClassicAPI.toc` or `<Name>_Turtle.toc`) and the matching keybinding file (`Bindings_ClassicAPI.xml` / `Bindings_Turtle.xml`), accepts a comma-separated `## Interface:` version list (compatible when it includes the client version `11200`), and honors per-line `[AllowLoadGameType]` / `[AllowLoadTextLocale]` conditions and `[Family]` / `[Game]` / `[TextLocale]` path variables inside a TOC. See [Conditional and multi-flavor TOC loading](docs/API.md#conditional-and-multi-flavor-toc-loading). |
240240
| SavedVariables loaded first | Honors the modern `## LoadSavedVariablesFirst` TOC directive: a flagged addon's SavedVariables load before its Lua runs, so file-scope code sees restored config (instead of vanilla's `nil`). See [SavedVariables loaded first](docs/API.md#savedvariables-loaded-first). |

docs/API.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8497,17 +8497,18 @@ just the 5.0→5.1 renames — `string.gmatch`←`gfind`, `math.fmod`←`math.mo
84978497

84988498
### Lua 5.1 syntax
84998499

8500-
1.12 runs Lua 5.0. It cannot compile four pieces of Lua 5.1 syntax that
8500+
1.12 runs Lua 5.0. It cannot compile five pieces of Lua 5.1 syntax that
85018501
modern addons use. These are the length operator `#`, the modulo operator
8502-
`%`, `...` used as an expression, and `0x` hexadecimal number literals.
8503-
ClassicAPI rewrites addon source to the 5.0 equivalent before it compiles,
8504-
so all four work:
8502+
`%`, `...` used as an expression, `0x` hexadecimal number literals, and
8503+
leveled long brackets (`[=[ ... ]=]`). ClassicAPI rewrites addon source to
8504+
the 5.0 equivalent before it compiles, so all five work:
85058505

85068506
```lua
85078507
local n = #myTable -- length operator
85088508
local r = a % b -- modulo operator
85098509
local args = { ... } -- ... as an expression, not only in a parameter list
85108510
local mask = 0xFF00 -- hex number literal
8511+
local doc = [=[ has ]] in it ]=] -- leveled long bracket
85118512
```
85128513

85138514
The rewrite is transparent. You do not call anything. It runs on every
@@ -8526,6 +8527,11 @@ What each form does:
85268527
- `0xFF00` becomes its decimal value (`65280`) — the same number Lua 5.1
85278528
produces. Vanilla's lexer rejects `0x` literals, so without this an addon
85288529
needs `tonumber("0xFF00", 16)`.
8530+
- `[=[ ... ]=]` (a leveled long bracket, with any number of `=`) holds text
8531+
a plain `[[ ... ]]` cannot, such as text that contains `]]`. ClassicAPI
8532+
rewrites a leveled long string to a plain long string, or to a quoted
8533+
string when its body needs one. It removes a leveled long comment
8534+
(`--[=[ ... ]=]`).
85298535

85308536
**Addon file arguments.** A modern addon reads its name and its private
85318537
table from the file arguments:
@@ -8545,9 +8551,13 @@ needs that addon's opt-in.
85458551

85468552
- The rewrite reads strings and comments correctly. A `%` in `"%d"` or a
85478553
`#` in `--[[ # ]]` is left alone.
8548-
- A nested long string or comment (`[[ a [[ b ]] c ]]`) matches at the
8549-
first close, not by depth. This is a 5.0-only form that addons almost
8550-
never use.
8554+
- A plain long string that contains `[[` nests and closes by depth, the
8555+
same as the 5.0 engine. Lua 5.1 does not nest. For text that contains
8556+
`[[` or `]]`, use a leveled bracket (`[=[ ... ]=]`).
8557+
- ClassicAPI rewrites a leveled long string to a plain `[[ ... ]]` when it
8558+
can, which keeps the exact value. A body that contains `[[` or `]]`, or
8559+
ends with `]`, becomes a quoted string instead. A quoted string keeps a
8560+
leading newline in the value. Lua 5.1 drops that newline.
85518561
- Only integer hex is converted. Hex *floats* (`0x1.8p3`) and literals
85528562
wider than 64 bits are left as-is. Both are almost nonexistent in addon
85538563
code.
@@ -8556,7 +8566,8 @@ needs that addon's opt-in.
85568566
Treat them as internal. Do not call them directly.
85578567
- To turn a rewrite off for diagnosis, call
85588568
`_classicapi_SetTranspileOption(name, false)`, where `name` is
8559-
`"Length"`, `"Modulo"`, `"VarargExpansion"`, or `"HexLiterals"`. This
8569+
`"Length"`, `"Modulo"`, `"VarargExpansion"`, `"HexLiterals"`, or
8570+
`"LongBrackets"`. This
85608571
reverts affected chunks to the state that fails to compile, so use it
85618572
only to answer "is the rewrite breaking this addon?".
85628573

0 commit comments

Comments
 (0)