@tilua/compiler 1.0.0

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