lua-obfuscator 1.0.4 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +59 -0
  2. package/dist/index.cjs +4552 -0
  3. package/dist/index.d.cts +399 -0
  4. package/dist/index.d.ts +397 -12
  5. package/dist/index.js +4505 -65
  6. package/package.json +24 -19
  7. package/.github/workflows/release.yml +0 -40
  8. package/dist/prometheus/LICENSE +0 -661
  9. package/dist/prometheus/benchmark.lua +0 -34
  10. package/dist/prometheus/build.bat +0 -10
  11. package/dist/prometheus/cli.lua +0 -12
  12. package/dist/prometheus/doc/README.md +0 -11
  13. package/dist/prometheus/doc/SUMMARY.md +0 -27
  14. package/dist/prometheus/doc/advanced/using-prometheus-in-your-lua-application.md +0 -31
  15. package/dist/prometheus/doc/getting-started/command-line-options.md +0 -13
  16. package/dist/prometheus/doc/getting-started/installation.md +0 -11
  17. package/dist/prometheus/doc/getting-started/obfuscating-your-first-script.md +0 -50
  18. package/dist/prometheus/doc/getting-started/presets.md +0 -10
  19. package/dist/prometheus/doc/getting-started/the-config-object.md +0 -58
  20. package/dist/prometheus/doc/getting-started/writing-a-custom-config-file.md +0 -56
  21. package/dist/prometheus/doc/steps/anti-tamper.md +0 -11
  22. package/dist/prometheus/doc/steps/constantarray.md +0 -71
  23. package/dist/prometheus/doc/steps/encryptstrings.md +0 -86
  24. package/dist/prometheus/doc/steps/proxifylocals.md +0 -47
  25. package/dist/prometheus/doc/steps/splitstrings.md +0 -40
  26. package/dist/prometheus/doc/steps/vmify.md +0 -9
  27. package/dist/prometheus/doc/steps/wrapinfunction.md +0 -29
  28. package/dist/prometheus/prometheus-main.lua +0 -1
  29. package/dist/prometheus/readme.md +0 -57
  30. package/dist/prometheus/readme.txt +0 -5
  31. package/dist/prometheus/src/cli.lua +0 -154
  32. package/dist/prometheus/src/colors.lua +0 -61
  33. package/dist/prometheus/src/config.lua +0 -35
  34. package/dist/prometheus/src/highlightlua.lua +0 -61
  35. package/dist/prometheus/src/logger.lua +0 -62
  36. package/dist/prometheus/src/presets.lua +0 -174
  37. package/dist/prometheus/src/prometheus/ast.lua +0 -792
  38. package/dist/prometheus/src/prometheus/bit.lua +0 -521
  39. package/dist/prometheus/src/prometheus/compiler/compiler.lua +0 -2365
  40. package/dist/prometheus/src/prometheus/enums.lua +0 -106
  41. package/dist/prometheus/src/prometheus/namegenerators/Il.lua +0 -41
  42. package/dist/prometheus/src/prometheus/namegenerators/confuse.lua +0 -169
  43. package/dist/prometheus/src/prometheus/namegenerators/mangled.lua +0 -26
  44. package/dist/prometheus/src/prometheus/namegenerators/mangled_shuffled.lua +0 -35
  45. package/dist/prometheus/src/prometheus/namegenerators/number.lua +0 -11
  46. package/dist/prometheus/src/prometheus/namegenerators.lua +0 -7
  47. package/dist/prometheus/src/prometheus/parser.lua +0 -969
  48. package/dist/prometheus/src/prometheus/pipeline.lua +0 -250
  49. package/dist/prometheus/src/prometheus/randomLiterals.lua +0 -41
  50. package/dist/prometheus/src/prometheus/randomStrings.lua +0 -24
  51. package/dist/prometheus/src/prometheus/scope.lua +0 -332
  52. package/dist/prometheus/src/prometheus/step.lua +0 -79
  53. package/dist/prometheus/src/prometheus/steps/AddVararg.lua +0 -33
  54. package/dist/prometheus/src/prometheus/steps/AntiTamper.lua +0 -194
  55. package/dist/prometheus/src/prometheus/steps/ConstantArray.lua +0 -521
  56. package/dist/prometheus/src/prometheus/steps/EncryptStrings.lua +0 -239
  57. package/dist/prometheus/src/prometheus/steps/NumbersToExpressions.lua +0 -82
  58. package/dist/prometheus/src/prometheus/steps/ProxifyLocals.lua +0 -313
  59. package/dist/prometheus/src/prometheus/steps/SplitStrings.lua +0 -338
  60. package/dist/prometheus/src/prometheus/steps/Vmify.lua +0 -30
  61. package/dist/prometheus/src/prometheus/steps/Watermark.lua +0 -61
  62. package/dist/prometheus/src/prometheus/steps/WatermarkCheck.lua +0 -50
  63. package/dist/prometheus/src/prometheus/steps/WrapInFunction.lua +0 -45
  64. package/dist/prometheus/src/prometheus/steps.lua +0 -12
  65. package/dist/prometheus/src/prometheus/tokenizer.lua +0 -546
  66. package/dist/prometheus/src/prometheus/unparser.lua +0 -866
  67. package/dist/prometheus/src/prometheus/util.lua +0 -297
  68. package/dist/prometheus/src/prometheus/visitast.lua +0 -245
  69. package/dist/prometheus/src/prometheus.lua +0 -71
  70. package/dist/prometheus/tests/closures.lua +0 -12
  71. package/dist/prometheus/tests/fibonacci.lua +0 -10
  72. package/dist/prometheus/tests/loops.lua +0 -8
  73. package/dist/prometheus/tests/primes.lua +0 -18
  74. package/dist/prometheus/tests.lua +0 -149
  75. package/src/index.ts +0 -100
  76. package/tsconfig.json +0 -13
package/dist/index.js CHANGED
@@ -1,79 +1,4519 @@
1
- import { LuaFactory } from 'wasmoon';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
- const prometheusPath = path.join(__dirname, '../dist/prometheus');
7
- let factory = null;
8
- function getFactory() {
9
- if (!factory)
10
- factory = new LuaFactory();
11
- return factory;
12
- }
13
- let mountPromise = null;
14
- async function mountDirectory(dir, prefix = '') {
15
- const entries = fs.readdirSync(dir, { withFileTypes: true });
16
- for (const entry of entries) {
17
- const fullPath = path.join(dir, entry.name);
18
- const luaPath = prefix ? `${prefix}/${entry.name}` : entry.name;
19
- if (entry.isDirectory()) {
20
- await mountDirectory(fullPath, luaPath);
21
- }
22
- else if (entry.name.endsWith('.lua')) {
23
- await getFactory().mountFile(luaPath, fs.readFileSync(fullPath));
24
- }
25
- }
26
- }
27
- function ensureMounted() {
28
- if (!mountPromise) {
29
- mountPromise = mountDirectory(prometheusPath).catch(err => {
30
- mountPromise = null;
31
- throw err;
1
+ // src/lexer/tokens.ts
2
+ var LiteralTypes = 2 /* StringLiteral */ | 16 /* NumericLiteral */ | 64 /* BooleanLiteral */ | 128 /* NilLiteral */ | 256 /* VarargLiteral */;
3
+ var KEYWORDS = /* @__PURE__ */ new Set([
4
+ "and",
5
+ "break",
6
+ "do",
7
+ "else",
8
+ "elseif",
9
+ "end",
10
+ "false",
11
+ "for",
12
+ "function",
13
+ "if",
14
+ "in",
15
+ "local",
16
+ "nil",
17
+ "not",
18
+ "or",
19
+ "repeat",
20
+ "return",
21
+ "then",
22
+ "true",
23
+ "until",
24
+ "while"
25
+ ]);
26
+ var LUAU_ONLY_KEYWORDS = /* @__PURE__ */ new Set(["continue"]);
27
+ var GOTO_KEYWORDS = /* @__PURE__ */ new Set(["goto"]);
28
+ function isKeyword(word, continueKeyword, gotoKeyword) {
29
+ if (KEYWORDS.has(word)) return true;
30
+ if (continueKeyword && LUAU_ONLY_KEYWORDS.has(word)) return true;
31
+ if (gotoKeyword && GOTO_KEYWORDS.has(word)) return true;
32
+ return false;
33
+ }
34
+
35
+ // src/lexer/lexer.ts
36
+ var WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\r", "\n"]);
37
+ function isDigit(ch) {
38
+ return ch >= "0" && ch <= "9";
39
+ }
40
+ function isHexDigit(ch) {
41
+ return isDigit(ch) || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
42
+ }
43
+ function isIdentStart(ch) {
44
+ return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_";
45
+ }
46
+ function isIdentPart(ch) {
47
+ return isIdentStart(ch) || isDigit(ch);
48
+ }
49
+ function parseHexNumber(raw) {
50
+ const match = /^0[xX]([0-9a-fA-F]*)(?:\.([0-9a-fA-F]*))?(?:[pP]([+-]?[0-9]+))?$/.exec(raw);
51
+ if (!match || match[1] === "" && (match[2] ?? "") === "") return NaN;
52
+ const [, intDigits, fracDigits, expDigits] = match;
53
+ let mantissa = intDigits.length > 0 ? parseInt(intDigits, 16) : 0;
54
+ if (fracDigits && fracDigits.length > 0) {
55
+ mantissa += parseInt(fracDigits, 16) / Math.pow(16, fracDigits.length);
56
+ }
57
+ const exponent = expDigits !== void 0 ? parseInt(expDigits, 10) : 0;
58
+ return mantissa * Math.pow(2, exponent);
59
+ }
60
+ var LexError = class extends Error {
61
+ constructor(message, line, column) {
62
+ super(`[${line}:${column}] ${message}`);
63
+ this.line = line;
64
+ this.column = column;
65
+ }
66
+ line;
67
+ column;
68
+ };
69
+ var Lexer = class {
70
+ // pos where the current line starts (for column calculation)
71
+ constructor(src, dialect) {
72
+ this.src = src;
73
+ this.dialect = dialect;
74
+ }
75
+ src;
76
+ dialect;
77
+ pos = 0;
78
+ line = 1;
79
+ lineStart = 0;
80
+ get column() {
81
+ return this.pos - this.lineStart + 1;
82
+ }
83
+ peek(offset = 0) {
84
+ return this.src.charAt(this.pos + offset);
85
+ }
86
+ advance() {
87
+ const ch = this.src.charAt(this.pos);
88
+ this.pos++;
89
+ if (ch === "\n") {
90
+ this.line++;
91
+ this.lineStart = this.pos;
92
+ }
93
+ return ch;
94
+ }
95
+ match(ch) {
96
+ if (this.peek() === ch) {
97
+ this.advance();
98
+ return true;
99
+ }
100
+ return false;
101
+ }
102
+ error(msg) {
103
+ throw new LexError(msg, this.line, this.column);
104
+ }
105
+ /** Pulls the entire source into a token array in one pass (includes the EOF token). */
106
+ tokenize() {
107
+ const tokens = [];
108
+ for (; ; ) {
109
+ const tok = this.next();
110
+ tokens.push(tok);
111
+ if (tok.type === 1 /* EOF */) break;
112
+ }
113
+ return tokens;
114
+ }
115
+ /** Reads and returns the next single token. */
116
+ next() {
117
+ this.skipWhitespaceAndComments();
118
+ const startLine = this.line;
119
+ const startColumn = this.column;
120
+ const startPos = this.pos;
121
+ if (this.pos >= this.src.length) {
122
+ return this.makeToken(1 /* EOF */, null, startPos, startLine, startColumn);
123
+ }
124
+ const ch = this.peek();
125
+ if (isIdentStart(ch)) return this.scanIdentifierOrKeyword(startPos, startLine, startColumn);
126
+ if (isDigit(ch) || ch === "." && isDigit(this.peek(1))) {
127
+ return this.scanNumber(startPos, startLine, startColumn);
128
+ }
129
+ if (ch === '"' || ch === "'") return this.scanQuotedString(startPos, startLine, startColumn);
130
+ if (ch === "`") return this.scanInterpolatedString(startPos, startLine, startColumn);
131
+ if (ch === "[" && (this.peek(1) === "[" || this.peek(1) === "=")) {
132
+ const long = this.tryScanLongBracket();
133
+ if (long !== null) {
134
+ return this.makeToken(2 /* StringLiteral */, long, startPos, startLine, startColumn);
135
+ }
136
+ }
137
+ return this.scanPunctuator(startPos, startLine, startColumn);
138
+ }
139
+ // ---- Whitespace / comments ----
140
+ skipWhitespaceAndComments() {
141
+ for (; ; ) {
142
+ const ch = this.peek();
143
+ if (WHITESPACE.has(ch)) {
144
+ this.advance();
145
+ continue;
146
+ }
147
+ if (ch === "-" && this.peek(1) === "-") {
148
+ this.advance();
149
+ this.advance();
150
+ if (this.peek() === "[" && (this.peek(1) === "[" || this.peek(1) === "=")) {
151
+ const long = this.tryScanLongBracket();
152
+ if (long !== null) continue;
153
+ }
154
+ while (this.pos < this.src.length && this.peek() !== "\n") this.advance();
155
+ continue;
156
+ }
157
+ break;
158
+ }
159
+ }
160
+ // ---- Identifiers / keywords ----
161
+ scanIdentifierOrKeyword(startPos, line, column) {
162
+ while (isIdentPart(this.peek())) this.advance();
163
+ const word = this.src.slice(startPos, this.pos);
164
+ if (word === "true" || word === "false") {
165
+ return this.makeToken(64 /* BooleanLiteral */, word === "true", startPos, line, column);
166
+ }
167
+ if (word === "nil") {
168
+ return this.makeToken(128 /* NilLiteral */, null, startPos, line, column);
169
+ }
170
+ if (isKeyword(word, this.dialect.continueKeyword, this.dialect.gotoStatements)) {
171
+ return this.makeToken(4 /* Keyword */, word, startPos, line, column);
172
+ }
173
+ return this.makeToken(8 /* Identifier */, word, startPos, line, column);
174
+ }
175
+ // ---- Numbers ----
176
+ scanNumber(startPos, line, column) {
177
+ if (this.peek() === "0" && (this.peek(1) === "x" || this.peek(1) === "X")) {
178
+ this.advance();
179
+ this.advance();
180
+ while (isHexDigit(this.peek())) this.advance();
181
+ if (this.peek() === ".") {
182
+ this.advance();
183
+ while (isHexDigit(this.peek())) this.advance();
184
+ }
185
+ if (this.peek() === "p" || this.peek() === "P") {
186
+ this.advance();
187
+ if (this.peek() === "+" || this.peek() === "-") this.advance();
188
+ while (isDigit(this.peek())) this.advance();
189
+ }
190
+ } else {
191
+ while (isDigit(this.peek())) this.advance();
192
+ if (this.peek() === ".") {
193
+ this.advance();
194
+ while (isDigit(this.peek())) this.advance();
195
+ }
196
+ if (this.peek() === "e" || this.peek() === "E") {
197
+ this.advance();
198
+ if (this.peek() === "+" || this.peek() === "-") this.advance();
199
+ while (isDigit(this.peek())) this.advance();
200
+ }
201
+ }
202
+ const raw = this.src.slice(startPos, this.pos);
203
+ const value = raw.toLowerCase().startsWith("0x") ? parseHexNumber(raw) : parseFloat(raw);
204
+ return this.makeToken(16 /* NumericLiteral */, value, startPos, line, column);
205
+ }
206
+ // ---- Plain quoted strings ("..." / '...') ----
207
+ scanQuotedString(startPos, line, column) {
208
+ const quote = this.advance();
209
+ let value = "";
210
+ for (; ; ) {
211
+ if (this.pos >= this.src.length) this.error("unfinished string");
212
+ const ch = this.peek();
213
+ if (ch === quote) {
214
+ this.advance();
215
+ break;
216
+ }
217
+ if (ch === "\n") this.error("unfinished string");
218
+ if (ch === "\\") {
219
+ this.advance();
220
+ value += this.readEscape();
221
+ continue;
222
+ }
223
+ value += this.advance();
224
+ }
225
+ return this.makeToken(2 /* StringLiteral */, value, startPos, line, column);
226
+ }
227
+ readEscape() {
228
+ const ch = this.advance();
229
+ switch (ch) {
230
+ case "n":
231
+ return "\n";
232
+ case "t":
233
+ return " ";
234
+ case "r":
235
+ return "\r";
236
+ case "a":
237
+ return "\x07";
238
+ case "b":
239
+ return "\b";
240
+ case "f":
241
+ return "\f";
242
+ case "v":
243
+ return "\v";
244
+ case "\\":
245
+ return "\\";
246
+ case '"':
247
+ return '"';
248
+ case "'":
249
+ return "'";
250
+ case "\n":
251
+ return "\n";
252
+ case "x": {
253
+ const hex = this.advance() + this.advance();
254
+ return String.fromCharCode(parseInt(hex, 16));
255
+ }
256
+ default:
257
+ if (isDigit(ch)) {
258
+ let digits = ch;
259
+ for (let i = 0; i < 2 && isDigit(this.peek()); i++) digits += this.advance();
260
+ return String.fromCharCode(parseInt(digits, 10));
261
+ }
262
+ return ch;
263
+ }
264
+ }
265
+ // ---- Long-bracket strings/comments [[ ... ]], [=[ ... ]=], etc. ----
266
+ // A null return means it wasn't a long bracket to begin with (e.g. just a `[` symbol).
267
+ tryScanLongBracket() {
268
+ const save = { pos: this.pos, line: this.line, lineStart: this.lineStart };
269
+ if (this.peek() !== "[") return null;
270
+ this.advance();
271
+ let level = 0;
272
+ while (this.peek() === "=") {
273
+ level++;
274
+ this.advance();
275
+ }
276
+ if (this.peek() !== "[") {
277
+ this.pos = save.pos;
278
+ this.line = save.line;
279
+ this.lineStart = save.lineStart;
280
+ return null;
281
+ }
282
+ this.advance();
283
+ if (this.peek() === "\r" && this.peek(1) === "\n") {
284
+ this.advance();
285
+ this.advance();
286
+ } else if (this.peek() === "\n" || this.peek() === "\r") {
287
+ this.advance();
288
+ }
289
+ const contentStart = this.pos;
290
+ const closer = "]" + "=".repeat(level) + "]";
291
+ const closeIdx = this.src.indexOf(closer, this.pos);
292
+ if (closeIdx === -1) this.error("unfinished long string/comment");
293
+ const content = this.src.slice(contentStart, closeIdx);
294
+ while (this.pos < closeIdx + closer.length) this.advance();
295
+ return content;
296
+ }
297
+ // ---- Luau string interpolation: `...{expr}...` ----
298
+ scanInterpolatedString(startPos, line, column) {
299
+ this.advance();
300
+ const parts = [];
301
+ let buf = "";
302
+ for (; ; ) {
303
+ if (this.pos >= this.src.length) this.error("unfinished interpolated string");
304
+ const ch = this.peek();
305
+ if (ch === "`") {
306
+ this.advance();
307
+ if (buf.length > 0 || parts.length === 0) parts.push({ kind: "string", value: buf });
308
+ break;
309
+ }
310
+ if (ch === "\\") {
311
+ this.advance();
312
+ buf += this.readEscape();
313
+ continue;
314
+ }
315
+ if (ch === "{") {
316
+ if (this.peek(1) === "{") {
317
+ this.advance();
318
+ this.advance();
319
+ buf += "{";
320
+ continue;
321
+ }
322
+ parts.push({ kind: "string", value: buf });
323
+ buf = "";
324
+ this.advance();
325
+ const exprLine = this.line;
326
+ const exprColumn = this.column;
327
+ const exprStart = this.pos;
328
+ this.skipBalancedInterpolationExpr();
329
+ const exprSource = this.src.slice(exprStart, this.pos);
330
+ this.advance();
331
+ parts.push({ kind: "expr", source: exprSource, line: exprLine, column: exprColumn });
332
+ continue;
333
+ }
334
+ buf += this.advance();
335
+ }
336
+ return this.makeToken(
337
+ 512 /* InterpolatedStringLiteral */,
338
+ null,
339
+ startPos,
340
+ line,
341
+ column,
342
+ parts
343
+ );
344
+ }
345
+ /**
346
+ * Right after consuming `{`, advances pos up to the matching `}` (that `}`
347
+ * is left unconsumed - the caller slices out exprSource and consumes it).
348
+ * Any `"..."` / `'...'` / `` `...` `` string encountered along the way is
349
+ * skipped wholesale, so its inner `{`/`}` are ignored for depth counting.
350
+ */
351
+ skipBalancedInterpolationExpr() {
352
+ let depth = 1;
353
+ while (depth > 0) {
354
+ if (this.pos >= this.src.length) this.error("unfinished interpolation expression");
355
+ const c = this.peek();
356
+ if (c === '"' || c === "'") {
357
+ this.skipRawQuotedString();
358
+ continue;
359
+ }
360
+ if (c === "`") {
361
+ this.skipRawInterpolatedString();
362
+ continue;
363
+ }
364
+ if (c === "{") {
365
+ depth++;
366
+ this.advance();
367
+ continue;
368
+ }
369
+ if (c === "}") {
370
+ depth--;
371
+ if (depth === 0) return;
372
+ this.advance();
373
+ continue;
374
+ }
375
+ this.advance();
376
+ }
377
+ }
378
+ /** Skips over `"..."` / `'...'` as raw text without interpreting it as a value (escapes are just skipped). */
379
+ skipRawQuotedString() {
380
+ const quote = this.advance();
381
+ while (true) {
382
+ if (this.pos >= this.src.length) this.error("unfinished string");
383
+ const ch = this.peek();
384
+ if (ch === quote) {
385
+ this.advance();
386
+ return;
387
+ }
388
+ if (ch === "\n") this.error("unfinished string");
389
+ if (ch === "\\") {
390
+ this.advance();
391
+ if (this.pos < this.src.length) this.advance();
392
+ continue;
393
+ }
394
+ this.advance();
395
+ }
396
+ }
397
+ /**
398
+ * Skip logic for an interpolated string nested inside another interpolation
399
+ * expression (`` `{`nested {x}`}` ``). The inner `{expr}` also needs to be
400
+ * skipped recursively in a balanced way, so its strings/braces don't
401
+ * disturb the outer depth count.
402
+ */
403
+ skipRawInterpolatedString() {
404
+ this.advance();
405
+ while (true) {
406
+ if (this.pos >= this.src.length) this.error("unfinished interpolated string");
407
+ const ch = this.peek();
408
+ if (ch === "`") {
409
+ this.advance();
410
+ return;
411
+ }
412
+ if (ch === "\\") {
413
+ this.advance();
414
+ if (this.pos < this.src.length) this.advance();
415
+ continue;
416
+ }
417
+ if (ch === "{") {
418
+ if (this.peek(1) === "{") {
419
+ this.advance();
420
+ this.advance();
421
+ continue;
422
+ }
423
+ this.advance();
424
+ this.skipBalancedInterpolationExpr();
425
+ if (this.peek() === "}") this.advance();
426
+ continue;
427
+ }
428
+ this.advance();
429
+ }
430
+ }
431
+ // ---- Punctuators (operators/symbols), including Luau's compound assignment operators ----
432
+ scanPunctuator(startPos, line, column) {
433
+ const ch = this.advance();
434
+ if (ch === "." && this.peek() === "." && this.peek(1) === ".") {
435
+ this.advance();
436
+ this.advance();
437
+ return this.makeToken(256 /* VarargLiteral */, "...", startPos, line, column);
438
+ }
439
+ const two = ch + this.peek();
440
+ const twoCharPunctuators = /* @__PURE__ */ new Set([
441
+ "==",
442
+ "~=",
443
+ "<=",
444
+ ">=",
445
+ "::",
446
+ "..",
447
+ "+=",
448
+ "-=",
449
+ "*=",
450
+ "/=",
451
+ "%=",
452
+ "^=",
453
+ // Luau compound assignment
454
+ "->",
455
+ // Luau function type: (number) -> string
456
+ "?.",
457
+ "?["
458
+ // Luau optional chaining: a?.b / a?[b]
459
+ ]);
460
+ if (twoCharPunctuators.has(two)) {
461
+ this.advance();
462
+ return this.makeToken(32 /* Punctuator */, two, startPos, line, column);
463
+ }
464
+ if (ch === "/" && this.peek() === "/") {
465
+ this.advance();
466
+ if (this.peek() === "=") {
467
+ this.advance();
468
+ return this.makeToken(32 /* Punctuator */, "//=", startPos, line, column);
469
+ }
470
+ return this.makeToken(32 /* Punctuator */, "//", startPos, line, column);
471
+ }
472
+ if (ch === "." && this.peek() === "." && this.peek(1) === "=") {
473
+ this.advance();
474
+ this.advance();
475
+ return this.makeToken(32 /* Punctuator */, "..=", startPos, line, column);
476
+ }
477
+ const singleCharPunctuators = /* @__PURE__ */ new Set([
478
+ "+",
479
+ "-",
480
+ "*",
481
+ "/",
482
+ "%",
483
+ "^",
484
+ "#",
485
+ "&",
486
+ "~",
487
+ "|",
488
+ "<",
489
+ ">",
490
+ "=",
491
+ "(",
492
+ ")",
493
+ "{",
494
+ "}",
495
+ "[",
496
+ "]",
497
+ ";",
498
+ ":",
499
+ ",",
500
+ ".",
501
+ "?"
502
+ // '?' is used in Luau optional types (T?)
503
+ ]);
504
+ if (singleCharPunctuators.has(ch)) {
505
+ return this.makeToken(32 /* Punctuator */, ch, startPos, line, column);
506
+ }
507
+ this.error(`unexpected symbol '${ch}'`);
508
+ }
509
+ makeToken(type, value, startPos, line, column, parts) {
510
+ return {
511
+ type,
512
+ value,
513
+ raw: this.src.slice(startPos, this.pos),
514
+ line,
515
+ column,
516
+ range: [startPos, this.pos],
517
+ parts
518
+ };
519
+ }
520
+ };
521
+
522
+ // src/parser/cursor.ts
523
+ var ParseError = class extends Error {
524
+ constructor(message, line, column) {
525
+ super(`[${line}:${column}] ${message}`);
526
+ this.line = line;
527
+ this.column = column;
528
+ }
529
+ line;
530
+ column;
531
+ };
532
+ var Cursor = class {
533
+ constructor(tokens, dialect) {
534
+ this.tokens = tokens;
535
+ this.dialect = dialect;
536
+ }
537
+ tokens;
538
+ dialect;
539
+ pos = 0;
540
+ // ---- Basic lookup / movement ----
541
+ current() {
542
+ return this.tokens[this.pos];
543
+ }
544
+ peek(offset = 0) {
545
+ const idx = this.pos + offset;
546
+ return idx < this.tokens.length ? this.tokens[idx] : this.tokens[this.tokens.length - 1];
547
+ }
548
+ previous() {
549
+ return this.tokens[this.pos - 1];
550
+ }
551
+ isAtEnd() {
552
+ return this.current().type === 1 /* EOF */;
553
+ }
554
+ advance() {
555
+ const tok = this.current();
556
+ if (!this.isAtEnd()) this.pos++;
557
+ return tok;
558
+ }
559
+ // ---- Checks ----
560
+ check(type, value) {
561
+ const tok = this.current();
562
+ if (tok.type !== type) return false;
563
+ if (value !== void 0 && tok.value !== value) return false;
564
+ return true;
565
+ }
566
+ checkKeyword(word) {
567
+ return this.check(4 /* Keyword */, word);
568
+ }
569
+ checkPunct(value) {
570
+ return this.check(32 /* Punctuator */, value);
571
+ }
572
+ match(type, value) {
573
+ if (this.check(type, value)) {
574
+ this.advance();
575
+ return true;
576
+ }
577
+ return false;
578
+ }
579
+ matchKeyword(word) {
580
+ return this.match(4 /* Keyword */, word);
581
+ }
582
+ matchPunct(value) {
583
+ return this.match(32 /* Punctuator */, value);
584
+ }
585
+ // ---- Mandatory consumption (error on failure) ----
586
+ expect(type, value, message) {
587
+ if (!this.check(type, value)) {
588
+ this.error(message);
589
+ }
590
+ return this.advance();
591
+ }
592
+ expectPunct(value) {
593
+ return this.expect(32 /* Punctuator */, value, `expected '${value}' but found ${this.describeCurrent()}`);
594
+ }
595
+ expectKeyword(word) {
596
+ return this.expect(4 /* Keyword */, word, `expected '${word}' but found ${this.describeCurrent()}`);
597
+ }
598
+ expectIdentifierName() {
599
+ return this.expect(8 /* Identifier */, void 0, `expected an identifier but found ${this.describeCurrent()}`);
600
+ }
601
+ describeCurrent() {
602
+ const tok = this.current();
603
+ if (tok.type === 1 /* EOF */) return "<eof>";
604
+ return `'${tok.raw}'`;
605
+ }
606
+ // ---- Dialect gating ----
607
+ /** Error if the current token requires a feature that's disabled in this dialect. No-op if enabled (caller proceeds). */
608
+ requireDialectFeature(enabled, featureName) {
609
+ if (!enabled) {
610
+ this.error(`'${featureName}' is Luau-only syntax (current dialect: ${this.dialect.name})`);
611
+ }
612
+ }
613
+ // ---- Errors ----
614
+ error(msg) {
615
+ const tok = this.current();
616
+ throw new ParseError(msg, tok.line, tok.column);
617
+ }
618
+ // ---- Position calculation ----
619
+ posOf(tok) {
620
+ return { line: tok.line, column: tok.column };
621
+ }
622
+ /** Computes the loc/range spanning from startTok to the "last consumed token". */
623
+ finishRange(startTok, endTok = this.previous()) {
624
+ return {
625
+ range: [startTok.range[0], endTok.range[1]],
626
+ loc: {
627
+ start: this.posOf(startTok),
628
+ end: { line: endTok.line, column: endTok.column + (endTok.raw?.length ?? 0) }
629
+ }
630
+ };
631
+ }
632
+ };
633
+
634
+ // src/parser/types.ts
635
+ var TypeParser = class extends Cursor {
636
+ // ---- Entry point ----
637
+ parseTypeAnnotation() {
638
+ return this.parseType();
639
+ }
640
+ parseType() {
641
+ return this.parseUnionType();
642
+ }
643
+ parseUnionType() {
644
+ this.matchPunct("|");
645
+ const start = this.current();
646
+ const first = this.parseIntersectionType();
647
+ if (!this.checkPunct("|")) return first;
648
+ const types = [first];
649
+ while (this.matchPunct("|")) {
650
+ types.push(this.parseIntersectionType());
651
+ }
652
+ return { type: "TypeUnion", types, ...this.finishRange(start) };
653
+ }
654
+ parseIntersectionType() {
655
+ this.matchPunct("&");
656
+ const start = this.current();
657
+ const first = this.parseOptionalType();
658
+ if (!this.checkPunct("&")) return first;
659
+ const types = [first];
660
+ while (this.matchPunct("&")) {
661
+ types.push(this.parseOptionalType());
662
+ }
663
+ return { type: "TypeIntersection", types, ...this.finishRange(start) };
664
+ }
665
+ parseOptionalType() {
666
+ const start = this.current();
667
+ let base2 = this.parsePrimaryType();
668
+ while (this.matchPunct("?")) {
669
+ base2 = { type: "TypeOptional", base: base2, ...this.finishRange(start) };
670
+ }
671
+ return base2;
672
+ }
673
+ // ---- Primary types ----
674
+ parsePrimaryType() {
675
+ const start = this.current();
676
+ if (this.checkPunct("(")) return this.parseParenOrFunctionType();
677
+ if (this.checkPunct("{")) return this.parseTableType();
678
+ if (this.check(2 /* StringLiteral */)) {
679
+ const tok = this.advance();
680
+ return { type: "TypeLiteralString", value: tok.value, ...this.finishRange(start) };
681
+ }
682
+ if (this.checkKeyword("true") || this.checkKeyword("false")) {
683
+ }
684
+ if (this.check(64 /* BooleanLiteral */)) {
685
+ const tok = this.advance();
686
+ return { type: "TypeLiteralBoolean", value: tok.value, ...this.finishRange(start) };
687
+ }
688
+ if (this.checkKeyword("nil")) {
689
+ this.advance();
690
+ return { type: "TypeReference", name: "nil", typeArguments: [], ...this.finishRange(start) };
691
+ }
692
+ if (this.check(8 /* Identifier */, "typeof")) {
693
+ return this.parseTypeofType();
694
+ }
695
+ if (this.check(8 /* Identifier */) || this.checkKeyword("function")) {
696
+ return this.parseTypeReference();
697
+ }
698
+ if (this.checkPunct("<")) {
699
+ return this.parseFunctionTypeWithGenerics();
700
+ }
701
+ this.error(`expected a type but found ${this.current().type === 1 /* EOF */ ? "<eof>" : `'${this.current().raw}'`}`);
702
+ }
703
+ parseTypeReference() {
704
+ const start = this.current();
705
+ let name = this.expect(8 /* Identifier */, void 0, "expected a type name").value;
706
+ while (this.matchPunct(".")) {
707
+ const part = this.expectIdentifierName();
708
+ name += "." + part.value;
709
+ }
710
+ const typeArguments = this.checkPunct("<") ? this.parseTypeArgumentList() : [];
711
+ return { type: "TypeReference", name, typeArguments, ...this.finishRange(start) };
712
+ }
713
+ parseTypeArgumentList() {
714
+ this.expectPunct("<");
715
+ const args = [];
716
+ if (!this.checkPunct(">")) {
717
+ args.push(this.parseType());
718
+ while (this.matchPunct(",")) args.push(this.parseType());
719
+ }
720
+ this.closeAngleBracket();
721
+ return args;
722
+ }
723
+ /**
724
+ * `<`/`>` are lexed as plain Punctuators, so two of them stuck together
725
+ * like `>>` don't get merged into a single token (that two-char combo isn't
726
+ * in twoCharPunctuators) — but that's fine, because even nested generics
727
+ * like `Foo<Bar<Baz>>` always lex correctly as two separate '>' tokens.
728
+ */
729
+ closeAngleBracket() {
730
+ this.expectPunct(">");
731
+ }
732
+ parseTypeofType() {
733
+ const start = this.current();
734
+ this.advance();
735
+ this.expectPunct("(");
736
+ const expression = this.parseExpression();
737
+ this.expectPunct(")");
738
+ return { type: "TypeTypeof", expression, ...this.finishRange(start) };
739
+ }
740
+ parseTableType() {
741
+ const start = this.current();
742
+ this.expectPunct("{");
743
+ const fields = [];
744
+ while (!this.checkPunct("}")) {
745
+ if (this.checkPunct("[")) {
746
+ this.advance();
747
+ const keyType = this.parseType();
748
+ this.expectPunct("]");
749
+ this.expectPunct(":");
750
+ const valueType = this.parseType();
751
+ fields.push({ key: keyType, value: valueType });
752
+ } else if (this.check(8 /* Identifier */) && this.peek(1).type === 32 /* Punctuator */ && this.peek(1).value === ":") {
753
+ const name = this.advance().value;
754
+ this.advance();
755
+ const valueType = this.parseType();
756
+ fields.push({ key: name, value: valueType });
757
+ } else {
758
+ const valueType = this.parseType();
759
+ fields.push({ key: null, value: valueType });
760
+ }
761
+ if (!this.matchPunct(",") && !this.matchPunct(";")) break;
762
+ }
763
+ this.expectPunct("}");
764
+ return { type: "TypeTable", fields, ...this.finishRange(start) };
765
+ }
766
+ /** Having just seen '(': decide whether this is a function type `(A, B) -> C` or a plain parenthesized type `(T)` */
767
+ parseParenOrFunctionType() {
768
+ const start = this.current();
769
+ const { parameters, vararg } = this.parseFunctionParamList();
770
+ if (this.matchPunct("->")) {
771
+ const returns = this.parseReturnTypeList();
772
+ return { type: "TypeFunction", generics: [], parameters, returns, ...this.finishRange(start) };
773
+ }
774
+ if (parameters.length === 1 && !vararg && parameters[0].name === null) {
775
+ return { type: "TypeParenthesized", type_: parameters[0].type, ...this.finishRange(start) };
776
+ }
777
+ this.error("a parenthesized type without '->' can only contain a single type (if this was meant to be a function type, check that you didn't forget the '->')");
778
+ }
779
+ parseFunctionTypeWithGenerics() {
780
+ const start = this.current();
781
+ const generics = this.parseGenericTypeParameterList();
782
+ const { parameters, vararg } = this.parseFunctionParamList();
783
+ this.expectPunct("->");
784
+ const returns = this.parseReturnTypeList();
785
+ void vararg;
786
+ return { type: "TypeFunction", generics, parameters, returns, ...this.finishRange(start) };
787
+ }
788
+ parseFunctionParamList() {
789
+ this.expectPunct("(");
790
+ const parameters = [];
791
+ let vararg = null;
792
+ if (!this.checkPunct(")")) {
793
+ for (; ; ) {
794
+ if (this.check(256 /* VarargLiteral */)) {
795
+ this.advance();
796
+ const varStart = this.previous();
797
+ if (this.checkPunct(":")) {
798
+ this.advance();
799
+ const base2 = this.parseType();
800
+ vararg = { type: "TypeVariadic", base: base2, ...this.finishRange(varStart) };
801
+ } else {
802
+ const anyRef = {
803
+ type: "TypeReference",
804
+ name: "any",
805
+ typeArguments: [],
806
+ ...this.finishRange(varStart)
807
+ };
808
+ vararg = { type: "TypeVariadic", base: anyRef, ...this.finishRange(varStart) };
809
+ }
810
+ break;
811
+ }
812
+ if (this.check(8 /* Identifier */) && this.peek(1).type === 32 /* Punctuator */ && this.peek(1).value === ":") {
813
+ const name = this.advance().value;
814
+ this.advance();
815
+ const t = this.parseType();
816
+ parameters.push({ name, type: t });
817
+ } else {
818
+ const t = this.parseType();
819
+ parameters.push({ name: null, type: t });
820
+ }
821
+ if (!this.matchPunct(",")) break;
822
+ }
823
+ }
824
+ this.expectPunct(")");
825
+ return { parameters, vararg };
826
+ }
827
+ parseReturnTypeList() {
828
+ if (this.checkPunct("(")) {
829
+ const { parameters, vararg } = this.parseFunctionParamList();
830
+ return { types: parameters.map((p) => p.type), vararg };
831
+ }
832
+ if (this.check(256 /* VarargLiteral */)) {
833
+ const start = this.advance();
834
+ this.expectPunct(":");
835
+ const base2 = this.parseType();
836
+ return { types: [], vararg: { type: "TypeVariadic", base: base2, ...this.finishRange(start) } };
837
+ }
838
+ const single = this.parseType();
839
+ return { types: [single], vararg: null };
840
+ }
841
+ // ---- Generic parameter list: <T, U = DefaultType, V...> ----
842
+ parseGenericTypeParameterList() {
843
+ if (!this.checkPunct("<")) return [];
844
+ this.advance();
845
+ const params = [];
846
+ if (!this.checkPunct(">")) {
847
+ params.push(this.parseGenericTypeParameter());
848
+ while (this.matchPunct(",")) params.push(this.parseGenericTypeParameter());
849
+ }
850
+ this.closeAngleBracket();
851
+ return params;
852
+ }
853
+ parseGenericTypeParameter() {
854
+ const name = this.expectIdentifierName().value;
855
+ if (this.check(256 /* VarargLiteral */)) {
856
+ this.advance();
857
+ return { name: name + "...", defaultType: null };
858
+ }
859
+ let defaultType = null;
860
+ if (this.matchPunct("=")) {
861
+ defaultType = this.parseType();
862
+ }
863
+ return { name, defaultType };
864
+ }
865
+ };
866
+
867
+ // src/parser/precedence.ts
868
+ var UNARY_PRECEDENCE = 8;
869
+ var TABLE = {
870
+ or: { precedence: 1, rightAssoc: false },
871
+ and: { precedence: 2, rightAssoc: false },
872
+ "<": { precedence: 3, rightAssoc: false },
873
+ ">": { precedence: 3, rightAssoc: false },
874
+ "<=": { precedence: 3, rightAssoc: false },
875
+ ">=": { precedence: 3, rightAssoc: false },
876
+ "~=": { precedence: 3, rightAssoc: false },
877
+ "==": { precedence: 3, rightAssoc: false },
878
+ "..": { precedence: 4, rightAssoc: true },
879
+ "+": { precedence: 5, rightAssoc: false },
880
+ "-": { precedence: 5, rightAssoc: false },
881
+ "*": { precedence: 6, rightAssoc: false },
882
+ "/": { precedence: 6, rightAssoc: false },
883
+ "//": { precedence: 6, rightAssoc: false },
884
+ // Luau-only; if lua5.1, the parser already rejects the token before this matters
885
+ "%": { precedence: 6, rightAssoc: false },
886
+ "^": { precedence: 9, rightAssoc: true }
887
+ // binds even tighter than unary (8): -x^2 === -(x^2)
888
+ };
889
+ function getBinaryOpInfo(op) {
890
+ return TABLE[op] ?? null;
891
+ }
892
+ function isLogicalOperator(op) {
893
+ return op === "and" || op === "or";
894
+ }
895
+
896
+ // src/parser/parser.ts
897
+ var COMPOUND_ASSIGNMENT_OPS = /* @__PURE__ */ new Set(["+=", "-=", "*=", "/=", "//=", "%=", "^=", "..="]);
898
+ var Parser = class _Parser extends TypeParser {
899
+ constructor(tokens, dialect) {
900
+ super(tokens, dialect);
901
+ }
902
+ // =========================================================================
903
+ // Entry point
904
+ // =========================================================================
905
+ parseChunk() {
906
+ const start = this.current();
907
+ const body = this.parseBlock();
908
+ const eof = this.expect(1 /* EOF */, void 0, `expected <eof> but found ${this.currentDescription()}`);
909
+ return {
910
+ type: "Chunk",
911
+ body,
912
+ range: [start.range[0], eof.range[1]],
913
+ loc: { start: this.posOf(start), end: this.posOf(eof) }
914
+ };
915
+ }
916
+ currentDescription() {
917
+ const tok = this.current();
918
+ return tok.type === 1 /* EOF */ ? "<eof>" : `'${tok.raw}'`;
919
+ }
920
+ // =========================================================================
921
+ // Blocks / statements
922
+ // =========================================================================
923
+ isBlockEnd() {
924
+ if (this.isAtEnd()) return true;
925
+ return this.checkKeyword("end") || this.checkKeyword("else") || this.checkKeyword("elseif") || this.checkKeyword("until");
926
+ }
927
+ parseBlock() {
928
+ const stmts = [];
929
+ while (!this.isBlockEnd()) {
930
+ if (this.checkKeyword("return")) {
931
+ stmts.push(this.parseReturnStatement());
932
+ break;
933
+ }
934
+ const stmt = this.parseStatement();
935
+ if (stmt) stmts.push(stmt);
936
+ }
937
+ return stmts;
938
+ }
939
+ parseStatement() {
940
+ if (this.matchPunct(";")) return null;
941
+ if (this.checkKeyword("if")) return this.parseIfStatement();
942
+ if (this.checkKeyword("while")) return this.parseWhileStatement();
943
+ if (this.checkKeyword("do")) return this.parseDoStatement();
944
+ if (this.checkKeyword("for")) return this.parseForStatement();
945
+ if (this.checkKeyword("repeat")) return this.parseRepeatStatement();
946
+ if (this.checkKeyword("function")) return this.parseFunctionStatement();
947
+ if (this.checkKeyword("local")) return this.parseLocalStatement();
948
+ if (this.checkKeyword("break")) return this.parseBreakStatement();
949
+ if (this.checkKeyword("continue")) return this.parseContinueStatement();
950
+ if (this.checkPunct("::")) return this.parseLabelStatement();
951
+ if (this.checkKeyword("goto")) return this.parseGotoStatement();
952
+ if (this.isTypeAliasStart()) return this.parseTypeAliasStatement();
953
+ return this.parseExpressionStatement();
954
+ }
955
+ // ---- Individual statements ----
956
+ parseIfStatement() {
957
+ const start = this.current();
958
+ this.advance();
959
+ const clauses = [];
960
+ const firstClauseStart = start;
961
+ const condition = this.parseExpression();
962
+ this.expectKeyword("then");
963
+ const body = this.parseBlock();
964
+ clauses.push({ type: "IfClause", condition, body, ...this.finishRange(firstClauseStart) });
965
+ while (this.checkKeyword("elseif")) {
966
+ const clauseStart = this.current();
967
+ this.advance();
968
+ const cond = this.parseExpression();
969
+ this.expectKeyword("then");
970
+ const b = this.parseBlock();
971
+ clauses.push({ type: "ElseifClause", condition: cond, body: b, ...this.finishRange(clauseStart) });
972
+ }
973
+ if (this.checkKeyword("else")) {
974
+ const clauseStart = this.current();
975
+ this.advance();
976
+ const b = this.parseBlock();
977
+ clauses.push({ type: "ElseClause", body: b, ...this.finishRange(clauseStart) });
978
+ }
979
+ this.expectKeyword("end");
980
+ return { type: "IfStatement", clauses, ...this.finishRange(start) };
981
+ }
982
+ parseWhileStatement() {
983
+ const start = this.current();
984
+ this.advance();
985
+ const condition = this.parseExpression();
986
+ this.expectKeyword("do");
987
+ const body = this.parseBlock();
988
+ this.expectKeyword("end");
989
+ return { type: "WhileStatement", condition, body, ...this.finishRange(start) };
990
+ }
991
+ parseDoStatement() {
992
+ const start = this.current();
993
+ this.advance();
994
+ const body = this.parseBlock();
995
+ this.expectKeyword("end");
996
+ return { type: "DoStatement", body, ...this.finishRange(start) };
997
+ }
998
+ parseRepeatStatement() {
999
+ const start = this.current();
1000
+ this.advance();
1001
+ const body = this.parseBlock();
1002
+ this.expectKeyword("until");
1003
+ const condition = this.parseExpression();
1004
+ return { type: "RepeatStatement", condition, body, ...this.finishRange(start) };
1005
+ }
1006
+ parseForStatement() {
1007
+ const start = this.current();
1008
+ this.advance();
1009
+ const firstNameTok = this.expectIdentifierName();
1010
+ const firstVar = this.identifierFromToken(firstNameTok, "local", false);
1011
+ if (this.matchPunct("=")) {
1012
+ const from = this.parseExpression();
1013
+ this.expectPunct(",");
1014
+ const to = this.parseExpression();
1015
+ let step = null;
1016
+ if (this.matchPunct(",")) step = this.parseExpression();
1017
+ this.expectKeyword("do");
1018
+ const body2 = this.parseBlock();
1019
+ this.expectKeyword("end");
1020
+ return { type: "ForNumericStatement", variable: firstVar, start: from, end: to, step, body: body2, ...this.finishRange(start) };
1021
+ }
1022
+ const variables = [firstVar];
1023
+ while (this.matchPunct(",")) {
1024
+ variables.push(this.identifierFromToken(this.expectIdentifierName(), "local", false));
1025
+ }
1026
+ this.expectKeyword("in");
1027
+ const iterators = this.parseExpressionList();
1028
+ this.expectKeyword("do");
1029
+ const body = this.parseBlock();
1030
+ this.expectKeyword("end");
1031
+ return { type: "ForGenericStatement", variables, iterators, body, ...this.finishRange(start) };
1032
+ }
1033
+ parseFunctionStatement() {
1034
+ const start = this.current();
1035
+ this.advance();
1036
+ const { identifier, isMethod } = this.parseFuncName();
1037
+ const body = this.parseFunctionBody(isMethod);
1038
+ return { type: "FunctionDeclaration", identifier, isLocal: false, isMethod, ...body, ...this.finishRange(start) };
1039
+ }
1040
+ /** funcname: Name {'.' Name} [':' Name] */
1041
+ parseFuncName() {
1042
+ const start = this.current();
1043
+ const nameTok = this.expectIdentifierName();
1044
+ let node = this.identifierFromToken(nameTok, "global", false);
1045
+ while (this.matchPunct(".")) {
1046
+ const idTok = this.expectIdentifierName();
1047
+ node = {
1048
+ type: "MemberExpression",
1049
+ indexer: ".",
1050
+ base: node,
1051
+ identifier: this.identifierFromToken(idTok, "global", true),
1052
+ optional: false,
1053
+ ...this.finishRange(start)
1054
+ };
1055
+ }
1056
+ let isMethod = false;
1057
+ if (this.matchPunct(":")) {
1058
+ const idTok = this.expectIdentifierName();
1059
+ node = {
1060
+ type: "MemberExpression",
1061
+ indexer: ":",
1062
+ base: node,
1063
+ identifier: this.identifierFromToken(idTok, "global", true),
1064
+ optional: false,
1065
+ ...this.finishRange(start)
1066
+ };
1067
+ isMethod = true;
1068
+ }
1069
+ return { identifier: node, isMethod };
1070
+ }
1071
+ parseLocalStatement() {
1072
+ const start = this.current();
1073
+ this.advance();
1074
+ if (this.matchKeyword("function")) {
1075
+ const nameTok = this.expectIdentifierName();
1076
+ const identifier = this.identifierFromToken(nameTok, "local", false);
1077
+ const body = this.parseFunctionBody(false);
1078
+ return { type: "FunctionDeclaration", identifier, isLocal: true, isMethod: false, ...body, ...this.finishRange(start) };
1079
+ }
1080
+ const variables = [];
1081
+ for (; ; ) {
1082
+ const nameTok = this.expectIdentifierName();
1083
+ const id = this.identifierFromToken(nameTok, "local", false);
1084
+ if (this.checkPunct("<")) {
1085
+ if (!this.dialect.attributes) {
1086
+ this.error(`variable attributes (<const>/<close>) are Luau-only syntax (current dialect: ${this.dialect.name})`);
1087
+ }
1088
+ this.advance();
1089
+ const attrTok = this.expectIdentifierName();
1090
+ const attrName = attrTok.value;
1091
+ if (attrName !== "const" && attrName !== "close") {
1092
+ this.error(`unknown variable attribute '${attrName}' (only const or close are allowed)`);
1093
+ }
1094
+ id.attribute = attrName;
1095
+ this.expectPunct(">");
1096
+ }
1097
+ if (this.checkPunct(":")) {
1098
+ this.advance();
1099
+ if (!this.dialect.typeAnnotations) {
1100
+ this.error(`type annotations are Luau-only syntax (current dialect: ${this.dialect.name})`);
1101
+ }
1102
+ this.parseType();
1103
+ }
1104
+ variables.push(id);
1105
+ if (!this.matchPunct(",")) break;
1106
+ }
1107
+ let init = [];
1108
+ if (this.matchPunct("=")) init = this.parseExpressionList();
1109
+ return { type: "LocalStatement", variables, init, ...this.finishRange(start) };
1110
+ }
1111
+ parseReturnStatement() {
1112
+ const start = this.current();
1113
+ this.advance();
1114
+ let args = [];
1115
+ if (!this.isBlockEnd() && !this.checkPunct(";")) {
1116
+ args = this.parseExpressionList();
1117
+ }
1118
+ this.matchPunct(";");
1119
+ return { type: "ReturnStatement", arguments: args, ...this.finishRange(start) };
1120
+ }
1121
+ parseBreakStatement() {
1122
+ const start = this.advance();
1123
+ return { type: "BreakStatement", ...this.finishRange(start) };
1124
+ }
1125
+ parseContinueStatement() {
1126
+ const start = this.advance();
1127
+ return { type: "ContinueStatement", ...this.finishRange(start) };
1128
+ }
1129
+ parseGotoStatement() {
1130
+ const start = this.current();
1131
+ this.advance();
1132
+ const label = this.expectIdentifierName().value;
1133
+ return { type: "GotoStatement", label, ...this.finishRange(start) };
1134
+ }
1135
+ parseLabelStatement() {
1136
+ const start = this.current();
1137
+ if (!this.dialect.gotoStatements) {
1138
+ this.error(
1139
+ `'::label::' is not supported (current dialect: ${this.dialect.name}) \u2014 goto/labels are a Lua 5.2+ feature; neither Lua 5.1 nor Luau implement them`
1140
+ );
1141
+ }
1142
+ this.advance();
1143
+ const name = this.expectIdentifierName().value;
1144
+ this.expectPunct("::");
1145
+ return { type: "LabelStatement", name, ...this.finishRange(start) };
1146
+ }
1147
+ /** `type`/`export` aren't reserved words — they're identifiers judged by context, so we have to look ahead before consuming them */
1148
+ isTypeAliasStart() {
1149
+ if (!this.dialect.typeAnnotations) return false;
1150
+ if (this.check(8 /* Identifier */, "type") && this.peek(1).type === 8 /* Identifier */) return true;
1151
+ if (this.check(8 /* Identifier */, "export") && this.peek(1).type === 8 /* Identifier */ && this.peek(1).value === "type" && this.peek(2).type === 8 /* Identifier */) return true;
1152
+ return false;
1153
+ }
1154
+ /**
1155
+ * `type X = ...` / `export type X = ...`. Fully consumed per Luau's type
1156
+ * grammar so the parser's position stays correct, but no AST node is ever
1157
+ * built for it — the statement is ignored outright, the same as a
1158
+ * comment, rather than being represented and then stripped later.
1159
+ */
1160
+ parseTypeAliasStatement() {
1161
+ if (this.check(8 /* Identifier */, "export")) {
1162
+ this.advance();
1163
+ }
1164
+ this.advance();
1165
+ this.expectIdentifierName();
1166
+ this.parseGenericTypeParameterList();
1167
+ this.expectPunct("=");
1168
+ this.parseType();
1169
+ return null;
1170
+ }
1171
+ /** A statement that resolves to one of CallStatement / AssignmentStatement / CompoundAssignmentStatement */
1172
+ parseExpressionStatement() {
1173
+ const start = this.current();
1174
+ const first = this.parseSuffixedExpression();
1175
+ const opTok = this.current();
1176
+ if (opTok.type === 32 /* Punctuator */ && COMPOUND_ASSIGNMENT_OPS.has(opTok.value)) {
1177
+ if (!this.dialect.compoundAssignment) {
1178
+ this.error(`compound assignment operator '${opTok.value}' is Luau-only syntax (current dialect: ${this.dialect.name})`);
1179
+ }
1180
+ const variable = this.toAssignmentTarget(first);
1181
+ this.advance();
1182
+ const value = this.parseExpression();
1183
+ return {
1184
+ type: "CompoundAssignmentStatement",
1185
+ operator: opTok.value,
1186
+ variable,
1187
+ value,
1188
+ ...this.finishRange(start)
1189
+ };
1190
+ }
1191
+ if (this.checkPunct(",") || this.checkPunct("=")) {
1192
+ const variables = [this.toAssignmentTarget(first)];
1193
+ while (this.matchPunct(",")) {
1194
+ variables.push(this.toAssignmentTarget(this.parseSuffixedExpression()));
1195
+ }
1196
+ this.expectPunct("=");
1197
+ const init = this.parseExpressionList();
1198
+ return { type: "AssignmentStatement", variables, init, ...this.finishRange(start) };
1199
+ }
1200
+ if (first.type === "CallExpression" || first.type === "TableCallExpression" || first.type === "StringCallExpression") {
1201
+ return { type: "CallStatement", expression: first, ...this.finishRange(start) };
1202
+ }
1203
+ this.error("syntax error: this expression cannot be used as a statement (it must be a call or an assignment)");
1204
+ }
1205
+ toAssignmentTarget(expr) {
1206
+ if (expr.type === "Identifier" || expr.type === "MemberExpression" || expr.type === "IndexExpression") {
1207
+ return expr;
1208
+ }
1209
+ this.error("this expression cannot be an assignment target (only a variable, obj.field, or obj[key] form is allowed)");
1210
+ }
1211
+ // =========================================================================
1212
+ // Function body (shared by statements and expressions): [<generics>] '(' params ')' [':' returnType] block 'end'
1213
+ // =========================================================================
1214
+ parseFunctionBody(isMethod) {
1215
+ if (this.checkPunct("<")) {
1216
+ if (!this.dialect.genericFunctions) {
1217
+ this.error(`generic functions are Luau-only syntax (current dialect: ${this.dialect.name})`);
1218
+ }
1219
+ this.parseGenericTypeParameterList();
1220
+ }
1221
+ this.expectPunct("(");
1222
+ const parameters = [];
1223
+ if (isMethod) parameters.push(this.makeSelfIdentifier(this.previous()));
1224
+ let hasVararg = false;
1225
+ if (!this.checkPunct(")")) {
1226
+ for (; ; ) {
1227
+ if (this.check(256 /* VarargLiteral */)) {
1228
+ const varTok = this.advance();
1229
+ hasVararg = true;
1230
+ const varargNode = { type: "VarargLiteral", value: "...", ...this.finishRange(varTok, varTok) };
1231
+ parameters.push(varargNode);
1232
+ if (this.checkPunct(":")) {
1233
+ this.advance();
1234
+ if (!this.dialect.typeAnnotations) {
1235
+ this.error(`type annotations are Luau-only syntax (current dialect: ${this.dialect.name})`);
1236
+ }
1237
+ this.parseType();
1238
+ }
1239
+ break;
1240
+ }
1241
+ const nameTok = this.expectIdentifierName();
1242
+ const id = this.identifierFromToken(nameTok, "parameter", false);
1243
+ if (this.checkPunct(":")) {
1244
+ this.advance();
1245
+ if (!this.dialect.typeAnnotations) {
1246
+ this.error(`type annotations are Luau-only syntax (current dialect: ${this.dialect.name})`);
1247
+ }
1248
+ this.parseType();
1249
+ }
1250
+ parameters.push(id);
1251
+ if (!this.matchPunct(",")) break;
1252
+ }
1253
+ }
1254
+ this.expectPunct(")");
1255
+ if (this.checkPunct(":")) {
1256
+ this.advance();
1257
+ if (!this.dialect.typeAnnotations) {
1258
+ this.error(`type annotations are Luau-only syntax (current dialect: ${this.dialect.name})`);
1259
+ }
1260
+ this.parseReturnTypeList();
1261
+ }
1262
+ const body = this.parseBlock();
1263
+ this.expectKeyword("end");
1264
+ return { parameters, body, hasVararg, varargTypeAnnotation: null, generics: [], returnTypeAnnotation: null };
1265
+ }
1266
+ makeSelfIdentifier(refTok) {
1267
+ return { type: "Identifier", name: "self", attribute: null, typeAnnotation: null, ...this.finishRange(refTok, refTok), scope: "parameter", isField: false, bindingId: null };
1268
+ }
1269
+ // NOTE: `scope` here is only ever the parser's syntactic best guess
1270
+ // ('local' at a `local`/for-loop declaration site, 'parameter' for a
1271
+ // function parameter, 'global' as a placeholder everywhere else). The
1272
+ // parser has no visibility into the surrounding scope chain, so a plain
1273
+ // reference like the `x` in `print(x)` is always stamped 'global' here
1274
+ // even when it actually resolves to an enclosing local or upvalue.
1275
+ // resolveScopes() (src/analysis/scope.ts) fixes this up for every
1276
+ // non-field Identifier in a single pass immediately after parseChunk()
1277
+ // returns — nothing downstream should trust `.scope`/`.bindingId` off the
1278
+ // raw parser output.
1279
+ identifierFromToken(tok, scope, isField) {
1280
+ return { type: "Identifier", name: tok.value, attribute: null, typeAnnotation: null, ...this.finishRange(tok, tok), scope, isField, bindingId: null };
1281
+ }
1282
+ // =========================================================================
1283
+ // Expressions (Pratt / precedence climbing)
1284
+ // =========================================================================
1285
+ parseExpression() {
1286
+ return this.parseBinaryExpression(0);
1287
+ }
1288
+ parseExpressionList() {
1289
+ const list = [this.parseExpression()];
1290
+ while (this.matchPunct(",")) list.push(this.parseExpression());
1291
+ return list;
1292
+ }
1293
+ currentOperatorString() {
1294
+ const tok = this.current();
1295
+ if (tok.type === 4 /* Keyword */ && (tok.value === "and" || tok.value === "or")) return tok.value;
1296
+ if (tok.type === 32 /* Punctuator */) return tok.value;
1297
+ return null;
1298
+ }
1299
+ parseBinaryExpression(minPrecedence) {
1300
+ const start = this.current();
1301
+ let left = this.parseUnaryExpression();
1302
+ for (; ; ) {
1303
+ const opStr = this.currentOperatorString();
1304
+ if (!opStr) break;
1305
+ const opInfo = getBinaryOpInfo(opStr);
1306
+ if (!opInfo || opInfo.precedence < minPrecedence) break;
1307
+ if (opStr === "//") this.requireDialectFeature(this.dialect.floorDivision, "// (integer division)");
1308
+ this.advance();
1309
+ const nextMinPrecedence = opInfo.rightAssoc ? opInfo.precedence : opInfo.precedence + 1;
1310
+ const right = this.parseBinaryExpression(nextMinPrecedence);
1311
+ left = isLogicalOperator(opStr) ? { type: "LogicalExpression", operator: opStr, left, right, ...this.finishRange(start) } : { type: "BinaryExpression", operator: opStr, left, right, ...this.finishRange(start) };
1312
+ }
1313
+ return left;
1314
+ }
1315
+ isUnaryOperatorToken() {
1316
+ return this.checkKeyword("not") || this.checkPunct("-") || this.checkPunct("#");
1317
+ }
1318
+ parseUnaryExpression() {
1319
+ if (this.isUnaryOperatorToken()) {
1320
+ const start = this.current();
1321
+ const opTok = this.advance();
1322
+ const argument = this.parseBinaryExpression(UNARY_PRECEDENCE);
1323
+ return {
1324
+ type: "UnaryExpression",
1325
+ operator: opTok.value,
1326
+ argument,
1327
+ ...this.finishRange(start)
1328
+ };
1329
+ }
1330
+ return this.parseCastExpression();
1331
+ }
1332
+ /**
1333
+ * Luau's type-ascription operator: `exp '::' Type`. Cast is a
1334
+ * compile-time-only annotation — it never changes the runtime value — so
1335
+ * it's parsed here (tightly, right after the suffixed/primary expression)
1336
+ * purely to consume the tokens correctly; the parsed Type is discarded
1337
+ * and the inner expression is returned completely unchanged. Chained
1338
+ * casts (`x :: any :: string`) are handled by the loop.
1339
+ */
1340
+ parseCastExpression() {
1341
+ const expr = this.parseSuffixedExpression();
1342
+ while (this.checkPunct("::")) {
1343
+ this.advance();
1344
+ if (!this.dialect.typeAnnotations) {
1345
+ this.error(`type ascription ('::') is Luau-only syntax (current dialect: ${this.dialect.name})`);
1346
+ }
1347
+ this.parseType();
1348
+ }
1349
+ return expr;
1350
+ }
1351
+ /** primaryexp { '.' Name | ':' Name call | '[' exp ']' | call } */
1352
+ parseSuffixedExpression() {
1353
+ const start = this.current();
1354
+ let expr = this.parsePrimaryAtom();
1355
+ for (; ; ) {
1356
+ if (this.matchPunct(".")) {
1357
+ const idTok = this.expectIdentifierName();
1358
+ expr = {
1359
+ type: "MemberExpression",
1360
+ indexer: ".",
1361
+ base: expr,
1362
+ identifier: this.identifierFromToken(idTok, "global", true),
1363
+ optional: false,
1364
+ ...this.finishRange(start)
1365
+ };
1366
+ } else if (this.matchPunct("[")) {
1367
+ const index = this.parseExpression();
1368
+ this.expectPunct("]");
1369
+ expr = { type: "IndexExpression", base: expr, index, optional: false, ...this.finishRange(start) };
1370
+ } else if (this.checkPunct("?.") || this.checkPunct("?[")) {
1371
+ this.requireDialectFeature(this.dialect.optionalChaining, "optional chaining (?./?[)");
1372
+ if (this.matchPunct("?.")) {
1373
+ const idTok = this.expectIdentifierName();
1374
+ expr = {
1375
+ type: "MemberExpression",
1376
+ indexer: ".",
1377
+ base: expr,
1378
+ identifier: this.identifierFromToken(idTok, "global", true),
1379
+ optional: true,
1380
+ ...this.finishRange(start)
1381
+ };
1382
+ } else {
1383
+ this.advance();
1384
+ const index = this.parseExpression();
1385
+ this.expectPunct("]");
1386
+ expr = { type: "IndexExpression", base: expr, index, optional: true, ...this.finishRange(start) };
1387
+ }
1388
+ } else if (this.matchPunct(":")) {
1389
+ const idTok = this.expectIdentifierName();
1390
+ const member = {
1391
+ type: "MemberExpression",
1392
+ indexer: ":",
1393
+ base: expr,
1394
+ identifier: this.identifierFromToken(idTok, "global", true),
1395
+ optional: false,
1396
+ ...this.finishRange(start)
1397
+ };
1398
+ expr = this.applyCallSuffix(member, start, true);
1399
+ } else if (this.checkPunct("(") || this.check(2 /* StringLiteral */) || this.checkPunct("{")) {
1400
+ expr = this.applyCallSuffix(expr, start, false);
1401
+ } else {
1402
+ break;
1403
+ }
1404
+ }
1405
+ return expr;
1406
+ }
1407
+ applyCallSuffix(base2, start, mandatory) {
1408
+ if (this.matchPunct("(")) {
1409
+ let args = [];
1410
+ if (!this.checkPunct(")")) args = this.parseExpressionList();
1411
+ this.expectPunct(")");
1412
+ return { type: "CallExpression", base: base2, arguments: args, ...this.finishRange(start) };
1413
+ }
1414
+ if (this.check(2 /* StringLiteral */)) {
1415
+ const tok = this.advance();
1416
+ const argument = { type: "StringLiteral", value: tok.value, raw: tok.raw, ...this.finishRange(tok, tok) };
1417
+ return { type: "StringCallExpression", base: base2, argument, ...this.finishRange(start) };
1418
+ }
1419
+ if (this.checkPunct("{")) {
1420
+ const table = this.parseTableConstructor();
1421
+ return { type: "TableCallExpression", base: base2, arguments: [table], ...this.finishRange(start) };
1422
+ }
1423
+ if (mandatory) {
1424
+ this.error('a method call requires arguments (e.g. obj:method(...), obj:method"str", obj:method{...})');
1425
+ }
1426
+ this.error("internal parser error: applyCallSuffix was entered in an invalid state");
1427
+ }
1428
+ parsePrimaryAtom() {
1429
+ const start = this.current();
1430
+ if (this.check(16 /* NumericLiteral */)) {
1431
+ const tok = this.advance();
1432
+ return { type: "NumericLiteral", value: tok.value, raw: tok.raw, ...this.finishRange(tok, tok) };
1433
+ }
1434
+ if (this.check(2 /* StringLiteral */)) {
1435
+ const tok = this.advance();
1436
+ return { type: "StringLiteral", value: tok.value, raw: tok.raw, ...this.finishRange(tok, tok) };
1437
+ }
1438
+ if (this.check(64 /* BooleanLiteral */)) {
1439
+ const tok = this.advance();
1440
+ return { type: "BooleanLiteral", value: tok.value, ...this.finishRange(tok, tok) };
1441
+ }
1442
+ if (this.check(128 /* NilLiteral */)) {
1443
+ const tok = this.advance();
1444
+ return { type: "NilLiteral", ...this.finishRange(tok, tok) };
1445
+ }
1446
+ if (this.check(256 /* VarargLiteral */)) {
1447
+ const tok = this.advance();
1448
+ return { type: "VarargLiteral", value: "...", ...this.finishRange(tok, tok) };
1449
+ }
1450
+ if (this.check(512 /* InterpolatedStringLiteral */)) {
1451
+ this.requireDialectFeature(this.dialect.stringInterpolation, "string interpolation (`...{expr}...`)");
1452
+ return this.parseInterpolatedString();
1453
+ }
1454
+ if (this.checkKeyword("function")) {
1455
+ this.advance();
1456
+ const body = this.parseFunctionBody(false);
1457
+ return { type: "FunctionDeclaration", identifier: null, isLocal: false, isMethod: false, ...body, ...this.finishRange(start) };
1458
+ }
1459
+ if (this.checkPunct("{")) {
1460
+ return this.parseTableConstructor();
1461
+ }
1462
+ if (this.matchPunct("(")) {
1463
+ const expression = this.parseExpression();
1464
+ this.expectPunct(")");
1465
+ return { type: "ParenthesizedExpression", expression, ...this.finishRange(start) };
1466
+ }
1467
+ if (this.checkKeyword("if")) {
1468
+ this.requireDialectFeature(this.dialect.ifExpression, "if-then-else expression");
1469
+ return this.parseIfExpression();
1470
+ }
1471
+ if (this.check(8 /* Identifier */)) {
1472
+ const tok = this.advance();
1473
+ return this.identifierFromToken(tok, "global", false);
1474
+ }
1475
+ this.error(`expected an expression but found ${this.currentDescription()}`);
1476
+ }
1477
+ parseIfExpression() {
1478
+ const start = this.current();
1479
+ this.advance();
1480
+ const clauses = [];
1481
+ const cond = this.parseExpression();
1482
+ this.expectKeyword("then");
1483
+ const body = this.parseExpression();
1484
+ clauses.push({ condition: cond, body });
1485
+ while (this.matchKeyword("elseif")) {
1486
+ const c = this.parseExpression();
1487
+ this.expectKeyword("then");
1488
+ const b = this.parseExpression();
1489
+ clauses.push({ condition: c, body: b });
1490
+ }
1491
+ this.expectKeyword("else");
1492
+ const elseBody = this.parseExpression();
1493
+ clauses.push({ condition: null, body: elseBody });
1494
+ return { type: "IfExpression", clauses, ...this.finishRange(start) };
1495
+ }
1496
+ parseTableConstructor() {
1497
+ const start = this.current();
1498
+ this.expectPunct("{");
1499
+ const fields = [];
1500
+ while (!this.checkPunct("}")) {
1501
+ const fieldStart = this.current();
1502
+ if (this.matchPunct("[")) {
1503
+ const key = this.parseExpression();
1504
+ this.expectPunct("]");
1505
+ this.expectPunct("=");
1506
+ const value = this.parseExpression();
1507
+ fields.push({ type: "TableKey", key, value, ...this.finishRange(fieldStart) });
1508
+ } else if (this.check(8 /* Identifier */) && this.peek(1).type === 32 /* Punctuator */ && this.peek(1).value === "=") {
1509
+ const idTok = this.advance();
1510
+ this.advance();
1511
+ const value = this.parseExpression();
1512
+ fields.push({ type: "TableKeyString", key: this.identifierFromToken(idTok, "global", true), value, ...this.finishRange(fieldStart) });
1513
+ } else {
1514
+ const value = this.parseExpression();
1515
+ fields.push({ type: "TableValue", value, ...this.finishRange(fieldStart) });
1516
+ }
1517
+ if (!this.matchPunct(",") && !this.matchPunct(";")) break;
1518
+ }
1519
+ this.expectPunct("}");
1520
+ return { type: "TableConstructorExpression", fields, ...this.finishRange(start) };
1521
+ }
1522
+ /** `text {expr} text` -> InterpolatedStringExpression(strings, expressions) */
1523
+ parseInterpolatedString() {
1524
+ const start = this.current();
1525
+ const tok = this.advance();
1526
+ const parts = tok.parts ?? [];
1527
+ const strings = [];
1528
+ const expressions = [];
1529
+ for (const part of parts) {
1530
+ if (part.kind === "string") {
1531
+ strings.push(part.value);
1532
+ } else {
1533
+ const subLexer = new Lexer(part.source, this.dialect);
1534
+ const subParser = new _Parser(subLexer.tokenize(), this.dialect);
1535
+ expressions.push(subParser.parseExpression());
1536
+ }
1537
+ }
1538
+ if (strings.length === expressions.length) strings.push("");
1539
+ return { type: "InterpolatedStringExpression", strings, expressions, ...this.finishRange(start) };
1540
+ }
1541
+ };
1542
+
1543
+ // src/dialect.ts
1544
+ var LUA51_FLAGS = {
1545
+ name: "lua5.1",
1546
+ continueKeyword: false,
1547
+ gotoStatements: false,
1548
+ compoundAssignment: false,
1549
+ stringInterpolation: false,
1550
+ attributes: false,
1551
+ typeAnnotations: false,
1552
+ floorDivision: false,
1553
+ genericFunctions: false,
1554
+ ifExpression: false,
1555
+ optionalChaining: false
1556
+ };
1557
+ var LUAU_FLAGS = {
1558
+ name: "luaU",
1559
+ continueKeyword: true,
1560
+ gotoStatements: false,
1561
+ compoundAssignment: true,
1562
+ stringInterpolation: true,
1563
+ attributes: true,
1564
+ typeAnnotations: true,
1565
+ floorDivision: true,
1566
+ genericFunctions: true,
1567
+ ifExpression: true,
1568
+ optionalChaining: true
1569
+ };
1570
+ var UnknownDialectError = class extends Error {
1571
+ constructor(given) {
1572
+ super(`unknown dialect '${given}' (expected 'lua5.1' or 'luaU')`);
1573
+ }
1574
+ };
1575
+ function resolveDialect(name) {
1576
+ switch (name) {
1577
+ case "lua5.1":
1578
+ return LUA51_FLAGS;
1579
+ case "luaU":
1580
+ return LUAU_FLAGS;
1581
+ default:
1582
+ throw new UnknownDialectError(name);
1583
+ }
1584
+ }
1585
+
1586
+ // src/analysis/scope.ts
1587
+ var Resolver = class {
1588
+ nextBindingId = 0;
1589
+ functionDepth = 0;
1590
+ top = { parent: null, functionDepth: 0, bindings: /* @__PURE__ */ new Map() };
1591
+ pushBlock() {
1592
+ this.top = { parent: this.top, functionDepth: this.functionDepth, bindings: /* @__PURE__ */ new Map() };
1593
+ }
1594
+ pushFunction() {
1595
+ this.functionDepth++;
1596
+ this.top = { parent: this.top, functionDepth: this.functionDepth, bindings: /* @__PURE__ */ new Map() };
1597
+ }
1598
+ pop() {
1599
+ this.top = this.top.parent;
1600
+ }
1601
+ popFunction() {
1602
+ this.top = this.top.parent;
1603
+ this.functionDepth--;
1604
+ }
1605
+ declare(id, kind) {
1606
+ const binding = { id: this.nextBindingId++, name: id.name, kind, declaration: id };
1607
+ this.top.bindings.set(id.name, binding);
1608
+ id.bindingId = binding.id;
1609
+ id.scope = kind === "parameter" ? "parameter" : "local";
1610
+ }
1611
+ resolveReference(id) {
1612
+ if (id.isField) return;
1613
+ let frame = this.top;
1614
+ while (frame) {
1615
+ const binding = frame.bindings.get(id.name);
1616
+ if (binding) {
1617
+ id.bindingId = binding.id;
1618
+ id.scope = frame.functionDepth === this.functionDepth ? binding.kind === "parameter" ? "parameter" : "local" : "upvalue";
1619
+ return;
1620
+ }
1621
+ frame = frame.parent;
1622
+ }
1623
+ id.bindingId = null;
1624
+ id.scope = "global";
1625
+ }
1626
+ run(chunk) {
1627
+ this.block(chunk.body);
1628
+ }
1629
+ block(stmts) {
1630
+ this.pushBlock();
1631
+ for (const stmt of stmts) this.statement(stmt);
1632
+ this.pop();
1633
+ }
1634
+ statement(stmt) {
1635
+ switch (stmt.type) {
1636
+ case "LocalStatement":
1637
+ stmt.init.forEach((e) => this.expr(e));
1638
+ stmt.variables.forEach((v) => {
1639
+ this.visitType(v.typeAnnotation);
1640
+ this.declare(v, "local");
32
1641
  });
1642
+ break;
1643
+ case "CallStatement":
1644
+ this.expr(stmt.expression);
1645
+ break;
1646
+ case "WhileStatement":
1647
+ this.expr(stmt.condition);
1648
+ this.block(stmt.body);
1649
+ break;
1650
+ case "RepeatStatement":
1651
+ this.pushBlock();
1652
+ for (const s of stmt.body) this.statement(s);
1653
+ this.expr(stmt.condition);
1654
+ this.pop();
1655
+ break;
1656
+ case "AssignmentStatement":
1657
+ stmt.init.forEach((e) => this.expr(e));
1658
+ stmt.variables.forEach((v) => this.expr(v));
1659
+ break;
1660
+ case "CompoundAssignmentStatement":
1661
+ this.expr(stmt.value);
1662
+ this.expr(stmt.variable);
1663
+ break;
1664
+ case "FunctionDeclaration":
1665
+ this.functionDecl(stmt);
1666
+ break;
1667
+ case "ForNumericStatement":
1668
+ this.expr(stmt.start);
1669
+ this.expr(stmt.end);
1670
+ if (stmt.step) this.expr(stmt.step);
1671
+ this.pushBlock();
1672
+ this.declare(stmt.variable, "for-loop");
1673
+ for (const s of stmt.body) this.statement(s);
1674
+ this.pop();
1675
+ break;
1676
+ case "ForGenericStatement":
1677
+ stmt.iterators.forEach((e) => this.expr(e));
1678
+ this.pushBlock();
1679
+ stmt.variables.forEach((v) => this.declare(v, "for-in"));
1680
+ for (const s of stmt.body) this.statement(s);
1681
+ this.pop();
1682
+ break;
1683
+ case "IfStatement":
1684
+ for (const c of stmt.clauses) {
1685
+ if (c.type !== "ElseClause") this.expr(c.condition);
1686
+ this.block(c.body);
1687
+ }
1688
+ break;
1689
+ case "DoStatement":
1690
+ this.block(stmt.body);
1691
+ break;
1692
+ case "ReturnStatement":
1693
+ stmt.arguments.forEach((e) => this.expr(e));
1694
+ break;
1695
+ default:
1696
+ break;
1697
+ }
1698
+ }
1699
+ functionDecl(fn) {
1700
+ if (fn.identifier) {
1701
+ if (fn.isLocal && fn.identifier.type === "Identifier") {
1702
+ this.declare(fn.identifier, "local-function");
1703
+ } else {
1704
+ this.expr(fn.identifier);
1705
+ }
1706
+ }
1707
+ this.pushFunction();
1708
+ for (const p of fn.parameters) {
1709
+ if (p.type === "Identifier") {
1710
+ this.visitType(p.typeAnnotation);
1711
+ this.declare(p, "parameter");
1712
+ }
1713
+ }
1714
+ fn.generics.forEach((g) => this.visitType(g.defaultType));
1715
+ this.visitType(fn.varargTypeAnnotation);
1716
+ this.visitTypeList(fn.returnTypeAnnotation);
1717
+ for (const s of fn.body) this.statement(s);
1718
+ this.popFunction();
1719
+ }
1720
+ expr(expr) {
1721
+ switch (expr.type) {
1722
+ case "Identifier":
1723
+ this.resolveReference(expr);
1724
+ break;
1725
+ case "FunctionDeclaration":
1726
+ this.functionDecl(expr);
1727
+ break;
1728
+ case "TableConstructorExpression":
1729
+ for (const f of expr.fields) {
1730
+ if (f.type === "TableKey") {
1731
+ this.expr(f.key);
1732
+ this.expr(f.value);
1733
+ } else {
1734
+ this.expr(f.value);
1735
+ }
1736
+ }
1737
+ break;
1738
+ case "BinaryExpression":
1739
+ case "LogicalExpression":
1740
+ this.expr(expr.left);
1741
+ this.expr(expr.right);
1742
+ break;
1743
+ case "UnaryExpression":
1744
+ this.expr(expr.argument);
1745
+ break;
1746
+ case "MemberExpression":
1747
+ this.expr(expr.base);
1748
+ break;
1749
+ case "IndexExpression":
1750
+ this.expr(expr.base);
1751
+ this.expr(expr.index);
1752
+ break;
1753
+ case "CallExpression":
1754
+ this.expr(expr.base);
1755
+ expr.arguments.forEach((a) => this.expr(a));
1756
+ break;
1757
+ case "TableCallExpression":
1758
+ this.expr(expr.base);
1759
+ this.expr(expr.arguments[0]);
1760
+ break;
1761
+ case "StringCallExpression":
1762
+ this.expr(expr.base);
1763
+ break;
1764
+ case "ParenthesizedExpression":
1765
+ this.expr(expr.expression);
1766
+ break;
1767
+ case "IfExpression":
1768
+ for (const c of expr.clauses) {
1769
+ if (c.condition) this.expr(c.condition);
1770
+ this.expr(c.body);
1771
+ }
1772
+ break;
1773
+ case "InterpolatedStringExpression":
1774
+ expr.expressions.forEach((e) => this.expr(e));
1775
+ break;
1776
+ default:
1777
+ break;
1778
+ }
1779
+ }
1780
+ // ---- Luau type annotations ----
1781
+ // Types live in their own namespace (a TypeReference's `name` is a plain
1782
+ // string, not an Identifier) so most of a type tree has nothing to
1783
+ // resolve. The one exception is `typeof(expr)`, which embeds a real
1784
+ // value-level Expression that must see the current scope.
1785
+ visitTypeList(list) {
1786
+ if (!list) return;
1787
+ list.types.forEach((t) => this.visitType(t));
1788
+ if (list.vararg) this.visitType(list.vararg);
1789
+ }
1790
+ visitType(type) {
1791
+ if (!type) return;
1792
+ switch (type.type) {
1793
+ case "TypeTypeof":
1794
+ this.expr(type.expression);
1795
+ break;
1796
+ case "TypeUnion":
1797
+ case "TypeIntersection":
1798
+ type.types.forEach((t) => this.visitType(t));
1799
+ break;
1800
+ case "TypeOptional":
1801
+ this.visitType(type.base);
1802
+ break;
1803
+ case "TypeParenthesized":
1804
+ this.visitType(type.type_);
1805
+ break;
1806
+ case "TypeVariadic":
1807
+ this.visitType(type.base);
1808
+ break;
1809
+ case "TypeFunction":
1810
+ type.parameters.forEach((p) => this.visitType(p.type));
1811
+ this.visitTypeList(type.returns);
1812
+ break;
1813
+ case "TypeTable":
1814
+ for (const f of type.fields) {
1815
+ if (f.key && typeof f.key !== "string") this.visitType(f.key);
1816
+ this.visitType(f.value);
1817
+ }
1818
+ break;
1819
+ case "TypeReference":
1820
+ type.typeArguments.forEach((t) => this.visitType(t));
1821
+ break;
1822
+ default:
1823
+ break;
1824
+ }
1825
+ }
1826
+ };
1827
+ function resolveScopes(chunk) {
1828
+ new Resolver().run(chunk);
1829
+ return chunk;
1830
+ }
1831
+
1832
+ // src/codegen/generator.ts
1833
+ var BIN_PRECEDENCE = {
1834
+ or: 1,
1835
+ and: 2,
1836
+ "<": 3,
1837
+ ">": 3,
1838
+ "<=": 3,
1839
+ ">=": 3,
1840
+ "~=": 3,
1841
+ "==": 3,
1842
+ "|": 4,
1843
+ "~": 5,
1844
+ "&": 6,
1845
+ "<<": 7,
1846
+ ">>": 7,
1847
+ "..": 9,
1848
+ "+": 10,
1849
+ "-": 10,
1850
+ "*": 11,
1851
+ "/": 11,
1852
+ "//": 11,
1853
+ "%": 11,
1854
+ "^": 14
1855
+ };
1856
+ var UNARY_PRECEDENCE2 = 12;
1857
+ var RIGHT_ASSOC = /* @__PURE__ */ new Set(["..", "^"]);
1858
+ var Generator = class {
1859
+ indentLevel = 0;
1860
+ minify;
1861
+ constructor(options = {}) {
1862
+ this.minify = options.minify ?? false;
1863
+ }
1864
+ indent() {
1865
+ return this.minify ? "" : " ".repeat(this.indentLevel);
1866
+ }
1867
+ generate(chunk) {
1868
+ return this.printBlock(chunk.body);
1869
+ }
1870
+ printBlock(stmts) {
1871
+ if (this.minify) {
1872
+ return stmts.map((s) => this.printStatement(s)).join("; ");
1873
+ }
1874
+ return stmts.map((s) => this.indent() + this.printStatement(s)).join("\n");
1875
+ }
1876
+ withIndent(fn) {
1877
+ this.indentLevel++;
1878
+ const out = fn();
1879
+ this.indentLevel--;
1880
+ return out;
1881
+ }
1882
+ /**
1883
+ * Renders `head <body> tail` (e.g. `while x do`, block, `end`) in either
1884
+ * pretty-printed (indented, multi-line) or minified (single-line,
1885
+ * `;`-separated body) form depending on `this.minify`.
1886
+ */
1887
+ wrapBlock(head, body, tail) {
1888
+ const inner = this.withIndent(() => this.printBlock(body));
1889
+ if (this.minify) {
1890
+ return inner.length ? `${head} ${inner} ${tail}` : `${head} ${tail}`;
1891
+ }
1892
+ return `${head}
1893
+ ${inner}
1894
+ ${this.indent()}${tail}`;
1895
+ }
1896
+ // -------------------------------------------------------------------
1897
+ // Statements
1898
+ // -------------------------------------------------------------------
1899
+ printStatement(stmt) {
1900
+ switch (stmt.type) {
1901
+ case "LocalStatement": {
1902
+ const vars = stmt.variables.map((v) => this.printIdentifierDecl(v)).join(", ");
1903
+ if (stmt.init.length === 0) return `local ${vars}`;
1904
+ return `local ${vars} = ${stmt.init.map((e) => this.printExpr(e)).join(", ")}`;
1905
+ }
1906
+ case "CallStatement":
1907
+ return this.printExpr(stmt.expression);
1908
+ case "WhileStatement":
1909
+ return this.wrapBlock(`while ${this.printExpr(stmt.condition)} do`, stmt.body, "end");
1910
+ case "RepeatStatement":
1911
+ return this.wrapBlock("repeat", stmt.body, `until ${this.printExpr(stmt.condition)}`);
1912
+ case "AssignmentStatement":
1913
+ return `${stmt.variables.map((v) => this.printExpr(v)).join(", ")} = ${stmt.init.map((e) => this.printExpr(e)).join(", ")}`;
1914
+ case "CompoundAssignmentStatement": {
1915
+ const op = stmt.operator.slice(0, -1);
1916
+ const target = this.printExpr(stmt.variable);
1917
+ return `${target} = ${target} ${op} ${this.printExpr(stmt.value)}`;
1918
+ }
1919
+ case "FunctionDeclaration":
1920
+ return this.printFunctionDeclaration(stmt);
1921
+ case "ForNumericStatement": {
1922
+ const step = stmt.step ? `, ${this.printExpr(stmt.step)}` : "";
1923
+ return this.wrapBlock(
1924
+ `for ${stmt.variable.name} = ${this.printExpr(stmt.start)}, ${this.printExpr(stmt.end)}${step} do`,
1925
+ stmt.body,
1926
+ "end"
1927
+ );
1928
+ }
1929
+ case "ForGenericStatement":
1930
+ return this.wrapBlock(
1931
+ `for ${stmt.variables.map((v) => v.name).join(", ")} in ${stmt.iterators.map((e) => this.printExpr(e)).join(", ")} do`,
1932
+ stmt.body,
1933
+ "end"
1934
+ );
1935
+ case "IfStatement":
1936
+ return this.printIfStatement(stmt);
1937
+ case "DoStatement":
1938
+ return this.wrapBlock("do", stmt.body, "end");
1939
+ case "ReturnStatement":
1940
+ return stmt.arguments.length ? `return ${stmt.arguments.map((e) => this.printExpr(e)).join(", ")}` : "return";
1941
+ case "BreakStatement":
1942
+ return "break";
1943
+ case "ContinueStatement":
1944
+ return "continue";
1945
+ case "GotoStatement":
1946
+ return `goto ${stmt.label}`;
1947
+ case "LabelStatement":
1948
+ return `::${stmt.name}::`;
1949
+ default:
1950
+ throw new Error(`Generator: unhandled statement type ${stmt.type}`);
1951
+ }
1952
+ }
1953
+ printIdentifierDecl(id) {
1954
+ const attr = id.attribute ? ` <${id.attribute}>` : "";
1955
+ return `${id.name}${attr}`;
1956
+ }
1957
+ printIfStatement(stmt) {
1958
+ let out = "";
1959
+ for (let i = 0; i < stmt.clauses.length; i++) {
1960
+ const clause = stmt.clauses[i];
1961
+ let head;
1962
+ if (clause.type === "IfClause") head = `if ${this.printExpr(clause.condition)} then`;
1963
+ else if (clause.type === "ElseifClause") head = `elseif ${this.printExpr(clause.condition)} then`;
1964
+ else head = "else";
1965
+ const inner = this.withIndent(() => this.printBlock(clause.body));
1966
+ if (this.minify) {
1967
+ out += inner.length ? `${head} ${inner} ` : `${head} `;
1968
+ } else {
1969
+ out += `${head}
1970
+ ${inner}
1971
+ ${this.indent()}`;
1972
+ }
1973
+ }
1974
+ return out + "end";
1975
+ }
1976
+ printFunctionDeclaration(fn) {
1977
+ const visibleParams = fn.isMethod ? fn.parameters.slice(1) : fn.parameters;
1978
+ const params = visibleParams.map((p) => p.type === "VarargLiteral" ? "..." : p.name).join(", ");
1979
+ const kw = fn.isLocal ? "local function" : "function";
1980
+ const nameStr = fn.identifier ? this.printExpr(fn.identifier) : "";
1981
+ const head = fn.identifier ? `${kw} ${nameStr}(${params})` : `function(${params})`;
1982
+ return this.wrapBlock(head, fn.body, "end");
1983
+ }
1984
+ // -------------------------------------------------------------------
1985
+ // Expressions
1986
+ // -------------------------------------------------------------------
1987
+ printExpr(expr, parentPrec = 0) {
1988
+ switch (expr.type) {
1989
+ case "Identifier":
1990
+ return expr.name;
1991
+ case "StringLiteral":
1992
+ return printStringLiteral(expr.value);
1993
+ case "NumericLiteral":
1994
+ return formatNumber(expr.value);
1995
+ case "BooleanLiteral":
1996
+ return expr.value ? "true" : "false";
1997
+ case "NilLiteral":
1998
+ return "nil";
1999
+ case "VarargLiteral":
2000
+ return "...";
2001
+ case "InterpolatedStringExpression":
2002
+ return this.printInterpolatedString(expr);
2003
+ case "FunctionDeclaration":
2004
+ return this.printFunctionDeclaration(expr);
2005
+ case "TableConstructorExpression":
2006
+ return this.printTableConstructor(expr);
2007
+ case "BinaryExpression":
2008
+ return this.printBinary(expr.operator, expr.left, expr.right, parentPrec);
2009
+ case "LogicalExpression":
2010
+ return this.printBinary(expr.operator, expr.left, expr.right, parentPrec);
2011
+ case "UnaryExpression": {
2012
+ const opStr = expr.operator === "not" ? "not " : expr.operator;
2013
+ const inner = `${opStr}${this.printExpr(expr.argument, UNARY_PRECEDENCE2)}`;
2014
+ return UNARY_PRECEDENCE2 < parentPrec ? `(${inner})` : inner;
2015
+ }
2016
+ case "MemberExpression":
2017
+ return `${this.printExpr(expr.base, 100)}${expr.optional ? "?" : ""}${expr.indexer}${expr.identifier.name}`;
2018
+ case "IndexExpression":
2019
+ return `${this.printExpr(expr.base, 100)}${expr.optional ? "?" : ""}[${this.printExpr(
2020
+ expr.index
2021
+ )}]`;
2022
+ case "CallExpression":
2023
+ return `${this.printExpr(expr.base, 100)}(${expr.arguments.map((a) => this.printExpr(a)).join(", ")})`;
2024
+ case "TableCallExpression":
2025
+ return `${this.printExpr(expr.base, 100)}${this.printTableConstructor(expr.arguments[0])}`;
2026
+ case "StringCallExpression":
2027
+ return `${this.printExpr(expr.base, 100)}${printStringLiteral(expr.argument.value)}`;
2028
+ case "ParenthesizedExpression":
2029
+ return `(${this.printExpr(expr.expression)})`;
2030
+ case "IfExpression":
2031
+ return this.printIfExpression(expr);
2032
+ default:
2033
+ throw new Error(`Generator: unhandled expression type ${expr.type}`);
2034
+ }
2035
+ }
2036
+ printBinary(op, left, right, parentPrec) {
2037
+ const prec = BIN_PRECEDENCE[op] ?? 5;
2038
+ const rightAssoc = RIGHT_ASSOC.has(op);
2039
+ const leftStr = this.printExpr(left, rightAssoc ? prec + 1 : prec);
2040
+ const rightStr = this.printExpr(right, rightAssoc ? prec : prec + 1);
2041
+ const inner = `${leftStr} ${op} ${rightStr}`;
2042
+ return prec < parentPrec ? `(${inner})` : inner;
2043
+ }
2044
+ printTableConstructor(t) {
2045
+ const fields = t.fields.map((f) => {
2046
+ if (f.type === "TableKey") return `[${this.printExpr(f.key)}] = ${this.printExpr(f.value)}`;
2047
+ if (f.type === "TableKeyString") return `${f.key.name} = ${this.printExpr(f.value)}`;
2048
+ return this.printExpr(f.value);
2049
+ });
2050
+ return `{${fields.join(", ")}}`;
2051
+ }
2052
+ printInterpolatedString(e) {
2053
+ const parts = [];
2054
+ for (let i = 0; i < e.strings.length; i++) {
2055
+ if (e.strings[i] !== "") parts.push(printStringLiteral(e.strings[i]));
2056
+ if (i < e.expressions.length) {
2057
+ parts.push(`tostring(${this.printExpr(e.expressions[i])})`);
2058
+ }
2059
+ }
2060
+ if (parts.length === 0) return `""`;
2061
+ return parts.join(" .. ");
2062
+ }
2063
+ printIfExpression(e) {
2064
+ let out = "";
2065
+ for (let i = 0; i < e.clauses.length; i++) {
2066
+ const c = e.clauses[i];
2067
+ if (c.condition === null) {
2068
+ out += `else ${this.printExpr(c.body)}`;
2069
+ } else if (i === 0) {
2070
+ out += `if ${this.printExpr(c.condition)} then ${this.printExpr(c.body)} `;
2071
+ } else {
2072
+ out += `elseif ${this.printExpr(c.condition)} then ${this.printExpr(c.body)} `;
2073
+ }
33
2074
  }
34
- return mountPromise;
2075
+ return out.trim();
2076
+ }
2077
+ };
2078
+ function formatNumber(n) {
2079
+ if (Number.isInteger(n)) return n.toString();
2080
+ return n.toString();
35
2081
  }
36
- export async function obfuscate(text, config = 'Strong', luaVersion) {
37
- await ensureMounted();
38
- const lua = await getFactory().createEngine();
2082
+ function printStringLiteral(value) {
2083
+ let out = '"';
2084
+ for (const ch of value) {
2085
+ const code = ch.codePointAt(0);
2086
+ if (ch === '"') out += '\\"';
2087
+ else if (ch === "\\") out += "\\\\";
2088
+ else if (ch === "\n") out += "\\n";
2089
+ else if (ch === "\r") out += "\\r";
2090
+ else if (ch === " ") out += "\\t";
2091
+ else if (code < 32 || code >= 127) out += `\\${String(code).padStart(3, "0")}`;
2092
+ else out += ch;
2093
+ }
2094
+ return out + '"';
2095
+ }
2096
+ function generate(chunk, options = {}) {
2097
+ return new Generator(options).generate(chunk);
2098
+ }
2099
+
2100
+ // src/utils/random.ts
2101
+ function webcrypto() {
2102
+ return typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
2103
+ }
2104
+ function randomUint32() {
2105
+ const c = webcrypto();
2106
+ if (c && typeof c.getRandomValues === "function") {
2107
+ return c.getRandomValues(new Uint32Array(1))[0];
2108
+ }
2109
+ return Math.floor(Math.random() * 4294967296);
2110
+ }
2111
+ function randomHex32() {
2112
+ const c = webcrypto();
2113
+ if (c && typeof c.randomUUID === "function") {
39
2114
  try {
40
- await lua.doString(`arg = {}`);
41
- lua.global.set('__input_code', text);
42
- let configExpr;
43
- if (typeof config === 'string') {
44
- lua.global.set('__preset_name', config);
45
- configExpr = 'Prometheus.Presets[__preset_name]';
2115
+ return c.randomUUID().replace(/-/g, "");
2116
+ } catch {
2117
+ }
2118
+ }
2119
+ if (c && typeof c.getRandomValues === "function") {
2120
+ const bytes = c.getRandomValues(new Uint8Array(16));
2121
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2122
+ }
2123
+ let s = "";
2124
+ for (let i = 0; i < 32; i++) s += Math.floor(Math.random() * 16).toString(16);
2125
+ return s;
2126
+ }
2127
+ function randInt(min, max) {
2128
+ const range = max - min + 1;
2129
+ return min + randomUint32() % range;
2130
+ }
2131
+ function choice(arr) {
2132
+ return arr[randInt(0, arr.length - 1)];
2133
+ }
2134
+ function shuffle(arr) {
2135
+ const out = arr.slice();
2136
+ for (let i = out.length - 1; i > 0; i--) {
2137
+ const j = randInt(0, i);
2138
+ [out[i], out[j]] = [out[j], out[i]];
2139
+ }
2140
+ return out;
2141
+ }
2142
+ var NAME_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
2143
+ var VALID_LUA_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
2144
+ function randomVarName(existing) {
2145
+ let name;
2146
+ do {
2147
+ const len = randInt(5, 10);
2148
+ name = `${randomHex32().slice(0, len)}`;
2149
+ } while (!VALID_LUA_IDENTIFIER.test(name) || existing.has(name) || RESERVED.has(name));
2150
+ existing.add(name);
2151
+ return name;
2152
+ }
2153
+ function randomKey(len = 5) {
2154
+ let s = "";
2155
+ for (let i = 0; i < len; i++) s += NAME_CHARS[randInt(0, NAME_CHARS.length - 1)];
2156
+ return s;
2157
+ }
2158
+ var RESERVED = /* @__PURE__ */ new Set([
2159
+ "and",
2160
+ "break",
2161
+ "do",
2162
+ "else",
2163
+ "elseif",
2164
+ "end",
2165
+ "false",
2166
+ "for",
2167
+ "function",
2168
+ "goto",
2169
+ "if",
2170
+ "in",
2171
+ "local",
2172
+ "nil",
2173
+ "not",
2174
+ "or",
2175
+ "repeat",
2176
+ "return",
2177
+ "then",
2178
+ "true",
2179
+ "until",
2180
+ "while",
2181
+ "continue",
2182
+ "self"
2183
+ ]);
2184
+
2185
+ // src/passes/rename-variables.ts
2186
+ function lookup(scopes, name) {
2187
+ for (let i = scopes.length - 1; i >= 0; i--) {
2188
+ const hit = scopes[i].get(name);
2189
+ if (hit) return hit;
2190
+ }
2191
+ return null;
2192
+ }
2193
+ function declare(scopes, allNames, id, skip = false) {
2194
+ if (skip || id.name === "self") return;
2195
+ const newName = randomVarName(allNames);
2196
+ scopes[scopes.length - 1].set(id.name, newName);
2197
+ id.name = newName;
2198
+ }
2199
+ var Renamer = class {
2200
+ allNames = /* @__PURE__ */ new Set();
2201
+ run(chunk) {
2202
+ this.block([/* @__PURE__ */ new Map()], chunk.body);
2203
+ }
2204
+ block(scopes, stmts) {
2205
+ scopes.push(/* @__PURE__ */ new Map());
2206
+ for (const stmt of stmts) this.statement(scopes, stmt);
2207
+ scopes.pop();
2208
+ }
2209
+ statement(scopes, stmt) {
2210
+ switch (stmt.type) {
2211
+ case "LocalStatement":
2212
+ stmt.init.forEach((e) => this.expr(scopes, e));
2213
+ stmt.variables.forEach((v) => declare(scopes, this.allNames, v));
2214
+ break;
2215
+ case "CallStatement":
2216
+ this.expr(scopes, stmt.expression);
2217
+ break;
2218
+ case "WhileStatement":
2219
+ this.expr(scopes, stmt.condition);
2220
+ this.block(scopes, stmt.body);
2221
+ break;
2222
+ case "RepeatStatement": {
2223
+ scopes.push(/* @__PURE__ */ new Map());
2224
+ for (const s of stmt.body) this.statement(scopes, s);
2225
+ this.expr(scopes, stmt.condition);
2226
+ scopes.pop();
2227
+ break;
2228
+ }
2229
+ case "AssignmentStatement":
2230
+ stmt.init.forEach((e) => this.expr(scopes, e));
2231
+ stmt.variables.forEach((v) => this.expr(scopes, v));
2232
+ break;
2233
+ case "CompoundAssignmentStatement":
2234
+ this.expr(scopes, stmt.value);
2235
+ this.expr(scopes, stmt.variable);
2236
+ break;
2237
+ case "FunctionDeclaration":
2238
+ this.functionDecl(scopes, stmt);
2239
+ break;
2240
+ case "ForNumericStatement":
2241
+ this.expr(scopes, stmt.start);
2242
+ this.expr(scopes, stmt.end);
2243
+ if (stmt.step) this.expr(scopes, stmt.step);
2244
+ scopes.push(/* @__PURE__ */ new Map());
2245
+ declare(scopes, this.allNames, stmt.variable);
2246
+ for (const s of stmt.body) this.statement(scopes, s);
2247
+ scopes.pop();
2248
+ break;
2249
+ case "ForGenericStatement":
2250
+ stmt.iterators.forEach((e) => this.expr(scopes, e));
2251
+ scopes.push(/* @__PURE__ */ new Map());
2252
+ stmt.variables.forEach((v) => declare(scopes, this.allNames, v));
2253
+ for (const s of stmt.body) this.statement(scopes, s);
2254
+ scopes.pop();
2255
+ break;
2256
+ case "IfStatement":
2257
+ for (const c of stmt.clauses) {
2258
+ if (c.type !== "ElseClause") this.expr(scopes, c.condition);
2259
+ this.block(scopes, c.body);
2260
+ }
2261
+ break;
2262
+ case "DoStatement":
2263
+ this.block(scopes, stmt.body);
2264
+ break;
2265
+ case "ReturnStatement":
2266
+ stmt.arguments.forEach((e) => this.expr(scopes, e));
2267
+ break;
2268
+ default:
2269
+ break;
2270
+ }
2271
+ }
2272
+ functionDecl(scopes, fn) {
2273
+ if (fn.identifier) {
2274
+ if (fn.isLocal && fn.identifier.type === "Identifier") {
2275
+ declare(scopes, this.allNames, fn.identifier);
2276
+ } else {
2277
+ this.expr(scopes, fn.identifier);
2278
+ }
2279
+ }
2280
+ scopes.push(/* @__PURE__ */ new Map());
2281
+ for (const p of fn.parameters) {
2282
+ if (p.type === "Identifier") declare(scopes, this.allNames, p, p.name === "self");
2283
+ }
2284
+ for (const s of fn.body) this.statement(scopes, s);
2285
+ scopes.pop();
2286
+ }
2287
+ expr(scopes, expr) {
2288
+ switch (expr.type) {
2289
+ case "Identifier": {
2290
+ const resolved = lookup(scopes, expr.name);
2291
+ if (resolved) expr.name = resolved;
2292
+ break;
2293
+ }
2294
+ case "FunctionDeclaration":
2295
+ this.functionDecl(scopes, expr);
2296
+ break;
2297
+ case "TableConstructorExpression":
2298
+ for (const f of expr.fields) {
2299
+ if (f.type === "TableKey") {
2300
+ this.expr(scopes, f.key);
2301
+ this.expr(scopes, f.value);
2302
+ } else {
2303
+ this.expr(scopes, f.value);
2304
+ }
46
2305
  }
47
- else {
48
- lua.global.set('__custom_config', config);
49
- configExpr = '__custom_config';
2306
+ break;
2307
+ case "BinaryExpression":
2308
+ case "LogicalExpression":
2309
+ this.expr(scopes, expr.left);
2310
+ this.expr(scopes, expr.right);
2311
+ break;
2312
+ case "UnaryExpression":
2313
+ this.expr(scopes, expr.argument);
2314
+ break;
2315
+ case "MemberExpression":
2316
+ this.expr(scopes, expr.base);
2317
+ break;
2318
+ case "IndexExpression":
2319
+ this.expr(scopes, expr.base);
2320
+ this.expr(scopes, expr.index);
2321
+ break;
2322
+ case "CallExpression":
2323
+ this.expr(scopes, expr.base);
2324
+ expr.arguments.forEach((a) => this.expr(scopes, a));
2325
+ break;
2326
+ case "TableCallExpression":
2327
+ this.expr(scopes, expr.base);
2328
+ this.expr(scopes, expr.arguments[0]);
2329
+ break;
2330
+ case "StringCallExpression":
2331
+ this.expr(scopes, expr.base);
2332
+ break;
2333
+ case "ParenthesizedExpression":
2334
+ this.expr(scopes, expr.expression);
2335
+ break;
2336
+ case "IfExpression":
2337
+ for (const c of expr.clauses) {
2338
+ if (c.condition) this.expr(scopes, c.condition);
2339
+ this.expr(scopes, c.body);
50
2340
  }
51
- if (luaVersion !== undefined) {
52
- await lua.global.set('__lua_version', luaVersion);
2341
+ break;
2342
+ case "InterpolatedStringExpression":
2343
+ expr.expressions.forEach((e) => this.expr(scopes, e));
2344
+ break;
2345
+ default:
2346
+ break;
2347
+ }
2348
+ }
2349
+ };
2350
+ var renameVariables = (chunk) => {
2351
+ new Renamer().run(chunk);
2352
+ return chunk;
2353
+ };
2354
+
2355
+ // src/utils/walk.ts
2356
+ function tExpr(expr, fn) {
2357
+ switch (expr.type) {
2358
+ case "FunctionDeclaration":
2359
+ expr.body = tStmts(expr.body, fn);
2360
+ break;
2361
+ case "TableConstructorExpression":
2362
+ for (const f of expr.fields) {
2363
+ if (f.type === "TableKey") {
2364
+ f.key = tExpr(f.key, fn);
2365
+ f.value = tExpr(f.value, fn);
2366
+ } else if (f.type === "TableKeyString") {
2367
+ f.value = tExpr(f.value, fn);
2368
+ } else {
2369
+ f.value = tExpr(f.value, fn);
53
2370
  }
54
- const obfuscated = await lua.doString(`
55
- local Prometheus = require("src.prometheus")
56
- Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error
2371
+ }
2372
+ break;
2373
+ case "BinaryExpression":
2374
+ case "LogicalExpression":
2375
+ expr.left = tExpr(expr.left, fn);
2376
+ expr.right = tExpr(expr.right, fn);
2377
+ break;
2378
+ case "UnaryExpression":
2379
+ expr.argument = tExpr(expr.argument, fn);
2380
+ break;
2381
+ case "MemberExpression":
2382
+ expr.base = tExpr(expr.base, fn);
2383
+ break;
2384
+ case "IndexExpression":
2385
+ expr.base = tExpr(expr.base, fn);
2386
+ expr.index = tExpr(expr.index, fn);
2387
+ break;
2388
+ case "CallExpression":
2389
+ expr.base = tExpr(expr.base, fn);
2390
+ expr.arguments = expr.arguments.map((a) => tExpr(a, fn));
2391
+ break;
2392
+ case "TableCallExpression":
2393
+ expr.base = tExpr(expr.base, fn);
2394
+ expr.arguments[0] = tExpr(expr.arguments[0], fn);
2395
+ break;
2396
+ case "StringCallExpression":
2397
+ expr.base = tExpr(expr.base, fn);
2398
+ break;
2399
+ case "ParenthesizedExpression":
2400
+ expr.expression = tExpr(expr.expression, fn);
2401
+ break;
2402
+ case "IfExpression":
2403
+ for (const c of expr.clauses) {
2404
+ if (c.condition) c.condition = tExpr(c.condition, fn);
2405
+ c.body = tExpr(c.body, fn);
2406
+ }
2407
+ break;
2408
+ case "InterpolatedStringExpression":
2409
+ expr.expressions = expr.expressions.map((e) => tExpr(e, fn));
2410
+ break;
2411
+ default:
2412
+ break;
2413
+ }
2414
+ const replaced = fn(expr);
2415
+ return replaced ?? expr;
2416
+ }
2417
+ function tStmt(stmt, fn) {
2418
+ switch (stmt.type) {
2419
+ case "LocalStatement":
2420
+ stmt.init = stmt.init.map((e) => tExpr(e, fn));
2421
+ break;
2422
+ case "CallStatement":
2423
+ stmt.expression = tExpr(stmt.expression, fn);
2424
+ break;
2425
+ case "WhileStatement":
2426
+ stmt.condition = tExpr(stmt.condition, fn);
2427
+ stmt.body = tStmts(stmt.body, fn);
2428
+ break;
2429
+ case "RepeatStatement":
2430
+ stmt.body = tStmts(stmt.body, fn);
2431
+ stmt.condition = tExpr(stmt.condition, fn);
2432
+ break;
2433
+ case "AssignmentStatement":
2434
+ stmt.variables = stmt.variables.map((v) => tExpr(v, fn));
2435
+ stmt.init = stmt.init.map((e) => tExpr(e, fn));
2436
+ break;
2437
+ case "CompoundAssignmentStatement":
2438
+ stmt.variable = tExpr(stmt.variable, fn);
2439
+ stmt.value = tExpr(stmt.value, fn);
2440
+ break;
2441
+ case "FunctionDeclaration":
2442
+ stmt.body = tStmts(stmt.body, fn);
2443
+ break;
2444
+ case "ForNumericStatement":
2445
+ stmt.start = tExpr(stmt.start, fn);
2446
+ stmt.end = tExpr(stmt.end, fn);
2447
+ if (stmt.step) stmt.step = tExpr(stmt.step, fn);
2448
+ stmt.body = tStmts(stmt.body, fn);
2449
+ break;
2450
+ case "ForGenericStatement":
2451
+ stmt.iterators = stmt.iterators.map((e) => tExpr(e, fn));
2452
+ stmt.body = tStmts(stmt.body, fn);
2453
+ break;
2454
+ case "IfStatement":
2455
+ for (const c of stmt.clauses) {
2456
+ if (c.type !== "ElseClause") c.condition = tExpr(c.condition, fn);
2457
+ c.body = tStmts(c.body, fn);
2458
+ }
2459
+ break;
2460
+ case "DoStatement":
2461
+ stmt.body = tStmts(stmt.body, fn);
2462
+ break;
2463
+ case "ReturnStatement":
2464
+ stmt.arguments = stmt.arguments.map((e) => tExpr(e, fn));
2465
+ break;
2466
+ default:
2467
+ break;
2468
+ }
2469
+ return stmt;
2470
+ }
2471
+ function tStmts(stmts, fn) {
2472
+ return stmts.map((s) => tStmt(s, fn));
2473
+ }
2474
+ function transformExpressions(chunk, fn) {
2475
+ chunk.body = tStmts(chunk.body, fn);
2476
+ }
57
2477
 
58
- local cfg = ${configExpr}
59
- if cfg == nil then
60
- error("Invalid preset or config")
61
- end
2478
+ // src/ast/builders.ts
2479
+ var DUMMY_LOC = { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
2480
+ var DUMMY_RANGE = [0, 0];
2481
+ function base() {
2482
+ return { range: DUMMY_RANGE, loc: DUMMY_LOC };
2483
+ }
2484
+ function ident(name) {
2485
+ return { type: "Identifier", name, attribute: null, typeAnnotation: null, scope: "global", isField: false, bindingId: null, ...base() };
2486
+ }
2487
+ function varargParam() {
2488
+ return { type: "VarargLiteral", value: "...", ...base() };
2489
+ }
2490
+ function numLit(value) {
2491
+ return { type: "NumericLiteral", value, raw: String(value), ...base() };
2492
+ }
2493
+ function strLit(value) {
2494
+ return { type: "StringLiteral", value, raw: JSON.stringify(value), ...base() };
2495
+ }
2496
+ function boolLit(value) {
2497
+ return { type: "BooleanLiteral", value, ...base() };
2498
+ }
2499
+ function nilLit() {
2500
+ return { type: "NilLiteral", ...base() };
2501
+ }
2502
+ function binExpr(operator, left, right) {
2503
+ return { type: "BinaryExpression", operator, left, right, ...base() };
2504
+ }
2505
+ function paren(expression) {
2506
+ return { type: "ParenthesizedExpression", expression, ...base() };
2507
+ }
2508
+ function callExpr(calleeBase, args) {
2509
+ return { type: "CallExpression", base: calleeBase, arguments: args, ...base() };
2510
+ }
2511
+ function memberExpr(objBase, name) {
2512
+ return { type: "MemberExpression", indexer: ".", base: objBase, identifier: { ...ident(name), isField: true }, optional: false, ...base() };
2513
+ }
2514
+ function indexExpr(objBase, index) {
2515
+ return { type: "IndexExpression", base: objBase, index, optional: false, ...base() };
2516
+ }
2517
+ function tableCtor(fields) {
2518
+ return { type: "TableConstructorExpression", fields, ...base() };
2519
+ }
2520
+ function tableValue(value) {
2521
+ return { type: "TableValue", value, ...base() };
2522
+ }
2523
+ function localStmt(variables, init) {
2524
+ return { type: "LocalStatement", variables, init, ...base() };
2525
+ }
2526
+ function assignStmt(variables, init) {
2527
+ return { type: "AssignmentStatement", variables, init, ...base() };
2528
+ }
2529
+ function returnStmt(args) {
2530
+ return { type: "ReturnStatement", arguments: args, ...base() };
2531
+ }
2532
+ function funcExpr(parameters, body, hasVararg = false) {
2533
+ return {
2534
+ type: "FunctionDeclaration",
2535
+ identifier: null,
2536
+ isLocal: false,
2537
+ isMethod: false,
2538
+ parameters,
2539
+ body,
2540
+ hasVararg,
2541
+ varargTypeAnnotation: null,
2542
+ generics: [],
2543
+ returnTypeAnnotation: null,
2544
+ ...base()
2545
+ };
2546
+ }
2547
+
2548
+ // src/passes/numbers-to-expressions.ts
2549
+ function buildNumberExpr(value, min, max) {
2550
+ const steps = randInt(min, max);
2551
+ const deltas = [];
2552
+ for (let i = 0; i < steps; i++) {
2553
+ const magnitude = randInt(1e3, 6e4);
2554
+ deltas.push(randInt(0, 1) === 0 ? magnitude : -magnitude);
2555
+ }
2556
+ const sum = deltas.reduce((a, b) => a + b, 0);
2557
+ const initial = value + sum;
2558
+ const scope = /* @__PURE__ */ new Set();
2559
+ const varName = randomVarName(scope);
2560
+ const v = ident(varName);
2561
+ const stmts = [localStmt([ident(varName)], [numLit(initial)])];
2562
+ for (const d of deltas) {
2563
+ const op = d >= 0 ? "-" : "+";
2564
+ stmts.push(assignStmt([ident(varName)], [binExpr(op, v, numLit(Math.abs(d)))]));
2565
+ }
2566
+ stmts.push(returnStmt([v]));
2567
+ const fn = funcExpr([], stmts);
2568
+ return callExpr(paren(fn), []);
2569
+ }
2570
+ var numbersToExpressions = (chunk, _ctx, opts) => {
2571
+ const min = opts?.min ?? 3;
2572
+ const max = Math.max(min, opts?.max ?? 8);
2573
+ transformExpressions(chunk, (expr) => {
2574
+ if (expr.type === "NumericLiteral" && Number.isFinite(expr.value)) {
2575
+ return buildNumberExpr(expr.value, min, max);
2576
+ }
2577
+ return null;
2578
+ });
2579
+ return chunk;
2580
+ };
62
2581
 
63
- if __lua_version ~= nil then
64
- cfg.LuaVersion = __lua_version
65
- end
2582
+ // src/passes/strings-to-expressions.ts
2583
+ function stringCharCall(chunkStr) {
2584
+ const codes = Array.from(chunkStr).map((c) => numLit(c.codePointAt(0)));
2585
+ return callExpr(memberExpr(ident("string"), "char"), codes);
2586
+ }
2587
+ function splitInto(s, parts) {
2588
+ if (parts <= 1 || s.length === 0) return [s];
2589
+ parts = Math.min(parts, s.length);
2590
+ const chars = Array.from(s);
2591
+ const base2 = Math.floor(chars.length / parts);
2592
+ const remainder = chars.length % parts;
2593
+ const out = [];
2594
+ let idx = 0;
2595
+ for (let i = 0; i < parts; i++) {
2596
+ const len = base2 + (i < remainder ? 1 : 0);
2597
+ out.push(chars.slice(idx, idx + len).join(""));
2598
+ idx += len;
2599
+ }
2600
+ return out.filter((p) => p.length > 0);
2601
+ }
2602
+ function buildStringExpr(value, min, max) {
2603
+ if (value.length === 0) return strLit("");
2604
+ const steps = Math.max(1, Math.min(randInt(min, max), value.length));
2605
+ const pieces = splitInto(value, steps);
2606
+ let result = null;
2607
+ for (const piece of pieces) {
2608
+ const pieceExpr = randInt(0, 1) === 0 ? strLit(piece) : stringCharCall(piece);
2609
+ result = result === null ? pieceExpr : binExpr("..", result, pieceExpr);
2610
+ }
2611
+ return result;
2612
+ }
2613
+ var stringsToExpressions = (chunk, _ctx, opts) => {
2614
+ const min = opts?.min ?? 3;
2615
+ const max = Math.max(min, opts?.max ?? 8);
2616
+ transformExpressions(chunk, (expr) => {
2617
+ if (expr.type === "StringLiteral") {
2618
+ return buildStringExpr(expr.value, min, max);
2619
+ }
2620
+ return null;
2621
+ });
2622
+ return chunk;
2623
+ };
2624
+
2625
+ // src/utils/parse-snippet.ts
2626
+ function parseSnippet(source, dialect) {
2627
+ const flags = resolveDialect(dialect);
2628
+ const tokens = new Lexer(source, flags).tokenize();
2629
+ const chunk = new Parser(tokens, flags).parseChunk();
2630
+ resolveScopes(chunk);
2631
+ return chunk;
2632
+ }
2633
+
2634
+ // src/passes/encrypt-strings.ts
2635
+ function buildDecryptHelper(fnName, keyName, key, dialect) {
2636
+ const keyLiteral = `{${key.join(", ")}}`;
2637
+ const src = `
2638
+ local ${keyName} = ${keyLiteral}
2639
+ local function ${fnName}(_enc)
2640
+ local _out = {}
2641
+ for _i = 1, #_enc do
2642
+ local _k = ${keyName}[((_i - 1) % #${keyName}) + 1]
2643
+ local _b = string.byte(_enc, _i)
2644
+ local _x = _b
2645
+ if _b >= _k then _x = _b - _k else _x = _b + (256 - _k) end
2646
+ _out[_i] = string.char(_x % 256)
2647
+ end
2648
+ return table.concat(_out)
2649
+ end
2650
+ `;
2651
+ return parseSnippet(src, dialect.name).body;
2652
+ }
2653
+ function modEncrypt(value, key) {
2654
+ const bytes = Array.from(value).map((c) => c.codePointAt(0) & 255);
2655
+ let out = "";
2656
+ for (let i = 0; i < bytes.length; i++) {
2657
+ const k = key[i % key.length];
2658
+ const enc = (bytes[i] + k) % 256;
2659
+ out += String.fromCharCode(enc);
2660
+ }
2661
+ return out;
2662
+ }
2663
+ var encryptStrings = (chunk, ctx) => {
2664
+ const key = Array.from({ length: randInt(4, 8) }, () => randInt(1, 255));
2665
+ const names = /* @__PURE__ */ new Set();
2666
+ const fnName = randomVarName(names);
2667
+ const keyName = randomVarName(names);
2668
+ let touched = false;
2669
+ transformExpressions(chunk, (expr) => {
2670
+ if (expr.type === "StringLiteral") {
2671
+ touched = true;
2672
+ const enc = modEncrypt(expr.value, key);
2673
+ return callExpr(ident(fnName), [strLit(enc)]);
2674
+ }
2675
+ return null;
2676
+ });
2677
+ if (touched) {
2678
+ const helper = buildDecryptHelper(fnName, keyName, key, ctx.dialect);
2679
+ chunk.body = [...helper, ...chunk.body];
2680
+ }
2681
+ return chunk;
2682
+ };
2683
+
2684
+ // src/passes/constant-array.ts
2685
+ var constantArray = (chunk) => {
2686
+ const pool = [];
2687
+ const slotOf = /* @__PURE__ */ new Map();
2688
+ transformExpressions(chunk, (expr) => {
2689
+ if (expr.type === "StringLiteral" || expr.type === "NumericLiteral") {
2690
+ pool.push(expr);
2691
+ slotOf.set(expr, pool.length - 1);
2692
+ return null;
2693
+ }
2694
+ return null;
2695
+ });
2696
+ if (pool.length === 0) return chunk;
2697
+ const order = shuffle(pool.map((_, i) => i));
2698
+ const physicalIndexOf = /* @__PURE__ */ new Map();
2699
+ order.forEach((originalSlot, physicalPos) => {
2700
+ physicalIndexOf.set(originalSlot, physicalPos + 1);
2701
+ });
2702
+ const names = /* @__PURE__ */ new Set();
2703
+ const arrName = randomVarName(names);
2704
+ const offset = randInt(1, 50);
2705
+ const arrayFields = order.map((originalSlot) => tableValue(pool[originalSlot]));
2706
+ const decl = localStmt([ident(arrName)], [tableCtor(arrayFields)]);
2707
+ let slotCounter = 0;
2708
+ transformExpressions(chunk, (expr) => {
2709
+ if (expr.type === "StringLiteral" || expr.type === "NumericLiteral") {
2710
+ const originalSlot = slotCounter++;
2711
+ const physicalIndex = physicalIndexOf.get(originalSlot);
2712
+ const shownIndex = physicalIndex - offset;
2713
+ const indexExprNode = offset === 0 ? numLit(physicalIndex) : binExpr("+", numLit(shownIndex), numLit(offset));
2714
+ return indexExpr(ident(arrName), paren(indexExprNode));
2715
+ }
2716
+ return expr;
2717
+ });
2718
+ chunk.body = [decl, ...chunk.body];
2719
+ return chunk;
2720
+ };
66
2721
 
67
- local pipeline = Prometheus.Pipeline:fromConfig(cfg)
68
- local result = pipeline:apply(__input_code)
69
- return result
70
- `);
71
- if (typeof obfuscated !== 'string') {
72
- throw new Error('Prometheus returned invalid result');
2722
+ // src/passes/vmify.ts
2723
+ function hoistNamesFromStatement(stmt, into) {
2724
+ if (stmt.type === "LocalStatement") {
2725
+ for (const v of stmt.variables) {
2726
+ if (v.type === "Identifier") {
2727
+ into.push({
2728
+ ...v,
2729
+ attribute: null
2730
+ });
2731
+ }
2732
+ }
2733
+ return {
2734
+ type: "AssignmentStatement",
2735
+ variables: stmt.variables,
2736
+ init: stmt.init,
2737
+ range: stmt.range,
2738
+ loc: stmt.loc
2739
+ };
2740
+ }
2741
+ if (stmt.type === "FunctionDeclaration" && stmt.isLocal && stmt.identifier?.type === "Identifier") {
2742
+ into.push({
2743
+ ...stmt.identifier,
2744
+ attribute: null
2745
+ });
2746
+ return {
2747
+ type: "AssignmentStatement",
2748
+ variables: [
2749
+ stmt.identifier
2750
+ ],
2751
+ init: [
2752
+ // Anonymous function values don't get their own AST node type —
2753
+ // per FunctionDeclaration's own doc comment, `identifier: null`
2754
+ // IS how an anonymous function/closure literal is represented,
2755
+ // both as a statement and (here) as an expression. This used to
2756
+ // stamp a fictitious 'FunctionExpression' type that no part of
2757
+ // this codebase's AST (or compileExpression's switch) actually
2758
+ // recognizes, silently making every `local function` un-compilable
2759
+ // as a value — see the CLOSURES section.
2760
+ {
2761
+ ...stmt,
2762
+ type: "FunctionDeclaration",
2763
+ identifier: null,
2764
+ isLocal: false
2765
+ }
2766
+ ],
2767
+ range: stmt.range,
2768
+ loc: stmt.loc
2769
+ };
2770
+ }
2771
+ return stmt;
2772
+ }
2773
+ function hoistAll(stmts, into) {
2774
+ return stmts.map((s) => hoistOne(s, into));
2775
+ }
2776
+ function hoistOne(stmt, into) {
2777
+ const rewritten = hoistNamesFromStatement(stmt, into);
2778
+ switch (rewritten.type) {
2779
+ case "IfStatement":
2780
+ return {
2781
+ ...rewritten,
2782
+ clauses: rewritten.clauses.map((c) => ({
2783
+ ...c,
2784
+ body: hoistAll(c.body, into)
2785
+ }))
2786
+ };
2787
+ case "WhileStatement":
2788
+ return {
2789
+ ...rewritten,
2790
+ body: hoistAll(rewritten.body, into)
2791
+ };
2792
+ case "RepeatStatement":
2793
+ return {
2794
+ ...rewritten,
2795
+ body: hoistAll(rewritten.body, into)
2796
+ };
2797
+ case "ForNumericStatement":
2798
+ into.push({ ...rewritten.variable, attribute: null });
2799
+ return {
2800
+ ...rewritten,
2801
+ body: hoistAll(rewritten.body, into)
2802
+ };
2803
+ case "ForGenericStatement":
2804
+ for (const v of rewritten.variables) {
2805
+ if (v.type === "Identifier") {
2806
+ into.push({ ...v, attribute: null });
73
2807
  }
74
- return obfuscated;
2808
+ }
2809
+ return {
2810
+ ...rewritten,
2811
+ body: hoistAll(rewritten.body, into)
2812
+ };
2813
+ case "DoStatement":
2814
+ return {
2815
+ ...rewritten,
2816
+ body: hoistAll(rewritten.body, into)
2817
+ };
2818
+ default:
2819
+ return rewritten;
2820
+ }
2821
+ }
2822
+ var OPCODE_NAMES = [
2823
+ "MOVE",
2824
+ "LOADK",
2825
+ "GETGLOBAL",
2826
+ "SETGLOBAL",
2827
+ "GETINDEX",
2828
+ "SETINDEX",
2829
+ "NEWTABLE",
2830
+ "ADD",
2831
+ "SUB",
2832
+ "MUL",
2833
+ "DIV",
2834
+ "IDIV",
2835
+ "MOD",
2836
+ "POW",
2837
+ "CONCAT",
2838
+ "UNM",
2839
+ "NOT",
2840
+ "LEN",
2841
+ "EQ",
2842
+ "LT",
2843
+ "LE",
2844
+ "JMP",
2845
+ "JMPIF",
2846
+ "JMPIFNOT",
2847
+ "CALL",
2848
+ "RETURN",
2849
+ "VARARG",
2850
+ "TOSTRING",
2851
+ "GETUPVAL",
2852
+ "SETUPVAL",
2853
+ "LOADRAW",
2854
+ "SPREADVARARG",
2855
+ "SPREADMULTRET",
2856
+ "NOP",
2857
+ "XOR"
2858
+ ];
2859
+ function buildShuffledOpcodes() {
2860
+ const shuffled = shuffle(OPCODE_NAMES);
2861
+ const map = {};
2862
+ shuffled.forEach((name, i) => {
2863
+ map[name] = i + 1;
2864
+ });
2865
+ return map;
2866
+ }
2867
+ var ConstantPool = class {
2868
+ values = [];
2869
+ map = /* @__PURE__ */ new Map();
2870
+ add(v) {
2871
+ const key = typeof v + ":" + String(v);
2872
+ const old = this.map.get(key);
2873
+ if (old !== void 0) return old;
2874
+ const id = this.values.length;
2875
+ this.values.push(v);
2876
+ this.map.set(key, id);
2877
+ return id;
2878
+ }
2879
+ };
2880
+ function constNodeFor(v) {
2881
+ if (v === null) return nilLit();
2882
+ switch (typeof v) {
2883
+ case "number":
2884
+ return numLit(v);
2885
+ case "string":
2886
+ return strLit(v);
2887
+ case "boolean":
2888
+ return boolLit(v);
2889
+ default:
2890
+ throw new Error(`VMify: unsupported constant type in pool: ${typeof v}`);
2891
+ }
2892
+ }
2893
+ var RegisterAllocator = class {
2894
+ next = 0;
2895
+ alloc() {
2896
+ const r = this.next;
2897
+ this.next++;
2898
+ return r;
2899
+ }
2900
+ reset(base2 = 0) {
2901
+ this.next = base2;
2902
+ }
2903
+ high() {
2904
+ return this.next;
2905
+ }
2906
+ };
2907
+ function keyAt(seed, pc) {
2908
+ let x = seed + pc * 2654435761 >>> 0;
2909
+ x = Math.imul(x, 1664525) + 1013904223 >>> 0;
2910
+ return x % 251 + 1;
2911
+ }
2912
+ function push(state, instr) {
2913
+ const pc = state.code.length;
2914
+ state.code.push(instr);
2915
+ return pc;
2916
+ }
2917
+ function keyedConst(state, pcForKey, v) {
2918
+ const k = keyAt(state.keySeed, pcForKey);
2919
+ return state.pool.add(v) ^ k;
2920
+ }
2921
+ function keyedGlobalConst(state, pcForKey, name) {
2922
+ state.usedGlobals.add(name);
2923
+ return keyedConst(state, pcForKey, name);
2924
+ }
2925
+ function readVarInto(id, state, target) {
2926
+ const bid = id.bindingId;
2927
+ if (bid == null) return false;
2928
+ const reg = state.regs.get(bid);
2929
+ if (reg !== void 0) {
2930
+ state.code.push({ op: state.opcodes.MOVE, a: target, b: reg, c: 0 });
2931
+ return true;
2932
+ }
2933
+ const box = state.boxIndex.get(bid);
2934
+ if (box !== void 0) {
2935
+ state.code.push({ op: state.opcodes.GETUPVAL, a: target, b: box, c: 0 });
2936
+ return true;
2937
+ }
2938
+ return false;
2939
+ }
2940
+ function writeVarFrom(id, state, srcReg) {
2941
+ const bid = id.bindingId;
2942
+ if (bid == null) return false;
2943
+ const reg = state.regs.get(bid);
2944
+ if (reg !== void 0) {
2945
+ state.code.push({ op: state.opcodes.MOVE, a: reg, b: srcReg, c: 0 });
2946
+ return true;
2947
+ }
2948
+ const box = state.boxIndex.get(bid);
2949
+ if (box !== void 0) {
2950
+ state.code.push({ op: state.opcodes.SETUPVAL, a: box, b: srcReg, c: 0 });
2951
+ return true;
2952
+ }
2953
+ return false;
2954
+ }
2955
+ function computeNeededBoxes(body) {
2956
+ const needed = /* @__PURE__ */ new Set();
2957
+ const wrapper = { type: "Chunk", body, range: [0, 0], loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } } };
2958
+ transformExpressions(wrapper, (expr) => {
2959
+ if (expr.type === "Identifier" && !expr.isField && expr.scope === "upvalue" && expr.bindingId != null) {
2960
+ needed.add(expr.bindingId);
2961
+ }
2962
+ return expr;
2963
+ });
2964
+ return needed;
2965
+ }
2966
+ function rewriteCapturedRefs(fn, state) {
2967
+ const cloned = structuredClone(fn);
2968
+ const wrapper = { type: "Chunk", body: [returnStmt([cloned])], range: [0, 0], loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } } };
2969
+ transformExpressions(wrapper, (expr) => {
2970
+ if (expr.type !== "Identifier" || expr.isField || expr.bindingId == null) return expr;
2971
+ const box = state.boxIndex.get(expr.bindingId);
2972
+ if (box === void 0) return expr;
2973
+ return memberExpr(indexExpr(ident(state.upvalsName), numLit(box + 1)), "v");
2974
+ });
2975
+ return wrapper.body[0].arguments[0];
2976
+ }
2977
+ function compileClosureLiteral(fn, state) {
2978
+ const rewritten = rewriteCapturedRefs(fn, state);
2979
+ const idx = state.rawPool.length;
2980
+ state.rawPool.push(rewritten);
2981
+ return idx;
2982
+ }
2983
+ function compileExpression(expr, state, target) {
2984
+ if (!expr) return;
2985
+ switch (expr.type) {
2986
+ case "NilLiteral": {
2987
+ const pc = state.code.length;
2988
+ state.code.push({ op: state.opcodes.LOADK, a: target, b: keyedConst(state, pc, null), c: 0 });
2989
+ return;
2990
+ }
2991
+ case "NumericLiteral":
2992
+ case "StringLiteral":
2993
+ case "BooleanLiteral": {
2994
+ const pc = state.code.length;
2995
+ state.code.push({ op: state.opcodes.LOADK, a: target, b: keyedConst(state, pc, expr.value), c: 0 });
2996
+ return;
2997
+ }
2998
+ case "VarargLiteral": {
2999
+ state.code.push({ op: state.opcodes.VARARG, a: target, b: 0, c: 0 });
3000
+ return;
3001
+ }
3002
+ case "Identifier": {
3003
+ if (readVarInto(expr, state, target)) return;
3004
+ const pc = state.code.length;
3005
+ state.code.push({ op: state.opcodes.GETGLOBAL, a: target, b: keyedGlobalConst(state, pc, expr.name), c: 0 });
3006
+ return;
3007
+ }
3008
+ case "FunctionDeclaration": {
3009
+ const rawIdx = compileClosureLiteral(expr, state);
3010
+ state.code.push({ op: state.opcodes.LOADRAW, a: target, b: rawIdx, c: 0 });
3011
+ return;
3012
+ }
3013
+ case "MemberExpression": {
3014
+ const baseReg = state.allocator.alloc();
3015
+ compileExpression(expr.base, state, baseReg);
3016
+ const keyReg = state.allocator.alloc();
3017
+ const pc = state.code.length;
3018
+ state.code.push({ op: state.opcodes.LOADK, a: keyReg, b: keyedConst(state, pc, expr.identifier.name), c: 0 });
3019
+ state.code.push({ op: state.opcodes.GETINDEX, a: target, b: baseReg, c: keyReg });
3020
+ return;
3021
+ }
3022
+ case "IndexExpression": {
3023
+ const baseReg = state.allocator.alloc();
3024
+ compileExpression(expr.base, state, baseReg);
3025
+ const keyReg = state.allocator.alloc();
3026
+ compileExpression(expr.index, state, keyReg);
3027
+ state.code.push({ op: state.opcodes.GETINDEX, a: target, b: baseReg, c: keyReg });
3028
+ return;
3029
+ }
3030
+ case "UnaryExpression": {
3031
+ const src = state.allocator.alloc();
3032
+ compileExpression(expr.argument, state, src);
3033
+ const operator = expr.operator;
3034
+ let op;
3035
+ switch (operator) {
3036
+ case "-":
3037
+ op = state.opcodes.UNM;
3038
+ break;
3039
+ case "not":
3040
+ op = state.opcodes.NOT;
3041
+ break;
3042
+ case "#":
3043
+ op = state.opcodes.LEN;
3044
+ break;
3045
+ default:
3046
+ throw new Error(`VMify: unsupported unary operator '${operator}'`);
3047
+ }
3048
+ state.code.push({ op, a: target, b: src, c: 0 });
3049
+ return;
3050
+ }
3051
+ case "LogicalExpression": {
3052
+ compileExpression(expr.left, state, target);
3053
+ const testOp = expr.operator === "and" ? state.opcodes.JMPIFNOT : state.opcodes.JMPIF;
3054
+ const jmpPc = push(state, { op: testOp, a: target, b: -1, c: 0 });
3055
+ compileExpression(expr.right, state, target);
3056
+ patchJumpTarget(state, jmpPc, state.code.length);
3057
+ return;
3058
+ }
3059
+ case "BinaryExpression": {
3060
+ const left = state.allocator.alloc();
3061
+ compileExpression(expr.left, state, left);
3062
+ const right = state.allocator.alloc();
3063
+ compileExpression(expr.right, state, right);
3064
+ let op;
3065
+ let swapForGt = false;
3066
+ switch (expr.operator) {
3067
+ case "+":
3068
+ op = state.opcodes.ADD;
3069
+ break;
3070
+ case "-":
3071
+ op = state.opcodes.SUB;
3072
+ break;
3073
+ case "*":
3074
+ op = state.opcodes.MUL;
3075
+ break;
3076
+ case "/":
3077
+ op = state.opcodes.DIV;
3078
+ break;
3079
+ case "%":
3080
+ op = state.opcodes.MOD;
3081
+ break;
3082
+ case "^":
3083
+ op = state.opcodes.POW;
3084
+ break;
3085
+ case "..":
3086
+ op = state.opcodes.CONCAT;
3087
+ break;
3088
+ case "==":
3089
+ op = state.opcodes.EQ;
3090
+ break;
3091
+ case "<":
3092
+ op = state.opcodes.LT;
3093
+ break;
3094
+ case "<=":
3095
+ op = state.opcodes.LE;
3096
+ break;
3097
+ // ~=, >, >= are all expressed via EQ/LT/LE: `a ~= b` is `not (a ==
3098
+ // b)`, and `a > b` / `a >= b` are `b < a` / `b <= a` with operands
3099
+ // swapped, so the instruction set doesn't need four more opcodes.
3100
+ case "~=":
3101
+ op = state.opcodes.EQ;
3102
+ break;
3103
+ case ">":
3104
+ op = state.opcodes.LT;
3105
+ swapForGt = true;
3106
+ break;
3107
+ case ">=":
3108
+ op = state.opcodes.LE;
3109
+ swapForGt = true;
3110
+ break;
3111
+ case "//":
3112
+ op = state.opcodes.IDIV;
3113
+ break;
3114
+ default:
3115
+ throw new Error(`VMify: unsupported binary operator '${expr.operator}'`);
3116
+ }
3117
+ const a = swapForGt ? right : left;
3118
+ const b = swapForGt ? left : right;
3119
+ state.code.push({ op, a: target, b: a, c: b });
3120
+ if (expr.operator === "~=") {
3121
+ state.code.push({ op: state.opcodes.NOT, a: target, b: target, c: 0 });
3122
+ }
3123
+ return;
3124
+ }
3125
+ case "IfExpression": {
3126
+ const endJumps = [];
3127
+ for (let i = 0; i < expr.clauses.length; i++) {
3128
+ const clause = expr.clauses[i];
3129
+ const isLast = clause.condition === null;
3130
+ let skipPc = -1;
3131
+ if (!isLast) {
3132
+ const condReg = state.allocator.alloc();
3133
+ compileExpression(clause.condition, state, condReg);
3134
+ skipPc = push(state, { op: state.opcodes.JMPIFNOT, a: condReg, b: -1, c: 0 });
3135
+ }
3136
+ compileExpression(clause.body, state, target);
3137
+ if (!isLast) {
3138
+ endJumps.push(push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 }));
3139
+ patchJumpTarget(state, skipPc, state.code.length);
3140
+ }
3141
+ }
3142
+ const endPc = state.code.length;
3143
+ for (const j of endJumps) {
3144
+ patchJumpTarget(state, j, endPc);
3145
+ }
3146
+ return;
3147
+ }
3148
+ case "InterpolatedStringExpression": {
3149
+ const { strings, expressions } = expr;
3150
+ let haveValue = false;
3151
+ for (let i = 0; i < strings.length; i++) {
3152
+ if (strings[i].length > 0 || i === 0) {
3153
+ if (!haveValue) {
3154
+ const pc = state.code.length;
3155
+ state.code.push({ op: state.opcodes.LOADK, a: target, b: keyedConst(state, pc, strings[i]), c: 0 });
3156
+ haveValue = true;
3157
+ } else {
3158
+ const piece = state.allocator.alloc();
3159
+ const pc = state.code.length;
3160
+ state.code.push({ op: state.opcodes.LOADK, a: piece, b: keyedConst(state, pc, strings[i]), c: 0 });
3161
+ state.code.push({ op: state.opcodes.CONCAT, a: target, b: target, c: piece });
3162
+ }
3163
+ }
3164
+ if (i < expressions.length) {
3165
+ const raw = state.allocator.alloc();
3166
+ compileExpression(expressions[i], state, raw);
3167
+ if (!haveValue) {
3168
+ state.code.push({ op: state.opcodes.TOSTRING, a: target, b: raw, c: 0 });
3169
+ haveValue = true;
3170
+ } else {
3171
+ const piece = state.allocator.alloc();
3172
+ state.code.push({ op: state.opcodes.TOSTRING, a: piece, b: raw, c: 0 });
3173
+ state.code.push({ op: state.opcodes.CONCAT, a: target, b: target, c: piece });
3174
+ }
3175
+ }
3176
+ }
3177
+ if (!haveValue) {
3178
+ const pc = state.code.length;
3179
+ state.code.push({ op: state.opcodes.LOADK, a: target, b: keyedConst(state, pc, ""), c: 0 });
3180
+ }
3181
+ return;
3182
+ }
3183
+ case "TableConstructorExpression": {
3184
+ state.code.push({ op: state.opcodes.NEWTABLE, a: target, b: 0, c: 0 });
3185
+ let arrayIndex = 1;
3186
+ expr.fields.forEach((field, i) => {
3187
+ const isLast = i === expr.fields.length - 1;
3188
+ if (field.type === "TableValue") {
3189
+ if (isLast && field.value.type === "VarargLiteral") {
3190
+ state.code.push({ op: state.opcodes.SPREADVARARG, a: target, b: arrayIndex, c: 0 });
3191
+ return;
3192
+ }
3193
+ if (isLast && field.value.type === "CallExpression") {
3194
+ const scratch = state.allocator.alloc();
3195
+ compileCallIntoWithCapture(field.value, state, scratch);
3196
+ state.code.push({ op: state.opcodes.SPREADMULTRET, a: target, b: arrayIndex, c: 0 });
3197
+ return;
3198
+ }
3199
+ const valReg2 = state.allocator.alloc();
3200
+ compileExpression(field.value, state, valReg2);
3201
+ const keyReg2 = state.allocator.alloc();
3202
+ const pc = state.code.length;
3203
+ state.code.push({ op: state.opcodes.LOADK, a: keyReg2, b: keyedConst(state, pc, arrayIndex), c: 0 });
3204
+ state.code.push({ op: state.opcodes.SETINDEX, a: target, b: keyReg2, c: valReg2 });
3205
+ arrayIndex++;
3206
+ return;
3207
+ }
3208
+ if (field.type === "TableKeyString") {
3209
+ const valReg2 = state.allocator.alloc();
3210
+ compileExpression(field.value, state, valReg2);
3211
+ const keyReg2 = state.allocator.alloc();
3212
+ const pc = state.code.length;
3213
+ state.code.push({ op: state.opcodes.LOADK, a: keyReg2, b: keyedConst(state, pc, field.key.name), c: 0 });
3214
+ state.code.push({ op: state.opcodes.SETINDEX, a: target, b: keyReg2, c: valReg2 });
3215
+ return;
3216
+ }
3217
+ const keyReg = state.allocator.alloc();
3218
+ compileExpression(field.key, state, keyReg);
3219
+ const valReg = state.allocator.alloc();
3220
+ compileExpression(field.value, state, valReg);
3221
+ state.code.push({ op: state.opcodes.SETINDEX, a: target, b: keyReg, c: valReg });
3222
+ });
3223
+ return;
3224
+ }
3225
+ case "CallExpression": {
3226
+ compileCallInto(expr, state, target);
3227
+ return;
3228
+ }
3229
+ case "TableCallExpression": {
3230
+ compileCallInto(callExpr(expr.base, [expr.arguments[0]]), state, target);
3231
+ return;
75
3232
  }
76
- finally {
77
- lua.global.close();
3233
+ case "StringCallExpression": {
3234
+ compileCallInto(callExpr(expr.base, [expr.argument]), state, target);
3235
+ return;
78
3236
  }
3237
+ case "ParenthesizedExpression": {
3238
+ compileExpression(expr.expression, state, target);
3239
+ return;
3240
+ }
3241
+ }
3242
+ throw new Error(
3243
+ `VMify: unsupported expression type '${expr.type}' (only ParenthesizedExpression was missing \u2014 see the case added above; anything else here is a genuinely new gap, not the closures/upvalues work)`
3244
+ );
3245
+ }
3246
+ function compileCallInto(call, state, target, nret = 1) {
3247
+ let unwrappedBase = call.base;
3248
+ while (unwrappedBase.type === "ParenthesizedExpression") {
3249
+ unwrappedBase = unwrappedBase.expression;
3250
+ }
3251
+ const isMethodCall = unwrappedBase.type === "MemberExpression" && unwrappedBase.indexer === ":";
3252
+ let func;
3253
+ let selfReg;
3254
+ if (isMethodCall) {
3255
+ const memberBase = unwrappedBase;
3256
+ selfReg = state.allocator.alloc();
3257
+ compileExpression(memberBase.base, state, selfReg);
3258
+ const keyReg = state.allocator.alloc();
3259
+ const pc = state.code.length;
3260
+ state.code.push({ op: state.opcodes.LOADK, a: keyReg, b: keyedConst(state, pc, memberBase.identifier.name), c: 0 });
3261
+ func = state.allocator.alloc();
3262
+ state.code.push({ op: state.opcodes.GETINDEX, a: func, b: selfReg, c: keyReg });
3263
+ } else {
3264
+ func = state.allocator.alloc();
3265
+ compileExpression(call.base, state, func);
3266
+ }
3267
+ const lastArg = call.arguments[call.arguments.length - 1];
3268
+ const varargSpread = call.arguments.length > 0 && lastArg.type === "VarargLiteral";
3269
+ const callSpread = !varargSpread && call.arguments.length > 0 && lastArg.type === "CallExpression";
3270
+ const fixedArgs = varargSpread || callSpread ? call.arguments.slice(0, -1) : call.arguments;
3271
+ let argBase;
3272
+ if (selfReg === void 0 && fixedArgs.length === 0) {
3273
+ argBase = state.allocator.alloc();
3274
+ } else {
3275
+ const argScratch = selfReg !== void 0 ? [selfReg] : [];
3276
+ for (const argExpr of fixedArgs) {
3277
+ const r = state.allocator.alloc();
3278
+ compileExpression(argExpr, state, r);
3279
+ argScratch.push(r);
3280
+ }
3281
+ argBase = state.allocator.alloc();
3282
+ for (let i = 1; i < argScratch.length; i++) state.allocator.alloc();
3283
+ argScratch.forEach((r, i) => {
3284
+ state.code.push({ op: state.opcodes.MOVE, a: argBase + i, b: r, c: 0 });
3285
+ });
3286
+ }
3287
+ let spreadKind = 0;
3288
+ if (varargSpread) {
3289
+ spreadKind = 1;
3290
+ } else if (callSpread) {
3291
+ const scratch = state.allocator.alloc();
3292
+ compileCallIntoWithCapture(lastArg, state, scratch);
3293
+ spreadKind = 2;
3294
+ }
3295
+ state.code.push({
3296
+ op: state.opcodes.CALL,
3297
+ a: target,
3298
+ b: func,
3299
+ c: argBase,
3300
+ nargs: (selfReg !== void 0 ? 1 : 0) + fixedArgs.length,
3301
+ nret,
3302
+ spreadKind
3303
+ });
3304
+ }
3305
+ function compileCallIntoWithCapture(call, state, target) {
3306
+ compileCallInto(call, state, target, 1);
3307
+ state.code[state.code.length - 1].captureMultret = true;
3308
+ }
3309
+ function patchJumpTarget(state, jmpPc, destPc) {
3310
+ const instr = state.code[jmpPc];
3311
+ const k = keyAt(state.keySeed, jmpPc);
3312
+ instr.b = destPc ^ k;
3313
+ }
3314
+ function writeAssignTarget(v, srcReg, state) {
3315
+ if (v.type === "Identifier") {
3316
+ if (writeVarFrom(v, state, srcReg)) return;
3317
+ const pc = state.code.length;
3318
+ state.code.push({ op: state.opcodes.SETGLOBAL, a: srcReg, b: keyedGlobalConst(state, pc, v.name), c: 0 });
3319
+ return;
3320
+ }
3321
+ if (v.type === "MemberExpression" || v.type === "IndexExpression") {
3322
+ const baseReg = state.allocator.alloc();
3323
+ compileExpression(v.base, state, baseReg);
3324
+ const keyReg = state.allocator.alloc();
3325
+ if (v.type === "MemberExpression") {
3326
+ const pc = state.code.length;
3327
+ state.code.push({ op: state.opcodes.LOADK, a: keyReg, b: keyedConst(state, pc, v.identifier.name), c: 0 });
3328
+ } else {
3329
+ compileExpression(v.index, state, keyReg);
3330
+ }
3331
+ state.code.push({ op: state.opcodes.SETINDEX, a: baseReg, b: keyReg, c: srcReg });
3332
+ return;
3333
+ }
3334
+ throw new Error(
3335
+ `VMify: unsupported assignment target '${v.type}' (only identifiers and t.k / t[k] are supported)`
3336
+ );
3337
+ }
3338
+ function compileSingleInit(v, expr, state) {
3339
+ if (v.type === "Identifier") {
3340
+ const bid = v.bindingId;
3341
+ const local = bid != null ? state.regs.get(bid) : void 0;
3342
+ if (local !== void 0) {
3343
+ if (expr) {
3344
+ compileExpression(expr, state, local);
3345
+ } else {
3346
+ const pc = state.code.length;
3347
+ state.code.push({ op: state.opcodes.LOADK, a: local, b: keyedConst(state, pc, null), c: 0 });
3348
+ }
3349
+ return;
3350
+ }
3351
+ const box = bid != null ? state.boxIndex.get(bid) : void 0;
3352
+ if (box !== void 0) {
3353
+ const temp = state.allocator.alloc();
3354
+ if (expr) {
3355
+ compileExpression(expr, state, temp);
3356
+ } else {
3357
+ const pc = state.code.length;
3358
+ state.code.push({ op: state.opcodes.LOADK, a: temp, b: keyedConst(state, pc, null), c: 0 });
3359
+ }
3360
+ state.code.push({ op: state.opcodes.SETUPVAL, a: box, b: temp, c: 0 });
3361
+ return;
3362
+ }
3363
+ {
3364
+ const temp = state.allocator.alloc();
3365
+ if (expr) {
3366
+ compileExpression(expr, state, temp);
3367
+ } else {
3368
+ const pc = state.code.length;
3369
+ state.code.push({ op: state.opcodes.LOADK, a: temp, b: keyedConst(state, pc, null), c: 0 });
3370
+ }
3371
+ const pc2 = state.code.length;
3372
+ state.code.push({ op: state.opcodes.SETGLOBAL, a: temp, b: keyedGlobalConst(state, pc2, v.name), c: 0 });
3373
+ }
3374
+ return;
3375
+ }
3376
+ if (v.type === "MemberExpression" || v.type === "IndexExpression") {
3377
+ const baseReg = state.allocator.alloc();
3378
+ compileExpression(v.base, state, baseReg);
3379
+ const keyReg = state.allocator.alloc();
3380
+ if (v.type === "MemberExpression") {
3381
+ const pc = state.code.length;
3382
+ state.code.push({ op: state.opcodes.LOADK, a: keyReg, b: keyedConst(state, pc, v.identifier.name), c: 0 });
3383
+ } else {
3384
+ compileExpression(v.index, state, keyReg);
3385
+ }
3386
+ const valReg = state.allocator.alloc();
3387
+ if (expr) {
3388
+ compileExpression(expr, state, valReg);
3389
+ } else {
3390
+ const pc = state.code.length;
3391
+ state.code.push({ op: state.opcodes.LOADK, a: valReg, b: keyedConst(state, pc, null), c: 0 });
3392
+ }
3393
+ state.code.push({ op: state.opcodes.SETINDEX, a: baseReg, b: keyReg, c: valReg });
3394
+ return;
3395
+ }
3396
+ throw new Error(
3397
+ `VMify: unsupported assignment target '${v.type}' (only identifiers and t.k / t[k] are supported)`
3398
+ );
3399
+ }
3400
+ function compileAssignment(stmt, state) {
3401
+ const nInit = stmt.init.length;
3402
+ const nVars = stmt.variables.length;
3403
+ if (nInit > 0 && nVars > nInit && stmt.init[nInit - 1].type === "CallExpression") {
3404
+ for (let i = 0; i < nInit - 1; i++) {
3405
+ state.allocator.reset(state.regFloor);
3406
+ compileSingleInit(stmt.variables[i], stmt.init[i], state);
3407
+ }
3408
+ state.allocator.reset(state.regFloor);
3409
+ const remaining = nVars - (nInit - 1);
3410
+ const base2 = state.allocator.alloc();
3411
+ for (let i = 1; i < remaining; i++) state.allocator.alloc();
3412
+ compileCallInto(stmt.init[nInit - 1], state, base2, remaining);
3413
+ for (let i = 0; i < remaining; i++) {
3414
+ writeAssignTarget(stmt.variables[nInit - 1 + i], base2 + i, state);
3415
+ }
3416
+ return;
3417
+ }
3418
+ stmt.variables.forEach((v, i) => {
3419
+ state.allocator.reset(state.regFloor);
3420
+ compileSingleInit(v, stmt.init[i], state);
3421
+ });
3422
+ }
3423
+ function opForBinary(op, state) {
3424
+ switch (op) {
3425
+ case "+":
3426
+ return state.opcodes.ADD;
3427
+ case "-":
3428
+ return state.opcodes.SUB;
3429
+ case "*":
3430
+ return state.opcodes.MUL;
3431
+ case "/":
3432
+ return state.opcodes.DIV;
3433
+ case "//":
3434
+ return state.opcodes.IDIV;
3435
+ case "%":
3436
+ return state.opcodes.MOD;
3437
+ case "^":
3438
+ return state.opcodes.POW;
3439
+ case "..":
3440
+ return state.opcodes.CONCAT;
3441
+ default:
3442
+ throw new Error(`VMify: internal error \u2014 unexpected compound-assignment base operator '${op}'`);
3443
+ }
3444
+ }
3445
+ function compileCompoundAssignment(stmt, state) {
3446
+ const opMap = {
3447
+ "+=": "+",
3448
+ "-=": "-",
3449
+ "*=": "*",
3450
+ "/=": "/",
3451
+ "//=": "//",
3452
+ "%=": "%",
3453
+ "^=": "^",
3454
+ "..=": ".."
3455
+ };
3456
+ const binOp = opMap[stmt.operator];
3457
+ const opcode = opForBinary(binOp, state);
3458
+ const v = stmt.variable;
3459
+ if (v.type === "Identifier") {
3460
+ const bid = v.bindingId;
3461
+ const local = bid != null ? state.regs.get(bid) : void 0;
3462
+ if (local !== void 0) {
3463
+ state.allocator.reset(state.regFloor);
3464
+ const rhs2 = state.allocator.alloc();
3465
+ compileExpression(stmt.value, state, rhs2);
3466
+ state.code.push({ op: opcode, a: local, b: local, c: rhs2 });
3467
+ return;
3468
+ }
3469
+ const box = bid != null ? state.boxIndex.get(bid) : void 0;
3470
+ if (box !== void 0) {
3471
+ state.allocator.reset(state.regFloor);
3472
+ const cur2 = state.allocator.alloc();
3473
+ state.code.push({ op: state.opcodes.GETUPVAL, a: cur2, b: box, c: 0 });
3474
+ const rhs2 = state.allocator.alloc();
3475
+ compileExpression(stmt.value, state, rhs2);
3476
+ state.code.push({ op: opcode, a: cur2, b: cur2, c: rhs2 });
3477
+ state.code.push({ op: state.opcodes.SETUPVAL, a: box, b: cur2, c: 0 });
3478
+ return;
3479
+ }
3480
+ state.allocator.reset(state.regFloor);
3481
+ const cur = state.allocator.alloc();
3482
+ const pc = state.code.length;
3483
+ state.code.push({ op: state.opcodes.GETGLOBAL, a: cur, b: keyedGlobalConst(state, pc, v.name), c: 0 });
3484
+ const rhs = state.allocator.alloc();
3485
+ compileExpression(stmt.value, state, rhs);
3486
+ state.code.push({ op: opcode, a: cur, b: cur, c: rhs });
3487
+ const pc2 = state.code.length;
3488
+ state.code.push({ op: state.opcodes.SETGLOBAL, a: cur, b: keyedGlobalConst(state, pc2, v.name), c: 0 });
3489
+ return;
3490
+ }
3491
+ if (v.type === "MemberExpression" || v.type === "IndexExpression") {
3492
+ state.allocator.reset(state.regFloor);
3493
+ const baseReg = state.allocator.alloc();
3494
+ compileExpression(v.base, state, baseReg);
3495
+ const keyReg = state.allocator.alloc();
3496
+ if (v.type === "MemberExpression") {
3497
+ const pc = state.code.length;
3498
+ state.code.push({ op: state.opcodes.LOADK, a: keyReg, b: keyedConst(state, pc, v.identifier.name), c: 0 });
3499
+ } else {
3500
+ compileExpression(v.index, state, keyReg);
3501
+ }
3502
+ const cur = state.allocator.alloc();
3503
+ state.code.push({ op: state.opcodes.GETINDEX, a: cur, b: baseReg, c: keyReg });
3504
+ const rhs = state.allocator.alloc();
3505
+ compileExpression(stmt.value, state, rhs);
3506
+ state.code.push({ op: opcode, a: cur, b: cur, c: rhs });
3507
+ state.code.push({ op: state.opcodes.SETINDEX, a: baseReg, b: keyReg, c: cur });
3508
+ return;
3509
+ }
3510
+ throw new Error(
3511
+ `VMify: unsupported compound-assignment target '${v.type}'`
3512
+ );
3513
+ }
3514
+ function compileIf(stmt, state) {
3515
+ const endJumps = [];
3516
+ for (let i = 0; i < stmt.clauses.length; i++) {
3517
+ const clause = stmt.clauses[i];
3518
+ const isLast = i === stmt.clauses.length - 1;
3519
+ let skipPc = -1;
3520
+ if (clause.type !== "ElseClause") {
3521
+ state.allocator.reset(state.regFloor);
3522
+ const condReg = state.allocator.alloc();
3523
+ compileExpression(clause.condition, state, condReg);
3524
+ skipPc = push(state, { op: state.opcodes.JMPIFNOT, a: condReg, b: -1, c: 0 });
3525
+ }
3526
+ for (const s of clause.body) {
3527
+ state.allocator.reset(state.regFloor);
3528
+ compileStatement(s, state);
3529
+ }
3530
+ if (!isLast) {
3531
+ endJumps.push(push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 }));
3532
+ }
3533
+ if (skipPc !== -1) {
3534
+ patchJumpTarget(state, skipPc, state.code.length);
3535
+ }
3536
+ }
3537
+ const endPc = state.code.length;
3538
+ for (const j of endJumps) {
3539
+ patchJumpTarget(state, j, endPc);
3540
+ }
3541
+ }
3542
+ function compileWhile(stmt, state) {
3543
+ const loopStart = state.code.length;
3544
+ state.allocator.reset(state.regFloor);
3545
+ const condReg = state.allocator.alloc();
3546
+ compileExpression(stmt.condition, state, condReg);
3547
+ const exitJmp = push(state, { op: state.opcodes.JMPIFNOT, a: condReg, b: -1, c: 0 });
3548
+ state.loopStack.push({ continueJumps: [], breakJumps: [] });
3549
+ for (const s of stmt.body) {
3550
+ state.allocator.reset(state.regFloor);
3551
+ compileStatement(s, state);
3552
+ }
3553
+ const backJmp = push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 });
3554
+ patchJumpTarget(state, backJmp, loopStart);
3555
+ const exitPc = state.code.length;
3556
+ patchJumpTarget(state, exitJmp, exitPc);
3557
+ const frame = state.loopStack.pop();
3558
+ for (const j of frame.continueJumps) patchJumpTarget(state, j, loopStart);
3559
+ for (const j of frame.breakJumps) patchJumpTarget(state, j, exitPc);
3560
+ }
3561
+ function compileRepeat(stmt, state) {
3562
+ const loopStart = state.code.length;
3563
+ state.loopStack.push({ continueJumps: [], breakJumps: [] });
3564
+ for (const s of stmt.body) {
3565
+ state.allocator.reset(state.regFloor);
3566
+ compileStatement(s, state);
3567
+ }
3568
+ const condPc = state.code.length;
3569
+ state.allocator.reset(state.regFloor);
3570
+ const condReg = state.allocator.alloc();
3571
+ compileExpression(stmt.condition, state, condReg);
3572
+ const backJmp = push(state, { op: state.opcodes.JMPIFNOT, a: condReg, b: -1, c: 0 });
3573
+ patchJumpTarget(state, backJmp, loopStart);
3574
+ const exitPc = state.code.length;
3575
+ const frame = state.loopStack.pop();
3576
+ for (const j of frame.continueJumps) patchJumpTarget(state, j, condPc);
3577
+ for (const j of frame.breakJumps) patchJumpTarget(state, j, exitPc);
3578
+ }
3579
+ function compileForNumeric(stmt, state) {
3580
+ if (stmt.variable.type !== "Identifier") {
3581
+ throw new Error("VMify: for-loop variable must be a plain identifier");
3582
+ }
3583
+ const loopBid = stmt.variable.bindingId;
3584
+ if (loopBid != null && state.boxIndex.has(loopBid)) {
3585
+ throw new Error(
3586
+ `VMify: closures capturing the for-loop variable '${stmt.variable.name}' are not supported yet \u2014 this VM does not give loop variables a fresh binding per iteration, so the closure would see a stale/shared value instead of its own. Copy the loop variable into a plain local declared inside the loop body first (e.g. \`local i_ = i\`) and capture that instead.`
3587
+ );
3588
+ }
3589
+ const loopReg = loopBid != null ? state.regs.get(loopBid) : void 0;
3590
+ if (loopReg === void 0) {
3591
+ throw new Error("VMify: internal error \u2014 for-loop variable was not hoisted to a register");
3592
+ }
3593
+ state.allocator.reset(state.regFloor);
3594
+ compileExpression(stmt.start, state, loopReg);
3595
+ const limitReg = state.allocator.alloc();
3596
+ compileExpression(stmt.end, state, limitReg);
3597
+ const stepReg = state.allocator.alloc();
3598
+ if (stmt.step) {
3599
+ compileExpression(stmt.step, state, stepReg);
3600
+ } else {
3601
+ const pc = state.code.length;
3602
+ state.code.push({ op: state.opcodes.LOADK, a: stepReg, b: keyedConst(state, pc, 1), c: 0 });
3603
+ }
3604
+ const stepNonNegReg = state.allocator.alloc();
3605
+ {
3606
+ const zeroReg = state.allocator.alloc();
3607
+ const pc = state.code.length;
3608
+ state.code.push({ op: state.opcodes.LOADK, a: zeroReg, b: keyedConst(state, pc, 0), c: 0 });
3609
+ state.code.push({ op: state.opcodes.LE, a: stepNonNegReg, b: zeroReg, c: stepReg });
3610
+ }
3611
+ const loopStart = state.code.length;
3612
+ const outerFloor = state.regFloor;
3613
+ state.regFloor = state.allocator.high();
3614
+ state.allocator.reset(state.regFloor);
3615
+ const condReg = state.allocator.alloc();
3616
+ const negBranchJmp = push(state, { op: state.opcodes.JMPIFNOT, a: stepNonNegReg, b: -1, c: 0 });
3617
+ state.code.push({ op: state.opcodes.LE, a: condReg, b: loopReg, c: limitReg });
3618
+ const condDoneJmp = push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 });
3619
+ patchJumpTarget(state, negBranchJmp, state.code.length);
3620
+ state.code.push({ op: state.opcodes.LE, a: condReg, b: limitReg, c: loopReg });
3621
+ patchJumpTarget(state, condDoneJmp, state.code.length);
3622
+ const exitJmp = push(state, { op: state.opcodes.JMPIFNOT, a: condReg, b: -1, c: 0 });
3623
+ state.loopStack.push({ continueJumps: [], breakJumps: [] });
3624
+ for (const s of stmt.body) {
3625
+ state.allocator.reset(state.regFloor);
3626
+ compileStatement(s, state);
3627
+ }
3628
+ const stepPc = state.code.length;
3629
+ state.allocator.reset(state.regFloor);
3630
+ state.code.push({ op: state.opcodes.ADD, a: loopReg, b: loopReg, c: stepReg });
3631
+ const backJmp = push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 });
3632
+ patchJumpTarget(state, backJmp, loopStart);
3633
+ const exitPc = state.code.length;
3634
+ patchJumpTarget(state, exitJmp, exitPc);
3635
+ const frame = state.loopStack.pop();
3636
+ for (const j of frame.continueJumps) patchJumpTarget(state, j, stepPc);
3637
+ for (const j of frame.breakJumps) patchJumpTarget(state, j, exitPc);
3638
+ state.regFloor = outerFloor;
3639
+ }
3640
+ function compileForGeneric(stmt, state) {
3641
+ state.allocator.reset(state.regFloor);
3642
+ const fReg = state.allocator.alloc();
3643
+ const sReg = state.allocator.alloc();
3644
+ const cReg = state.allocator.alloc();
3645
+ const outerFloor = state.regFloor;
3646
+ state.regFloor = outerFloor + 3;
3647
+ if (stmt.iterators.length === 1 && stmt.iterators[0].type === "CallExpression") {
3648
+ compileCallInto(stmt.iterators[0], state, fReg, 3);
3649
+ } else {
3650
+ compileExpression(stmt.iterators[0], state, fReg);
3651
+ if (stmt.iterators[1]) {
3652
+ compileExpression(stmt.iterators[1], state, sReg);
3653
+ } else {
3654
+ const pc = state.code.length;
3655
+ state.code.push({ op: state.opcodes.LOADK, a: sReg, b: keyedConst(state, pc, null), c: 0 });
3656
+ }
3657
+ if (stmt.iterators[2]) {
3658
+ compileExpression(stmt.iterators[2], state, cReg);
3659
+ } else {
3660
+ const pc = state.code.length;
3661
+ state.code.push({ op: state.opcodes.LOADK, a: cReg, b: keyedConst(state, pc, null), c: 0 });
3662
+ }
3663
+ }
3664
+ const varRegs = stmt.variables.map((v) => {
3665
+ if (v.type !== "Identifier") {
3666
+ throw new Error("VMify: generic-for variables must be plain identifiers");
3667
+ }
3668
+ const bid = v.bindingId;
3669
+ if (bid != null && state.boxIndex.has(bid)) {
3670
+ throw new Error(
3671
+ `VMify: closures capturing the for-in variable '${v.name}' are not supported yet \u2014 this VM does not give loop variables a fresh binding per iteration. Copy it into a plain local declared inside the loop body first and capture that.`
3672
+ );
3673
+ }
3674
+ const r = bid != null ? state.regs.get(bid) : void 0;
3675
+ if (r === void 0) {
3676
+ throw new Error("VMify: internal error \u2014 generic-for variable was not hoisted to a register");
3677
+ }
3678
+ return r;
3679
+ });
3680
+ const loopStart = state.code.length;
3681
+ state.allocator.reset(state.regFloor);
3682
+ const callFunc = state.allocator.alloc();
3683
+ state.code.push({ op: state.opcodes.MOVE, a: callFunc, b: fReg, c: 0 });
3684
+ const argBase = state.allocator.alloc();
3685
+ state.code.push({ op: state.opcodes.MOVE, a: argBase, b: sReg, c: 0 });
3686
+ const argCtrl = state.allocator.alloc();
3687
+ state.code.push({ op: state.opcodes.MOVE, a: argCtrl, b: cReg, c: 0 });
3688
+ const retBase = state.allocator.alloc();
3689
+ for (let i = 1; i < varRegs.length; i++) state.allocator.alloc();
3690
+ state.code.push({
3691
+ op: state.opcodes.CALL,
3692
+ a: retBase,
3693
+ b: callFunc,
3694
+ c: argBase,
3695
+ nargs: 2,
3696
+ nret: Math.max(varRegs.length, 1)
3697
+ });
3698
+ varRegs.forEach((r, i) => {
3699
+ state.code.push({ op: state.opcodes.MOVE, a: r, b: retBase + i, c: 0 });
3700
+ });
3701
+ const nilReg = state.allocator.alloc();
3702
+ {
3703
+ const pc = state.code.length;
3704
+ state.code.push({ op: state.opcodes.LOADK, a: nilReg, b: keyedConst(state, pc, null), c: 0 });
3705
+ }
3706
+ const testReg = state.allocator.alloc();
3707
+ state.code.push({ op: state.opcodes.EQ, a: testReg, b: varRegs[0], c: nilReg });
3708
+ const exitJmp = push(state, { op: state.opcodes.JMPIF, a: testReg, b: -1, c: 0 });
3709
+ state.code.push({ op: state.opcodes.MOVE, a: cReg, b: varRegs[0], c: 0 });
3710
+ state.loopStack.push({ continueJumps: [], breakJumps: [] });
3711
+ for (const s of stmt.body) {
3712
+ state.allocator.reset(state.regFloor);
3713
+ compileStatement(s, state);
3714
+ }
3715
+ const backJmp = push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 });
3716
+ patchJumpTarget(state, backJmp, loopStart);
3717
+ const exitPc = state.code.length;
3718
+ patchJumpTarget(state, exitJmp, exitPc);
3719
+ const frame = state.loopStack.pop();
3720
+ for (const j of frame.continueJumps) patchJumpTarget(state, j, loopStart);
3721
+ for (const j of frame.breakJumps) patchJumpTarget(state, j, exitPc);
3722
+ state.regFloor = outerFloor;
3723
+ }
3724
+ function compileStatement(stmt, state) {
3725
+ switch (stmt.type) {
3726
+ case "AssignmentStatement":
3727
+ compileAssignment(stmt, state);
3728
+ return;
3729
+ case "CompoundAssignmentStatement":
3730
+ state.allocator.reset(state.regFloor);
3731
+ compileCompoundAssignment(stmt, state);
3732
+ return;
3733
+ case "CallStatement": {
3734
+ const call = stmt.expression;
3735
+ const asCall = call.type === "CallExpression" ? call : call.type === "TableCallExpression" ? callExpr(call.base, [call.arguments[0]]) : call.type === "StringCallExpression" ? callExpr(call.base, [call.argument]) : (() => {
3736
+ throw new Error(`VMify: unsupported call statement form '${call.type}'`);
3737
+ })();
3738
+ state.allocator.reset(state.regFloor);
3739
+ const scratch = state.allocator.alloc();
3740
+ compileCallInto(asCall, state, scratch);
3741
+ return;
3742
+ }
3743
+ case "IfStatement":
3744
+ compileIf(stmt, state);
3745
+ return;
3746
+ case "WhileStatement":
3747
+ compileWhile(stmt, state);
3748
+ return;
3749
+ case "RepeatStatement":
3750
+ compileRepeat(stmt, state);
3751
+ return;
3752
+ case "ForNumericStatement":
3753
+ compileForNumeric(stmt, state);
3754
+ return;
3755
+ case "ForGenericStatement":
3756
+ compileForGeneric(stmt, state);
3757
+ return;
3758
+ case "DoStatement":
3759
+ for (const s of stmt.body) {
3760
+ state.allocator.reset(state.regFloor);
3761
+ compileStatement(s, state);
3762
+ }
3763
+ return;
3764
+ case "BreakStatement": {
3765
+ const frame = state.loopStack[state.loopStack.length - 1];
3766
+ if (!frame) {
3767
+ throw new Error("VMify: break used outside of a loop");
3768
+ }
3769
+ frame.breakJumps.push(push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 }));
3770
+ return;
3771
+ }
3772
+ case "ContinueStatement": {
3773
+ const frame = state.loopStack[state.loopStack.length - 1];
3774
+ if (!frame) {
3775
+ throw new Error("VMify: continue used outside of a loop");
3776
+ }
3777
+ frame.continueJumps.push(push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 }));
3778
+ return;
3779
+ }
3780
+ case "GotoStatement": {
3781
+ const pc = push(state, { op: state.opcodes.JMP, a: 0, b: -1, c: 0 });
3782
+ state.pendingGotos.push({ name: stmt.label, pc });
3783
+ return;
3784
+ }
3785
+ case "LabelStatement": {
3786
+ if (state.labels.has(stmt.name)) {
3787
+ throw new Error(`VMify: duplicate label '${stmt.name}'`);
3788
+ }
3789
+ state.labels.set(stmt.name, state.code.length);
3790
+ return;
3791
+ }
3792
+ case "FunctionDeclaration": {
3793
+ if (stmt.identifier === null) {
3794
+ throw new Error("VMify: internal error \u2014 anonymous FunctionDeclaration reached compileStatement");
3795
+ }
3796
+ const anon = { ...stmt, identifier: null, isLocal: false };
3797
+ const tmp = state.allocator.alloc();
3798
+ compileExpression(anon, state, tmp);
3799
+ writeAssignTarget(stmt.identifier, tmp, state);
3800
+ return;
3801
+ }
3802
+ default:
3803
+ throw new Error(
3804
+ `VMify: unsupported statement type '${stmt.type}'`
3805
+ );
3806
+ }
3807
+ }
3808
+ function buildPoolDecl(poolName, pool) {
3809
+ return localStmt(
3810
+ [ident(poolName)],
3811
+ [tableCtor(pool.values.map((v) => tableValue(constNodeFor(v))))]
3812
+ );
3813
+ }
3814
+ function buildUpvalsDecl(upvalsName, count) {
3815
+ const box = () => tableCtor([
3816
+ {
3817
+ type: "TableKeyString",
3818
+ key: { type: "Identifier", name: "v", attribute: null, typeAnnotation: null, scope: "global", isField: true, bindingId: null, range: [0, 0], loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } } },
3819
+ value: nilLit(),
3820
+ range: [0, 0],
3821
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } }
3822
+ }
3823
+ ]);
3824
+ return localStmt(
3825
+ [ident(upvalsName)],
3826
+ [tableCtor(Array.from({ length: count }, box).map(tableValue))]
3827
+ );
3828
+ }
3829
+ function buildRawPoolDecl(rawPoolName, entries) {
3830
+ return localStmt(
3831
+ [ident(rawPoolName)],
3832
+ [tableCtor(entries.map(tableValue))]
3833
+ );
3834
+ }
3835
+ var DUMMY_RANGE2 = [0, 0];
3836
+ var DUMMY_LOC2 = { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
3837
+ function buildEnvDecl(envName, usedGlobals) {
3838
+ const fields = Array.from(usedGlobals).map((name) => ({
3839
+ type: "TableKeyString",
3840
+ key: {
3841
+ type: "Identifier",
3842
+ name,
3843
+ attribute: null,
3844
+ typeAnnotation: null,
3845
+ scope: "global",
3846
+ isField: true,
3847
+ bindingId: null,
3848
+ range: DUMMY_RANGE2,
3849
+ loc: DUMMY_LOC2
3850
+ },
3851
+ value: {
3852
+ type: "Identifier",
3853
+ name,
3854
+ attribute: null,
3855
+ typeAnnotation: null,
3856
+ scope: "global",
3857
+ isField: false,
3858
+ bindingId: null,
3859
+ range: DUMMY_RANGE2,
3860
+ loc: DUMMY_LOC2
3861
+ },
3862
+ range: DUMMY_RANGE2,
3863
+ loc: DUMMY_LOC2
3864
+ }));
3865
+ return localStmt([ident(envName)], [tableCtor(fields)]);
3866
+ }
3867
+ function buildCodeDecl(codeName, code) {
3868
+ return localStmt(
3869
+ [ident(codeName)],
3870
+ [
3871
+ tableCtor(
3872
+ code.map(
3873
+ (ins) => tableValue(
3874
+ tableCtor([
3875
+ tableValue(numLit(ins.op)),
3876
+ tableValue(numLit(ins.a)),
3877
+ tableValue(numLit(ins.b)),
3878
+ tableValue(numLit(ins.c)),
3879
+ tableValue(numLit(ins.nargs ?? 0)),
3880
+ tableValue(numLit(ins.spreadKind ?? 0)),
3881
+ tableValue(numLit(ins.nret ?? 1)),
3882
+ tableValue(numLit(ins.captureMultret ? 1 : 0))
3883
+ ])
3884
+ )
3885
+ )
3886
+ )
3887
+ ]
3888
+ );
3889
+ }
3890
+ function buildRuntimeSource(names, opcodes, keySeed) {
3891
+ const {
3892
+ poolName,
3893
+ codeName,
3894
+ rawPoolName,
3895
+ upvalsName,
3896
+ xorName,
3897
+ keyName,
3898
+ regsName,
3899
+ pcName,
3900
+ insName,
3901
+ opName,
3902
+ aName,
3903
+ bName,
3904
+ cName,
3905
+ nName,
3906
+ argsName,
3907
+ iName,
3908
+ bucketName,
3909
+ vaName,
3910
+ jumpedName,
3911
+ envName,
3912
+ mrName
3913
+ } = names;
3914
+ const BUCKETS = 4;
3915
+ const buckets = Array.from({ length: BUCKETS }, () => []);
3916
+ for (const name of OPCODE_NAMES) {
3917
+ buckets[opcodes[name] % BUCKETS].push(name);
3918
+ }
3919
+ const bReg = `(${bName} + 1)`;
3920
+ const keyVal = `${keyName}_v`;
3921
+ function branchFor(name) {
3922
+ switch (name) {
3923
+ case "MOVE":
3924
+ return `${regsName}[${aName}] = ${regsName}[${bReg}]`;
3925
+ case "LOADK":
3926
+ return `${regsName}[${aName}] = ${poolName}[${xorName}(${bName}, ${keyVal}) + 1]`;
3927
+ case "GETGLOBAL":
3928
+ return `${regsName}[${aName}] = ${envName}[${poolName}[${xorName}(${bName}, ${keyVal}) + 1]]`;
3929
+ case "SETGLOBAL":
3930
+ return `${envName}[${poolName}[${xorName}(${bName}, ${keyVal}) + 1]] = ${regsName}[${aName}]`;
3931
+ case "GETINDEX":
3932
+ return `${regsName}[${aName}] = ${regsName}[${bReg}][${regsName}[${cName}]]`;
3933
+ case "SETINDEX":
3934
+ return `${regsName}[${aName}][${regsName}[${bReg}]] = ${regsName}[${cName}]`;
3935
+ case "NEWTABLE":
3936
+ return `${regsName}[${aName}] = {}`;
3937
+ case "ADD":
3938
+ return `${regsName}[${aName}] = ${regsName}[${bReg}] + ${regsName}[${cName}]`;
3939
+ case "SUB":
3940
+ return `${regsName}[${aName}] = ${regsName}[${bReg}] - ${regsName}[${cName}]`;
3941
+ case "MUL":
3942
+ return `${regsName}[${aName}] = ${regsName}[${bReg}] * ${regsName}[${cName}]`;
3943
+ case "DIV":
3944
+ return `${regsName}[${aName}] = ${regsName}[${bReg}] / ${regsName}[${cName}]`;
3945
+ case "IDIV":
3946
+ return `${regsName}[${aName}] = math.floor(${regsName}[${bReg}] / ${regsName}[${cName}])`;
3947
+ case "MOD":
3948
+ return `${regsName}[${aName}] = ${regsName}[${bReg}] % ${regsName}[${cName}]`;
3949
+ case "POW":
3950
+ return `${regsName}[${aName}] = ${regsName}[${bReg}] ^ ${regsName}[${cName}]`;
3951
+ case "CONCAT":
3952
+ return `${regsName}[${aName}] = ${regsName}[${bReg}] .. ${regsName}[${cName}]`;
3953
+ case "UNM":
3954
+ return `${regsName}[${aName}] = -${regsName}[${bReg}]`;
3955
+ case "NOT":
3956
+ return `${regsName}[${aName}] = not ${regsName}[${bReg}]`;
3957
+ case "LEN":
3958
+ return `${regsName}[${aName}] = #${regsName}[${bReg}]`;
3959
+ case "EQ":
3960
+ return `${regsName}[${aName}] = (${regsName}[${bReg}] == ${regsName}[${cName}])`;
3961
+ case "LT":
3962
+ return `${regsName}[${aName}] = (${regsName}[${bReg}] < ${regsName}[${cName}])`;
3963
+ case "LE":
3964
+ return `${regsName}[${aName}] = (${regsName}[${bReg}] <= ${regsName}[${cName}])`;
3965
+ case "JMP":
3966
+ return `${pcName} = ${xorName}(${bName}, ${keyVal})
3967
+ ${jumpedName} = true`;
3968
+ case "JMPIF":
3969
+ return `if ${regsName}[${aName}] then
3970
+ ${pcName} = ${xorName}(${bName}, ${keyVal})
3971
+ ${jumpedName} = true
3972
+ end`;
3973
+ case "JMPIFNOT":
3974
+ return `if not ${regsName}[${aName}] then
3975
+ ${pcName} = ${xorName}(${bName}, ${keyVal})
3976
+ ${jumpedName} = true
3977
+ end`;
3978
+ case "VARARG":
3979
+ return `${regsName}[${aName}] = ${vaName}[1]`;
3980
+ case "TOSTRING":
3981
+ return `${regsName}[${aName}] = tostring(${regsName}[${bReg}])`;
3982
+ case "GETUPVAL":
3983
+ return `${regsName}[${aName}] = ${upvalsName}[${bName} + 1].v`;
3984
+ case "SETUPVAL":
3985
+ return `${upvalsName}[${aName}].v = ${regsName}[${bReg}]`;
3986
+ case "LOADRAW":
3987
+ return `${regsName}[${aName}] = ${rawPoolName}[${bName} + 1]`;
3988
+ case "SPREADVARARG":
3989
+ return `for __si = 1, #${vaName} do ${regsName}[${aName}][${bName} + __si - 1] = ${vaName}[__si] end`;
3990
+ case "SPREADMULTRET":
3991
+ return `for __si = 1, #${mrName} do ${regsName}[${aName}][${bName} + __si - 1] = ${mrName}[__si] end`;
3992
+ case "CALL": {
3993
+ return `local ${argsName} = {}
3994
+ local ${iName}n = ${nName}
3995
+ for ${iName} = 1, ${nName} do ${argsName}[${iName}] = ${regsName}[${cName} + ${iName} - 1] end
3996
+ if ${insName}[6] == 1 then
3997
+ for ${iName} = 1, #${vaName} do ${argsName}[${nName} + ${iName}] = ${vaName}[${iName}] end
3998
+ ${iName}n = ${nName} + #${vaName}
3999
+ elseif ${insName}[6] == 2 then
4000
+ for ${iName} = 1, #${mrName} do ${argsName}[${nName} + ${iName}] = ${mrName}[${iName}] end
4001
+ ${iName}n = ${nName} + #${mrName}
4002
+ end
4003
+ local __rets = { ${regsName}[${bReg}](table.unpack(${argsName}, 1, ${iName}n)) }
4004
+ if ${insName}[8] == 1 then ${mrName} = __rets end
4005
+ local __nret = ${insName}[7] or 1
4006
+ for __ri = 1, __nret do ${regsName}[${aName} + __ri - 1] = __rets[__ri] end`;
4007
+ }
4008
+ case "RETURN":
4009
+ return `do
4010
+ if ${insName}[6] == 1 then
4011
+ local __rt = {}
4012
+ for __i = 1, ${nName} do __rt[__i] = ${regsName}[__i] end
4013
+ for __i = 1, #${vaName} do __rt[${nName} + __i] = ${vaName}[__i] end
4014
+ return table.unpack(__rt, 1, ${nName} + #${vaName})
4015
+ elseif ${insName}[6] == 2 then
4016
+ local __rt = {}
4017
+ for __i = 1, ${nName} do __rt[__i] = ${regsName}[__i] end
4018
+ for __i = 1, #${mrName} do __rt[${nName} + __i] = ${mrName}[__i] end
4019
+ return table.unpack(__rt, 1, ${nName} + #${mrName})
4020
+ else
4021
+ if ${nName} == 0 then return end
4022
+ return table.unpack(${regsName}, 1, ${nName})
4023
+ end
4024
+ end`;
4025
+ case "NOP":
4026
+ case "XOR":
4027
+ return `${regsName}[${aName}] = ${xorName}(${regsName}[${bReg}], ${regsName}[${cName}])`;
4028
+ default:
4029
+ throw new Error(`VMify: no runtime codegen for opcode '${name}'`);
4030
+ }
4031
+ }
4032
+ const bucketBlocks = buckets.map((names2, idx) => {
4033
+ const arms = names2.map(
4034
+ (n) => ` if ${opName} == ${opcodes[n]} then
4035
+ ${branchFor(n)}
4036
+ end`
4037
+ ).join("\n");
4038
+ return ` if ${bucketName} == ${idx} then
4039
+ ` + arms + `
4040
+ end`;
4041
+ }).join("\n");
4042
+ return `
4043
+ local function ${xorName}(a, b)
4044
+ local r = 0
4045
+ local bit = 1
4046
+ while a > 0 or b > 0 do
4047
+ local aa = a % 2
4048
+ local bb = b % 2
4049
+ if aa ~= bb then
4050
+ r = r + bit
4051
+ end
4052
+ a = math.floor(a / 2)
4053
+ b = math.floor(b / 2)
4054
+ bit = bit * 2
4055
+ end
4056
+ return r
4057
+ end
4058
+
4059
+ -- Reproduces the compile-time key stream: keyAt(seed, pc) in vmify.ts.
4060
+ local function ${keyName}(pc)
4061
+ local x = (${keySeed} + pc * 2654435761) % 4294967296
4062
+ x = (x * 1664525 + 1013904223) % 4294967296
4063
+ return (x % 251) + 1
4064
+ end
4065
+
4066
+ local ${vaName} = { ... }
4067
+ -- Shared "most recent captured call" return-value buffer, used to expand
4068
+ -- a plain call (not '...') that sits in the LAST position of a call's
4069
+ -- argument list or a return statement into ALL of its return values
4070
+ -- (spreadKind 2 \u2014 see the Instr.spreadKind doc in vmify.ts).
4071
+ local ${mrName} = {}
4072
+
4073
+ local ${regsName} = {}
4074
+ local ${pcName} = 0
4075
+
4076
+ while true do
4077
+ local ${insName} = ${codeName}[${pcName} + 1]
4078
+ if not ${insName} then break end
4079
+
4080
+ local ${opName} = ${insName}[1]
4081
+ local ${aName} = ${insName}[2] + 1
4082
+ local ${bName} = ${insName}[3]
4083
+ local ${cName} = ${insName}[4] + 1
4084
+ local ${nName} = ${insName}[5]
4085
+ local ${keyName}_v = ${keyName}(${pcName})
4086
+ local ${bucketName} = ${opName} % ${BUCKETS}
4087
+ local ${jumpedName} = false
4088
+
4089
+ ${bucketBlocks}
4090
+
4091
+ if not ${jumpedName} then ${pcName} = ${pcName} + 1 end
4092
+ end
4093
+ `;
4094
+ }
4095
+ var vmify = (chunk, ctx) => {
4096
+ const neededBoxes = computeNeededBoxes(chunk.body);
4097
+ const body = chunk.body.slice();
4098
+ if (body.length === 0) return chunk;
4099
+ let trailingReturn = null;
4100
+ if (body[body.length - 1].type === "ReturnStatement") {
4101
+ trailingReturn = body.pop();
4102
+ }
4103
+ const hoisted = [];
4104
+ const stmts = hoistAll(body, hoisted);
4105
+ const regs = /* @__PURE__ */ new Map();
4106
+ const boxIndex = /* @__PURE__ */ new Map();
4107
+ for (const id of hoisted) {
4108
+ const bid = id.bindingId;
4109
+ if (neededBoxes.has(bid)) {
4110
+ boxIndex.set(bid, boxIndex.size);
4111
+ } else {
4112
+ regs.set(bid, regs.size);
4113
+ }
4114
+ }
4115
+ const opcodes = buildShuffledOpcodes();
4116
+ const keySeed = randInt(1, 2147483647);
4117
+ const pool = new ConstantPool();
4118
+ const allocator = new RegisterAllocator();
4119
+ const used = /* @__PURE__ */ new Set();
4120
+ const upvalsName = randomVarName(used);
4121
+ const state = {
4122
+ pool,
4123
+ regs,
4124
+ boxIndex,
4125
+ rawPool: [],
4126
+ upvalsName,
4127
+ allocator,
4128
+ regFloor: regs.size,
4129
+ opcodes,
4130
+ keySeed,
4131
+ code: [],
4132
+ loopStack: [],
4133
+ labels: /* @__PURE__ */ new Map(),
4134
+ pendingGotos: [],
4135
+ usedGlobals: /* @__PURE__ */ new Set()
4136
+ };
4137
+ for (const stmt of stmts) {
4138
+ allocator.reset(regs.size);
4139
+ compileStatement(stmt, state);
4140
+ }
4141
+ if (trailingReturn) {
4142
+ const args = trailingReturn.arguments ?? [];
4143
+ const lastArg = args[args.length - 1];
4144
+ const varargSpread = args.length > 0 && lastArg.type === "VarargLiteral";
4145
+ const callSpread = !varargSpread && args.length > 0 && lastArg.type === "CallExpression";
4146
+ const fixedArgs = varargSpread || callSpread ? args.slice(0, -1) : args;
4147
+ fixedArgs.forEach((arg, i) => compileExpression(arg, state, i));
4148
+ let spreadKind = 0;
4149
+ if (varargSpread) {
4150
+ spreadKind = 1;
4151
+ } else if (callSpread) {
4152
+ state.allocator.reset(Math.max(state.allocator.high(), fixedArgs.length));
4153
+ const scratch = state.allocator.alloc();
4154
+ compileCallIntoWithCapture(lastArg, state, scratch);
4155
+ spreadKind = 2;
4156
+ }
4157
+ state.code.push({ op: opcodes.RETURN, a: 0, b: 0, c: 0, nargs: fixedArgs.length, spreadKind });
4158
+ }
4159
+ for (const { name, pc } of state.pendingGotos) {
4160
+ const dest = state.labels.get(name);
4161
+ if (dest === void 0) {
4162
+ throw new Error(`VMify: goto references undefined label '${name}'`);
4163
+ }
4164
+ patchJumpTarget(state, pc, dest);
4165
+ }
4166
+ const names = {
4167
+ poolName: randomVarName(used),
4168
+ codeName: randomVarName(used),
4169
+ rawPoolName: randomVarName(used),
4170
+ upvalsName: state.upvalsName,
4171
+ xorName: randomVarName(used),
4172
+ keyName: randomVarName(used),
4173
+ regsName: randomVarName(used),
4174
+ pcName: randomVarName(used),
4175
+ insName: randomVarName(used),
4176
+ opName: randomVarName(used),
4177
+ aName: randomVarName(used),
4178
+ bName: randomVarName(used),
4179
+ cName: randomVarName(used),
4180
+ nName: randomVarName(used),
4181
+ argsName: randomVarName(used),
4182
+ iName: randomVarName(used),
4183
+ bucketName: randomVarName(used),
4184
+ vaName: randomVarName(used),
4185
+ jumpedName: randomVarName(used),
4186
+ envName: randomVarName(used),
4187
+ mrName: randomVarName(used)
4188
+ };
4189
+ const poolDecl = buildPoolDecl(names.poolName, pool);
4190
+ const codeDecl = buildCodeDecl(names.codeName, state.code);
4191
+ const envDecl = buildEnvDecl(names.envName, state.usedGlobals);
4192
+ const upvalsDecl = buildUpvalsDecl(names.upvalsName, boxIndex.size);
4193
+ const rawPoolDecl = buildRawPoolDecl(names.rawPoolName, state.rawPool);
4194
+ const dialect = ctx.dialect.name;
4195
+ const runtimeSource = buildRuntimeSource(names, opcodes, keySeed);
4196
+ const runtimeChunk = parseSnippet(runtimeSource, dialect);
4197
+ const VMIFY_DEBUG = false;
4198
+ if (VMIFY_DEBUG) {
4199
+ const inv = {};
4200
+ for (const k of Object.keys(opcodes)) inv[opcodes[k]] = k;
4201
+ state.code.forEach((ins, pc) => {
4202
+ console.error(
4203
+ `pc=${pc} ${inv[ins.op]}(${ins.op}) a=${ins.a} b=${ins.b} c=${ins.c} nargs=${ins.nargs} spreadKind=${ins.spreadKind} nret=${ins.nret} captureMultret=${ins.captureMultret}`
4204
+ );
4205
+ });
4206
+ }
4207
+ const finalChunk = {
4208
+ ...chunk,
4209
+ body: [
4210
+ poolDecl,
4211
+ codeDecl,
4212
+ envDecl,
4213
+ upvalsDecl,
4214
+ rawPoolDecl,
4215
+ ...runtimeChunk.body
4216
+ ]
4217
+ };
4218
+ resolveScopes(finalChunk);
4219
+ return finalChunk;
4220
+ };
4221
+
4222
+ // src/passes/junk.ts
4223
+ var dummyLoc = { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
4224
+ var dummyRange = [0, 0];
4225
+ var numLiteral = (value) => ({
4226
+ type: "NumericLiteral",
4227
+ value,
4228
+ raw: value.toString(),
4229
+ range: dummyRange,
4230
+ loc: dummyLoc
4231
+ });
4232
+ var JUNK_TEMPLATES = [
4233
+ // 1. 의미 없는 local
4234
+ () => {
4235
+ const name = randomVarName(/* @__PURE__ */ new Set());
4236
+ return localStmt([ident(name)], [numLiteral(randInt(100, 99999))]);
4237
+ },
4238
+ // 2. 의미 없는 연산
4239
+ () => {
4240
+ const name = randomVarName(/* @__PURE__ */ new Set());
4241
+ return assignStmt([ident(name)], [{
4242
+ type: "BinaryExpression",
4243
+ operator: choice(["+", "-", "*", "//", "%", "^"]),
4244
+ left: numLiteral(randInt(10, 500)),
4245
+ right: numLiteral(randInt(10, 500)),
4246
+ range: dummyRange,
4247
+ loc: dummyLoc
4248
+ }]);
4249
+ },
4250
+ // 3. 항상 false인 if
4251
+ () => {
4252
+ const dummyName = randomVarName(/* @__PURE__ */ new Set());
4253
+ return {
4254
+ type: "IfStatement",
4255
+ clauses: [{
4256
+ type: "IfClause",
4257
+ condition: {
4258
+ type: "BinaryExpression",
4259
+ operator: ">",
4260
+ left: numLiteral(randInt(100, 400)),
4261
+ right: numLiteral(randInt(500, 999)),
4262
+ range: dummyRange,
4263
+ loc: dummyLoc
4264
+ },
4265
+ body: [localStmt([ident(dummyName)], [{ type: "NilLiteral", range: dummyRange, loc: dummyLoc }])],
4266
+ range: dummyRange,
4267
+ loc: dummyLoc
4268
+ }],
4269
+ range: dummyRange,
4270
+ loc: dummyLoc
4271
+ };
4272
+ },
4273
+ // 4. pcall junk
4274
+ () => ({
4275
+ type: "CallStatement",
4276
+ expression: callExpr(ident("pcall"), [funcExpr([], [])]),
4277
+ range: dummyRange,
4278
+ loc: dummyLoc
4279
+ })
4280
+ ];
4281
+ var insertJunk = (chunk, ctx, options) => {
4282
+ const probability = options.probability ?? 0.33;
4283
+ const maxPerBlock = options.maxPerBlock ?? 30;
4284
+ let junkCount = 0;
4285
+ const visit = (stmts) => {
4286
+ const result = [];
4287
+ for (const stmt of stmts) {
4288
+ if (junkCount < maxPerBlock && Math.random() < probability) {
4289
+ result.push(choice(JUNK_TEMPLATES)());
4290
+ junkCount++;
4291
+ }
4292
+ result.push(stmt);
4293
+ if ("body" in stmt && Array.isArray(stmt.body)) {
4294
+ stmt.body = visit(stmt.body);
4295
+ }
4296
+ if (stmt.type === "IfStatement") {
4297
+ const ifStmt = stmt;
4298
+ for (const clause of ifStmt.clauses) {
4299
+ if ("body" in clause && Array.isArray(clause.body)) {
4300
+ clause.body = visit(clause.body);
4301
+ }
4302
+ }
4303
+ }
4304
+ }
4305
+ return result;
4306
+ };
4307
+ chunk.body = visit(chunk.body);
4308
+ return chunk;
4309
+ };
4310
+
4311
+ // src/passes/global-mapping.ts
4312
+ var dummyRange2 = [0, 0];
4313
+ var dummyLoc2 = {
4314
+ start: { line: 0, column: 0 },
4315
+ end: { line: 0, column: 0 }
4316
+ };
4317
+ var globalMapping = (chunk, ctx, options = {}) => {
4318
+ const globalTableName = options.globalTableName ?? "G";
4319
+ const keyMap = /* @__PURE__ */ new Map();
4320
+ resolveScopes(chunk);
4321
+ const transform = (node) => {
4322
+ if (!node || typeof node !== "object") return node;
4323
+ if (node.type === "Identifier" && node.name) {
4324
+ if (node.isField || node.scope !== "global") {
4325
+ for (const key2 in node) {
4326
+ if (key2 === "range" || key2 === "loc") continue;
4327
+ if (Array.isArray(node[key2])) {
4328
+ node[key2] = node[key2].map(transform);
4329
+ } else {
4330
+ node[key2] = transform(node[key2]);
4331
+ }
4332
+ }
4333
+ return node;
4334
+ }
4335
+ if (!keyMap.has(node.name)) {
4336
+ keyMap.set(node.name, randomKey(5));
4337
+ }
4338
+ const key = keyMap.get(node.name);
4339
+ return {
4340
+ type: "IndexExpression",
4341
+ base: ident(globalTableName),
4342
+ index: {
4343
+ type: "StringLiteral",
4344
+ value: key,
4345
+ raw: `"${key}"`,
4346
+ range: dummyRange2,
4347
+ loc: dummyLoc2
4348
+ },
4349
+ range: dummyRange2,
4350
+ loc: dummyLoc2
4351
+ };
4352
+ }
4353
+ for (const key in node) {
4354
+ if (key === "range" || key === "loc") continue;
4355
+ if (Array.isArray(node[key])) {
4356
+ node[key] = node[key].map(transform);
4357
+ } else {
4358
+ node[key] = transform(node[key]);
4359
+ }
4360
+ }
4361
+ return node;
4362
+ };
4363
+ transform(chunk);
4364
+ const globalFields = Array.from(keyMap.entries()).map(([name, key]) => ({
4365
+ type: "TableKeyString",
4366
+ key: {
4367
+ type: "Identifier",
4368
+ name: key,
4369
+ attribute: null,
4370
+ typeAnnotation: null,
4371
+ scope: "global",
4372
+ isField: true,
4373
+ bindingId: null,
4374
+ range: dummyRange2,
4375
+ loc: dummyLoc2
4376
+ },
4377
+ value: {
4378
+ type: "Identifier",
4379
+ name,
4380
+ attribute: null,
4381
+ typeAnnotation: null,
4382
+ scope: "global",
4383
+ isField: false,
4384
+ bindingId: null,
4385
+ range: dummyRange2,
4386
+ loc: dummyLoc2
4387
+ },
4388
+ range: dummyRange2,
4389
+ loc: dummyLoc2
4390
+ }));
4391
+ const globalTable = {
4392
+ type: "LocalStatement",
4393
+ variables: [{
4394
+ type: "Identifier",
4395
+ name: globalTableName,
4396
+ attribute: null,
4397
+ typeAnnotation: null,
4398
+ scope: "local",
4399
+ isField: false,
4400
+ bindingId: null,
4401
+ range: dummyRange2,
4402
+ loc: dummyLoc2
4403
+ }],
4404
+ init: [{
4405
+ type: "TableConstructorExpression",
4406
+ fields: globalFields,
4407
+ range: dummyRange2,
4408
+ loc: dummyLoc2
4409
+ }],
4410
+ range: dummyRange2,
4411
+ loc: dummyLoc2
4412
+ };
4413
+ if (!chunk.body) chunk.body = [];
4414
+ chunk.body.unshift(globalTable);
4415
+ return chunk;
4416
+ };
4417
+
4418
+ // src/passes/wrap-function.ts
4419
+ var wrapInFunction = (chunk, ctx) => {
4420
+ const fn = funcExpr([varargParam()], chunk.body, true);
4421
+ const call = callExpr(paren(fn), [varargParam()]);
4422
+ chunk.body = [returnStmt([call])];
4423
+ return chunk;
4424
+ };
4425
+
4426
+ // src/passes/encrypt-numbers.ts
4427
+ function buildDecryptHelper2(fnName, keyName, key, dialect) {
4428
+ const keyLiteral = `{${key.join(", ")}}`;
4429
+ const src = `
4430
+ local ${keyName} = ${keyLiteral}
4431
+ local function ${fnName}(_enc, _slot)
4432
+ local _k = ${keyName}[((_slot - 1) % #${keyName}) + 1]
4433
+ return _enc - _k
4434
+ end
4435
+ `;
4436
+ return parseSnippet(src, dialect.name).body;
4437
+ }
4438
+ var encryptNumbers = (chunk, ctx) => {
4439
+ const key = Array.from({ length: randInt(4, 8) }, () => randInt(1e3, 999999));
4440
+ const names = /* @__PURE__ */ new Set();
4441
+ const fnName = randomVarName(names);
4442
+ const keyName = randomVarName(names);
4443
+ let slot = 0;
4444
+ let touched = false;
4445
+ transformExpressions(chunk, (expr) => {
4446
+ if (expr.type === "NumericLiteral" && Number.isFinite(expr.value)) {
4447
+ touched = true;
4448
+ slot += 1;
4449
+ const k = key[(slot - 1) % key.length];
4450
+ return callExpr(ident(fnName), [numLit(expr.value + k), numLit(slot)]);
4451
+ }
4452
+ return null;
4453
+ });
4454
+ if (touched) {
4455
+ const helper = buildDecryptHelper2(fnName, keyName, key, ctx.dialect);
4456
+ chunk.body = [...helper, ...chunk.body];
4457
+ }
4458
+ return chunk;
4459
+ };
4460
+
4461
+ // src/obfuscate.ts
4462
+ function obfuscate(source, dialect, options) {
4463
+ const flags = resolveDialect(dialect);
4464
+ const ctx = { dialect: flags };
4465
+ const tokens = new Lexer(source, flags).tokenize();
4466
+ let chunk = new Parser(tokens, flags).parseChunk();
4467
+ resolveScopes(chunk);
4468
+ for (const step of options.steps) {
4469
+ chunk = runStep(chunk, ctx, step);
4470
+ }
4471
+ return generate(chunk, { minify: options.minify });
4472
+ }
4473
+ function runStep(chunk, ctx, step) {
4474
+ switch (step.name) {
4475
+ case "RenameVariables":
4476
+ return renameVariables(chunk, ctx, {});
4477
+ case "NumbersToExpressions":
4478
+ return numbersToExpressions(chunk, ctx, { min: step.min, max: step.max });
4479
+ case "StringsToExpressions":
4480
+ return stringsToExpressions(chunk, ctx, { min: step.min, max: step.max });
4481
+ case "EncryptStrings":
4482
+ return encryptStrings(chunk, ctx, {});
4483
+ case "EncryptNumbers":
4484
+ return encryptNumbers(chunk, ctx, {});
4485
+ case "ConstantArray":
4486
+ return constantArray(chunk, ctx, {});
4487
+ case "Vmify":
4488
+ return vmify(chunk, ctx, {});
4489
+ case "InsertJunk":
4490
+ return insertJunk(chunk, ctx, { probability: step.probability, maxPerBlock: step.maxPerBlock });
4491
+ case "GlobalMapping":
4492
+ return globalMapping(chunk, ctx, { globalTableName: step.globalTableName });
4493
+ case "WrapInFunction":
4494
+ return wrapInFunction(chunk, ctx, {});
4495
+ default: {
4496
+ const _exhaustive = step;
4497
+ throw new Error(`obfuscate: unknown step ${JSON.stringify(_exhaustive)}`);
4498
+ }
4499
+ }
4500
+ }
4501
+
4502
+ // src/index.ts
4503
+ function parse(source, dialect) {
4504
+ const flags = resolveDialect(dialect);
4505
+ const tokens = new Lexer(source, flags).tokenize();
4506
+ const parser = new Parser(tokens, flags);
4507
+ const chunk = parser.parseChunk();
4508
+ resolveScopes(chunk);
4509
+ return chunk;
79
4510
  }
4511
+ export {
4512
+ LexError,
4513
+ ParseError,
4514
+ UnknownDialectError,
4515
+ generate,
4516
+ obfuscate,
4517
+ parse,
4518
+ resolveScopes
4519
+ };