@tilua/compiler 3.1.0 → 5.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/README.md +122 -12
- package/dist/{chunk-VANZBLCL.js → chunk-3J2QMA4W.js} +699 -135
- package/dist/cli.cjs +764 -178
- package/dist/cli.js +32 -9
- package/dist/index.cjs +721 -156
- package/dist/index.d.cts +44 -6
- package/dist/index.d.ts +44 -6
- package/dist/index.js +6 -4
- package/package.json +2 -2
- package/runtime/array.luau +17 -9
- package/runtime/console.luau +227 -0
- package/runtime/object.luau +4 -4
- package/runtime/result.luau +122 -0
- package/runtime/string.luau +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -6,13 +6,13 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
6
6
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
|
-
var
|
|
9
|
+
var import_node_fs4 = require("fs");
|
|
10
10
|
var import_node_path3 = require("path");
|
|
11
11
|
|
|
12
12
|
// src/bundle.ts
|
|
13
|
-
var
|
|
13
|
+
var import_node_fs3 = require("fs");
|
|
14
14
|
var import_node_path2 = require("path");
|
|
15
|
-
var
|
|
15
|
+
var import_parser7 = require("@tilua/parser");
|
|
16
16
|
var import_luau_parser2 = require("luau-parser");
|
|
17
17
|
|
|
18
18
|
// src/config.ts
|
|
@@ -27,7 +27,7 @@ function resolveConfig(input, entry2, cwd = process.cwd()) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// src/lower.ts
|
|
30
|
-
var
|
|
30
|
+
var import_parser5 = require("@tilua/parser");
|
|
31
31
|
var import_luau_parser = require("luau-parser");
|
|
32
32
|
|
|
33
33
|
// src/luau.ts
|
|
@@ -91,6 +91,7 @@ var functionBody = (params, statements, hasVarargs = false) => ({
|
|
|
91
91
|
...span()
|
|
92
92
|
});
|
|
93
93
|
var localFunction = (name, func) => ({ type: "LocalFunctionStatement", name: identifier(name), func, ...span() });
|
|
94
|
+
var program = (statements) => ({ type: "Program", body: block(statements), ...span() });
|
|
94
95
|
|
|
95
96
|
// src/names.ts
|
|
96
97
|
var Names = class _Names {
|
|
@@ -114,7 +115,7 @@ var Names = class _Names {
|
|
|
114
115
|
if (key !== "line" && key !== "column") walk(child);
|
|
115
116
|
}
|
|
116
117
|
};
|
|
117
|
-
for (const
|
|
118
|
+
for (const program2 of programs) walk(program2);
|
|
118
119
|
return new _Names(taken);
|
|
119
120
|
}
|
|
120
121
|
/** A copy that goes on to take names independently — one per module, each
|
|
@@ -188,9 +189,80 @@ var plugin = {
|
|
|
188
189
|
};
|
|
189
190
|
var languageLowering = { plugin, from: "tilua" };
|
|
190
191
|
|
|
192
|
+
// src/standard.ts
|
|
193
|
+
var import_node_fs2 = require("fs");
|
|
194
|
+
var import_parser4 = require("@tilua/parser");
|
|
195
|
+
var read2 = (name) => (0, import_node_fs2.readFileSync)(new URL(`../runtime/${name}`, importMetaUrl), "utf8");
|
|
196
|
+
var console2 = read2("console.luau");
|
|
197
|
+
var result = read2("result.luau");
|
|
198
|
+
var place = (at) => (0, import_parser4.luauString)(at.file === void 0 ? `line ${at.line}` : `${at.file}:${at.line}`);
|
|
199
|
+
var tag = (at) => (0, import_parser4.luauString)(`[${at.file === void 0 ? `line ${at.line}` : `${at.file.replace(/\.tilua$/, "")}:${at.line}`}]:`);
|
|
200
|
+
function consoleMeta(args2) {
|
|
201
|
+
const fields = [];
|
|
202
|
+
args2.forEach((argument, index2) => {
|
|
203
|
+
if (argument.spread || argument.typeText === void 0 || !(0, import_parser4.isFunctionType)(argument.type)) return;
|
|
204
|
+
const written = argument.declaration ?? argument.source;
|
|
205
|
+
fields.push(`[${index2 + 1}] = { ${(0, import_parser4.luauString)(argument.typeText)}, ${(0, import_parser4.luauString)(written)} }`);
|
|
206
|
+
});
|
|
207
|
+
return fields.length ? `{ ${fields.join(", ")} }` : "nil";
|
|
208
|
+
}
|
|
209
|
+
var CONSOLE_METHODS = /* @__PURE__ */ new Set(["log", "warn", "error"]);
|
|
210
|
+
var REPLACED = /* @__PURE__ */ new Map([
|
|
211
|
+
["pcall", "pcall"],
|
|
212
|
+
["xpcall", "xpcall"],
|
|
213
|
+
["ypcall", "pcall"],
|
|
214
|
+
["coroutine.resume", "resume"],
|
|
215
|
+
["string.find", "find"],
|
|
216
|
+
["string.match", "match"],
|
|
217
|
+
["string.gsub", "gsub"],
|
|
218
|
+
["string.byte", "byte"],
|
|
219
|
+
["math.modf", "modf"],
|
|
220
|
+
["math.frexp", "frexp"],
|
|
221
|
+
["next", "next"],
|
|
222
|
+
["select", "select"],
|
|
223
|
+
["unpack", "unpack"],
|
|
224
|
+
["table.unpack", "unpack"],
|
|
225
|
+
["loadstring", "loadstring"],
|
|
226
|
+
["pairs", "pairs"],
|
|
227
|
+
["ipairs", "ipairs"],
|
|
228
|
+
["string.gmatch", "gmatch"]
|
|
229
|
+
]);
|
|
230
|
+
var plugin2 = {
|
|
231
|
+
runtime: { console: console2, result },
|
|
232
|
+
methodCall({ method, receiverGlobal, at, arguments: args2, use }) {
|
|
233
|
+
if (receiverGlobal === "console" && CONSOLE_METHODS.has(method)) {
|
|
234
|
+
return {
|
|
235
|
+
callee: `${use("console")}.${method}`,
|
|
236
|
+
prepend: [tag(at), consoleMeta(args2)],
|
|
237
|
+
passReceiver: false
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return void 0;
|
|
241
|
+
},
|
|
242
|
+
// `print(x)` and `warn(x)` are left exactly as written: the place a line
|
|
243
|
+
// came from is what `console:log` / `console:warn` are for. `error` has to
|
|
244
|
+
// put the place inside the message it raises, so it goes through the
|
|
245
|
+
// runtime.
|
|
246
|
+
globalCall({ name, at, use }) {
|
|
247
|
+
if (name === "error") return { callee: `${use("console")}.raise`, prepend: [place(at)] };
|
|
248
|
+
const replaced = REPLACED.get(name);
|
|
249
|
+
if (replaced) return { callee: `${use("result")}.${replaced}` };
|
|
250
|
+
return void 0;
|
|
251
|
+
},
|
|
252
|
+
// Read as a value there is no call site to write down; the runtime finds
|
|
253
|
+
// one on the stack.
|
|
254
|
+
globalValue({ name, use }) {
|
|
255
|
+
if (name === "error") return `${use("console")}.raiseValue`;
|
|
256
|
+
const replaced = REPLACED.get(name);
|
|
257
|
+
if (replaced) return `${use("result")}.${replaced}`;
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
|
|
262
|
+
|
|
191
263
|
// src/lower.ts
|
|
192
|
-
function lower(
|
|
193
|
-
return new Lowerer(
|
|
264
|
+
function lower(program2, scopes, options = {}) {
|
|
265
|
+
return new Lowerer(program2, scopes, options).run();
|
|
194
266
|
}
|
|
195
267
|
var Lowerer = class {
|
|
196
268
|
constructor(source, scopes, options) {
|
|
@@ -226,11 +298,11 @@ var Lowerer = class {
|
|
|
226
298
|
origins = /* @__PURE__ */ new WeakMap();
|
|
227
299
|
run() {
|
|
228
300
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
301
|
+
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
229
302
|
const helpers = this.helperDefinitions();
|
|
230
303
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
231
|
-
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
|
|
232
304
|
return {
|
|
233
|
-
statements: [...
|
|
305
|
+
statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
|
|
234
306
|
exportsName: this.exportsName,
|
|
235
307
|
module: this.info,
|
|
236
308
|
diagnostics: this.diagnostics,
|
|
@@ -277,14 +349,43 @@ var Lowerer = class {
|
|
|
277
349
|
}
|
|
278
350
|
return identifier(local2);
|
|
279
351
|
}
|
|
352
|
+
/** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
|
|
353
|
+
move() {
|
|
354
|
+
return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
|
|
355
|
+
}
|
|
280
356
|
helper(kind) {
|
|
281
357
|
let name = this.helpers.get(kind);
|
|
282
358
|
if (!name) {
|
|
283
|
-
name = this.names.fresh(`tilua_${kind}`);
|
|
359
|
+
name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
|
|
284
360
|
this.helpers.set(kind, name);
|
|
285
361
|
}
|
|
286
362
|
return identifier(name);
|
|
287
363
|
}
|
|
364
|
+
// `typeof v` tells an array from an object, which Luau cannot: both are
|
|
365
|
+
// tables. So every array the program makes — `[a, b]`, a rest parameter,
|
|
366
|
+
// what `:map` answers — is written down as one, in a table that holds its
|
|
367
|
+
// keys weakly, and `typeof` looks there. A table nothing wrote down (one a
|
|
368
|
+
// Lua library made) is told by its type where that says, and by its shape
|
|
369
|
+
// where it does not.
|
|
370
|
+
/** `value`, a new array, written down as one. */
|
|
371
|
+
array(value) {
|
|
372
|
+
const marked = call(this.helper("array"), [value]);
|
|
373
|
+
this.marked.add(marked);
|
|
374
|
+
return marked;
|
|
375
|
+
}
|
|
376
|
+
/** The calls `array` made: each answers the one table it was given. */
|
|
377
|
+
marked = /* @__PURE__ */ new WeakSet();
|
|
378
|
+
/** Luau reaching the record of arrays: the bundle's, or this file's own. */
|
|
379
|
+
arrays() {
|
|
380
|
+
return this.options.module?.arrays ?? this.helper("arrays").name;
|
|
381
|
+
}
|
|
382
|
+
typeofExpression(node) {
|
|
383
|
+
const known = this.options.types?.typeofTables.get(node);
|
|
384
|
+
let argument = node.argument;
|
|
385
|
+
while (argument.type === "ParenthesizedExpression") argument = argument.expression;
|
|
386
|
+
const value = this.expression(argument);
|
|
387
|
+
return call(this.helper("typeof"), known ? [value, string(known)] : [this.keptToOne(value)]);
|
|
388
|
+
}
|
|
288
389
|
/** The local a library's runtime table gets in this file, emitting it the
|
|
289
390
|
* first time something asks. Keyed by library and by the key the library
|
|
290
391
|
* itself chose, so two libraries' tables never collide. */
|
|
@@ -352,7 +453,7 @@ var Lowerer = class {
|
|
|
352
453
|
const type = this.options.types?.typeOf.get(argument);
|
|
353
454
|
return {
|
|
354
455
|
type,
|
|
355
|
-
typeText: type ? (0,
|
|
456
|
+
typeText: type ? (0, import_parser5.formatType)(type) : void 0,
|
|
356
457
|
source: this.writtenSource(argument),
|
|
357
458
|
declaration: declaration ? this.writtenSource(declaration) : void 0,
|
|
358
459
|
spread: argument.type === "SpreadElement"
|
|
@@ -374,7 +475,11 @@ var Lowerer = class {
|
|
|
374
475
|
/** The language's own lowering, then each library's, in load order. A
|
|
375
476
|
* runtime is named by its lowering's place here (`loweringRuntime`). */
|
|
376
477
|
get lowerings() {
|
|
377
|
-
return this.allLowerings ??= [
|
|
478
|
+
return this.allLowerings ??= [
|
|
479
|
+
languageLowering,
|
|
480
|
+
...this.options.noLib ? [] : [standardLowering],
|
|
481
|
+
...this.options.lowerings ?? []
|
|
482
|
+
];
|
|
378
483
|
}
|
|
379
484
|
allLowerings;
|
|
380
485
|
/** What `receiver:method(...)` becomes, when Luau alone would not run it.
|
|
@@ -388,22 +493,28 @@ var Lowerer = class {
|
|
|
388
493
|
* (`console:log`), the last library loaded first. No answer leaves an
|
|
389
494
|
* ordinary Luau method call: `text:upper()` reaches Lua's own. */
|
|
390
495
|
loweredMethodCall(node) {
|
|
496
|
+
return this.loweredMethod(node.method.name, this.options.types?.methodSources.get(node), node.object, node.arguments, node.method, node);
|
|
497
|
+
}
|
|
498
|
+
/** `loweredMethodCall`, for a method named `method` of what `object`
|
|
499
|
+
* holds — called with `:` on it, or read off it with `.` and handed its
|
|
500
|
+
* `self` like any other argument. `args` are the arguments after the
|
|
501
|
+
* receiver; a read that is not called has none. */
|
|
502
|
+
loweredMethod(method, source, object2, args2, name, node) {
|
|
391
503
|
const lowerings = this.lowerings;
|
|
392
|
-
const source = this.options.types?.methodSources.get(node);
|
|
393
504
|
const asked = source?.origin === "language" ? [0] : source?.origin === "library" ? lowerings.flatMap((l, i) => l.library === source.library ? [i] : []) : source?.origin === "program" ? [] : lowerings.map((_, i) => i).slice(1).reverse();
|
|
394
505
|
if (!asked.some((i) => lowerings[i].plugin.methodCall)) return void 0;
|
|
395
|
-
const receiver = this.options.types?.typeOf.get(
|
|
396
|
-
const receiverGlobal = this.globalName(
|
|
397
|
-
const info = this.lazyArguments(
|
|
506
|
+
const receiver = this.options.types?.typeOf.get(object2);
|
|
507
|
+
const receiverGlobal = this.globalName(object2);
|
|
508
|
+
const info = this.lazyArguments(args2);
|
|
398
509
|
for (const i of asked) {
|
|
399
|
-
const
|
|
400
|
-
if (!
|
|
401
|
-
const answer =
|
|
402
|
-
method
|
|
510
|
+
const plugin3 = lowerings[i].plugin;
|
|
511
|
+
if (!plugin3.methodCall) continue;
|
|
512
|
+
const answer = plugin3.methodCall({
|
|
513
|
+
method,
|
|
403
514
|
receiver,
|
|
404
515
|
receiverGlobal,
|
|
405
|
-
argumentCount:
|
|
406
|
-
at: this.callSite(
|
|
516
|
+
argumentCount: args2.length,
|
|
517
|
+
at: this.callSite(name),
|
|
407
518
|
get arguments() {
|
|
408
519
|
return info();
|
|
409
520
|
},
|
|
@@ -427,9 +538,9 @@ var Lowerer = class {
|
|
|
427
538
|
if (name === void 0) return void 0;
|
|
428
539
|
const info = this.lazyArguments(node.arguments);
|
|
429
540
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
430
|
-
const
|
|
431
|
-
if (!
|
|
432
|
-
const answer =
|
|
541
|
+
const plugin3 = lowerings[i].plugin;
|
|
542
|
+
if (!plugin3.globalCall) continue;
|
|
543
|
+
const answer = plugin3.globalCall({
|
|
433
544
|
name,
|
|
434
545
|
at: this.callSite(node.callee),
|
|
435
546
|
get arguments() {
|
|
@@ -479,6 +590,114 @@ var Lowerer = class {
|
|
|
479
590
|
}
|
|
480
591
|
helperDefinitions() {
|
|
481
592
|
const out2 = [];
|
|
593
|
+
const runtimes = this.runtimeDefinitions();
|
|
594
|
+
if (this.helpers.has("concat")) this.helper("array");
|
|
595
|
+
if (this.helpers.has("concat")) this.move();
|
|
596
|
+
const move = this.helpers.get("move");
|
|
597
|
+
if (move) {
|
|
598
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
599
|
+
local function ${move}(from, first, last, at, to)
|
|
600
|
+
for i = first, last do
|
|
601
|
+
to[at + i - first] = from[i]
|
|
602
|
+
end
|
|
603
|
+
return to
|
|
604
|
+
end`).body.statements);
|
|
605
|
+
}
|
|
606
|
+
const each = this.helpers.get("each");
|
|
607
|
+
if (each) {
|
|
608
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
609
|
+
local function ${each}(source, ...)
|
|
610
|
+
if ${this.builtin("type").name}(source) == "function" then
|
|
611
|
+
return source, ...
|
|
612
|
+
end
|
|
613
|
+
local meta = ${this.builtin("getmetatable").name}(source)
|
|
614
|
+
if meta ~= nil and meta.__iter ~= nil then
|
|
615
|
+
return meta.__iter(source)
|
|
616
|
+
end
|
|
617
|
+
return ${this.builtin("next").name}, source
|
|
618
|
+
end`).body.statements);
|
|
619
|
+
}
|
|
620
|
+
const marker = this.helpers.get("array");
|
|
621
|
+
const typeOf = this.helpers.get("typeof");
|
|
622
|
+
if (marker || typeOf) {
|
|
623
|
+
const arrays = this.arrays();
|
|
624
|
+
const own = this.helpers.get("arrays");
|
|
625
|
+
if (own) {
|
|
626
|
+
out2.push(...(0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
|
|
627
|
+
}
|
|
628
|
+
if (marker) {
|
|
629
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
630
|
+
local function ${marker}(t)
|
|
631
|
+
${arrays}[t] = true
|
|
632
|
+
return t
|
|
633
|
+
end`).body.statements);
|
|
634
|
+
}
|
|
635
|
+
if (typeOf) {
|
|
636
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
637
|
+
local function ${typeOf}(value, known)
|
|
638
|
+
local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
|
|
639
|
+
if name ~= "table" then
|
|
640
|
+
return name
|
|
641
|
+
end
|
|
642
|
+
if known ~= nil then
|
|
643
|
+
return known
|
|
644
|
+
end
|
|
645
|
+
if ${arrays}[value] then
|
|
646
|
+
return "array"
|
|
647
|
+
end
|
|
648
|
+
if ${this.builtin("getmetatable").name}(value) ~= nil then
|
|
649
|
+
return "object"
|
|
650
|
+
end
|
|
651
|
+
if #value > 0 then
|
|
652
|
+
return "array"
|
|
653
|
+
end
|
|
654
|
+
return "object"
|
|
655
|
+
end`).body.statements);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const instanceOf = this.helpers.get("instanceof");
|
|
659
|
+
if (instanceOf) {
|
|
660
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
661
|
+
local function ${instanceOf}(value, class)
|
|
662
|
+
if ${this.builtin("type").name}(value) ~= "table" then
|
|
663
|
+
return false
|
|
664
|
+
end
|
|
665
|
+
local current = value.ClassObject
|
|
666
|
+
while current ~= nil do
|
|
667
|
+
if current == class then
|
|
668
|
+
return true
|
|
669
|
+
end
|
|
670
|
+
current = current.ParentClass
|
|
671
|
+
end
|
|
672
|
+
return false
|
|
673
|
+
end`).body.statements);
|
|
674
|
+
}
|
|
675
|
+
const accessors = this.helpers.get("accessors");
|
|
676
|
+
if (accessors) {
|
|
677
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
678
|
+
local function ${accessors}(target, getters, setters)
|
|
679
|
+
return ${this.builtin("setmetatable").name}(target, {
|
|
680
|
+
__index = function(self, key)
|
|
681
|
+
local get = getters[key]
|
|
682
|
+
if get ~= nil then
|
|
683
|
+
return get(self)
|
|
684
|
+
end
|
|
685
|
+
return nil
|
|
686
|
+
end,
|
|
687
|
+
__newindex = function(self, key, value)
|
|
688
|
+
local set = setters[key]
|
|
689
|
+
if set ~= nil then
|
|
690
|
+
set(self, value)
|
|
691
|
+
return
|
|
692
|
+
end
|
|
693
|
+
if getters[key] ~= nil then
|
|
694
|
+
${this.builtin("error").name}("Cannot assign to '" .. ${this.builtin("tostring").name}(key) .. "': it has a getter and no setter", 2)
|
|
695
|
+
end
|
|
696
|
+
${this.builtin("rawset").name}(self, key, value)
|
|
697
|
+
end,
|
|
698
|
+
})
|
|
699
|
+
end`).body.statements);
|
|
700
|
+
}
|
|
482
701
|
const assign2 = this.helpers.get("assign");
|
|
483
702
|
if (assign2) {
|
|
484
703
|
out2.push(localFunction(assign2, functionBody(["target"], [
|
|
@@ -493,28 +712,14 @@ var Lowerer = class {
|
|
|
493
712
|
returns([identifier("target")])
|
|
494
713
|
], true)));
|
|
495
714
|
}
|
|
496
|
-
|
|
497
|
-
const { plugin: plugin2, from } = this.lowerings[index2];
|
|
498
|
-
const source = plugin2.runtime?.[runtime2];
|
|
499
|
-
if (source === void 0) {
|
|
500
|
-
this.diagnostics.push({
|
|
501
|
-
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
502
|
-
line: 1,
|
|
503
|
-
column: 1
|
|
504
|
-
});
|
|
505
|
-
continue;
|
|
506
|
-
}
|
|
507
|
-
const lines = this.options.module?.lines ?? "nil";
|
|
508
|
-
const fail = this.options.module?.fail ?? "nil";
|
|
509
|
-
out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
|
|
510
|
-
}
|
|
715
|
+
out2.push(...runtimes);
|
|
511
716
|
const concat = this.helpers.get("concat");
|
|
512
717
|
if (concat) {
|
|
513
718
|
out2.push(localFunction(concat, functionBody([], [
|
|
514
|
-
local(["result"], [table([])]),
|
|
719
|
+
local(["result"], [this.array(table([]))]),
|
|
515
720
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
516
721
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
517
|
-
callStatement(call(
|
|
722
|
+
callStatement(call(this.move(), [
|
|
518
723
|
identifier("part"),
|
|
519
724
|
number(1),
|
|
520
725
|
unary("#", identifier("part")),
|
|
@@ -527,6 +732,26 @@ var Lowerer = class {
|
|
|
527
732
|
}
|
|
528
733
|
return out2;
|
|
529
734
|
}
|
|
735
|
+
/** The runtime tables the file's libraries were asked for. */
|
|
736
|
+
runtimeDefinitions() {
|
|
737
|
+
const out2 = [];
|
|
738
|
+
for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
|
|
739
|
+
const { plugin: plugin3, from } = this.lowerings[index2];
|
|
740
|
+
const source = plugin3.runtime?.[runtime2];
|
|
741
|
+
if (source === void 0) {
|
|
742
|
+
this.diagnostics.push({
|
|
743
|
+
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
744
|
+
line: 1,
|
|
745
|
+
column: 1
|
|
746
|
+
});
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const lines = this.options.module?.lines ?? "nil";
|
|
750
|
+
const fail = this.options.module?.fail ?? "nil";
|
|
751
|
+
out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAY__/g, () => this.helper("array").name)).body.statements);
|
|
752
|
+
}
|
|
753
|
+
return out2;
|
|
754
|
+
}
|
|
530
755
|
// --------------------------------------------------------
|
|
531
756
|
// Modules
|
|
532
757
|
// --------------------------------------------------------
|
|
@@ -545,7 +770,7 @@ var Lowerer = class {
|
|
|
545
770
|
const requires = [];
|
|
546
771
|
const moduleLocals = /* @__PURE__ */ new Map();
|
|
547
772
|
const importedFrom = /* @__PURE__ */ new Map();
|
|
548
|
-
const
|
|
773
|
+
const resolve4 = (source) => {
|
|
549
774
|
const key = context.resolve(source.value);
|
|
550
775
|
if (key === void 0) this.report(source, `Cannot find module '${source.value}'`);
|
|
551
776
|
return key;
|
|
@@ -584,7 +809,7 @@ var Lowerer = class {
|
|
|
584
809
|
];
|
|
585
810
|
const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references.length ?? 0) > 0);
|
|
586
811
|
if (!used.length) continue;
|
|
587
|
-
const key =
|
|
812
|
+
const key = resolve4(statement.source);
|
|
588
813
|
if (key === void 0) continue;
|
|
589
814
|
const local2 = moduleLocal(key);
|
|
590
815
|
for (const s of used) {
|
|
@@ -607,8 +832,9 @@ var Lowerer = class {
|
|
|
607
832
|
names.push("default");
|
|
608
833
|
break;
|
|
609
834
|
case "ExportNamedStatement": {
|
|
835
|
+
if (statement.typeOnly) break;
|
|
610
836
|
if (statement.source) {
|
|
611
|
-
const key =
|
|
837
|
+
const key = resolve4(statement.source);
|
|
612
838
|
if (key === void 0) break;
|
|
613
839
|
load(key);
|
|
614
840
|
for (const s of statement.specifiers) links.push({ name: s.exported.name, module: key, imported: s.local.name });
|
|
@@ -624,7 +850,7 @@ var Lowerer = class {
|
|
|
624
850
|
break;
|
|
625
851
|
}
|
|
626
852
|
case "ExportAllStatement": {
|
|
627
|
-
const key =
|
|
853
|
+
const key = resolve4(statement.source);
|
|
628
854
|
if (key === void 0) break;
|
|
629
855
|
load(key);
|
|
630
856
|
stars.push(key);
|
|
@@ -690,6 +916,26 @@ var Lowerer = class {
|
|
|
690
916
|
for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
|
|
691
917
|
return [...declarations, ...hoisted, ...requires, ...body];
|
|
692
918
|
}
|
|
919
|
+
/** `import("./x")`: the bundle's `import`, called with the module's name.
|
|
920
|
+
* It answers the module's exports table — what `import * as` names. */
|
|
921
|
+
importExpression(node) {
|
|
922
|
+
const context = this.options.module;
|
|
923
|
+
if (!context) {
|
|
924
|
+
this.report(node, "import() needs a bundle: build the project with @tilua/compiler");
|
|
925
|
+
return nil();
|
|
926
|
+
}
|
|
927
|
+
const source = node.source;
|
|
928
|
+
if (source.type !== "StringLiteral") {
|
|
929
|
+
this.report(source, "import() takes a string literal: which module it loads is decided when the project is built");
|
|
930
|
+
return nil();
|
|
931
|
+
}
|
|
932
|
+
const key = context.resolve(source.value, true);
|
|
933
|
+
if (key === void 0) {
|
|
934
|
+
this.report(source, `Cannot find module '${source.value}'`);
|
|
935
|
+
return nil();
|
|
936
|
+
}
|
|
937
|
+
return call(context.import ?? context.require, [string(key)]);
|
|
938
|
+
}
|
|
693
939
|
isRewritten(node) {
|
|
694
940
|
const binding = this.bindingByDeclaration.get(node);
|
|
695
941
|
return binding !== void 0 && this.rewrites.has(binding.id);
|
|
@@ -836,7 +1082,7 @@ var Lowerer = class {
|
|
|
836
1082
|
case "VariableDeclaration":
|
|
837
1083
|
return this.variableDeclaration(node, "declare");
|
|
838
1084
|
case "FunctionDeclaration":
|
|
839
|
-
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: node.attributes }];
|
|
1085
|
+
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
|
|
840
1086
|
case "FunctionDeclarationStatement":
|
|
841
1087
|
return [this.functionDeclarationStatement(node)];
|
|
842
1088
|
case "ClassDeclaration":
|
|
@@ -867,8 +1113,13 @@ var Lowerer = class {
|
|
|
867
1113
|
// kept — Lua has no expression statement — and it cannot run
|
|
868
1114
|
// anything, since a call is a `CallStatement` instead, so dropping
|
|
869
1115
|
// it changes nothing but the noise.
|
|
870
|
-
case "ExpressionStatement":
|
|
871
|
-
|
|
1116
|
+
case "ExpressionStatement": {
|
|
1117
|
+
let expression = node.expression;
|
|
1118
|
+
while (expression.type === "ParenthesizedExpression") expression = expression.expression;
|
|
1119
|
+
if (expression.type !== "ImportExpression") return [];
|
|
1120
|
+
const call2 = this.importExpression(expression);
|
|
1121
|
+
return call2.type === "CallExpression" ? [this.from(callStatement(call2), node)] : [];
|
|
1122
|
+
}
|
|
872
1123
|
case "DoStatement":
|
|
873
1124
|
return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
|
|
874
1125
|
case "WhileStatement":
|
|
@@ -920,7 +1171,11 @@ var Lowerer = class {
|
|
|
920
1171
|
let source = this.expression(node.iterator);
|
|
921
1172
|
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
922
1173
|
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
923
|
-
|
|
1174
|
+
let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
|
|
1175
|
+
if (this.lua51 && form.walks === "values") {
|
|
1176
|
+
const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
|
|
1177
|
+
iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
|
|
1178
|
+
}
|
|
924
1179
|
return [genericFor(
|
|
925
1180
|
variables,
|
|
926
1181
|
iterators,
|
|
@@ -1173,7 +1428,8 @@ end
|
|
|
1173
1428
|
},
|
|
1174
1429
|
isMethod,
|
|
1175
1430
|
func,
|
|
1176
|
-
|
|
1431
|
+
// `@native` and the like are Luau's.
|
|
1432
|
+
attributes: this.lua51 ? void 0 : node.attributes,
|
|
1177
1433
|
...spanOf(node)
|
|
1178
1434
|
};
|
|
1179
1435
|
}
|
|
@@ -1274,12 +1530,12 @@ end
|
|
|
1274
1530
|
if (element) bind(element.value, index(source, number(i + 1)), element.default);
|
|
1275
1531
|
});
|
|
1276
1532
|
if (pattern.rest) {
|
|
1277
|
-
const elements = call(
|
|
1533
|
+
const elements = call(this.move(), [
|
|
1278
1534
|
source,
|
|
1279
1535
|
number(pattern.elements.length + 1),
|
|
1280
1536
|
unary("#", source),
|
|
1281
1537
|
number(1),
|
|
1282
|
-
table([])
|
|
1538
|
+
this.array(table([]))
|
|
1283
1539
|
]);
|
|
1284
1540
|
const rest = this.restTarget(pattern.rest, elements, mode, after);
|
|
1285
1541
|
after.push(...rest.then);
|
|
@@ -1333,7 +1589,7 @@ end
|
|
|
1333
1589
|
const names = [];
|
|
1334
1590
|
for (const p of params) {
|
|
1335
1591
|
if (p.rest) {
|
|
1336
|
-
prelude.push(local([this.name(p.name)], [table([{ type: "TableFieldPositional", value: vararg() }])]));
|
|
1592
|
+
prelude.push(local([this.name(p.name)], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))]));
|
|
1337
1593
|
continue;
|
|
1338
1594
|
}
|
|
1339
1595
|
const name = p.pattern ? this.names.fresh("arg") : this.name(p.name);
|
|
@@ -1385,12 +1641,20 @@ end
|
|
|
1385
1641
|
case "ArrayExpression":
|
|
1386
1642
|
return this.arrayExpression(node);
|
|
1387
1643
|
case "BinaryExpression": {
|
|
1644
|
+
if (node.operator === "??") return this.nullish(node);
|
|
1388
1645
|
const left = this.expression(node.left);
|
|
1389
1646
|
const right = this.expression(node.right);
|
|
1647
|
+
if (node.operator === "in") {
|
|
1648
|
+
return parenthesized(binary("~=", index(this.keptToOne(right), this.keptToOne(left)), nil()));
|
|
1649
|
+
}
|
|
1650
|
+
if (node.operator === "instanceof") {
|
|
1651
|
+
return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1652
|
+
}
|
|
1390
1653
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1391
1654
|
return binary(node.operator, left, right);
|
|
1392
1655
|
}
|
|
1393
1656
|
case "UnaryExpression":
|
|
1657
|
+
if (node.operator === "typeof") return this.typeofExpression(node);
|
|
1394
1658
|
return unary(node.operator, this.expression(node.argument));
|
|
1395
1659
|
case "SuperExpression":
|
|
1396
1660
|
return this.superClassReference(node);
|
|
@@ -1412,6 +1676,8 @@ end
|
|
|
1412
1676
|
}
|
|
1413
1677
|
case "ParenthesizedExpression":
|
|
1414
1678
|
return parenthesized(this.expression(node.expression));
|
|
1679
|
+
case "ImportExpression":
|
|
1680
|
+
return this.importExpression(node);
|
|
1415
1681
|
// Types only: the value is the expression itself.
|
|
1416
1682
|
case "TypeAssertionExpression":
|
|
1417
1683
|
case "SatisfiesExpression":
|
|
@@ -1445,7 +1711,7 @@ end
|
|
|
1445
1711
|
* kept to one there, `f((g()))`. Every value is taken only where the
|
|
1446
1712
|
* program asks for them, as an array's last element: `[g()]`. */
|
|
1447
1713
|
keptToOne(value) {
|
|
1448
|
-
return expandsToMany(value) ? parenthesized(value) : value;
|
|
1714
|
+
return expandsToMany(value) && !this.marked.has(value) ? parenthesized(value) : value;
|
|
1449
1715
|
}
|
|
1450
1716
|
/** Is this `scriptArgs` the language's own, not a name the file declared? */
|
|
1451
1717
|
isLanguageGlobal(node) {
|
|
@@ -1465,7 +1731,7 @@ end
|
|
|
1465
1731
|
const last2 = list.length - 1;
|
|
1466
1732
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1467
1733
|
}
|
|
1468
|
-
const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
|
|
1734
|
+
const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
|
|
1469
1735
|
const last = list[list.length - 1];
|
|
1470
1736
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1471
1737
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -1479,11 +1745,21 @@ end
|
|
|
1479
1745
|
/** One link of an access chain, read from `object`. */
|
|
1480
1746
|
link(node, object2) {
|
|
1481
1747
|
switch (node.type) {
|
|
1482
|
-
case "MemberExpression":
|
|
1748
|
+
case "MemberExpression": {
|
|
1749
|
+
if (this.options.types?.lengths.has(node)) return unary("#", object2);
|
|
1750
|
+
const source = this.options.types?.methodReads.get(node);
|
|
1751
|
+
const lowered = source && this.loweredMethod(node.property.name, source, node.object, [], node.property, node);
|
|
1752
|
+
if (lowered) {
|
|
1753
|
+
const forwarded = call(calleePath(lowered.callee), [...lowered.prepend, vararg()]);
|
|
1754
|
+
return functionExpression(functionBody(lowered.passReceiver ? [] : ["_"], [returns([forwarded])], true));
|
|
1755
|
+
}
|
|
1483
1756
|
return member(object2, node.property.name);
|
|
1757
|
+
}
|
|
1484
1758
|
case "IndexExpression":
|
|
1485
1759
|
return index(object2, this.expression(node.index));
|
|
1486
1760
|
case "CallExpression": {
|
|
1761
|
+
const method = this.methodCalled(node);
|
|
1762
|
+
if (method) return method;
|
|
1487
1763
|
const lowered = this.loweredGlobalCall(node);
|
|
1488
1764
|
const args2 = this.arguments(node.arguments);
|
|
1489
1765
|
if (!lowered) return call(object2, args2);
|
|
@@ -1506,6 +1782,24 @@ end
|
|
|
1506
1782
|
}
|
|
1507
1783
|
}
|
|
1508
1784
|
}
|
|
1785
|
+
/** `names.filter(names, f)`: a method of the value's metatable, read with
|
|
1786
|
+
* `.` and handed its `self` as the first argument. Written as a call of
|
|
1787
|
+
* the function the method really is — `array.filter(names, f)` — where
|
|
1788
|
+
* whoever declared the metatable says so; `undefined` leaves an ordinary
|
|
1789
|
+
* call, which is right for a string's Lua methods (`text.upper(text)`
|
|
1790
|
+
* reaches Lua's own `string.upper`). */
|
|
1791
|
+
methodCalled(node) {
|
|
1792
|
+
const callee = node.callee;
|
|
1793
|
+
if (callee.type !== "MemberExpression") return void 0;
|
|
1794
|
+
const source = this.options.types?.methodReads.get(callee);
|
|
1795
|
+
if (!source) return void 0;
|
|
1796
|
+
const spreads = node.arguments.map((a, i) => a.type === "SpreadElement" ? i : -1).filter((i) => i >= 0);
|
|
1797
|
+
if (!node.arguments.length || spreads.includes(0) || spreads.some((i) => i !== node.arguments.length - 1)) return void 0;
|
|
1798
|
+
const lowered = this.loweredMethod(callee.property.name, source, callee.object, node.arguments.slice(1), callee.property, node);
|
|
1799
|
+
if (!lowered) return void 0;
|
|
1800
|
+
const [self, ...rest] = this.arguments(node.arguments);
|
|
1801
|
+
return call(calleePath(lowered.callee), [...lowered.prepend, ...lowered.passReceiver ? [self] : [], ...rest]);
|
|
1802
|
+
}
|
|
1509
1803
|
// --------------------------------------------------------
|
|
1510
1804
|
// Optional chains
|
|
1511
1805
|
// --------------------------------------------------------
|
|
@@ -1551,7 +1845,7 @@ end
|
|
|
1551
1845
|
const segments = this.segments(chain);
|
|
1552
1846
|
const base = this.expression(chain.base);
|
|
1553
1847
|
const last = chain.links[chain.links.length - 1];
|
|
1554
|
-
if (base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
1848
|
+
if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
1555
1849
|
return {
|
|
1556
1850
|
type: "IfElseExpression",
|
|
1557
1851
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -1572,6 +1866,32 @@ end
|
|
|
1572
1866
|
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1573
1867
|
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1574
1868
|
}
|
|
1869
|
+
/** `a ?? b`: `a`, or `b` where `a` is nil — read only then. A `false`
|
|
1870
|
+
* stays, which is what sets it apart from `a or b`. A name is tested
|
|
1871
|
+
* in place; anything else is read once, into a local. */
|
|
1872
|
+
nullish(node) {
|
|
1873
|
+
const left = this.keptToOne(this.expression(node.left));
|
|
1874
|
+
const right = this.keptToOne(this.expression(node.right));
|
|
1875
|
+
if (!this.lua51 && left.type === "Identifier") {
|
|
1876
|
+
return {
|
|
1877
|
+
type: "IfElseExpression",
|
|
1878
|
+
clauses: [{ condition: binary("~=", left, nil()), body: left }],
|
|
1879
|
+
alternate: right,
|
|
1880
|
+
line: left.line,
|
|
1881
|
+
column: left.column
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
const ref = this.names.fresh("value");
|
|
1885
|
+
const at = identifier(ref);
|
|
1886
|
+
const statements = [
|
|
1887
|
+
local([ref], [left]),
|
|
1888
|
+
ifThen(binary("~=", at, nil()), [returns([at])]),
|
|
1889
|
+
returns([right])
|
|
1890
|
+
];
|
|
1891
|
+
const passesVarargs = statements.some(usesVararg);
|
|
1892
|
+
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1893
|
+
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1894
|
+
}
|
|
1575
1895
|
/** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
|
|
1576
1896
|
interpolatedString(node) {
|
|
1577
1897
|
let format = "";
|
|
@@ -1653,12 +1973,12 @@ end
|
|
|
1653
1973
|
}));
|
|
1654
1974
|
const alternate = this.expression(node.alternate);
|
|
1655
1975
|
if (clauses.every((c) => alwaysTruthy(c.body))) {
|
|
1656
|
-
let
|
|
1976
|
+
let result2 = alternate;
|
|
1657
1977
|
for (let i = clauses.length - 1; i >= 0; i--) {
|
|
1658
1978
|
const { condition, body } = clauses[i];
|
|
1659
|
-
|
|
1979
|
+
result2 = binary("or", binary("and", condition, body), result2);
|
|
1660
1980
|
}
|
|
1661
|
-
return parenthesized(
|
|
1981
|
+
return parenthesized(result2);
|
|
1662
1982
|
}
|
|
1663
1983
|
const statement = {
|
|
1664
1984
|
type: "IfStatement",
|
|
@@ -1715,18 +2035,27 @@ end
|
|
|
1715
2035
|
current = [];
|
|
1716
2036
|
parts.push(this.expression(f.argument));
|
|
1717
2037
|
break;
|
|
2038
|
+
case "TableFieldAccessor":
|
|
2039
|
+
break;
|
|
1718
2040
|
}
|
|
1719
2041
|
}
|
|
1720
|
-
|
|
1721
|
-
if (
|
|
1722
|
-
|
|
2042
|
+
let made;
|
|
2043
|
+
if (parts.length === 0) made = table(current);
|
|
2044
|
+
else {
|
|
2045
|
+
if (current.length) parts.push(table(current));
|
|
2046
|
+
made = call(this.helper("assign"), [table([]), ...parts]);
|
|
2047
|
+
}
|
|
2048
|
+
const accessors = node.fields.filter((f) => f.type === "TableFieldAccessor");
|
|
2049
|
+
if (!accessors.length) return made;
|
|
2050
|
+
const table2 = (kind) => table(accessors.filter((f) => f.kind === kind).map((f) => field(f.key.type === "Identifier" ? f.key.name : f.key.value, functionExpression(this.classFunctionBody(f.func)))));
|
|
2051
|
+
return call(this.helper("accessors"), [made, table2("get"), table2("set")]);
|
|
1723
2052
|
}
|
|
1724
2053
|
/** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
|
|
1725
2054
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
1726
2055
|
arrayExpression(node) {
|
|
1727
2056
|
const elements = packs(node.elements).items;
|
|
1728
2057
|
if (!elements.some((e) => e.type === "SpreadElement")) {
|
|
1729
|
-
return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
|
|
2058
|
+
return this.array(table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) }))));
|
|
1730
2059
|
}
|
|
1731
2060
|
const parts = [];
|
|
1732
2061
|
let current = [];
|
|
@@ -1976,7 +2305,7 @@ var CLASS_METAMETHODS = [
|
|
|
1976
2305
|
];
|
|
1977
2306
|
|
|
1978
2307
|
// src/linemap.ts
|
|
1979
|
-
var
|
|
2308
|
+
var import_parser6 = require("@tilua/parser");
|
|
1980
2309
|
function statementsOf(node) {
|
|
1981
2310
|
const out2 = [];
|
|
1982
2311
|
const visit = (value) => {
|
|
@@ -2009,7 +2338,7 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
2009
2338
|
}
|
|
2010
2339
|
function lineMapSource(map) {
|
|
2011
2340
|
const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2012
|
-
const quoted = (s) => (0,
|
|
2341
|
+
const quoted = (s) => (0, import_parser6.luauString)(s.replace(/\\/g, "/"));
|
|
2013
2342
|
return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
|
|
2014
2343
|
}
|
|
2015
2344
|
|
|
@@ -2021,15 +2350,15 @@ async function loadLowerings(modules) {
|
|
|
2021
2350
|
for (const module2 of modules) {
|
|
2022
2351
|
try {
|
|
2023
2352
|
const loaded = await import((0, import_node_url.pathToFileURL)(module2.file).href);
|
|
2024
|
-
const
|
|
2025
|
-
if (!
|
|
2353
|
+
const plugin3 = loaded.default;
|
|
2354
|
+
if (!plugin3 || typeof plugin3 !== "object") {
|
|
2026
2355
|
problems.push({
|
|
2027
2356
|
file: module2.file,
|
|
2028
2357
|
message: `'${module2.from}' lowering module has no default export`
|
|
2029
2358
|
});
|
|
2030
2359
|
continue;
|
|
2031
2360
|
}
|
|
2032
|
-
lowerings.push({ plugin:
|
|
2361
|
+
lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
|
|
2033
2362
|
} catch (error) {
|
|
2034
2363
|
problems.push({
|
|
2035
2364
|
file: module2.file,
|
|
@@ -2056,16 +2385,17 @@ async function bundle(options) {
|
|
|
2056
2385
|
const file = queue.shift();
|
|
2057
2386
|
if (sources.has(file)) continue;
|
|
2058
2387
|
const parsed = parseFile(file, diagnostics, directives);
|
|
2059
|
-
if (!parsed) return { modules: [], diagnostics };
|
|
2060
|
-
const { program:
|
|
2061
|
-
const scopes = (0,
|
|
2388
|
+
if (!parsed) return { files: [], modules: [], diagnostics };
|
|
2389
|
+
const { program: program3, text } = parsed;
|
|
2390
|
+
const scopes = (0, import_parser7.analyzeScopes)(program3, { noLib: config2?.noLib });
|
|
2062
2391
|
for (const d of scopes.diagnostics) {
|
|
2063
2392
|
diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
|
|
2064
2393
|
}
|
|
2065
|
-
sources.set(file, { file, name: nameOf(file), text, program:
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2394
|
+
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2395
|
+
const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
|
|
2396
|
+
for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
|
|
2397
|
+
const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
2398
|
+
if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
|
|
2069
2399
|
}
|
|
2070
2400
|
}
|
|
2071
2401
|
const analysis = analyzeModules([...sources.values()], config2);
|
|
@@ -2077,6 +2407,9 @@ async function bundle(options) {
|
|
|
2077
2407
|
const names = Names.from(...[...sources.values()].map((s) => s.program));
|
|
2078
2408
|
const G = names.fresh("G");
|
|
2079
2409
|
const requireExpression = member(identifier(G), "require");
|
|
2410
|
+
const importExpression = member(identifier(G), "import");
|
|
2411
|
+
const mode = options.output ?? config2?.output ?? "single";
|
|
2412
|
+
const target = options.target ?? config2?.target ?? "luau";
|
|
2080
2413
|
const modules = /* @__PURE__ */ new Map();
|
|
2081
2414
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
2082
2415
|
const pending = [entry2];
|
|
@@ -2085,106 +2418,235 @@ async function bundle(options) {
|
|
|
2085
2418
|
if (modules.has(file)) continue;
|
|
2086
2419
|
const source = sources.get(file);
|
|
2087
2420
|
const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
|
|
2088
|
-
const
|
|
2421
|
+
const imports = /* @__PURE__ */ new Set();
|
|
2422
|
+
const dynamicImports = /* @__PURE__ */ new Set();
|
|
2423
|
+
const lowered2 = lower(source.program, source.scopes, {
|
|
2089
2424
|
names,
|
|
2090
2425
|
types: analysis.types.get(file),
|
|
2091
2426
|
lowerings,
|
|
2092
|
-
target
|
|
2427
|
+
target,
|
|
2428
|
+
noLib: config2?.noLib,
|
|
2093
2429
|
source: source.text,
|
|
2094
2430
|
file: shown,
|
|
2095
2431
|
module: {
|
|
2096
2432
|
name: source.name,
|
|
2097
2433
|
require: requireExpression,
|
|
2098
|
-
|
|
2434
|
+
import: importExpression,
|
|
2435
|
+
// One file has one line map. Several have one each, and a
|
|
2436
|
+
// module reads the one of the file it is in.
|
|
2437
|
+
lines: mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
|
|
2099
2438
|
fail: `${G}.fail`,
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2439
|
+
arrays: `${G}.arrays`,
|
|
2440
|
+
resolve: (specifier, dynamic) => {
|
|
2441
|
+
const found = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
2442
|
+
if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
|
|
2443
|
+
pending.push(found);
|
|
2444
|
+
const name = sources.get(found).name;
|
|
2445
|
+
(dynamic ? dynamicImports : imports).add(name);
|
|
2446
|
+
return name;
|
|
2105
2447
|
}
|
|
2106
2448
|
}
|
|
2107
2449
|
});
|
|
2108
|
-
for (const d of
|
|
2109
|
-
for (const statement of allStatements(
|
|
2110
|
-
const line =
|
|
2450
|
+
for (const d of lowered2.diagnostics) diagnostics.push({ file, ...d, category: "module" });
|
|
2451
|
+
for (const statement of allStatements(lowered2.statements)) {
|
|
2452
|
+
const line = lowered2.origins.get(statement);
|
|
2111
2453
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
2112
2454
|
}
|
|
2113
2455
|
modules.set(file, {
|
|
2114
2456
|
name: source.name,
|
|
2115
|
-
statements:
|
|
2116
|
-
exportsName:
|
|
2117
|
-
info:
|
|
2118
|
-
usesScriptArgs:
|
|
2457
|
+
statements: lowered2.statements,
|
|
2458
|
+
exportsName: lowered2.exportsName,
|
|
2459
|
+
info: lowered2.module,
|
|
2460
|
+
usesScriptArgs: lowered2.usesScriptArgs,
|
|
2461
|
+
imports,
|
|
2462
|
+
dynamicImports
|
|
2119
2463
|
});
|
|
2120
2464
|
}
|
|
2121
2465
|
for (const [file, fileDirectives] of directives) {
|
|
2122
2466
|
const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
|
|
2123
|
-
const { kept, unusedExpectErrors } = (0,
|
|
2467
|
+
const { kept, unusedExpectErrors } = (0, import_parser7.applyDirectives)(fileDirectives, semantic, (d) => d.line);
|
|
2124
2468
|
const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
|
|
2125
2469
|
const remaining = diagnostics.filter((d) => !suppressed.has(d));
|
|
2126
2470
|
diagnostics.length = 0;
|
|
2127
2471
|
diagnostics.push(...remaining);
|
|
2128
2472
|
if (options.typeCheck === false) continue;
|
|
2129
2473
|
for (const d of unusedExpectErrors) {
|
|
2130
|
-
diagnostics.push({ file, message:
|
|
2474
|
+
diagnostics.push({ file, message: import_parser7.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
|
|
2131
2475
|
}
|
|
2132
2476
|
}
|
|
2133
2477
|
const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2134
2478
|
const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2135
2479
|
diagnostics.length = 0;
|
|
2136
2480
|
diagnostics.push(...reported);
|
|
2137
|
-
const
|
|
2481
|
+
const lowered = [...modules.values()];
|
|
2482
|
+
const moduleNames = lowered.map((m) => m.name);
|
|
2138
2483
|
if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
|
|
2139
|
-
return { modules: moduleNames, diagnostics };
|
|
2140
|
-
}
|
|
2141
|
-
const
|
|
2142
|
-
const
|
|
2143
|
-
|
|
2144
|
-
|
|
2484
|
+
return { files: [], modules: moduleNames, diagnostics };
|
|
2485
|
+
}
|
|
2486
|
+
const several = mode !== "single";
|
|
2487
|
+
const layout = layoutOf(mode, modules.get(entry2), lowered, options.outFile);
|
|
2488
|
+
const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
|
|
2489
|
+
const elsewhere = /* @__PURE__ */ new Map();
|
|
2490
|
+
for (const file of layout.slice(1)) {
|
|
2491
|
+
for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
|
|
2492
|
+
}
|
|
2493
|
+
const program2 = (0, import_luau_parser2.parse)(runtime(G, {
|
|
2494
|
+
several,
|
|
2495
|
+
target,
|
|
2496
|
+
files: elsewhere,
|
|
2497
|
+
imports: lowered.some((m) => m.dynamicImports.size > 0)
|
|
2498
|
+
}));
|
|
2499
|
+
const assignment = program2.body.statements[1];
|
|
2500
|
+
if (usesScriptArgs) {
|
|
2501
|
+
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2502
|
+
program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
|
|
2503
|
+
if (several) program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.scriptArgs = scriptArgs`).body.statements);
|
|
2145
2504
|
}
|
|
2146
2505
|
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;
|
|
2147
2506
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2148
|
-
for (const module2 of modules
|
|
2149
|
-
|
|
2150
|
-
const fields = [];
|
|
2151
|
-
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2152
|
-
if (links.length) {
|
|
2153
|
-
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2154
|
-
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2155
|
-
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2156
|
-
]))))));
|
|
2157
|
-
}
|
|
2158
|
-
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2159
|
-
fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
|
|
2160
|
-
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
|
|
2507
|
+
for (const module2 of layout[0].modules) {
|
|
2508
|
+
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
|
|
2161
2509
|
}
|
|
2162
2510
|
const ok = names.fresh("ok");
|
|
2163
|
-
const
|
|
2511
|
+
const result2 = names.fresh("result");
|
|
2164
2512
|
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
2165
|
-
local ${ok}, ${
|
|
2166
|
-
return ${G}.require(${(0,
|
|
2513
|
+
local ${ok}, ${result2} = xpcall(function()
|
|
2514
|
+
return ${G}.require(${(0, import_parser7.luauString)(sources.get(entry2).name)})
|
|
2167
2515
|
end, ${G}.fail)
|
|
2168
2516
|
if not ${ok} then
|
|
2169
|
-
error(${
|
|
2517
|
+
error(${result2}, 0)
|
|
2170
2518
|
end
|
|
2171
|
-
${modules.get(entry2).info.exports ? `return ${
|
|
2519
|
+
${modules.get(entry2).info.exports ? `return ${result2}` : ""}
|
|
2172
2520
|
`).body.statements;
|
|
2173
|
-
|
|
2174
|
-
const map = buildLineMap(
|
|
2175
|
-
if (map.size) {
|
|
2176
|
-
|
|
2521
|
+
program2.body.statements.push(...entryStatements);
|
|
2522
|
+
const map = buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins);
|
|
2523
|
+
if (map.size || several) {
|
|
2524
|
+
const mapStatements = [assign(
|
|
2177
2525
|
[member(identifier(G), "lines")],
|
|
2178
2526
|
[(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
|
|
2179
|
-
)
|
|
2527
|
+
)];
|
|
2528
|
+
if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
|
|
2529
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
|
|
2180
2530
|
}
|
|
2181
|
-
|
|
2531
|
+
const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
|
|
2532
|
+
for (const file of layout.slice(1)) {
|
|
2533
|
+
const body = [];
|
|
2534
|
+
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
2535
|
+
for (const module2 of file.modules) {
|
|
2536
|
+
body.push(assign(
|
|
2537
|
+
[index(member(identifier(G), "modules"), string(module2.name))],
|
|
2538
|
+
[moduleTable(module2)]
|
|
2539
|
+
));
|
|
2540
|
+
}
|
|
2541
|
+
const chunk = program([returns([functionExpression(functionBody([G], body))])]);
|
|
2542
|
+
const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
|
|
2543
|
+
body.push(...mapRegistration(G, file, lineMapSource(lines)));
|
|
2544
|
+
files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
|
|
2545
|
+
}
|
|
2546
|
+
return { code: files[0].code, files, modules: moduleNames, diagnostics };
|
|
2547
|
+
}
|
|
2548
|
+
function moduleTable(module2) {
|
|
2549
|
+
const { names: exported, links, stars } = module2.info;
|
|
2550
|
+
const fields = [];
|
|
2551
|
+
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2552
|
+
if (links.length) {
|
|
2553
|
+
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2554
|
+
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2555
|
+
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2556
|
+
]))))));
|
|
2557
|
+
}
|
|
2558
|
+
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2559
|
+
fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
|
|
2560
|
+
return table(fields);
|
|
2561
|
+
}
|
|
2562
|
+
function layoutOf(mode, entry2, modules, outFile) {
|
|
2563
|
+
const extension = outFile !== void 0 && (0, import_node_path2.extname)(outFile) || ".luau";
|
|
2564
|
+
const entryStem = outFile !== void 0 ? (0, import_node_path2.basename)(outFile, (0, import_node_path2.extname)(outFile)) : import_node_path2.posix.basename(entry2.name);
|
|
2565
|
+
const others = modules.filter((m) => m !== entry2);
|
|
2566
|
+
if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry2, ...others] }];
|
|
2567
|
+
if (mode === "modules") {
|
|
2568
|
+
const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
|
|
2569
|
+
return [entry2, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
|
|
2570
|
+
}
|
|
2571
|
+
const byName = new Map(modules.map((m) => [m.name, m]));
|
|
2572
|
+
const reach = (root) => {
|
|
2573
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2574
|
+
const stack = [root];
|
|
2575
|
+
while (stack.length) {
|
|
2576
|
+
const name = stack.pop();
|
|
2577
|
+
if (seen.has(name)) continue;
|
|
2578
|
+
seen.add(name);
|
|
2579
|
+
stack.push(...byName.get(name)?.imports ?? []);
|
|
2580
|
+
}
|
|
2581
|
+
return seen;
|
|
2582
|
+
};
|
|
2583
|
+
const inEntry = reach(entry2.name);
|
|
2584
|
+
const roots = [...new Set(modules.flatMap((m) => [...m.dynamicImports]))].filter((root) => !inEntry.has(root));
|
|
2585
|
+
const reached = new Map(roots.map((root) => [root, reach(root)]));
|
|
2586
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2587
|
+
for (const module2 of others) {
|
|
2588
|
+
if (inEntry.has(module2.name)) continue;
|
|
2589
|
+
const by = roots.filter((root) => reached.get(root).has(module2.name));
|
|
2590
|
+
const key = by.join("\n");
|
|
2591
|
+
if (!groups.has(key)) groups.set(key, { roots: by, modules: [] });
|
|
2592
|
+
groups.get(key).modules.push(module2);
|
|
2593
|
+
}
|
|
2594
|
+
const files = [{ path: `${entryStem}${extension}`, modules: modules.filter((m) => inEntry.has(m.name)) }];
|
|
2595
|
+
const taken = /* @__PURE__ */ new Set([entryStem]);
|
|
2596
|
+
for (const group of groups.values()) {
|
|
2597
|
+
const label = group.roots.length === 1 ? group.roots[0].replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "chunk" : "shared";
|
|
2598
|
+
let stem = `${entryStem}_${label}`;
|
|
2599
|
+
for (let n = 2; taken.has(stem); n++) stem = `${entryStem}_${label}_${n}`;
|
|
2600
|
+
taken.add(stem);
|
|
2601
|
+
files.push({ path: `${stem}${extension}`, modules: group.modules });
|
|
2602
|
+
}
|
|
2603
|
+
return files;
|
|
2604
|
+
}
|
|
2605
|
+
function loadPath(entryPath, file, target) {
|
|
2606
|
+
const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
|
|
2607
|
+
if (target === "lua51") return path;
|
|
2608
|
+
const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
|
|
2609
|
+
return bare.startsWith("../") ? bare : `./${bare}`;
|
|
2610
|
+
}
|
|
2611
|
+
function mapRegistration(G, file, map) {
|
|
2612
|
+
const extension = import_node_path2.posix.extname(file.path);
|
|
2613
|
+
const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
|
|
2614
|
+
const entry2 = `${G}.sources[#${G}.sources + 1]`;
|
|
2615
|
+
return (0, import_luau_parser2.parse)([
|
|
2616
|
+
`${entry2} = { { ${parts.map(import_parser7.luauString).join(", ")} }, ${map}, ${(0, import_parser7.luauString)(extension.slice(1))} }`,
|
|
2617
|
+
...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
|
|
2618
|
+
].join("\n")).body.statements;
|
|
2619
|
+
}
|
|
2620
|
+
function loader(G, target) {
|
|
2621
|
+
if (target === "lua51") {
|
|
2622
|
+
return `
|
|
2623
|
+
-- From beside this file, wherever that is: its own path says where.
|
|
2624
|
+
local folder = ""
|
|
2625
|
+
if debug ~= nil and debug.getinfo ~= nil then
|
|
2626
|
+
local at = debug.getinfo(${G}.load, "S").source
|
|
2627
|
+
if at:sub(1, 1) == "@" then
|
|
2628
|
+
folder = at:match("^@(.*[/\\\\])") or ""
|
|
2629
|
+
end
|
|
2630
|
+
end
|
|
2631
|
+
local chunk, problem = loadfile(folder .. file)
|
|
2632
|
+
if chunk == nil then
|
|
2633
|
+
error(problem, 0)
|
|
2634
|
+
end
|
|
2635
|
+
chunk()(${G})`;
|
|
2636
|
+
}
|
|
2637
|
+
return `
|
|
2638
|
+
require(file)(${G})`;
|
|
2182
2639
|
}
|
|
2183
|
-
function runtime(G) {
|
|
2640
|
+
function runtime(G, options) {
|
|
2641
|
+
const { several } = options;
|
|
2642
|
+
const files = [...options.files].map(([name, file]) => `[${(0, import_parser7.luauString)(name)}] = ${(0, import_parser7.luauString)(file)}`).join(", ");
|
|
2184
2643
|
return `
|
|
2185
2644
|
local ${G}
|
|
2186
2645
|
${G} = {
|
|
2187
2646
|
modules = {},
|
|
2647
|
+
-- Every array the program made, so \`typeof\` can tell one from an object.
|
|
2648
|
+
-- The keys are held weakly: being written down keeps no array alive.
|
|
2649
|
+
arrays = setmetatable({}, { __mode = "k" }),
|
|
2188
2650
|
-- Filled in just before the entry runs: bundle line -> { file, line }.
|
|
2189
2651
|
lines = nil,
|
|
2190
2652
|
-- What an error nothing caught says, in the project's files: the bundle's
|
|
@@ -2225,7 +2687,7 @@ ${G} = {
|
|
|
2225
2687
|
return message
|
|
2226
2688
|
end
|
|
2227
2689
|
local lines = ${G}.lines
|
|
2228
|
-
if lines == nil then
|
|
2690
|
+
if lines == nil ${several ? `and #${G}.sources == 0 ` : ""}then
|
|
2229
2691
|
return message .. (trace or "")
|
|
2230
2692
|
end
|
|
2231
2693
|
-- The bundle's name, as a position in a message spells it: where this
|
|
@@ -2245,6 +2707,81 @@ ${G} = {
|
|
|
2245
2707
|
chunk = info.short_src
|
|
2246
2708
|
end
|
|
2247
2709
|
end
|
|
2710
|
+
${several ? `
|
|
2711
|
+
-- A position in this file is read by this file's map. One in another
|
|
2712
|
+
-- of the bundle's files is read by that file's: the one whose name
|
|
2713
|
+
-- the position ends with, by the most folders where several do. A
|
|
2714
|
+
-- message says nothing about which file it starts in, so nothing is
|
|
2715
|
+
-- guessed from it here.
|
|
2716
|
+
local prefix = nil
|
|
2717
|
+
if chunk ~= nil then
|
|
2718
|
+
prefix = chunk:gsub("%p", "%%%0")
|
|
2719
|
+
end
|
|
2720
|
+
local function place(line)
|
|
2721
|
+
local origin = lines ~= nil and lines[tonumber(line)] or nil
|
|
2722
|
+
if origin == nil then
|
|
2723
|
+
return nil
|
|
2724
|
+
end
|
|
2725
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2726
|
+
end
|
|
2727
|
+
local function elsewhere(at, line)
|
|
2728
|
+
-- Already a place in the project.
|
|
2729
|
+
if at:sub(-6) == ".tilua" then
|
|
2730
|
+
return nil
|
|
2731
|
+
end
|
|
2732
|
+
local parts = {}
|
|
2733
|
+
for part in at:gmatch("[^/\\\\%.]+") do
|
|
2734
|
+
parts[#parts + 1] = part
|
|
2735
|
+
end
|
|
2736
|
+
local map, most, tied = nil, 0, false
|
|
2737
|
+
for _, source in ipairs(${G}.sources) do
|
|
2738
|
+
local names = source[1]
|
|
2739
|
+
local last = #parts
|
|
2740
|
+
if parts[last] == source[3] then
|
|
2741
|
+
last = last - 1
|
|
2742
|
+
end
|
|
2743
|
+
local same = 0
|
|
2744
|
+
while same < #names and same < last and names[#names - same] == parts[last - same] do
|
|
2745
|
+
same = same + 1
|
|
2746
|
+
end
|
|
2747
|
+
if same > most then
|
|
2748
|
+
map, most, tied = source[2], same, false
|
|
2749
|
+
elseif same == most then
|
|
2750
|
+
tied = true
|
|
2751
|
+
end
|
|
2752
|
+
end
|
|
2753
|
+
-- Two files it could as well be: a wrong place is worse than none.
|
|
2754
|
+
local origin = map ~= nil and not tied and map[tonumber(line)] or nil
|
|
2755
|
+
if origin == nil then
|
|
2756
|
+
return nil
|
|
2757
|
+
end
|
|
2758
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2759
|
+
end
|
|
2760
|
+
local mapped = message
|
|
2761
|
+
if prefix ~= nil then
|
|
2762
|
+
mapped = mapped:gsub(prefix .. ":(%d+)", function(line)
|
|
2763
|
+
return place(line)
|
|
2764
|
+
end)
|
|
2765
|
+
end
|
|
2766
|
+
mapped = mapped:gsub("(%S+):(%d+)", elsewhere)
|
|
2767
|
+
local frames = {}
|
|
2768
|
+
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2769
|
+
local at = nil
|
|
2770
|
+
if prefix ~= nil then
|
|
2771
|
+
local line = frame:match(prefix .. ":(%d+)")
|
|
2772
|
+
if line ~= nil then
|
|
2773
|
+
at = place(line)
|
|
2774
|
+
end
|
|
2775
|
+
end
|
|
2776
|
+
if at == nil then
|
|
2777
|
+
for source, line in frame:gmatch("(%S+):(%d+)") do
|
|
2778
|
+
at = elsewhere(source, line)
|
|
2779
|
+
if at ~= nil then
|
|
2780
|
+
break
|
|
2781
|
+
end
|
|
2782
|
+
end
|
|
2783
|
+
end
|
|
2784
|
+
if at ~= nil then` : `
|
|
2248
2785
|
if chunk == nil then
|
|
2249
2786
|
chunk = message:match("^(.-):%d+:")
|
|
2250
2787
|
end
|
|
@@ -2266,8 +2803,9 @@ ${G} = {
|
|
|
2266
2803
|
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2267
2804
|
local line = frame:match(prefix .. ":(%d+)")
|
|
2268
2805
|
local at = line ~= nil and place(line) or nil
|
|
2269
|
-
if at ~= nil then
|
|
2270
|
-
|
|
2806
|
+
if at ~= nil then`}
|
|
2807
|
+
-- Luau writes \`function name\`; Lua 5.1, \`function 'name'\`.
|
|
2808
|
+
local name = frame:match("function ([%w_.:]+)") or frame:match("function '([^']+)'")
|
|
2271
2809
|
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2272
2810
|
end
|
|
2273
2811
|
end
|
|
@@ -2276,13 +2814,36 @@ ${G} = {
|
|
|
2276
2814
|
end
|
|
2277
2815
|
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2278
2816
|
end,
|
|
2279
|
-
records = {}
|
|
2817
|
+
records = {},${several ? `
|
|
2818
|
+
-- The file each module outside this one is in, as \`load\` is handed it.
|
|
2819
|
+
files = { ${files} },
|
|
2820
|
+
loaded = {},
|
|
2821
|
+
-- Every file's line map: by the module, and by the file's name.
|
|
2822
|
+
maps = {},
|
|
2823
|
+
sources = {},
|
|
2824
|
+
-- Another of the bundle's files: a function that adds its modules here.
|
|
2825
|
+
load = function(file)
|
|
2826
|
+
if ${G}.loaded[file] then
|
|
2827
|
+
return
|
|
2828
|
+
end
|
|
2829
|
+
${G}.loaded[file] = true${loader(G, options.target)}
|
|
2830
|
+
end,` : ""}${options.imports ? `
|
|
2831
|
+
-- \`import("./x")\`: the module's exports, asked for where the code runs
|
|
2832
|
+
-- rather than before its module does. \`require\` does the rest \u2014 and
|
|
2833
|
+
-- loads the file the module is in, if it is not this one.
|
|
2834
|
+
import = function(name)
|
|
2835
|
+
return ${G}.require(name)
|
|
2836
|
+
end,` : ""}
|
|
2280
2837
|
require = function(name)
|
|
2281
2838
|
local record = ${G}.records[name]
|
|
2282
2839
|
if record ~= nil then
|
|
2283
2840
|
return record.exports
|
|
2284
2841
|
end
|
|
2285
|
-
local module = ${G}.modules[name]
|
|
2842
|
+
local module = ${G}.modules[name]${several ? `
|
|
2843
|
+
if module == nil then
|
|
2844
|
+
${G}.load(${G}.files[name])
|
|
2845
|
+
module = ${G}.modules[name]
|
|
2846
|
+
end` : ""}
|
|
2286
2847
|
local initialized = {}
|
|
2287
2848
|
local exports = setmetatable({}, {
|
|
2288
2849
|
__index = function(_, key)
|
|
@@ -2301,7 +2862,7 @@ ${G} = {
|
|
|
2301
2862
|
return source[link[2]]
|
|
2302
2863
|
end
|
|
2303
2864
|
if module.stars ~= nil and key ~= "default" then
|
|
2304
|
-
for _, star in module.stars do
|
|
2865
|
+
for _, star in ipairs(module.stars) do
|
|
2305
2866
|
local value = ${G}.require(star)[key]
|
|
2306
2867
|
if value ~= nil then
|
|
2307
2868
|
return value
|
|
@@ -2326,7 +2887,7 @@ ${G} = {
|
|
|
2326
2887
|
}
|
|
2327
2888
|
function stampOf(file) {
|
|
2328
2889
|
try {
|
|
2329
|
-
const at = (0,
|
|
2890
|
+
const at = (0, import_node_fs3.statSync)(file);
|
|
2330
2891
|
return `${at.size}:${at.mtimeMs}`;
|
|
2331
2892
|
} catch {
|
|
2332
2893
|
return void 0;
|
|
@@ -2342,19 +2903,19 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2342
2903
|
}
|
|
2343
2904
|
let text;
|
|
2344
2905
|
try {
|
|
2345
|
-
text = (0,
|
|
2906
|
+
text = (0, import_node_fs3.readFileSync)(file, "utf8");
|
|
2346
2907
|
} catch {
|
|
2347
2908
|
diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
|
|
2348
2909
|
return void 0;
|
|
2349
2910
|
}
|
|
2350
2911
|
try {
|
|
2351
|
-
const
|
|
2352
|
-
const found = (0,
|
|
2912
|
+
const program2 = (0, import_parser7.parse)(text);
|
|
2913
|
+
const found = (0, import_parser7.directivesOf)(text);
|
|
2353
2914
|
directives.set(file, found);
|
|
2354
|
-
if (stamp !== void 0) parsedFiles.set(file, { stamp, program, text, directives: found });
|
|
2355
|
-
return { program, text };
|
|
2915
|
+
if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
|
|
2916
|
+
return { program: program2, text };
|
|
2356
2917
|
} catch (error) {
|
|
2357
|
-
if (error instanceof
|
|
2918
|
+
if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
|
|
2358
2919
|
const { line, column } = error;
|
|
2359
2920
|
diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
|
|
2360
2921
|
return void 0;
|
|
@@ -2362,31 +2923,33 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2362
2923
|
throw error;
|
|
2363
2924
|
}
|
|
2364
2925
|
}
|
|
2365
|
-
function importedSpecifiers(
|
|
2366
|
-
return
|
|
2926
|
+
function importedSpecifiers(program2) {
|
|
2927
|
+
return program2.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
|
|
2367
2928
|
}
|
|
2368
2929
|
var libraryCache = /* @__PURE__ */ new Map();
|
|
2369
2930
|
function parsedLibrary(file) {
|
|
2370
2931
|
let stamp;
|
|
2371
2932
|
try {
|
|
2372
|
-
const at = (0,
|
|
2933
|
+
const at = (0, import_node_fs3.statSync)(file);
|
|
2373
2934
|
stamp = `${at.size}:${at.mtimeMs}`;
|
|
2374
2935
|
} catch {
|
|
2375
2936
|
stamp = "";
|
|
2376
2937
|
}
|
|
2377
2938
|
const cached = libraryCache.get(file);
|
|
2378
2939
|
if (cached && cached.stamp === stamp) return cached.program;
|
|
2379
|
-
const
|
|
2380
|
-
libraryCache.set(file, { stamp, program });
|
|
2381
|
-
return
|
|
2940
|
+
const program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(file, "utf8"));
|
|
2941
|
+
libraryCache.set(file, { stamp, program: program2 });
|
|
2942
|
+
return program2;
|
|
2382
2943
|
}
|
|
2383
2944
|
var moduleAnalyses = /* @__PURE__ */ new Map();
|
|
2384
2945
|
function analyzeModules(modules, config2) {
|
|
2385
2946
|
const out2 = [];
|
|
2386
2947
|
const analyses = /* @__PURE__ */ new Map();
|
|
2387
|
-
const libraries = config2 ? (0,
|
|
2948
|
+
const libraries = config2 ? (0, import_parser7.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
|
|
2388
2949
|
for (const p of libraries.problems) out2.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
|
|
2389
2950
|
const libs = libraries.files.map(parsedLibrary);
|
|
2951
|
+
const noLib = config2?.noLib ?? false;
|
|
2952
|
+
const checked = libs.length > 0 || !noLib;
|
|
2390
2953
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
2391
2954
|
const byFile = new Map(modules.map((m) => [m.file, m]));
|
|
2392
2955
|
const exportsCache = /* @__PURE__ */ new Map();
|
|
@@ -2395,53 +2958,54 @@ function analyzeModules(modules, config2) {
|
|
|
2395
2958
|
let partials = 0;
|
|
2396
2959
|
const readWhile = /* @__PURE__ */ new Map();
|
|
2397
2960
|
const settingFor = `${libraries.files.join("|")}#${config2?.directory ?? ""}#${JSON.stringify(config2?.paths ?? {})}`;
|
|
2398
|
-
const analyzeModule = (file,
|
|
2961
|
+
const analyzeModule = (file, program2) => {
|
|
2399
2962
|
const done = analyzed.get(file);
|
|
2400
2963
|
if (done) return done;
|
|
2401
2964
|
const remembered = moduleAnalyses.get(file);
|
|
2402
|
-
if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program ===
|
|
2965
|
+
if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([read3, stamp]) => stampOf(read3) === stamp)) {
|
|
2403
2966
|
analyzed.set(file, remembered.result);
|
|
2404
2967
|
readWhile.set(file, remembered.read);
|
|
2405
2968
|
return remembered.result;
|
|
2406
2969
|
}
|
|
2407
|
-
const scopes = (0,
|
|
2970
|
+
const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: checked, noLib });
|
|
2408
2971
|
const said = [];
|
|
2409
2972
|
for (const d of scopes.diagnostics) {
|
|
2410
2973
|
if (d.kind !== "undeclared") continue;
|
|
2411
2974
|
said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
|
|
2412
2975
|
}
|
|
2413
2976
|
const before = partials;
|
|
2414
|
-
const types = (0,
|
|
2977
|
+
const types = (0, import_parser7.analyzeTypes)(program2, scopes, {
|
|
2415
2978
|
libs,
|
|
2416
2979
|
resolveModule: resolverFor(file),
|
|
2417
|
-
|
|
2980
|
+
noLib,
|
|
2981
|
+
reportUnknownTypes: checked
|
|
2418
2982
|
});
|
|
2419
2983
|
for (const d of types.diagnostics) {
|
|
2420
2984
|
const at = d.node;
|
|
2421
2985
|
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
2422
2986
|
}
|
|
2423
|
-
const
|
|
2987
|
+
const result2 = { types, diagnostics: said };
|
|
2424
2988
|
if (partials === before) {
|
|
2425
|
-
analyzed.set(file,
|
|
2426
|
-
const
|
|
2989
|
+
analyzed.set(file, result2);
|
|
2990
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
2427
2991
|
const own = stampOf(file);
|
|
2428
|
-
if (own !== void 0)
|
|
2429
|
-
readWhile.set(file,
|
|
2430
|
-
if ([...
|
|
2431
|
-
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program, read:
|
|
2992
|
+
if (own !== void 0) read3.set(file, own);
|
|
2993
|
+
readWhile.set(file, read3);
|
|
2994
|
+
if ([...read3.values()].every((stamp) => stamp !== void 0)) {
|
|
2995
|
+
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read3, result: result2 });
|
|
2432
2996
|
}
|
|
2433
2997
|
}
|
|
2434
|
-
return
|
|
2998
|
+
return result2;
|
|
2435
2999
|
};
|
|
2436
3000
|
const resolverFor = (file) => (specifier) => {
|
|
2437
|
-
const target = (0,
|
|
3001
|
+
const target = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
2438
3002
|
if (!target) return void 0;
|
|
2439
3003
|
const noteRead = () => {
|
|
2440
|
-
const
|
|
2441
|
-
readWhile.set(file,
|
|
3004
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
3005
|
+
readWhile.set(file, read3);
|
|
2442
3006
|
const stamp = stampOf(target);
|
|
2443
|
-
if (stamp !== void 0)
|
|
2444
|
-
for (const [below, belowStamp] of readWhile.get(target) ?? [])
|
|
3007
|
+
if (stamp !== void 0) read3.set(target, stamp);
|
|
3008
|
+
for (const [below, belowStamp] of readWhile.get(target) ?? []) read3.set(below, belowStamp);
|
|
2445
3009
|
};
|
|
2446
3010
|
if (inProgress.has(target)) {
|
|
2447
3011
|
partials++;
|
|
@@ -2452,19 +3016,19 @@ function analyzeModules(modules, config2) {
|
|
|
2452
3016
|
noteRead();
|
|
2453
3017
|
return cached;
|
|
2454
3018
|
}
|
|
2455
|
-
let
|
|
2456
|
-
if (!
|
|
3019
|
+
let program2 = byFile.get(target)?.program;
|
|
3020
|
+
if (!program2) {
|
|
2457
3021
|
try {
|
|
2458
|
-
|
|
3022
|
+
program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(target, "utf8"));
|
|
2459
3023
|
} catch {
|
|
2460
3024
|
return void 0;
|
|
2461
3025
|
}
|
|
2462
3026
|
}
|
|
2463
3027
|
inProgress.add(target);
|
|
2464
3028
|
try {
|
|
2465
|
-
const scopes = (0,
|
|
2466
|
-
const types = byFile.has(target) ? analyzeModule(target,
|
|
2467
|
-
const exports2 = (0,
|
|
3029
|
+
const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, noLib });
|
|
3030
|
+
const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser7.analyzeTypes)(program2, scopes, { libs, noLib, resolveModule: resolverFor(target), diagnostics: false });
|
|
3031
|
+
const exports2 = (0, import_parser7.moduleExports)(program2, scopes, types, resolverFor(target));
|
|
2468
3032
|
exportsCache.set(target, exports2);
|
|
2469
3033
|
noteRead();
|
|
2470
3034
|
return exports2;
|
|
@@ -2497,13 +3061,14 @@ function allStatements(statements) {
|
|
|
2497
3061
|
}
|
|
2498
3062
|
|
|
2499
3063
|
// src/cli.ts
|
|
2500
|
-
var usage = "usage: tilua <entry> [--out <file>] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
|
|
3064
|
+
var usage = "usage: tilua <entry> [--out <file>] [--output single|chunks|modules] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
|
|
2501
3065
|
var args = process.argv.slice(2);
|
|
2502
3066
|
var entry;
|
|
2503
3067
|
var out;
|
|
2504
3068
|
var config;
|
|
2505
3069
|
var typeCheck = true;
|
|
2506
3070
|
var buildTarget;
|
|
3071
|
+
var output;
|
|
2507
3072
|
for (let i = 0; i < args.length; i++) {
|
|
2508
3073
|
const arg = args[i];
|
|
2509
3074
|
if (arg === "--out") out = args[++i];
|
|
@@ -2516,6 +3081,13 @@ for (let i = 0; i < args.length; i++) {
|
|
|
2516
3081
|
process.exit(2);
|
|
2517
3082
|
}
|
|
2518
3083
|
buildTarget = value;
|
|
3084
|
+
} else if (arg === "--output") {
|
|
3085
|
+
const value = args[++i];
|
|
3086
|
+
if (value !== "single" && value !== "chunks" && value !== "modules") {
|
|
3087
|
+
console.error(`--output must be 'single', 'chunks' or 'modules', not '${value}'`);
|
|
3088
|
+
process.exit(2);
|
|
3089
|
+
}
|
|
3090
|
+
output = value;
|
|
2519
3091
|
} else if (arg === "--help" || arg === "-h") {
|
|
2520
3092
|
console.log(usage);
|
|
2521
3093
|
process.exit(0);
|
|
@@ -2526,18 +3098,32 @@ if (!entry) {
|
|
|
2526
3098
|
process.exit(2);
|
|
2527
3099
|
}
|
|
2528
3100
|
async function main(from) {
|
|
2529
|
-
const
|
|
2530
|
-
|
|
3101
|
+
const found = resolveConfig(config, (0, import_node_path3.resolve)(from)).config;
|
|
3102
|
+
const mode = output ?? found?.output ?? "single";
|
|
3103
|
+
const entryFile = mode === "modules" ? void 0 : out ?? from.replace(/\.tilua$/, "") + ".luau";
|
|
3104
|
+
const folder = entryFile !== void 0 ? (0, import_node_path3.dirname)(entryFile) : out ?? found?.directory ?? (0, import_node_path3.dirname)(from);
|
|
3105
|
+
const result2 = await bundle({
|
|
3106
|
+
entry: from,
|
|
3107
|
+
config,
|
|
3108
|
+
typeCheck,
|
|
3109
|
+
target: buildTarget,
|
|
3110
|
+
output: mode,
|
|
3111
|
+
outFile: entryFile !== void 0 ? (0, import_node_path3.basename)(entryFile) : void 0
|
|
3112
|
+
});
|
|
3113
|
+
for (const d of result2.diagnostics) {
|
|
2531
3114
|
console.error(`${(0, import_node_path3.relative)(process.cwd(), d.file)}:${d.line}:${d.column} ${d.message}`);
|
|
2532
3115
|
}
|
|
2533
|
-
if (
|
|
3116
|
+
if (result2.code === void 0) {
|
|
2534
3117
|
console.error("no bundle written");
|
|
2535
3118
|
process.exit(1);
|
|
2536
3119
|
}
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
3120
|
+
for (const file of result2.files) {
|
|
3121
|
+
const target = (0, import_node_path3.join)(folder, file.path);
|
|
3122
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(target), { recursive: true });
|
|
3123
|
+
(0, import_node_fs4.writeFileSync)(target, file.code);
|
|
3124
|
+
console.log(`${target}: ${file.modules.length} module${file.modules.length === 1 ? "" : "s"}`);
|
|
3125
|
+
}
|
|
3126
|
+
process.exit(result2.diagnostics.length ? 1 : 0);
|
|
2541
3127
|
}
|
|
2542
3128
|
main(entry).catch((error) => {
|
|
2543
3129
|
console.error(error instanceof Error ? error.message : error);
|