lua-obfuscator 1.0.3 → 3.0.0

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