|
11 | 11 | // You should have received a copy of the GNU General Public License along with |
12 | 12 | // ClassicAPI. If not, see <https://www.gnu.org/licenses/>. |
13 | 13 |
|
14 | | -// Lua 5.1 string-library additions that 1.12's Lua 5.0 is missing: |
| 14 | +// String helpers that 1.12's Lua 5.0 is missing: |
15 | 15 | // |
16 | | -// - `string.match(s, pattern [, init])` — first-match extraction. |
17 | | -// - `string.gmatch(s, pattern)` — match iterator. |
| 16 | +// - `string.match(s, pattern [, init])` — first-match extraction (5.1). |
| 17 | +// - `string.gmatch(s, pattern)` — match iterator (5.1). |
| 18 | +// - `strsplit(sep, str [, pieces])` — WoW global; split on any char. |
18 | 19 | // |
19 | | -// Both are the same underlying pattern engine as functions 5.0 already ships: |
| 20 | +// The two `string.*` ones reuse pattern machinery 5.0 already ships: |
20 | 21 | // - `match` is `find` returning captures / the whole match instead of the |
21 | 22 | // start/end indices — so we call the engine's `str_find` and transform. |
22 | 23 | // - `gmatch` is exactly 5.0's `string.gfind` (renamed in 5.1); we register |
23 | 24 | // it as a direct alias of the engine's `gfind` C function. |
| 25 | +// `strsplit` is a hand-rolled port of 3.3.5's `strsplit` (see below). |
24 | 26 | // |
25 | 27 | // NOTE: only the `string.foo(s, ...)` call form works — NOT the |
26 | 28 | // `("x"):foo(...)` method sugar. WoW's Lua VM strips type-metatables for |
@@ -83,9 +85,68 @@ int __fastcall Script_string_match(void *L) { |
83 | 85 | const auto Script_string_gmatch = |
84 | 86 | reinterpret_cast<Game::Lua::CFunction>(Offsets::FUN_LUA_STR_GFIND); |
85 | 87 |
|
| 88 | +// `strsplit(sep, str [, pieces])` — the WoW global (also aliased as |
| 89 | +// `string.split`), split `str` on ANY character in `sep` and return the |
| 90 | +// pieces as multiple values. `pieces > 0` caps the result count, with the |
| 91 | +// unsplit remainder as the final piece; `0` / omitted = unlimited. Ported |
| 92 | +// from 3.3.5's `strsplit` (FUN_00816a60) with one deliberate change: we do |
| 93 | +// NOT `lua_settop(L, 0)` up front. Popping the args would leave the source |
| 94 | +// string unreferenced, so a GC step triggered by a `pushlstring` could free |
| 95 | +// the very buffer we're still scanning. Keeping the args on the stack keeps |
| 96 | +// the source GC-rooted; Lua returns the top N values (our pieces) regardless |
| 97 | +// of the args sitting below them. Each push is guarded by `lua_checkstack` |
| 98 | +// (this pushes an unbounded number of results), erroring like 3.3.5 on |
| 99 | +// genuine stack exhaustion. |
| 100 | +int __fastcall Script_strsplit(void *L) { |
| 101 | + const char *sep = Game::Lua::ToString(L, 1); |
| 102 | + const char *str = Game::Lua::ToString(L, 2); |
| 103 | + if (sep == nullptr || str == nullptr) { |
| 104 | + Game::Lua::Error(L, "Usage: strsplit(\"separators\", str [, pieces])"); |
| 105 | + return 0; // unreachable |
| 106 | + } |
| 107 | + const int pieces = |
| 108 | + Game::Lua::IsNumber(L, 3) ? static_cast<int>(Game::Lua::ToNumber(L, 3)) : 0; |
| 109 | + |
| 110 | + const char *segStart = str; |
| 111 | + int count = 0; |
| 112 | + // Only scan for separators while unlimited (pieces == 0) or the cap |
| 113 | + // hasn't been reached (pieces > 1). pieces == 1 (or <= 0 but non-zero) |
| 114 | + // yields the whole string as a single piece — matches 3.3.5. |
| 115 | + if (pieces == 0 || pieces > 1) { |
| 116 | + for (const char *p = str; *p != '\0'; ++p) { |
| 117 | + bool isSep = false; |
| 118 | + for (const char *s = sep; *s != '\0'; ++s) { |
| 119 | + if (*p == *s) { |
| 120 | + isSep = true; |
| 121 | + break; |
| 122 | + } |
| 123 | + } |
| 124 | + if (!isSep) |
| 125 | + continue; |
| 126 | + ++count; |
| 127 | + if (Game::Lua::CheckStack(L, count) == 0) { |
| 128 | + Game::Lua::Error(L, "strsplit(): Stack overflow"); |
| 129 | + return 0; // unreachable |
| 130 | + } |
| 131 | + Game::Lua::PushLString(L, segStart, |
| 132 | + static_cast<unsigned int>(p - segStart)); |
| 133 | + segStart = p + 1; |
| 134 | + if (count == pieces - 1) |
| 135 | + break; // cap hit; remainder becomes the final piece |
| 136 | + } |
| 137 | + } |
| 138 | + if (Game::Lua::CheckStack(L, count + 1) == 0) { |
| 139 | + Game::Lua::Error(L, "strsplit(): Stack overflow"); |
| 140 | + return 0; // unreachable |
| 141 | + } |
| 142 | + Game::Lua::PushString(L, segStart); // final piece (remainder to end) |
| 143 | + return count + 1; |
| 144 | +} |
| 145 | + |
86 | 146 | void RegisterFns() { |
87 | 147 | Game::Lua::RegisterTableFunction("string", "match", &Script_string_match); |
88 | 148 | Game::Lua::RegisterTableFunction("string", "gmatch", Script_string_gmatch); |
| 149 | + Game::Lua::RegisterGlobalFunction("strsplit", &Script_strsplit); |
89 | 150 | } |
90 | 151 |
|
91 | 152 | // Both states are Lua 5.0 and equally missing these; RegisterTableFunction |
|
0 commit comments