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