@tilua/compiler 1.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.
package/dist/cli.cjs ADDED
@@ -0,0 +1,2344 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/cli.ts
5
+ var import_node_fs2 = require("fs");
6
+ var import_node_path3 = require("path");
7
+
8
+ // src/bundle.ts
9
+ var import_node_fs = require("fs");
10
+ var import_node_path2 = require("path");
11
+ var import_parser3 = require("@tilua/parser");
12
+ var import_luau_parser2 = require("luau-parser");
13
+
14
+ // src/config.ts
15
+ var import_node_path = require("path");
16
+ var import_parser = require("@tilua/parser");
17
+ function resolveConfig(input, entry2, cwd = process.cwd()) {
18
+ if (input === void 0) return (0, import_parser.findConfig)(entry2);
19
+ if (typeof input === "string") return (0, import_parser.loadConfig)((0, import_node_path.resolve)(cwd, input));
20
+ const path = (0, import_node_path.resolve)(cwd, "tilua.config.json");
21
+ const text = JSON.stringify(input, null, 2);
22
+ return (0, import_parser.loadConfig)(path, { readFile: (file) => file === path ? text : import_parser.nodeHost.readFile(file) });
23
+ }
24
+
25
+ // src/lower.ts
26
+ var import_parser2 = require("@tilua/parser");
27
+ var import_luau_parser = require("luau-parser");
28
+
29
+ // src/luau.ts
30
+ var NO_SPAN = { line: { start: 0, end: 0 }, column: { start: 0, end: 0 } };
31
+ var span = () => ({ line: { ...NO_SPAN.line }, column: { ...NO_SPAN.column } });
32
+ var identifier = (name) => ({ type: "Identifier", name, ...span() });
33
+ var nil = () => ({ type: "NilLiteral", ...span() });
34
+ var boolean = (value) => ({ type: "BooleanLiteral", value, ...span() });
35
+ var number = (value, raw = String(value)) => ({ type: "NumberLiteral", value, raw, ...span() });
36
+ var string = (value) => ({ type: "StringLiteral", value, raw: "", ...span() });
37
+ var member = (object, property) => isLuauName(property) ? { type: "MemberExpression", object, property: identifier(property), ...span() } : index(object, string(property));
38
+ var index = (object, key) => ({ type: "IndexExpression", object, index: key, ...span() });
39
+ var call = (callee, args2) => ({ type: "CallExpression", callee, arguments: args2, ...span() });
40
+ var methodCall = (object, method, args2) => ({ type: "MethodCallExpression", object, method: identifier(method), arguments: args2, ...span() });
41
+ var functionExpression = (func) => ({ type: "FunctionExpression", func, ...span() });
42
+ var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left, right, ...span() });
43
+ var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
44
+ var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
45
+ var table = (fields) => ({ type: "TableExpression", fields, ...span() });
46
+ var field = (key, value) => isLuauName(key) ? { type: "TableFieldNamed", name: identifier(key), value } : { type: "TableFieldComputed", key: string(key), value };
47
+ var block = (statements) => ({ type: "Block", statements, ...span() });
48
+ var local = (names, init) => ({
49
+ type: "LocalStatement",
50
+ names: names.map((name) => ({ type: "TypedIdentifier", name, ...span() })),
51
+ init,
52
+ ...span()
53
+ });
54
+ var assign = (targets, values) => ({ type: "AssignmentStatement", targets, values, ...span() });
55
+ var callStatement = (expression) => ({ type: "CallStatement", expression, ...span() });
56
+ var ifThen = (condition, body) => ({
57
+ type: "IfStatement",
58
+ clauses: [{ type: "IfClause", condition, body: block(body), ...span() }],
59
+ ...span()
60
+ });
61
+ var doBlock = (body) => ({ type: "DoStatement", body: block(body), ...span() });
62
+ var breakStatement = () => ({ type: "BreakStatement", ...span() });
63
+ var repeatUntil = (body, condition) => ({ type: "RepeatStatement", body: block(body), condition, ...span() });
64
+ var genericFor = (variables, iterators, body) => ({
65
+ type: "GenericForStatement",
66
+ variables: variables.map((name) => ({ type: "TypedIdentifier", name, ...span() })),
67
+ iterators,
68
+ body: block(body),
69
+ ...span()
70
+ });
71
+ var numericFor = (variable, start, end, body) => ({
72
+ type: "NumericForStatement",
73
+ variable: { type: "TypedIdentifier", name: variable, ...span() },
74
+ start,
75
+ end,
76
+ body: block(body),
77
+ ...span()
78
+ });
79
+ var returns = (args2) => ({ type: "ReturnStatement", arguments: args2, ...span() });
80
+ var functionBody = (params, statements, hasVarargs = false) => ({
81
+ type: "FunctionBody",
82
+ generics: [],
83
+ params: params.map((name) => ({ type: "FunctionParameter", name, ...span() })),
84
+ hasVarargs,
85
+ body: block(statements),
86
+ ...span()
87
+ });
88
+ var localFunction = (name, func) => ({ type: "LocalFunctionStatement", name: identifier(name), func, ...span() });
89
+ var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
90
+ "and",
91
+ "break",
92
+ "do",
93
+ "else",
94
+ "elseif",
95
+ "end",
96
+ "false",
97
+ "for",
98
+ "function",
99
+ "if",
100
+ "in",
101
+ "local",
102
+ "nil",
103
+ "not",
104
+ "or",
105
+ "repeat",
106
+ "return",
107
+ "then",
108
+ "true",
109
+ "until",
110
+ "while"
111
+ ]);
112
+ function isLuauName(name) {
113
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
114
+ }
115
+
116
+ // src/console.ts
117
+ var CONSOLE_RUNTIME = `
118
+ local __NAME__ = (function()
119
+ local console = {}
120
+ local MAX_DEPTH = 4
121
+ local ESCAPES = { ["\\n"] = "\\\\n", ["\\r"] = "\\\\r", ["\\t"] = "\\\\t", ["\\""] = "\\\\\\"", ["\\\\"] = "\\\\\\\\" }
122
+
123
+ local function quote(text)
124
+ return '"' .. text:gsub('[%c"\\\\]', function(c) return ESCAPES[c] or ("\\\\%d"):format(c:byte()) end) .. '"'
125
+ end
126
+
127
+ -- A table with 1..n and nothing else prints as a list, as it was written.
128
+ local function isArray(value)
129
+ local count = 0
130
+ for key in pairs(value) do
131
+ if type(key) ~= "number" then return false end
132
+ count = count + 1
133
+ end
134
+ return count == #value
135
+ end
136
+
137
+ local function isIdentifier(key)
138
+ return type(key) == "string" and key:match("^[A-Za-z_][A-Za-z0-9_]*$") ~= nil
139
+ end
140
+
141
+ local render
142
+
143
+ local function renderTable(value, meta, seen, depth, indent)
144
+ if seen[value] then return "<cycle>" end
145
+ if depth > MAX_DEPTH then return "..." end
146
+ seen[value] = true
147
+ local inner = indent .. " "
148
+ local parts = {}
149
+ if isArray(value) then
150
+ for i = 1, #value do
151
+ parts[#parts + 1] = inner .. render(value[i], nil, seen, depth + 1, inner)
152
+ end
153
+ else
154
+ local keys = {}
155
+ for key in pairs(value) do keys[#keys + 1] = key end
156
+ table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
157
+ for _, key in ipairs(keys) do
158
+ local name = isIdentifier(key) and key or ("[" .. render(key, nil, seen, depth + 1, inner) .. "]")
159
+ parts[#parts + 1] = inner .. name .. " = " .. render(value[key], nil, seen, depth + 1, inner)
160
+ end
161
+ end
162
+ seen[value] = nil
163
+ if #parts == 0 then return "{}" end
164
+ local open = isArray(value) and "[" or "{"
165
+ local close = isArray(value) and "]" or "}"
166
+ return open .. "\\n" .. table.concat(parts, ",\\n") .. "\\n" .. indent .. close
167
+ end
168
+
169
+ -- \`meta\` is what the compiler knew about this value and the runtime cannot:
170
+ -- a function's type, and the code it was written as.
171
+ render = function(value, meta, seen, depth, indent)
172
+ local kind = type(value)
173
+ if kind == "string" then return quote(value) end
174
+ if kind == "number" or kind == "boolean" or kind == "nil" then return tostring(value) end
175
+ if kind == "function" then
176
+ if meta == nil then return "function" end
177
+ if meta[2] == nil or meta[2] == "" then return meta[1] end
178
+ return meta[1] .. " " .. meta[2]
179
+ end
180
+ if kind == "table" then
181
+ local shown = rawget(value, "__tostring") == nil and getmetatable(value)
182
+ -- A value with its own \`__tostring\` (a Roblox Vector3, a class
183
+ -- instance that says how it reads) is trusted to describe itself.
184
+ if shown ~= nil and rawget(shown, "__tostring") ~= nil then return tostring(value) end
185
+ return renderTable(value, meta, seen, depth, indent)
186
+ end
187
+ return tostring(value)
188
+ end
189
+
190
+ local function describe(meta, index, value)
191
+ return render(value, meta ~= nil and meta[index] or nil, {}, 1, "")
192
+ end
193
+
194
+ local function join(meta, count, ...)
195
+ local parts = {}
196
+ for i = 1, count do
197
+ parts[#parts + 1] = describe(meta, i, (select(i, ...)))
198
+ end
199
+ return table.concat(parts, " ")
200
+ end
201
+
202
+ -- A line of the bundle, as the place in the project that wrote it.
203
+ local lines = __LINES__
204
+ local function place(line)
205
+ local origin = lines ~= nil and lines[tonumber(line)]
206
+ if origin == nil then return nil end
207
+ return origin[1] .. ":" .. tostring(origin[2])
208
+ end
209
+
210
+ -- Luau names the bundle in every frame; the project's files are what the
211
+ -- reader knows. Frames the map has nothing for are dropped: they are this
212
+ -- runtime's own, and the bundle's.
213
+ local function trace(level)
214
+ local raw = debug.traceback(nil, level)
215
+ local out = {}
216
+ for frame in raw:gmatch("[^\\n]+") do
217
+ local line = frame:match(":(%d+)")
218
+ local at = line ~= nil and place(line) or nil
219
+ if at ~= nil then
220
+ local name = frame:match("function ([%w_.:]+)")
221
+ out[#out + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
222
+ end
223
+ end
224
+ return table.concat(out, "\\n")
225
+ end
226
+
227
+ -- Never on the caller's thread: reporting a problem must not become one.
228
+ local detach = (task ~= nil and task.spawn) or function(f) return f() end
229
+
230
+ local function raise(prefix, meta, count, ...)
231
+ local message = prefix .. join(meta, count, ...)
232
+ local where = trace(3)
233
+ detach(function()
234
+ error(where ~= "" and (message .. "\\n" .. where) or message, 0)
235
+ end)
236
+ end
237
+
238
+ function console.log(meta, ...)
239
+ print(join(meta, select("#", ...), ...))
240
+ end
241
+
242
+ function console.warn(meta, ...)
243
+ raise("[warn] ", meta, select("#", ...), ...)
244
+ end
245
+
246
+ function console.error(meta, ...)
247
+ raise("", meta, select("#", ...), ...)
248
+ end
249
+
250
+ return console
251
+ end)()
252
+ `;
253
+
254
+ // src/names.ts
255
+ var Names = class _Names {
256
+ constructor(taken) {
257
+ this.taken = taken;
258
+ }
259
+ taken;
260
+ /** The names `programs` mention — a bundle takes every module's, so that
261
+ * a name shared across modules (the module table) collides with none. */
262
+ static from(...programs) {
263
+ const taken = /* @__PURE__ */ new Set(["select", "pairs", "table", "tostring", "setmetatable", "error"]);
264
+ const walk = (value) => {
265
+ if (!value || typeof value !== "object") return;
266
+ if (Array.isArray(value)) {
267
+ for (const item of value) walk(item);
268
+ return;
269
+ }
270
+ const node = value;
271
+ if (typeof node.name === "string" && node.name) taken.add(node.name);
272
+ for (const [key, child] of Object.entries(value)) {
273
+ if (key !== "line" && key !== "column") walk(child);
274
+ }
275
+ };
276
+ for (const program of programs) walk(program);
277
+ return new _Names(taken);
278
+ }
279
+ /** A copy that goes on to take names independently — one per module, each
280
+ * starting from what the whole bundle already takes. */
281
+ fork() {
282
+ return new _Names(new Set(this.taken));
283
+ }
284
+ /** `base`, or `base2`, `base3`, ... — whichever is free first. */
285
+ fresh(base) {
286
+ let name = base;
287
+ for (let n = 2; this.taken.has(name); n++) name = `${base}${n}`;
288
+ this.taken.add(name);
289
+ return name;
290
+ }
291
+ };
292
+
293
+ // src/lower.ts
294
+ function lower(program, scopes, options = {}) {
295
+ return new Lowerer(program, scopes, options).run();
296
+ }
297
+ var Lowerer = class {
298
+ constructor(source, scopes, options) {
299
+ this.source = source;
300
+ this.scopes = scopes;
301
+ this.options = options;
302
+ this.lua51 = options.target === "lua51";
303
+ this.names = options.names ? options.names.fork() : Names.from(source);
304
+ this.exportsName = this.names.fresh("exports");
305
+ for (const binding of scopes.bindings.values()) {
306
+ if (binding.declarationNode) this.bindingByDeclaration.set(binding.declarationNode, binding);
307
+ }
308
+ }
309
+ source;
310
+ scopes;
311
+ options;
312
+ names;
313
+ diagnostics = [];
314
+ helpers = /* @__PURE__ */ new Map();
315
+ /** The binding each declaration node creates. */
316
+ bindingByDeclaration = /* @__PURE__ */ new Map();
317
+ /** tilua names that are Luau keywords, and what they are called instead. */
318
+ renamed = /* @__PURE__ */ new Map();
319
+ /** Bindings read through something else: an export through `exports.x`,
320
+ * an import through its module's table. */
321
+ rewrites = /* @__PURE__ */ new Map();
322
+ exportsName;
323
+ /** Globals generated code calls, renamed where the source shadows them. */
324
+ builtins = /* @__PURE__ */ new Map();
325
+ /** Whether the output has to be plain Lua 5.1 rather than Luau. */
326
+ lua51;
327
+ /** The source line each emitted statement came from. */
328
+ origins = /* @__PURE__ */ new WeakMap();
329
+ run() {
330
+ const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
331
+ const helpers = this.helperDefinitions();
332
+ const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
333
+ return {
334
+ statements: [...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
335
+ exportsName: this.exportsName,
336
+ module: this.info,
337
+ diagnostics: this.diagnostics,
338
+ origins: this.origins
339
+ };
340
+ }
341
+ report(node, message) {
342
+ this.diagnostics.push({ message, line: node.line.start, column: node.column.start });
343
+ }
344
+ // --------------------------------------------------------
345
+ // Names
346
+ // --------------------------------------------------------
347
+ /** A tilua name as Luau can write it: `local` is a keyword there. */
348
+ name(name) {
349
+ if (!LUAU_KEYWORDS.has(name)) return name;
350
+ let renamed = this.renamed.get(name);
351
+ if (!renamed) {
352
+ renamed = this.names.fresh(`${name}_`);
353
+ this.renamed.set(name, renamed);
354
+ }
355
+ return renamed;
356
+ }
357
+ bindingIdOf(node) {
358
+ return this.scopes.bindingOf.get(node) ?? this.bindingByDeclaration.get(node)?.id;
359
+ }
360
+ /** How Luau refers to a name: its own local, or what it was rewritten to. */
361
+ reference(node) {
362
+ const id = this.bindingIdOf(node);
363
+ const rewrite = id === void 0 ? void 0 : this.rewrites.get(id);
364
+ return rewrite ? rewrite() : identifier(this.name(node.name));
365
+ }
366
+ /** A global the generated code calls — `pairs`, `table`, ... — under a name
367
+ * the source cannot have shadowed. */
368
+ builtin(global) {
369
+ let local2 = this.builtins.get(global);
370
+ if (!local2) {
371
+ const shadowed = [...this.scopes.bindings.values()].some((b) => b.name === global && b.kind !== "global");
372
+ if (!shadowed) return identifier(global);
373
+ local2 = this.names.fresh(`tilua_${global}`);
374
+ this.builtins.set(global, local2);
375
+ }
376
+ return identifier(local2);
377
+ }
378
+ helper(kind) {
379
+ let name = this.helpers.get(kind);
380
+ if (!name) {
381
+ name = this.names.fresh(`tilua_${kind}`);
382
+ this.helpers.set(kind, name);
383
+ }
384
+ return identifier(name);
385
+ }
386
+ /** The local a library's runtime table gets in this file, emitting it the
387
+ * first time something asks. Keyed by library and by the key the library
388
+ * itself chose, so two libraries' tables never collide. */
389
+ loweringRuntime(index2, runtime2) {
390
+ const key = `${index2}:${runtime2}`;
391
+ let name = this.loweringNames.get(key);
392
+ if (!name) {
393
+ name = this.names.fresh(`tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
394
+ this.loweringNames.set(key, name);
395
+ this.loweringUsed.push({ index: index2, runtime: runtime2, name });
396
+ }
397
+ return name;
398
+ }
399
+ loweringNames = /* @__PURE__ */ new Map();
400
+ loweringUsed = [];
401
+ /** What a library says `receiver:method(...)` is. The last library
402
+ * loaded is asked first, so a project's own library can answer for a
403
+ * method an earlier one also claims; no answer leaves an ordinary Luau
404
+ * method call, which is what a value that answers to the method itself
405
+ * wants (`text:upper()`). */
406
+ /** `console:log(a, f)` and its two siblings.
407
+ *
408
+ * Handled here rather than through a library's lowering because what they
409
+ * need is not a different callee but *more than was written*: a function
410
+ * prints as its type and its code, and neither survives to runtime. The
411
+ * compiler is the only thing that has both, so it passes them along:
412
+ *
413
+ * console:log(a, double)
414
+ * -- becomes
415
+ * tilua_console.log({ [2] = { "(x: number) => number", "(x: number) => x * 2" } }, a, double)
416
+ *
417
+ * The meta table is `nil` when nothing was worth saying, which is most
418
+ * calls. `console` itself is never passed: it holds nothing. */
419
+ consoleCall(node) {
420
+ if (node.object.type !== "Identifier" || node.object.name !== "console") return void 0;
421
+ const method = node.method.name;
422
+ if (method !== "log" && method !== "warn" && method !== "error") return void 0;
423
+ const binding = this.scopes.bindingOf.get(node.object);
424
+ if (binding !== void 0 && this.scopes.bindings.get(binding)?.kind !== "global") return void 0;
425
+ const meta = this.consoleMeta(node.arguments);
426
+ return call(
427
+ member(this.helper("console"), method),
428
+ [meta ?? nil(), ...this.values(node.arguments)]
429
+ );
430
+ }
431
+ /** What the compiler knows about the arguments and the runtime cannot:
432
+ * for each function argument, the type it was inferred as and the code it
433
+ * was written as. Keyed by position, so an argument that needs nothing
434
+ * costs nothing. */
435
+ consoleMeta(args2) {
436
+ const fields = [];
437
+ args2.forEach((argument, index2) => {
438
+ const type = this.options.types?.typeOf.get(argument);
439
+ if (!type || !isFunctionType(type)) return;
440
+ const written = this.writtenSource(this.declarationOf(argument) ?? argument);
441
+ fields.push({
442
+ type: "TableFieldComputed",
443
+ key: number(index2 + 1),
444
+ value: table([
445
+ { type: "TableFieldPositional", value: string((0, import_parser2.formatType)(type)) },
446
+ { type: "TableFieldPositional", value: string(written) }
447
+ ])
448
+ });
449
+ });
450
+ return fields.length ? table(fields) : void 0;
451
+ }
452
+ /** What a name was declared as: the value of `const f = ...`, or the whole
453
+ * `function f() { ... }`. Built once, on the first `console` call that
454
+ * needs it — most files have none. */
455
+ declarationOf(argument) {
456
+ if (argument.type !== "Identifier") return void 0;
457
+ if (!this.declarations) {
458
+ this.declarations = /* @__PURE__ */ new Map();
459
+ for (const node of allNodes(this.source.body)) {
460
+ const statement = node;
461
+ if (statement.type === "VariableDeclaration") {
462
+ statement.names.forEach((target, index2) => {
463
+ const value = statement.init[index2];
464
+ if (target.type !== "IdentifierPattern" || !value) return;
465
+ const binding = this.bindingByDeclaration.get(target);
466
+ if (binding) this.declarations.set(binding.id, value);
467
+ });
468
+ } else if (statement.type === "FunctionDeclaration") {
469
+ const binding = this.bindingByDeclaration.get(statement.name);
470
+ if (binding) this.declarations.set(binding.id, statement);
471
+ }
472
+ }
473
+ }
474
+ const id = this.scopes.bindingOf.get(argument);
475
+ return id === void 0 ? void 0 : this.declarations.get(id);
476
+ }
477
+ declarations;
478
+ /** The source `node` was written as, read straight out of the file. */
479
+ writtenSource(node) {
480
+ const source = this.options.source;
481
+ if (source === void 0) return "";
482
+ const lines = source.split(/\r?\n/);
483
+ if (node.line.start === node.line.end) {
484
+ return (lines[node.line.start - 1] ?? "").slice(node.column.start - 1, node.column.end - 1);
485
+ }
486
+ const first = (lines[node.line.start - 1] ?? "").slice(node.column.start - 1);
487
+ const middle = lines.slice(node.line.start, node.line.end - 1);
488
+ const last = (lines[node.line.end - 1] ?? "").slice(0, node.column.end - 1);
489
+ const indent = /^\s*/.exec(lines[node.line.start - 1] ?? "")[0];
490
+ const trim = (line) => line.startsWith(indent) ? line.slice(indent.length) : line;
491
+ return [first, ...middle.map(trim), trim(last)].join("\n");
492
+ }
493
+ loweredMethodCall(node) {
494
+ const lowerings = this.options.lowerings;
495
+ if (!lowerings?.length) return void 0;
496
+ const receiver = this.options.types?.typeOf.get(node.object);
497
+ for (let i = lowerings.length - 1; i >= 0; i--) {
498
+ const answer = lowerings[i].plugin.methodCall?.({
499
+ method: node.method.name,
500
+ receiver,
501
+ argumentCount: node.arguments.length,
502
+ use: (runtime2) => this.loweringRuntime(i, runtime2)
503
+ });
504
+ if (answer) return { callee: answer.callee, passReceiver: answer.passReceiver !== false };
505
+ }
506
+ return void 0;
507
+ }
508
+ helperDefinitions() {
509
+ const out2 = [];
510
+ const assign2 = this.helpers.get("assign");
511
+ if (assign2) {
512
+ out2.push(localFunction(assign2, functionBody(["target"], [
513
+ numericFor("i", number(1), selectCount(this.builtin("select")), [
514
+ local(["source"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
515
+ ifThen(binary("~=", identifier("source"), nil()), [
516
+ genericFor(["key", "value"], [call(this.builtin("pairs"), [identifier("source")])], [
517
+ assign([index(identifier("target"), identifier("key"))], [identifier("value")])
518
+ ])
519
+ ])
520
+ ]),
521
+ returns([identifier("target")])
522
+ ], true)));
523
+ }
524
+ for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
525
+ const { plugin, from } = this.options.lowerings[index2];
526
+ const source = plugin.runtime?.[runtime2];
527
+ if (source === void 0) {
528
+ this.diagnostics.push({
529
+ message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
530
+ line: 1,
531
+ column: 1
532
+ });
533
+ continue;
534
+ }
535
+ out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name)).body.statements);
536
+ }
537
+ const console2 = this.helpers.get("console");
538
+ if (console2) {
539
+ const lines = this.options.module?.lines ?? "nil";
540
+ out2.push(...(0, import_luau_parser.parse)(CONSOLE_RUNTIME.replace(/__NAME__/g, console2).replace(/__LINES__/g, lines)).body.statements);
541
+ }
542
+ const concat = this.helpers.get("concat");
543
+ if (concat) {
544
+ out2.push(localFunction(concat, functionBody([], [
545
+ local(["result"], [table([])]),
546
+ numericFor("i", number(1), selectCount(this.builtin("select")), [
547
+ local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
548
+ callStatement(call(member(this.builtin("table"), "move"), [
549
+ identifier("part"),
550
+ number(1),
551
+ unary("#", identifier("part")),
552
+ binary("+", unary("#", identifier("result")), number(1)),
553
+ identifier("result")
554
+ ]))
555
+ ]),
556
+ returns([identifier("result")])
557
+ ], true)));
558
+ }
559
+ return out2;
560
+ }
561
+ // --------------------------------------------------------
562
+ // Modules
563
+ // --------------------------------------------------------
564
+ info = { names: [], links: [], stars: [], exports: false };
565
+ /** The module body, in the order an ES module runs:
566
+ *
567
+ * local a, b, util -- every top-level name, declared first
568
+ * function exports.f() ... -- every top-level function, hoisted
569
+ * util = require("util") -- the imports
570
+ * ... -- the rest, in source order */
571
+ module(context) {
572
+ const statements = this.source.body.statements;
573
+ const exportsTable = () => identifier(this.exportsName);
574
+ const { names, links, stars } = this.info;
575
+ const locals = [];
576
+ const requires = [];
577
+ const moduleLocals = /* @__PURE__ */ new Map();
578
+ const importedFrom = /* @__PURE__ */ new Map();
579
+ const resolve3 = (source) => {
580
+ const key = context.resolve(source.value);
581
+ if (key === void 0) this.report(source, `Cannot find module '${source.value}'`);
582
+ return key;
583
+ };
584
+ const moduleLocal = (key) => {
585
+ let local2 = moduleLocals.get(key);
586
+ if (!local2) {
587
+ local2 = this.names.fresh(moduleName(key));
588
+ moduleLocals.set(key, local2);
589
+ locals.push(local2);
590
+ requires.push(assign([identifier(local2)], [call(context.require, [string(key)])]));
591
+ }
592
+ return local2;
593
+ };
594
+ const load = (key) => {
595
+ if (!moduleLocals.has(key)) requires.push(callStatement(call(context.require, [string(key)])));
596
+ };
597
+ const exportBinding = (node, exported) => {
598
+ const binding = node && this.bindingByDeclaration.get(node);
599
+ if (!binding) return;
600
+ const existing = this.rewrites.get(binding.id);
601
+ if (existing) {
602
+ const first = memberChain(existing());
603
+ links.push({ name: exported, module: context.name, imported: first?.[first.length - 1] });
604
+ return;
605
+ }
606
+ names.push(exported);
607
+ this.rewrites.set(binding.id, () => member(exportsTable(), exported));
608
+ };
609
+ for (const statement of statements) {
610
+ if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
611
+ const specifiers = [
612
+ ...statement.defaultImport ? [{ local: statement.defaultImport, imported: "default" }] : [],
613
+ ...statement.namespaceImport ? [{ local: statement.namespaceImport, imported: void 0 }] : [],
614
+ ...statement.specifiers.map((s) => ({ local: s.local, imported: s.imported.name }))
615
+ ];
616
+ const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references.length ?? 0) > 0);
617
+ if (!used.length) continue;
618
+ const key = resolve3(statement.source);
619
+ if (key === void 0) continue;
620
+ const local2 = moduleLocal(key);
621
+ for (const s of used) {
622
+ const binding = this.bindingByDeclaration.get(s.local);
623
+ importedFrom.set(binding.id, { module: key, imported: s.imported });
624
+ const imported = s.imported;
625
+ this.rewrites.set(binding.id, imported === void 0 ? () => identifier(local2) : () => member(identifier(local2), imported));
626
+ }
627
+ }
628
+ for (const statement of statements) {
629
+ switch (statement.type) {
630
+ case "ExportStatement": {
631
+ const declaration = statement.declaration;
632
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
633
+ exportBinding(declaration.name, declaration.name.name);
634
+ } else for (const pattern of declaration.names.flatMap(identifierPatterns)) exportBinding(pattern, pattern.name);
635
+ break;
636
+ }
637
+ case "ExportDefaultStatement":
638
+ names.push("default");
639
+ break;
640
+ case "ExportNamedStatement": {
641
+ if (statement.source) {
642
+ const key = resolve3(statement.source);
643
+ if (key === void 0) break;
644
+ load(key);
645
+ for (const s of statement.specifiers) links.push({ name: s.exported.name, module: key, imported: s.local.name });
646
+ break;
647
+ }
648
+ for (const s of statement.specifiers) {
649
+ const declaration = topLevelDeclaration(statements, s.local.name);
650
+ const binding = declaration && this.bindingByDeclaration.get(declaration.node);
651
+ const from = binding && importedFrom.get(binding.id);
652
+ if (from) links.push({ name: s.exported.name, ...from });
653
+ else if (declaration?.type === "Declaration") exportBinding(declaration.node, s.exported.name);
654
+ }
655
+ break;
656
+ }
657
+ case "ExportAllStatement": {
658
+ const key = resolve3(statement.source);
659
+ if (key === void 0) break;
660
+ load(key);
661
+ stars.push(key);
662
+ break;
663
+ }
664
+ }
665
+ }
666
+ this.info.exports = names.length > 0 || links.length > 0 || stars.length > 0;
667
+ for (const statement of statements) {
668
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
669
+ if (declaration.type === "VariableDeclaration") {
670
+ for (const pattern of declaration.names.flatMap(identifierPatterns)) {
671
+ if (!this.isRewritten(pattern)) locals.push(this.name(pattern.name));
672
+ }
673
+ } else if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && !this.isRewritten(declaration.name)) {
674
+ locals.push(this.name(declaration.name.name));
675
+ }
676
+ }
677
+ const hoisted = [];
678
+ const body = [];
679
+ for (const statement of statements) {
680
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
681
+ if (declaration.type === "FunctionDeclaration") {
682
+ hoisted.push(this.from(this.functionStatement(
683
+ this.reference(declaration.name),
684
+ declaration,
685
+ this.functionBody(declaration.func),
686
+ false
687
+ ), declaration));
688
+ continue;
689
+ }
690
+ if (declaration.type === "ClassDeclaration") {
691
+ body.push(...this.classDeclaration(declaration, "assign"));
692
+ continue;
693
+ }
694
+ switch (declaration.type) {
695
+ case "VariableDeclaration":
696
+ body.push(...this.variableDeclaration(declaration, "assign"));
697
+ break;
698
+ case "ExportDefaultStatement": {
699
+ const exported = declaration.declaration;
700
+ if (exported.type === "ClassDeclaration") {
701
+ body.push(...this.classDeclaration(exported, "assign"));
702
+ body.push(assign([member(exportsTable(), "default")], [this.reference(exported.name)]));
703
+ } else {
704
+ body.push(assign([member(exportsTable(), "default")], [this.expression(exported)]));
705
+ }
706
+ break;
707
+ }
708
+ case "ReturnStatement":
709
+ if (declaration.arguments.length) this.report(declaration, "A module cannot return a value; export it instead");
710
+ body.push(returns([]));
711
+ break;
712
+ case "ImportStatement":
713
+ case "ExportNamedStatement":
714
+ case "ExportAllStatement":
715
+ break;
716
+ default:
717
+ body.push(...this.statement(declaration));
718
+ }
719
+ }
720
+ const declarations = [];
721
+ for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
722
+ return [...declarations, ...hoisted, ...requires, ...body];
723
+ }
724
+ /** Is `expression` a table iterated directly — not an iterator function
725
+ * such as `pairs(t)` or `string.gmatch(s, p)`? Known from its type. */
726
+ iteratesTable(expression) {
727
+ const types = this.options.types;
728
+ const type = types?.typeOf.get(expression);
729
+ if (!types || !type) return false;
730
+ const seen = /* @__PURE__ */ new Set();
731
+ const table2 = (t) => {
732
+ if (seen.has(t)) return false;
733
+ seen.add(t);
734
+ switch (t.kind) {
735
+ case "array":
736
+ case "tuple":
737
+ return true;
738
+ case "object":
739
+ return !t.class;
740
+ case "union":
741
+ return t.types.every((m) => m.kind === "primitive" && m.name === "nil" || table2(m));
742
+ case "intersection":
743
+ return t.types.some(table2);
744
+ case "genericRef": {
745
+ const alias = types.aliases.get(t.name);
746
+ return alias !== void 0 && table2(alias);
747
+ }
748
+ default:
749
+ return false;
750
+ }
751
+ };
752
+ return table2(type);
753
+ }
754
+ isRewritten(node) {
755
+ const binding = this.bindingByDeclaration.get(node);
756
+ return binding !== void 0 && this.rewrites.has(binding.id);
757
+ }
758
+ // --------------------------------------------------------
759
+ // Statements
760
+ // --------------------------------------------------------
761
+ block(block2, prelude = []) {
762
+ return block([...prelude, ...this.blockStatements(block2.statements)]);
763
+ }
764
+ // --------------------------------------------------------
765
+ // Hoisting
766
+ // --------------------------------------------------------
767
+ //
768
+ // A function declaration is visible to its whole block. Luau's
769
+ // `local function f` is not, so a function something above it refers to is
770
+ // declared as a local at the top of the block and assigned where it is
771
+ // written: another function's body can call it once the block has run
772
+ // that far. (Calling it straight away, above its declaration, is an error
773
+ // scope analysis reports.) A bundle's module and a file's top level hoist
774
+ // the whole function instead.
775
+ /** Declarations in `statements` that something before them reads: a
776
+ * function called above it, or a value a closure in its own initializer
777
+ * reads (`local t = { f = function() return t end }`). Lua's `local`
778
+ * starts after its statement, so those names are declared at the top of
779
+ * the block and assigned where they are written. */
780
+ referencedAhead(statements) {
781
+ const functions = /* @__PURE__ */ new Set();
782
+ const classes = /* @__PURE__ */ new Set();
783
+ const variables = /* @__PURE__ */ new Set();
784
+ const names = [];
785
+ const before = (node, line, column) => line < node.line.start || line === node.line.start && column < node.column.start;
786
+ for (const statement of statements) {
787
+ if (statement.type === "FunctionDeclaration" || statement.type === "ClassDeclaration") {
788
+ const binding = this.bindingByDeclaration.get(statement.name);
789
+ if (binding?.references.some((r) => before(statement.name, r.line.start, r.column.start))) {
790
+ if (statement.type === "ClassDeclaration") classes.add(statement);
791
+ else functions.add(statement);
792
+ names.push(this.name(statement.name.name));
793
+ }
794
+ continue;
795
+ }
796
+ if (statement.type !== "VariableDeclaration") continue;
797
+ const patterns = statement.names.flatMap(identifierPatterns);
798
+ const end = { line: { start: statement.line.end }, column: { start: statement.column.end } };
799
+ const needed = patterns.some((pattern) => {
800
+ const binding = this.bindingByDeclaration.get(pattern);
801
+ return binding?.references.some((r) => before(end, r.line.start, r.column.start));
802
+ });
803
+ if (!needed) continue;
804
+ variables.add(statement);
805
+ for (const pattern of patterns) names.push(this.name(pattern.name));
806
+ }
807
+ return { functions, classes, variables, names };
808
+ }
809
+ blockStatements(statements) {
810
+ const ahead = this.referencedAhead(statements);
811
+ if (!ahead.names.length) return statements.flatMap((s) => this.statement(s));
812
+ return [
813
+ local(ahead.names, []),
814
+ ...statements.flatMap((s) => {
815
+ if (s.type === "FunctionDeclaration" && ahead.functions.has(s)) {
816
+ return [this.functionStatement(this.reference(s.name), s, this.functionBody(s.func), false)];
817
+ }
818
+ if (s.type === "VariableDeclaration" && ahead.variables.has(s)) {
819
+ return this.variableDeclaration(s, "assign");
820
+ }
821
+ if (s.type === "ClassDeclaration" && ahead.classes.has(s)) {
822
+ return this.classDeclaration(s, "assign");
823
+ }
824
+ return this.statement(s);
825
+ })
826
+ ];
827
+ }
828
+ /** A file's statements outside a bundle. A function used above its
829
+ * declaration is hoisted whole, as in a module: every top-level name is
830
+ * declared first, then the functions are defined, then the rest runs. */
831
+ topLevel(statements) {
832
+ if (!this.referencedAhead(statements).names.length) return statements.flatMap((s) => this.statement(s));
833
+ const locals = [];
834
+ const hoisted = [];
835
+ const body = [];
836
+ for (const statement of statements) {
837
+ if (statement.type === "VariableDeclaration") {
838
+ for (const pattern of statement.names.flatMap(identifierPatterns)) locals.push(this.name(pattern.name));
839
+ body.push(...this.variableDeclaration(statement, "assign"));
840
+ } else if (statement.type === "FunctionDeclaration") {
841
+ locals.push(this.name(statement.name.name));
842
+ hoisted.push(this.from(this.functionStatement(
843
+ this.reference(statement.name),
844
+ statement,
845
+ this.functionBody(statement.func),
846
+ false
847
+ ), statement));
848
+ } else if (statement.type === "ClassDeclaration") {
849
+ locals.push(this.name(statement.name.name));
850
+ body.push(...this.classDeclaration(statement, "assign"));
851
+ } else {
852
+ body.push(...this.statement(statement));
853
+ }
854
+ }
855
+ const declarations = [];
856
+ for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
857
+ return [...declarations, ...hoisted, ...body];
858
+ }
859
+ /** Where each emitted statement came from. A bundle is one Luau file, so
860
+ * a line in it says nothing about which of the project's files wrote it;
861
+ * this is what lets a traceback be read back into the source. Statements
862
+ * are the unit because lines are: an expression shares its statement's.
863
+ *
864
+ * Only statements built without a span of their own are stamped — the few
865
+ * that carry one already kept a more exact position. */
866
+ statement(node) {
867
+ const emitted = this.statementOf(node);
868
+ for (const s of emitted) {
869
+ if (!s.line.start) {
870
+ s.line = { ...node.line };
871
+ s.column = { ...node.column };
872
+ }
873
+ if (!this.origins.has(s)) this.origins.set(s, node.line.start);
874
+ }
875
+ return emitted;
876
+ }
877
+ /** Stamps a statement built outside `statement` — a hoisted function, an
878
+ * assignment the module wrapper makes — with where it was written. */
879
+ from(statement, node) {
880
+ if (!statement.line.start) {
881
+ statement.line = { ...node.line };
882
+ statement.column = { ...node.column };
883
+ }
884
+ if (!this.origins.has(statement)) this.origins.set(statement, node.line.start);
885
+ return statement;
886
+ }
887
+ statementOf(node) {
888
+ switch (node.type) {
889
+ // Types, and what exists only for them.
890
+ case "TypeAliasStatement":
891
+ case "ExportTypeAliasStatement":
892
+ case "DeclareStatement":
893
+ case "DeclareClassStatement":
894
+ case "ErrorStatement":
895
+ return [];
896
+ case "VariableDeclaration":
897
+ return this.variableDeclaration(node, "declare");
898
+ case "FunctionDeclaration":
899
+ return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: node.attributes }];
900
+ case "FunctionDeclarationStatement":
901
+ return [this.functionDeclarationStatement(node)];
902
+ case "ClassDeclaration":
903
+ return this.classDeclaration(node, "declare");
904
+ case "AssignmentStatement":
905
+ return this.assignment(node);
906
+ case "CompoundAssignmentStatement":
907
+ if (this.lua51) return this.compoundAssignment51(node);
908
+ return [{
909
+ type: "CompoundAssignmentStatement",
910
+ operator: node.operator,
911
+ target: this.expression(node.target),
912
+ value: this.expression(node.value),
913
+ ...spanOf(node)
914
+ }];
915
+ case "CallStatement": {
916
+ const chain = optionalChain(node.expression);
917
+ if (chain) return [this.optionalCallStatement(chain)];
918
+ const expression = this.expression(node.expression);
919
+ if (expression.type !== "CallExpression" && expression.type !== "MethodCallExpression") {
920
+ this.report(node, "A statement must be a call");
921
+ return [];
922
+ }
923
+ return [callStatement(expression)];
924
+ }
925
+ // `value` on a line of its own: written to ask the editor about a
926
+ // name, and worth nothing once the file is compiled. It cannot be
927
+ // kept — Lua has no expression statement — and it cannot run
928
+ // anything, since a call is a `CallStatement` instead, so dropping
929
+ // it changes nothing but the noise.
930
+ case "ExpressionStatement":
931
+ return [];
932
+ case "DoStatement":
933
+ return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
934
+ case "WhileStatement":
935
+ return [{ type: "WhileStatement", condition: this.expression(node.condition), body: this.loopBody51(this.block(node.body)), ...spanOf(node) }];
936
+ case "RepeatStatement": {
937
+ const body = this.block(node.body);
938
+ const condition = this.expression(node.condition);
939
+ if (this.lua51 && hasOwnJump(body.statements, "ContinueStatement")) {
940
+ const hidden = declaredNames(body.statements).filter((name) => mentions(condition, name));
941
+ if (hidden.length) {
942
+ this.report(node, `'continue' in a 'repeat' whose 'until' reads ${hidden.map((n) => `'${n}'`).join(", ")} cannot be compiled for Lua 5.1, which has no 'continue' to lower it to. Move what the condition reads out of the loop body, or use a 'while' loop.`);
943
+ }
944
+ }
945
+ return [{ type: "RepeatStatement", body: this.loopBody51(body), condition, ...spanOf(node) }];
946
+ }
947
+ case "IfStatement":
948
+ return [{
949
+ type: "IfStatement",
950
+ clauses: node.clauses.map((c) => ({
951
+ type: "IfClause",
952
+ condition: this.expression(c.condition),
953
+ body: this.block(c.body),
954
+ ...spanOf(c)
955
+ })),
956
+ alternate: node.alternate && this.block(node.alternate),
957
+ ...spanOf(node)
958
+ }];
959
+ case "NumericForStatement":
960
+ return [{
961
+ type: "NumericForStatement",
962
+ variable: { type: "TypedIdentifier", name: this.name(node.variable.name), ...spanOf(node.variable) },
963
+ start: this.expression(node.start),
964
+ end: this.expression(node.end),
965
+ step: node.step && this.expression(node.step),
966
+ body: this.loopBody51(this.block(node.body)),
967
+ ...spanOf(node)
968
+ }];
969
+ case "GenericForStatement": {
970
+ const prelude = [];
971
+ const variables = node.variables.map((target) => {
972
+ if (target.type === "IdentifierPattern") return this.name(target.name);
973
+ const temp = this.names.fresh("item");
974
+ prelude.push(...this.destructure(target, identifier(temp), "declare"));
975
+ return temp;
976
+ });
977
+ if (variables.length === 1 && node.iterators.length === 1 && this.iteratesTable(node.iterators[0])) {
978
+ variables.unshift(this.names.fresh("_"));
979
+ }
980
+ return [genericFor(
981
+ variables,
982
+ node.iterators.map((e) => this.expression(e)),
983
+ this.loopBody51(this.block(node.body, prelude)).statements
984
+ )];
985
+ }
986
+ case "ReturnStatement":
987
+ return [returns(this.values(node.arguments))];
988
+ case "BreakStatement":
989
+ return [{ type: "BreakStatement", ...spanOf(node) }];
990
+ case "ContinueStatement":
991
+ return [{ type: "ContinueStatement", ...spanOf(node) }];
992
+ case "ImportStatement":
993
+ if (node.isTypeOnly) return [];
994
+ this.report(node, this.options.module ? "Imports and exports belong at the top level of a module" : "Imports and exports need a bundle: build the project with @tilua/compiler");
995
+ return [];
996
+ case "ExportStatement":
997
+ case "ExportDefaultStatement":
998
+ case "ExportNamedStatement":
999
+ case "ExportAllStatement":
1000
+ this.report(node, this.options.module ? "Imports and exports belong at the top level of a module" : "Imports and exports need a bundle: build the project with @tilua/compiler");
1001
+ return [];
1002
+ }
1003
+ }
1004
+ functionDeclarationStatement(node) {
1005
+ const statement = this.functionStatement(this.reference(node.target.base), node, this.functionBody(node.func), node.isMethod, node.target.path.map((p) => p.name));
1006
+ if (!node.target.method) return statement;
1007
+ return { ...statement, target: { ...statement.target, method: identifier(node.target.method.name) } };
1008
+ }
1009
+ /** `function a.b.c()`, for a target written as an expression. */
1010
+ // ============================================================
1011
+ // `class ... end`
1012
+ // ------------------------------------------------------------
1013
+ // The shape is Lua's own, and the memory model the language
1014
+ // promises: one table per class holding its methods and its
1015
+ // statics, and one table per instance whose metatable points at
1016
+ // it. An instance reaches its class directly — nothing is copied
1017
+ // per instance, and there is no prototype chain of its own.
1018
+ //
1019
+ // local Dog = __class(Animal)
1020
+ // function Dog.__init(this, name)
1021
+ // Animal.__init(this, name) -- super(name)
1022
+ // this.breed = "corgi" -- a field initializer
1023
+ // end
1024
+ // function Dog.new(...)
1025
+ // local this = setmetatable({}, Dog)
1026
+ // Dog.__init(this, ...)
1027
+ // return this
1028
+ // end
1029
+ // function Dog.speak(this) ... end
1030
+ //
1031
+ // `new Dog(x)` is `Dog.new(x)`, `dog:speak()` passes the instance
1032
+ // as `this`, and `super.speak()` is `Animal.speak(this)` — the
1033
+ // base's own function, run on this instance.
1034
+ // ============================================================
1035
+ /** The locals the file's classes are built with, named once. */
1036
+ classHelpers;
1037
+ classRuntime() {
1038
+ return this.classHelpers ??= {
1039
+ build: this.names.fresh("tilua_class"),
1040
+ accessors: this.names.fresh("tilua_accessors")
1041
+ };
1042
+ }
1043
+ /** The helpers, as Luau — emitted only in a file that declares a class.
1044
+ * `__getters`/`__setters` chain to the base's, so a derived class sees
1045
+ * the accessors it inherits; `__index` only becomes a function for a
1046
+ * class that has some, which leaves every other class on the plain
1047
+ * `__index = class` fast path.
1048
+ *
1049
+ * `ClassObject` and `ParentClass` are the links the memory model
1050
+ * promises: an instance reads `ClassObject` through its metatable and
1051
+ * lands on its own class, and a class reads `ParentClass` and lands on
1052
+ * the one it extends. Both live on the class table, so an instance
1053
+ * carries neither. */
1054
+ classRuntimeStatements() {
1055
+ if (!this.classHelpers) return [];
1056
+ const { build, accessors } = this.classHelpers;
1057
+ return (0, import_luau_parser.parse)(`
1058
+ local function ${build}(base)
1059
+ local class = { __getters = {}, __setters = {} }
1060
+ class.__index = class
1061
+ class.ClassObject = class
1062
+ class.ParentClass = base
1063
+ if base ~= nil then
1064
+ setmetatable(class, { __index = base })
1065
+ setmetatable(class.__getters, { __index = base.__getters })
1066
+ setmetatable(class.__setters, { __index = base.__setters })
1067
+ end
1068
+ return class
1069
+ end
1070
+ local function ${accessors}(class)
1071
+ if not class.__dynamic and next(class.__getters) == nil and next(class.__setters) == nil then
1072
+ return
1073
+ end
1074
+ class.__dynamic = true
1075
+ class.__index = function(this, key)
1076
+ local getter = class.__getters[key]
1077
+ if getter ~= nil then
1078
+ return getter(this)
1079
+ end
1080
+ return class[key]
1081
+ end
1082
+ class.__newindex = function(this, key, value)
1083
+ local setter = class.__setters[key]
1084
+ if setter ~= nil then
1085
+ setter(this, value)
1086
+ return
1087
+ end
1088
+ rawset(this, key, value)
1089
+ end
1090
+ end
1091
+ `).body.statements;
1092
+ }
1093
+ /** The class being lowered, so `super` knows what to name. */
1094
+ classContext;
1095
+ /** A class written where a value goes. Building it takes statements, and
1096
+ * an expression has no room for them, so they go in a function that runs
1097
+ * where the class is written and hands the class table back. */
1098
+ classExpression(node) {
1099
+ const local2 = this.names.fresh(node.name ? this.name(node.name.name) : "class");
1100
+ const statements = this.classStatements(node, identifier(local2), local2);
1101
+ return call(parenthesized(functionExpression(
1102
+ functionBody([], [...statements, returns([identifier(local2)])])
1103
+ )), []);
1104
+ }
1105
+ classDeclaration(node, mode) {
1106
+ const name = this.name(node.name.name);
1107
+ return mode === "declare" ? this.classStatements(node, identifier(name), name) : this.classStatements(node, this.reference(node.name));
1108
+ }
1109
+ /** `declare` names the local the class table goes in; without it the
1110
+ * table is assigned to `self`, which a module has already declared. */
1111
+ classStatements(node, self, declare) {
1112
+ const { build, accessors } = this.classRuntime();
1113
+ const create = call(identifier(build), [node.superclass ? this.reference(node.superclass) : nil()]);
1114
+ const out2 = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
1115
+ const previous = this.classContext;
1116
+ this.classContext = node;
1117
+ try {
1118
+ for (const member2 of node.members) {
1119
+ switch (member2.type) {
1120
+ case "ClassField":
1121
+ if (member2.isStatic) {
1122
+ out2.push(assign(
1123
+ [member(self, member2.name.name)],
1124
+ [member2.init ? this.expression(member2.init) : nil()]
1125
+ ));
1126
+ }
1127
+ break;
1128
+ case "ClassMethod":
1129
+ out2.push(this.functionStatement(
1130
+ member(self, member2.name.name),
1131
+ member2,
1132
+ this.classFunctionBody(member2.func),
1133
+ false
1134
+ ));
1135
+ break;
1136
+ case "ClassAccessor":
1137
+ out2.push(assign(
1138
+ [index(
1139
+ member(self, member2.kind === "get" ? "__getters" : "__setters"),
1140
+ string(member2.name.name)
1141
+ )],
1142
+ [functionExpression(this.classFunctionBody(member2.func))]
1143
+ ));
1144
+ break;
1145
+ case "ClassConstructor":
1146
+ break;
1147
+ }
1148
+ }
1149
+ out2.push(this.classInit(node, self));
1150
+ out2.push(this.classNew(node, self));
1151
+ out2.push(callStatement(call(identifier(accessors), [self])));
1152
+ } finally {
1153
+ this.classContext = previous;
1154
+ }
1155
+ return out2;
1156
+ }
1157
+ /** `Class.__init(this, ...)` — what every instance of the class runs.
1158
+ * Kept apart from `new` so `super(...)` can run the base's on the
1159
+ * instance already being built rather than making a second one. */
1160
+ classInit(node, self) {
1161
+ const instance = this.thisName();
1162
+ const initializers = node.members.filter((m) => m.type === "ClassField" && !m.isStatic && m.init !== void 0).map((field2) => assign(
1163
+ [member(identifier(instance), field2.name.name)],
1164
+ [this.expression(field2.init)]
1165
+ ));
1166
+ const constructor = node.members.find((m) => m.type === "ClassConstructor");
1167
+ if (!constructor) {
1168
+ const body2 = functionBody([instance], [
1169
+ ...node.superclass ? [callStatement(call(
1170
+ member(this.reference(node.superclass), "__init"),
1171
+ [identifier(instance), vararg()]
1172
+ ))] : [],
1173
+ ...initializers
1174
+ ], true);
1175
+ return this.functionStatement(member(self, "__init"), node, body2, false);
1176
+ }
1177
+ const body = this.classFunctionBody(constructor.func);
1178
+ const statements = body.body.statements;
1179
+ const afterSuper = node.superclass ? statements.findIndex(isSuperInit) + 1 : 0;
1180
+ statements.splice(Math.max(afterSuper, 0), 0, ...initializers);
1181
+ return this.functionStatement(member(self, "__init"), constructor, body, false);
1182
+ }
1183
+ /** `Class.new(...)`: the instance, its metatable, its constructor. */
1184
+ classNew(node, self) {
1185
+ const instance = this.thisName();
1186
+ const body = functionBody([], [
1187
+ local([instance], [call(this.builtin("setmetatable"), [table([]), self])]),
1188
+ callStatement(call(member(self, "__init"), [identifier(instance), vararg()])),
1189
+ returns([identifier(instance)])
1190
+ ], true);
1191
+ return this.functionStatement(member(self, "new"), node, body, false);
1192
+ }
1193
+ /** What `this` is called in emitted code. */
1194
+ thisName() {
1195
+ return this.name("this");
1196
+ }
1197
+ /** A class method keeps its receiver as a written parameter: `:` supplies
1198
+ * it at the call site, but the declaration spells it out so `super` and
1199
+ * `new` can pass it by hand. */
1200
+ classFunctionBody(func) {
1201
+ return this.functionBody(func.isMethod ? { ...func, isMethod: false } : func);
1202
+ }
1203
+ /** `super(...)` / `super.m(...)` — the base class, by the name this file
1204
+ * knows it under. */
1205
+ superClassReference(node) {
1206
+ const superclass = this.classContext?.superclass;
1207
+ if (!superclass) {
1208
+ this.report(node, "'super' is only available inside a class that extends another");
1209
+ return nil();
1210
+ }
1211
+ return this.reference(superclass);
1212
+ }
1213
+ functionStatement(target, node, func, isMethod, path = []) {
1214
+ const chain = memberChain(target);
1215
+ if (!chain) this.report(node, "This function name cannot be written in Luau");
1216
+ const [root, ...prefix] = chain ?? ["_"];
1217
+ return {
1218
+ type: "FunctionDeclarationStatement",
1219
+ target: {
1220
+ type: "FunctionName",
1221
+ base: identifier(root),
1222
+ path: [...prefix, ...path].map(identifier),
1223
+ ...spanOf(node)
1224
+ },
1225
+ isMethod,
1226
+ func,
1227
+ attributes: node.attributes,
1228
+ ...spanOf(node)
1229
+ };
1230
+ }
1231
+ /** `const a, { b } = x, y`. In "assign" mode — a module's top level, where
1232
+ * every name is declared up front — nothing is declared, only assigned. */
1233
+ variableDeclaration(node, mode) {
1234
+ if (mode === "assign" && !node.init.length) {
1235
+ const exported = node.names.flatMap(identifierPatterns).filter((p) => this.isRewritten(p));
1236
+ return exported.length ? [assign(exported.map((p) => this.reference(p)), exported.map(() => nil()))] : [];
1237
+ }
1238
+ const init = this.values(node.init);
1239
+ if (node.names.every((n) => n.type === "IdentifierPattern")) {
1240
+ const names2 = node.names;
1241
+ return mode === "declare" ? [local(names2.map((n) => this.name(n.name)), init)] : [assign(names2.map((n) => this.reference(n)), init)];
1242
+ }
1243
+ const only = node.names.length === 1 && node.init.length === 1 ? node.names[0] : void 0;
1244
+ if (only && only.type !== "IdentifierPattern") {
1245
+ if (init[0].type === "Identifier") return this.destructure(only, init[0], mode);
1246
+ const temp = this.names.fresh("ref");
1247
+ return [local([temp], init), ...this.destructure(only, identifier(temp), mode)];
1248
+ }
1249
+ const after = [];
1250
+ const names = [];
1251
+ const targets = [];
1252
+ for (const target of node.names) {
1253
+ if (target.type === "IdentifierPattern") {
1254
+ names.push(this.name(target.name));
1255
+ targets.push(this.reference(target));
1256
+ continue;
1257
+ }
1258
+ const temp = this.names.fresh("ref");
1259
+ names.push(temp);
1260
+ targets.push(identifier(temp));
1261
+ after.push(...this.destructure(target, identifier(temp), mode));
1262
+ }
1263
+ if (mode === "declare") return [local(names, init), ...after];
1264
+ const temps = node.names.flatMap((t, i) => t.type === "IdentifierPattern" ? [] : [names[i]]);
1265
+ return [...temps.length ? [local(temps, [])] : [], assign(targets, init), ...after];
1266
+ }
1267
+ assignment(node) {
1268
+ if (node.targets.every((t) => t.type !== "ObjectPattern" && t.type !== "ArrayPattern")) {
1269
+ return [assign(node.targets.map((t) => this.expression(t)), this.values(node.values))];
1270
+ }
1271
+ const values = this.values(node.values);
1272
+ const only = node.targets.length === 1 && values.length === 1 ? node.targets[0] : void 0;
1273
+ if (only && (only.type === "ObjectPattern" || only.type === "ArrayPattern") && values[0].type === "Identifier") {
1274
+ const statements = this.destructure(only, values[0], "assign");
1275
+ return statements.some((s) => s.type === "LocalStatement") ? [doBlock(statements)] : statements;
1276
+ }
1277
+ const temps = node.targets.map(() => this.names.fresh("ref"));
1278
+ const body = [local(temps, values)];
1279
+ node.targets.forEach((target, i) => {
1280
+ const value = identifier(temps[i]);
1281
+ if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
1282
+ body.push(...this.destructure(target, value, "assign"));
1283
+ } else {
1284
+ body.push(assign([this.expression(target)], [value]));
1285
+ }
1286
+ });
1287
+ return [doBlock(body)];
1288
+ }
1289
+ // --------------------------------------------------------
1290
+ // Destructuring
1291
+ // --------------------------------------------------------
1292
+ /** Bind (or assign) every name in `pattern` from `source`, which must be a
1293
+ * plain name so that reading it repeatedly has no side effects.
1294
+ *
1295
+ * The reads share one statement, in the pattern's order —
1296
+ * `local a, b = source.a, source.b` — with a temporary standing in for a
1297
+ * nested pattern. Defaults, nested patterns and rest follow it. */
1298
+ destructure(pattern, source, mode) {
1299
+ const keys = [];
1300
+ const reads = [];
1301
+ const after = [];
1302
+ const bind = (target, value, fallback) => {
1303
+ if (target.type === "IdentifierPattern") {
1304
+ const name2 = this.name(target.name);
1305
+ const reference = mode === "declare" ? identifier(name2) : this.reference(target);
1306
+ reads.push({ name: name2, target: reference, value, temp: false });
1307
+ if (fallback) after.push(this.defaultValue(reference, fallback));
1308
+ return;
1309
+ }
1310
+ const name = this.names.fresh("ref");
1311
+ reads.push({ name, target: identifier(name), value, temp: true });
1312
+ if (fallback) after.push(this.defaultValue(identifier(name), fallback));
1313
+ after.push(...this.destructure(target, identifier(name), mode));
1314
+ };
1315
+ if (pattern.type === "ObjectPattern") {
1316
+ const taken = [];
1317
+ for (const property of pattern.properties) {
1318
+ let key;
1319
+ if (!property.computed && (property.key.type === "Identifier" || property.key.type === "StringLiteral")) {
1320
+ const name = property.key.type === "Identifier" ? property.key.name : property.key.value;
1321
+ key = string(name);
1322
+ bind(property.value, member(source, name), property.default);
1323
+ } else {
1324
+ const name = this.names.fresh("key");
1325
+ keys.push({ name, value: this.expression(property.key) });
1326
+ key = identifier(name);
1327
+ bind(property.value, index(source, key), property.default);
1328
+ }
1329
+ taken.push(key);
1330
+ }
1331
+ if (pattern.rest) {
1332
+ const rest = this.restTarget(pattern.rest, table([]), mode, after);
1333
+ const key = this.names.fresh("key");
1334
+ const value = this.names.fresh("value");
1335
+ const kept = taken.reduce((condition, k) => {
1336
+ const differs = binary("~=", identifier(key), k);
1337
+ return condition ? binary("and", condition, differs) : differs;
1338
+ }, void 0);
1339
+ const copy = assign([index(rest.target, identifier(key))], [identifier(value)]);
1340
+ after.push(genericFor([key, value], [call(this.builtin("pairs"), [source])], kept ? [ifThen(kept, [copy])] : [copy]));
1341
+ after.push(...rest.then);
1342
+ }
1343
+ } else {
1344
+ pattern.elements.forEach((element, i) => {
1345
+ if (element) bind(element.value, index(source, number(i + 1)), element.default);
1346
+ });
1347
+ if (pattern.rest) {
1348
+ const elements = call(member(this.builtin("table"), "move"), [
1349
+ source,
1350
+ number(pattern.elements.length + 1),
1351
+ unary("#", source),
1352
+ number(1),
1353
+ table([])
1354
+ ]);
1355
+ const rest = this.restTarget(pattern.rest, elements, mode, after);
1356
+ after.push(...rest.then);
1357
+ }
1358
+ }
1359
+ const out2 = [];
1360
+ if (keys.length) out2.push(local(keys.map((k) => k.name), keys.map((k) => k.value)));
1361
+ if (mode === "declare") {
1362
+ if (reads.length) out2.push(local(reads.map((r) => r.name), reads.map((r) => r.value)));
1363
+ } else {
1364
+ const temps = reads.filter((r) => r.temp);
1365
+ const targets = reads.filter((r) => !r.temp);
1366
+ if (temps.length) out2.push(local(temps.map((r) => r.name), temps.map((r) => r.value)));
1367
+ if (targets.length) out2.push(assign(targets.map((r) => r.target), targets.map((r) => r.value)));
1368
+ }
1369
+ return [...out2, ...after];
1370
+ }
1371
+ /** Where a rest element's new table goes: straight into its name, or into
1372
+ * a temporary that a nested pattern then destructures (`then`). The
1373
+ * statement creating it is pushed onto `after`. */
1374
+ restTarget(target, initial, mode, after) {
1375
+ if (target.type === "IdentifierPattern") {
1376
+ if (mode === "declare") {
1377
+ const name2 = this.name(target.name);
1378
+ after.push(local([name2], [initial]));
1379
+ return { target: identifier(name2), then: [] };
1380
+ }
1381
+ const reference = this.reference(target);
1382
+ after.push(assign([reference], [initial]));
1383
+ return { target: reference, then: [] };
1384
+ }
1385
+ const name = this.names.fresh("rest");
1386
+ after.push(local([name], [initial]));
1387
+ return { target: identifier(name), then: this.destructure(target, identifier(name), mode) };
1388
+ }
1389
+ /** `if target == nil then target = fallback end` */
1390
+ defaultValue(target, fallback) {
1391
+ return ifThen(binary("==", target, nil()), [assign([target], [this.expression(fallback)])]);
1392
+ }
1393
+ // --------------------------------------------------------
1394
+ // Functions
1395
+ // --------------------------------------------------------
1396
+ functionBody(func) {
1397
+ const params = func.isMethod ? func.params.slice(1) : func.params;
1398
+ const prelude = [];
1399
+ const names = [];
1400
+ for (const p of params) {
1401
+ if (p.rest) {
1402
+ prelude.push(local([this.name(p.name)], [table([{ type: "TableFieldPositional", value: vararg() }])]));
1403
+ continue;
1404
+ }
1405
+ const name = p.pattern ? this.names.fresh("arg") : this.name(p.name);
1406
+ if (p.default) prelude.push(this.defaultValue(identifier(name), p.default));
1407
+ if (p.pattern) prelude.push(...this.destructure(p.pattern, identifier(name), "declare"));
1408
+ names.push(name);
1409
+ }
1410
+ return { ...functionBody(names, [], func.hasVarargs), body: this.block(func.body, prelude) };
1411
+ }
1412
+ // --------------------------------------------------------
1413
+ // Expressions
1414
+ // --------------------------------------------------------
1415
+ expression(node) {
1416
+ if (node.type === "CallExpression") {
1417
+ const callee = node.callee;
1418
+ const through = callee.type === "SuperExpression" ? "__init" : callee.type === "MemberExpression" && callee.object.type === "SuperExpression" ? callee.property.name : void 0;
1419
+ if (through !== void 0) {
1420
+ return call(member(this.superClassReference(node), through), [
1421
+ identifier(this.thisName()),
1422
+ ...this.values(node.arguments)
1423
+ ]);
1424
+ }
1425
+ }
1426
+ switch (node.type) {
1427
+ case "Identifier":
1428
+ return this.reference(node);
1429
+ case "NilLiteral":
1430
+ return nil();
1431
+ case "BooleanLiteral":
1432
+ return boolean(node.value);
1433
+ case "NumberLiteral":
1434
+ return number(node.value, this.numberRaw(node));
1435
+ case "StringLiteral":
1436
+ return string(node.value);
1437
+ case "VarargExpression":
1438
+ return vararg();
1439
+ // Only a recovering parse makes one, and a syntax error stops the build.
1440
+ case "ErrorExpression":
1441
+ this.report(node, "Syntax error");
1442
+ return nil();
1443
+ case "InterpolatedStringExpression":
1444
+ return this.interpolatedString(node);
1445
+ case "FunctionExpression":
1446
+ return functionExpression(this.functionBody(node.func));
1447
+ case "TableExpression":
1448
+ return this.tableExpression(node);
1449
+ case "ArrayExpression":
1450
+ return this.arrayExpression(node);
1451
+ case "BinaryExpression": {
1452
+ const left = this.expression(node.left);
1453
+ const right = this.expression(node.right);
1454
+ if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
1455
+ return binary(node.operator, left, right);
1456
+ }
1457
+ case "UnaryExpression":
1458
+ return unary(node.operator, this.expression(node.argument));
1459
+ case "NewExpression":
1460
+ return call(
1461
+ member(this.expression(node.callee), "new"),
1462
+ this.values(node.arguments)
1463
+ );
1464
+ case "SuperExpression":
1465
+ return this.superClassReference(node);
1466
+ case "SpreadElement":
1467
+ this.report(node, "'...' can only spread into a call's arguments or an array");
1468
+ return nil();
1469
+ case "ClassExpression":
1470
+ return this.classExpression(node);
1471
+ case "MemberExpression":
1472
+ case "IndexExpression":
1473
+ case "CallExpression":
1474
+ case "MethodCallExpression": {
1475
+ const chain = optionalChain(node);
1476
+ if (chain) return this.optionalChainExpression(chain);
1477
+ return this.link(node, this.expression(linkObject(node)));
1478
+ }
1479
+ case "ParenthesizedExpression":
1480
+ return parenthesized(this.expression(node.expression));
1481
+ // Types only: the value is the expression itself.
1482
+ case "TypeAssertionExpression":
1483
+ case "SatisfiesExpression":
1484
+ case "AsConstExpression":
1485
+ return this.expression(node.expression);
1486
+ case "IfElseExpression":
1487
+ if (this.lua51) return this.ifElse51(node);
1488
+ return {
1489
+ type: "IfElseExpression",
1490
+ clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
1491
+ alternate: this.expression(node.alternate),
1492
+ ...spanOf(node)
1493
+ };
1494
+ }
1495
+ }
1496
+ /** A list of values — a call's arguments, a `return`'s, a declaration's,
1497
+ * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1498
+ * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1499
+ * the last value of a list expands, so the whole list is built as an
1500
+ * array first and that is what expands. */
1501
+ values(list) {
1502
+ const spreads = list.filter((a) => a.type === "SpreadElement");
1503
+ if (!spreads.length) return list.map((a) => this.expression(a));
1504
+ const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1505
+ const last = list[list.length - 1];
1506
+ if (spreads.length === 1 && last.type === "SpreadElement") {
1507
+ return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
1508
+ }
1509
+ return [unpack(this.arrayExpression({
1510
+ type: "ArrayExpression",
1511
+ elements: [...list],
1512
+ ...spanOf(list[0])
1513
+ }))];
1514
+ }
1515
+ /** One link of an access chain, read from `object`. */
1516
+ link(node, object) {
1517
+ switch (node.type) {
1518
+ case "MemberExpression":
1519
+ return member(object, node.property.name);
1520
+ case "IndexExpression":
1521
+ return index(object, this.expression(node.index));
1522
+ case "CallExpression":
1523
+ return call(object, this.values(node.arguments));
1524
+ case "MethodCallExpression": {
1525
+ const consoleCall = this.consoleCall(node);
1526
+ if (consoleCall) return consoleCall;
1527
+ const args2 = this.values(node.arguments);
1528
+ const lowered = this.loweredMethodCall(node);
1529
+ if (lowered) {
1530
+ return call(calleePath(lowered.callee), lowered.passReceiver ? [object, ...args2] : args2);
1531
+ }
1532
+ if (!isLuauName(node.method.name)) {
1533
+ this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
1534
+ }
1535
+ return methodCall(object, node.method.name, args2);
1536
+ }
1537
+ }
1538
+ }
1539
+ // --------------------------------------------------------
1540
+ // Optional chains
1541
+ // --------------------------------------------------------
1542
+ //
1543
+ // `a?.b.c` is nil when `a` is, and then neither `.b` nor `.c` runs. Each
1544
+ // `?.` tests what the chain holds so far, once: the value is kept in a
1545
+ // local rather than read again.
1546
+ /** The links of a chain from `?.` onwards, split into segments that each
1547
+ * start at a `?.` / `?:`. */
1548
+ segments(chain) {
1549
+ const out2 = [];
1550
+ for (const link of chain.links) {
1551
+ if (link.optional || !out2.length) out2.push([link]);
1552
+ else out2[out2.length - 1].push(link);
1553
+ }
1554
+ return out2;
1555
+ }
1556
+ applySegment(segment, object) {
1557
+ return segment.reduce((value, link) => this.link(link, value), object);
1558
+ }
1559
+ /** `a?.b:m()` as a statement:
1560
+ * `do local ref = a if ref ~= nil then ref.b:m() end end` */
1561
+ optionalCallStatement(chain) {
1562
+ const segments = this.segments(chain);
1563
+ const base = this.expression(chain.base);
1564
+ const ref = base.type === "Identifier" && segments.length === 1 ? base.name : this.names.fresh("ref");
1565
+ const at = identifier(ref);
1566
+ const nest = (i) => {
1567
+ const value = this.applySegment(segments[i], at);
1568
+ if (i === segments.length - 1) {
1569
+ return [callStatement(value)];
1570
+ }
1571
+ return [assign([at], [value]), ifThen(binary("~=", at, nil()), nest(i + 1))];
1572
+ };
1573
+ const test = ifThen(binary("~=", at, nil()), nest(0));
1574
+ if (base.type === "Identifier" && base.name === ref) return test;
1575
+ return doBlock([local([ref], [base]), test]);
1576
+ }
1577
+ /** `a?.b` as a value. The one-test form on a name is an `if` expression;
1578
+ * anything else runs in a function, so every link is evaluated at most
1579
+ * once and in order. */
1580
+ optionalChainExpression(chain) {
1581
+ const segments = this.segments(chain);
1582
+ const base = this.expression(chain.base);
1583
+ const last = chain.links[chain.links.length - 1];
1584
+ if (base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
1585
+ return {
1586
+ type: "IfElseExpression",
1587
+ clauses: [{ condition: binary("==", base, nil()), body: nil() }],
1588
+ alternate: this.applySegment(segments[0], base),
1589
+ line: base.line,
1590
+ column: base.column
1591
+ };
1592
+ }
1593
+ const ref = this.names.fresh("ref");
1594
+ const at = identifier(ref);
1595
+ const statements = [local([ref], [base])];
1596
+ segments.forEach((segment, i) => {
1597
+ statements.push(ifThen(binary("==", at, nil()), [returns([nil()])]));
1598
+ const value = this.applySegment(segment, at);
1599
+ statements.push(i === segments.length - 1 ? returns([value]) : assign([at], [value]));
1600
+ });
1601
+ const passesVarargs = statements.some(usesVararg);
1602
+ const func = functionExpression(functionBody([], statements, passesVarargs));
1603
+ return call(parenthesized(func), passesVarargs ? [vararg()] : []);
1604
+ }
1605
+ /** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
1606
+ interpolatedString(node) {
1607
+ let format = "";
1608
+ let text = "";
1609
+ const args2 = [];
1610
+ for (const part of node.parts) {
1611
+ if (part.kind === "string") {
1612
+ format += part.value.replace(/%/g, "%%");
1613
+ text += part.value;
1614
+ } else {
1615
+ format += "%s";
1616
+ args2.push(call(this.builtin("tostring"), [this.expression(part.expression)]));
1617
+ }
1618
+ }
1619
+ if (!args2.length) return string(text);
1620
+ return methodCall(parenthesized(string(format)), "format", args2);
1621
+ }
1622
+ /** `a += b` for Lua 5.1, which has no compound assignment: `a = a + b`.
1623
+ *
1624
+ * The target has to be read and written, and writing it out twice would
1625
+ * evaluate whatever addresses it twice — `t[next()] += 1` would advance
1626
+ * twice and add to the wrong slot. So anything but a plain name has its
1627
+ * object and key taken into locals first, and both halves use those. */
1628
+ compoundAssignment51(node) {
1629
+ const operator = node.operator.slice(0, -1);
1630
+ const before = [];
1631
+ let target;
1632
+ if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
1633
+ const object = this.expression(node.target.object);
1634
+ const objectName = this.names.fresh("target");
1635
+ before.push(local([objectName], [object]));
1636
+ if (node.target.type === "MemberExpression") {
1637
+ target = member(identifier(objectName), node.target.property.name);
1638
+ } else {
1639
+ const keyName = this.names.fresh("key");
1640
+ before.push(local([keyName], [this.expression(node.target.index)]));
1641
+ target = index(identifier(objectName), identifier(keyName));
1642
+ }
1643
+ } else {
1644
+ target = this.expression(node.target);
1645
+ }
1646
+ const value = this.expression(node.value);
1647
+ const combined = operator === "//" ? this.floorDivide(target, value) : binary(operator, target, parenthesized(value));
1648
+ const statement = assign([target], [combined]);
1649
+ return before.length ? [doBlock([...before, statement])] : [statement];
1650
+ }
1651
+ /** A loop body for Lua 5.1, which has neither `continue` nor the `goto`
1652
+ * every other Lua lowers it to.
1653
+ *
1654
+ * `repeat ... until true` runs its body exactly once, so a `break` inside
1655
+ * it abandons the rest of the body and lands back in the enclosing loop —
1656
+ * which is what `continue` means. The catch is that a *real* `break` in
1657
+ * the same body would now leave only that inner `repeat`, so when the
1658
+ * body has one it sets a flag that the loop checks once the `repeat` is
1659
+ * done, and breaks for real there. A body with no `continue` is left
1660
+ * exactly as it was. */
1661
+ loopBody51(body) {
1662
+ if (!this.lua51 || !hasOwnJump(body.statements, "ContinueStatement")) return body;
1663
+ const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
1664
+ const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
1665
+ if (!flag) return block([once]);
1666
+ return block([
1667
+ local([flag], [boolean(false)]),
1668
+ once,
1669
+ ifThen(identifier(flag), [breakStatement()])
1670
+ ]);
1671
+ }
1672
+ /** `if c then a else b` as an expression, for Lua 5.1, which has none.
1673
+ *
1674
+ * `c and a or b` is the usual stand-in, but it is wrong whenever `a` can
1675
+ * be `false` or `nil`: it then yields `b` even though `c` held. So it is
1676
+ * only used where every branch is written as a value that can be neither,
1677
+ * and anything else becomes a call — one closure, but always the branch
1678
+ * that was written. */
1679
+ ifElse51(node) {
1680
+ const clauses = node.clauses.map((c) => ({
1681
+ condition: this.expression(c.condition),
1682
+ body: this.expression(c.body)
1683
+ }));
1684
+ const alternate = this.expression(node.alternate);
1685
+ if (clauses.every((c) => alwaysTruthy(c.body))) {
1686
+ let result = alternate;
1687
+ for (let i = clauses.length - 1; i >= 0; i--) {
1688
+ const { condition, body } = clauses[i];
1689
+ result = binary("or", binary("and", condition, body), result);
1690
+ }
1691
+ return parenthesized(result);
1692
+ }
1693
+ const statement = {
1694
+ type: "IfStatement",
1695
+ clauses: clauses.map((c) => ({
1696
+ type: "IfClause",
1697
+ condition: c.condition,
1698
+ body: block([returns([c.body])]),
1699
+ ...spanOf(node)
1700
+ })),
1701
+ alternate: block([returns([alternate])]),
1702
+ ...spanOf(node)
1703
+ };
1704
+ return call(
1705
+ parenthesized(functionExpression(functionBody([], [statement]))),
1706
+ []
1707
+ );
1708
+ }
1709
+ /** How a number is written out. Luau takes what was written; Lua 5.1 has
1710
+ * neither digit separators nor binary literals, so `1_000_000` loses its
1711
+ * underscores and `0b1010` becomes the decimal it means. A hex literal
1712
+ * keeps its base — 5.1 reads those. */
1713
+ numberRaw(node) {
1714
+ if (!this.lua51) return node.raw;
1715
+ if (/^0[bB]/.test(node.raw)) return String(node.value);
1716
+ return node.raw.replace(/_/g, "");
1717
+ }
1718
+ /** `a // b` for Lua 5.1, which has no floor-division operator. */
1719
+ floorDivide(left, right) {
1720
+ return call(
1721
+ member(this.builtin("math"), "floor"),
1722
+ [binary("/", left, parenthesized(right))]
1723
+ );
1724
+ }
1725
+ /** `{ a: 1, b, [k]: v }`. A spread splits the literal into parts that
1726
+ * `assign` copies into one table in order, so a later key still wins. */
1727
+ tableExpression(node) {
1728
+ const parts = [];
1729
+ let current = [];
1730
+ for (const f of node.fields) {
1731
+ switch (f.type) {
1732
+ case "TableFieldNamed": {
1733
+ const key = f.key.type === "Identifier" ? f.key.name : f.key.value;
1734
+ current.push(field(key, this.expression(f.value)));
1735
+ break;
1736
+ }
1737
+ case "TableFieldShorthand":
1738
+ current.push(field(f.name.name, this.reference(f.name)));
1739
+ break;
1740
+ case "TableFieldComputed":
1741
+ current.push({ type: "TableFieldComputed", key: this.expression(f.key), value: this.expression(f.value) });
1742
+ break;
1743
+ case "TableFieldSpread":
1744
+ if (current.length) parts.push(table(current));
1745
+ current = [];
1746
+ parts.push(this.expression(f.argument));
1747
+ break;
1748
+ }
1749
+ }
1750
+ if (parts.length === 0) return table(current);
1751
+ if (current.length) parts.push(table(current));
1752
+ return call(this.helper("assign"), [table([]), ...parts]);
1753
+ }
1754
+ /** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
1755
+ * runs of plain elements and the spread arrays are joined by `concat`. */
1756
+ arrayExpression(node) {
1757
+ if (!node.elements.some((e) => e.type === "SpreadElement")) {
1758
+ return table(node.elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1759
+ }
1760
+ const parts = [];
1761
+ let current = [];
1762
+ const flush = () => {
1763
+ if (!current.length) return;
1764
+ parts.push(table(current.map((e, i) => {
1765
+ const value = this.expression(e);
1766
+ const last = i === current.length - 1;
1767
+ return { type: "TableFieldPositional", value: last && expandsToMany(value) ? parenthesized(value) : value };
1768
+ })));
1769
+ current = [];
1770
+ };
1771
+ for (const element of node.elements) {
1772
+ if (element.type === "SpreadElement") {
1773
+ flush();
1774
+ parts.push(this.expression(element.argument));
1775
+ } else {
1776
+ current.push(element);
1777
+ }
1778
+ }
1779
+ flush();
1780
+ return call(this.helper("concat"), parts);
1781
+ }
1782
+ };
1783
+ function isLink(node) {
1784
+ return node.type === "MemberExpression" || node.type === "IndexExpression" || node.type === "CallExpression" || node.type === "MethodCallExpression";
1785
+ }
1786
+ function linkObject(node) {
1787
+ return node.type === "CallExpression" ? node.callee : node.object;
1788
+ }
1789
+ function optionalChain(node) {
1790
+ const spine = [];
1791
+ for (let e = node; isLink(e); e = linkObject(e)) spine.unshift(e);
1792
+ const first = spine.findIndex((link) => link.optional);
1793
+ if (first < 0) return void 0;
1794
+ return { base: linkObject(spine[first]), links: spine.slice(first) };
1795
+ }
1796
+ function usesVararg(node) {
1797
+ if (Array.isArray(node)) return node.some(usesVararg);
1798
+ if (!node || typeof node !== "object") return false;
1799
+ const record = node;
1800
+ if (record.type === "VarargExpression") return true;
1801
+ if (record.type === "FunctionExpression" || record.type === "LocalFunctionStatement" || record.type === "FunctionStatement") return false;
1802
+ return Object.values(record).some(usesVararg);
1803
+ }
1804
+ function calleePath(path) {
1805
+ const [head, ...rest] = path.split(".");
1806
+ return rest.reduce((value, name) => member(value, name), identifier(head));
1807
+ }
1808
+ function spanOf(node) {
1809
+ return { line: node.line, column: node.column };
1810
+ }
1811
+ function vararg() {
1812
+ return { type: "VarargExpression", line: { start: 0, end: 0 }, column: { start: 0, end: 0 } };
1813
+ }
1814
+ function selectCount(select) {
1815
+ return call(select, [string("#"), vararg()]);
1816
+ }
1817
+ function expandsToMany(e) {
1818
+ return e.type === "CallExpression" || e.type === "MethodCallExpression" || e.type === "VarargExpression";
1819
+ }
1820
+ function isSuperInit(statement) {
1821
+ return statement.type === "CallStatement" && statement.expression.type === "CallExpression" && statement.expression.callee.type === "MemberExpression" && statement.expression.callee.property.name === "__init";
1822
+ }
1823
+ function memberChain(e) {
1824
+ if (e.type === "Identifier") return [e.name];
1825
+ if (e.type === "MemberExpression") {
1826
+ const base = memberChain(e.object);
1827
+ return base && [...base, e.property.name];
1828
+ }
1829
+ return void 0;
1830
+ }
1831
+ function identifierPatterns(target) {
1832
+ switch (target.type) {
1833
+ case "IdentifierPattern":
1834
+ return [target];
1835
+ case "ObjectPattern":
1836
+ return [...target.properties.flatMap((p) => identifierPatterns(p.value)), ...target.rest ? identifierPatterns(target.rest) : []];
1837
+ case "ArrayPattern":
1838
+ return [
1839
+ ...target.elements.flatMap((e) => e ? identifierPatterns(e.value) : []),
1840
+ ...target.rest ? identifierPatterns(target.rest) : []
1841
+ ];
1842
+ }
1843
+ }
1844
+ function topLevelDeclaration(statements, name) {
1845
+ for (const statement of statements) {
1846
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1847
+ if (declaration.type === "VariableDeclaration") {
1848
+ const pattern = declaration.names.flatMap(identifierPatterns).find((p) => p.name === name);
1849
+ if (pattern) return { type: "Declaration", node: pattern };
1850
+ } else if (declaration.type === "FunctionDeclaration" && declaration.name.name === name) {
1851
+ return { type: "Declaration", node: declaration.name };
1852
+ } else if (declaration.type === "ImportStatement") {
1853
+ const local2 = declaration.defaultImport?.name === name ? declaration.defaultImport : declaration.specifiers.find((s) => s.local.name === name)?.local;
1854
+ if (local2) return { type: "ImportStatement", node: local2 };
1855
+ }
1856
+ }
1857
+ return void 0;
1858
+ }
1859
+ function moduleName(key) {
1860
+ const last = key.split("/").filter(Boolean).pop() ?? "module";
1861
+ const cleaned = last.replace(/[^A-Za-z0-9_]/g, "_");
1862
+ return /^[A-Za-z_]/.test(cleaned) && !LUAU_KEYWORDS.has(cleaned) ? cleaned : `module_${cleaned}`;
1863
+ }
1864
+ function alwaysTruthy(value) {
1865
+ switch (value.type) {
1866
+ case "NumberLiteral":
1867
+ case "StringLiteral":
1868
+ case "InterpolatedStringExpression":
1869
+ case "TableExpression":
1870
+ case "FunctionExpression":
1871
+ return true;
1872
+ case "BooleanLiteral":
1873
+ return value.value;
1874
+ case "ParenthesizedExpression":
1875
+ return alwaysTruthy(value.expression);
1876
+ default:
1877
+ return false;
1878
+ }
1879
+ }
1880
+ function ownBlocks(statement) {
1881
+ switch (statement.type) {
1882
+ case "DoStatement":
1883
+ return [statement.body];
1884
+ case "IfStatement":
1885
+ return [...statement.clauses.map((c) => c.body), ...statement.alternate ? [statement.alternate] : []];
1886
+ default:
1887
+ return [];
1888
+ }
1889
+ }
1890
+ function hasOwnJump(statements, type) {
1891
+ return statements.some((statement) => statement.type === type || ownBlocks(statement).some((b) => hasOwnJump(b.statements, type)));
1892
+ }
1893
+ function rewriteOwnJumps(statements, flag) {
1894
+ return statements.flatMap((statement) => {
1895
+ if (statement.type === "ContinueStatement") return [breakStatement()];
1896
+ if (statement.type === "BreakStatement") {
1897
+ return flag ? [assign([identifier(flag)], [boolean(true)]), breakStatement()] : [statement];
1898
+ }
1899
+ if (statement.type === "DoStatement") {
1900
+ return [doBlock(rewriteOwnJumps(statement.body.statements, flag))];
1901
+ }
1902
+ if (statement.type === "IfStatement") {
1903
+ return [{
1904
+ ...statement,
1905
+ clauses: statement.clauses.map((c) => ({
1906
+ ...c,
1907
+ body: block(rewriteOwnJumps(c.body.statements, flag))
1908
+ })),
1909
+ alternate: statement.alternate && block(rewriteOwnJumps(statement.alternate.statements, flag))
1910
+ }];
1911
+ }
1912
+ return [statement];
1913
+ });
1914
+ }
1915
+ function declaredNames(statements) {
1916
+ const names = [];
1917
+ for (const statement of statements) {
1918
+ if (statement.type === "LocalStatement") names.push(...statement.names.map((n) => n.name));
1919
+ else if (statement.type === "LocalFunctionStatement") names.push(statement.name.name);
1920
+ }
1921
+ return names;
1922
+ }
1923
+ function mentions(expression, name) {
1924
+ let found = false;
1925
+ const walk = (value) => {
1926
+ if (found || !value || typeof value !== "object") return;
1927
+ if (Array.isArray(value)) {
1928
+ for (const item of value) walk(item);
1929
+ return;
1930
+ }
1931
+ const node = value;
1932
+ if (node.type === "Identifier" && node.name === name) {
1933
+ found = true;
1934
+ return;
1935
+ }
1936
+ for (const [key, child] of Object.entries(value)) {
1937
+ if (key !== "line" && key !== "column") walk(child);
1938
+ }
1939
+ };
1940
+ walk(expression);
1941
+ return found;
1942
+ }
1943
+ function isFunctionType(type) {
1944
+ if (type.kind === "function") return true;
1945
+ return type.kind === "intersection" && type.types.some((t) => t.kind === "function");
1946
+ }
1947
+ function allNodes(root) {
1948
+ const out2 = [];
1949
+ const visit = (value) => {
1950
+ if (!value || typeof value !== "object") return;
1951
+ if (Array.isArray(value)) return void value.forEach(visit);
1952
+ const record = value;
1953
+ if (typeof record.type === "string") out2.push(value);
1954
+ for (const [key, child] of Object.entries(record)) {
1955
+ if (key !== "line" && key !== "column") visit(child);
1956
+ }
1957
+ };
1958
+ visit(root);
1959
+ return out2;
1960
+ }
1961
+
1962
+ // src/linemap.ts
1963
+ function statementsOf(node) {
1964
+ const out2 = [];
1965
+ const visit = (value) => {
1966
+ if (!value || typeof value !== "object") return;
1967
+ if (Array.isArray(value)) return void value.forEach(visit);
1968
+ const record = value;
1969
+ if (typeof record.type === "string" && record.type.endsWith("Statement")) {
1970
+ out2.push(value);
1971
+ }
1972
+ for (const [key, child] of Object.entries(record)) {
1973
+ if (key !== "line" && key !== "column") visit(child);
1974
+ }
1975
+ };
1976
+ visit(node);
1977
+ return out2;
1978
+ }
1979
+ function buildLineMap(emitted, printed, origins) {
1980
+ const before = statementsOf(emitted);
1981
+ const after = statementsOf(printed);
1982
+ if (before.length !== after.length) return /* @__PURE__ */ new Map();
1983
+ const map = /* @__PURE__ */ new Map();
1984
+ for (let i = 0; i < before.length; i++) {
1985
+ if (before[i].type !== after[i].type) return /* @__PURE__ */ new Map();
1986
+ const origin = origins.get(before[i]);
1987
+ if (!origin) continue;
1988
+ const line = after[i].line.start;
1989
+ if (!map.has(line)) map.set(line, origin);
1990
+ }
1991
+ return map;
1992
+ }
1993
+ function lineMapSource(map) {
1994
+ const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
1995
+ const quoted = (s) => JSON.stringify(s.replace(/\\/g, "/"));
1996
+ return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
1997
+ }
1998
+
1999
+ // src/lowering.ts
2000
+ var import_node_url = require("url");
2001
+ async function loadLowerings(modules) {
2002
+ const lowerings = [];
2003
+ const problems = [];
2004
+ for (const module2 of modules) {
2005
+ try {
2006
+ const loaded = await import((0, import_node_url.pathToFileURL)(module2.file).href);
2007
+ const plugin = loaded.default;
2008
+ if (!plugin || typeof plugin !== "object") {
2009
+ problems.push({
2010
+ file: module2.file,
2011
+ message: `'${module2.from}' lowering module has no default export`
2012
+ });
2013
+ continue;
2014
+ }
2015
+ lowerings.push({ plugin, from: module2.from });
2016
+ } catch (error) {
2017
+ problems.push({
2018
+ file: module2.file,
2019
+ message: `'${module2.from}' lowering module failed to load: ${error.message}`
2020
+ });
2021
+ }
2022
+ }
2023
+ return { lowerings, problems };
2024
+ }
2025
+
2026
+ // src/bundle.ts
2027
+ async function bundle(options) {
2028
+ const entry2 = (0, import_node_path2.resolve)(options.entry);
2029
+ const diagnostics = [];
2030
+ const inline = options.config !== void 0 && typeof options.config !== "string";
2031
+ const { config: config2, problems } = resolveConfig(options.config, entry2, options.root ?? (inline ? (0, import_node_path2.dirname)(entry2) : process.cwd()));
2032
+ for (const p of problems) diagnostics.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
2033
+ const root = (0, import_node_path2.resolve)(options.root ?? config2?.directory ?? (0, import_node_path2.dirname)(entry2));
2034
+ const nameOf = (file) => (0, import_node_path2.relative)(root, file).replace(/\\/g, "/").replace(/\.tilua$/, "");
2035
+ const sources = /* @__PURE__ */ new Map();
2036
+ const directives = /* @__PURE__ */ new Map();
2037
+ const queue = [entry2];
2038
+ while (queue.length) {
2039
+ const file = queue.shift();
2040
+ if (sources.has(file)) continue;
2041
+ const parsed = parseFile(file, diagnostics, directives);
2042
+ if (!parsed) return { modules: [], diagnostics };
2043
+ const { program: program2, text } = parsed;
2044
+ const scopes = (0, import_parser3.analyzeScopes)(program2);
2045
+ for (const d of scopes.diagnostics) {
2046
+ diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
2047
+ }
2048
+ sources.set(file, { file, name: nameOf(file), text, program: program2, scopes });
2049
+ for (const specifier of importedSpecifiers(program2)) {
2050
+ const target = (0, import_parser3.resolveModulePath)(file, specifier, config2);
2051
+ if (target && !target.endsWith(".d.tilua")) queue.push(target);
2052
+ }
2053
+ }
2054
+ const analysis = analyzeModules([...sources.values()], config2);
2055
+ const { lowerings, problems: loweringProblems } = await loadLowerings(analysis.lowerings);
2056
+ for (const p of loweringProblems) {
2057
+ diagnostics.push({ file: p.file, message: p.message, line: 1, column: 1, category: "config" });
2058
+ }
2059
+ if (options.typeCheck !== false) diagnostics.push(...analysis.diagnostics);
2060
+ const names = Names.from(...[...sources.values()].map((s) => s.program));
2061
+ const G = names.fresh("G");
2062
+ const requireExpression = member(identifier(G), "require");
2063
+ const modules = /* @__PURE__ */ new Map();
2064
+ const origins = /* @__PURE__ */ new WeakMap();
2065
+ const pending = [entry2];
2066
+ while (pending.length) {
2067
+ const file = pending.shift();
2068
+ if (modules.has(file)) continue;
2069
+ const source = sources.get(file);
2070
+ const lowered = lower(source.program, source.scopes, {
2071
+ names,
2072
+ types: analysis.types.get(file),
2073
+ lowerings,
2074
+ target: options.target ?? config2?.target,
2075
+ source: source.text,
2076
+ module: {
2077
+ name: source.name,
2078
+ require: requireExpression,
2079
+ lines: `${G}.lines`,
2080
+ resolve: (specifier) => {
2081
+ const target = (0, import_parser3.resolveModulePath)(file, specifier, config2);
2082
+ if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
2083
+ pending.push(target);
2084
+ return sources.get(target).name;
2085
+ }
2086
+ }
2087
+ });
2088
+ for (const d of lowered.diagnostics) diagnostics.push({ file, ...d, category: "module" });
2089
+ const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
2090
+ for (const statement of allStatements(lowered.statements)) {
2091
+ const line = lowered.origins.get(statement);
2092
+ if (line !== void 0) origins.set(statement, { file: shown, line });
2093
+ }
2094
+ modules.set(file, {
2095
+ name: source.name,
2096
+ statements: lowered.statements,
2097
+ exportsName: lowered.exportsName,
2098
+ info: lowered.module
2099
+ });
2100
+ }
2101
+ for (const [file, fileDirectives] of directives) {
2102
+ const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
2103
+ const { kept, unusedExpectErrors } = (0, import_parser3.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2104
+ const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
2105
+ const remaining = diagnostics.filter((d) => !suppressed.has(d));
2106
+ diagnostics.length = 0;
2107
+ diagnostics.push(...remaining);
2108
+ if (options.typeCheck === false) continue;
2109
+ for (const d of unusedExpectErrors) {
2110
+ diagnostics.push({ file, message: import_parser3.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2111
+ }
2112
+ }
2113
+ const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
2114
+ const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
2115
+ diagnostics.length = 0;
2116
+ diagnostics.push(...reported);
2117
+ const moduleNames = [...modules.values()].map((m) => m.name);
2118
+ if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
2119
+ return { modules: moduleNames, diagnostics };
2120
+ }
2121
+ const program = (0, import_luau_parser2.parse)(runtime(G));
2122
+ const assignment = program.body.statements[1];
2123
+ const modulesTable = assignment.type === "AssignmentStatement" && assignment.values[0].type === "TableExpression" ? assignment.values[0].fields.find((f) => f.type === "TableFieldNamed" && f.name.name === "modules")?.value : void 0;
2124
+ if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
2125
+ for (const module2 of modules.values()) {
2126
+ const { names: exported, links, stars } = module2.info;
2127
+ const fields = [];
2128
+ if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
2129
+ if (links.length) {
2130
+ fields.push(field("links", table(links.map((link) => field(link.name, table([
2131
+ { type: "TableFieldPositional", value: string(link.module) },
2132
+ ...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
2133
+ ]))))));
2134
+ }
2135
+ if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
2136
+ fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
2137
+ modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
2138
+ }
2139
+ const start = call(requireExpression, [string(sources.get(entry2).name)]);
2140
+ program.body.statements.push(modules.get(entry2).info.exports ? returns([start]) : callStatement(start));
2141
+ const map = buildLineMap(program, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program)), origins);
2142
+ if (map.size) {
2143
+ program.body.statements.splice(program.body.statements.length - 1, 0, assign(
2144
+ [member(identifier(G), "lines")],
2145
+ [(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
2146
+ ));
2147
+ }
2148
+ return { code: (0, import_luau_parser2.print)(program) + "\n", modules: moduleNames, diagnostics };
2149
+ }
2150
+ function runtime(G) {
2151
+ return `
2152
+ local ${G}
2153
+ ${G} = {
2154
+ modules = {},
2155
+ records = {},
2156
+ require = function(name)
2157
+ local record = ${G}.records[name]
2158
+ if record ~= nil then
2159
+ return record.exports
2160
+ end
2161
+ local module = ${G}.modules[name]
2162
+ local initialized = {}
2163
+ local exports = setmetatable({}, {
2164
+ __index = function(_, key)
2165
+ if initialized[key] then
2166
+ return nil
2167
+ end
2168
+ if module.names ~= nil and module.names[key] then
2169
+ error(("Cannot access '%s' before initialization: '%s' has not reached it yet"):format(tostring(key), name), 2)
2170
+ end
2171
+ local link = module.links ~= nil and module.links[key]
2172
+ if link then
2173
+ local source = ${G}.require(link[1])
2174
+ if link[2] == nil then
2175
+ return source
2176
+ end
2177
+ return source[link[2]]
2178
+ end
2179
+ if module.stars ~= nil and key ~= "default" then
2180
+ for _, star in module.stars do
2181
+ local value = ${G}.require(star)[key]
2182
+ if value ~= nil then
2183
+ return value
2184
+ end
2185
+ end
2186
+ end
2187
+ return nil
2188
+ end,
2189
+ __newindex = function(target, key, value)
2190
+ initialized[key] = true
2191
+ rawset(target, key, value)
2192
+ end,
2193
+ })
2194
+ record = { loading = true, exports = exports }
2195
+ ${G}.records[name] = record
2196
+ module.load(exports)
2197
+ record.loading = false
2198
+ return exports
2199
+ end,
2200
+ }
2201
+ `;
2202
+ }
2203
+ function parseFile(file, diagnostics, directives) {
2204
+ let text;
2205
+ try {
2206
+ text = (0, import_node_fs.readFileSync)(file, "utf8");
2207
+ } catch {
2208
+ diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
2209
+ return void 0;
2210
+ }
2211
+ try {
2212
+ const program = (0, import_parser3.parse)(text);
2213
+ directives.set(file, (0, import_parser3.directivesOf)(text));
2214
+ return { program, text };
2215
+ } catch (error) {
2216
+ if (error instanceof import_parser3.ParseError || error instanceof import_parser3.LexError) {
2217
+ const { line, column } = error;
2218
+ diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
2219
+ return void 0;
2220
+ }
2221
+ throw error;
2222
+ }
2223
+ }
2224
+ function importedSpecifiers(program) {
2225
+ return program.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
2226
+ }
2227
+ function analyzeModules(modules, config2) {
2228
+ const out2 = [];
2229
+ const analyses = /* @__PURE__ */ new Map();
2230
+ const libraries = config2 ? (0, import_parser3.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
2231
+ for (const p of libraries.problems) out2.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
2232
+ const libs = libraries.files.map((file) => (0, import_parser3.parse)((0, import_node_fs.readFileSync)(file, "utf8")));
2233
+ const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
2234
+ const byFile = new Map(modules.map((m) => [m.file, m]));
2235
+ const exportsCache = /* @__PURE__ */ new Map();
2236
+ const inProgress = /* @__PURE__ */ new Set();
2237
+ const resolverFor = (file) => (specifier) => {
2238
+ const target = (0, import_parser3.resolveModulePath)(file, specifier, config2);
2239
+ if (!target) return void 0;
2240
+ if (inProgress.has(target)) return { values: /* @__PURE__ */ new Map(), types: /* @__PURE__ */ new Map(), partial: true };
2241
+ const cached = exportsCache.get(target);
2242
+ if (cached) return cached;
2243
+ let program = byFile.get(target)?.program;
2244
+ if (!program) {
2245
+ try {
2246
+ program = (0, import_parser3.parse)((0, import_node_fs.readFileSync)(target, "utf8"));
2247
+ } catch {
2248
+ return void 0;
2249
+ }
2250
+ }
2251
+ inProgress.add(target);
2252
+ try {
2253
+ const scopes = (0, import_parser3.analyzeScopes)(program, { builtinGlobals: globals });
2254
+ const types = (0, import_parser3.analyzeTypes)(program, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2255
+ const exports2 = (0, import_parser3.moduleExports)(program, scopes, types, resolverFor(target));
2256
+ exportsCache.set(target, exports2);
2257
+ return exports2;
2258
+ } finally {
2259
+ inProgress.delete(target);
2260
+ }
2261
+ };
2262
+ for (const module2 of modules) {
2263
+ const scopes = (0, import_parser3.analyzeScopes)(module2.program, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2264
+ for (const d of scopes.diagnostics) {
2265
+ if (d.kind !== "undeclared") continue;
2266
+ out2.push({ file: module2.file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
2267
+ }
2268
+ const types = (0, import_parser3.analyzeTypes)(module2.program, scopes, {
2269
+ libs,
2270
+ resolveModule: resolverFor(module2.file),
2271
+ reportUnknownTypes: libs.length > 0
2272
+ });
2273
+ analyses.set(module2.file, types);
2274
+ for (const d of types.diagnostics) {
2275
+ const at = d.node;
2276
+ out2.push({ file: module2.file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
2277
+ }
2278
+ }
2279
+ return { types: analyses, diagnostics: out2, lowerings: [...libraries.lowerings] };
2280
+ }
2281
+ function allStatements(statements) {
2282
+ const out2 = [];
2283
+ const visit = (value) => {
2284
+ if (!value || typeof value !== "object") return;
2285
+ if (Array.isArray(value)) return void value.forEach(visit);
2286
+ const record = value;
2287
+ if (typeof record.type === "string" && record.type.endsWith("Statement")) {
2288
+ out2.push(value);
2289
+ }
2290
+ for (const [key, child] of Object.entries(record)) {
2291
+ if (key !== "line" && key !== "column") visit(child);
2292
+ }
2293
+ };
2294
+ visit(statements);
2295
+ return out2;
2296
+ }
2297
+
2298
+ // src/cli.ts
2299
+ var usage = "usage: tilua <entry> [--out <file>] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
2300
+ var args = process.argv.slice(2);
2301
+ var entry;
2302
+ var out;
2303
+ var config;
2304
+ var typeCheck = true;
2305
+ var buildTarget;
2306
+ for (let i = 0; i < args.length; i++) {
2307
+ const arg = args[i];
2308
+ if (arg === "--out") out = args[++i];
2309
+ else if (arg === "--config") config = args[++i];
2310
+ else if (arg === "--noCheck") typeCheck = false;
2311
+ else if (arg === "--target") {
2312
+ const value = args[++i];
2313
+ if (value !== "luau" && value !== "lua51") {
2314
+ console.error(`--target must be 'luau' or 'lua51', not '${value}'`);
2315
+ process.exit(2);
2316
+ }
2317
+ buildTarget = value;
2318
+ } else if (arg === "--help" || arg === "-h") {
2319
+ console.log(usage);
2320
+ process.exit(0);
2321
+ } else entry = arg;
2322
+ }
2323
+ if (!entry) {
2324
+ console.error(usage);
2325
+ process.exit(2);
2326
+ }
2327
+ async function main(from) {
2328
+ const result = await bundle({ entry: from, config, typeCheck, target: buildTarget });
2329
+ for (const d of result.diagnostics) {
2330
+ console.error(`${(0, import_node_path3.relative)(process.cwd(), d.file)}:${d.line}:${d.column} ${d.message}`);
2331
+ }
2332
+ if (result.code === void 0) {
2333
+ console.error("no bundle written");
2334
+ process.exit(1);
2335
+ }
2336
+ const target = out ?? from.replace(/\.tilua$/, "") + ".luau";
2337
+ (0, import_node_fs2.writeFileSync)(target, result.code);
2338
+ console.log(`${target}: ${result.modules.length} module${result.modules.length === 1 ? "" : "s"}`);
2339
+ process.exit(result.diagnostics.length ? 1 : 0);
2340
+ }
2341
+ main(entry).catch((error) => {
2342
+ console.error(error instanceof Error ? error.message : error);
2343
+ process.exit(1);
2344
+ });