@tilua/compiler 4.0.0 → 5.0.1
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 +101 -10
- package/dist/{chunk-VZ7XC4BG.js → chunk-Y7QGXLCV.js} +643 -127
- package/dist/cli.cjs +709 -171
- package/dist/cli.js +32 -9
- package/dist/index.cjs +665 -148
- package/dist/index.d.cts +41 -6
- package/dist/index.d.ts +41 -6
- package/dist/index.js +6 -4
- package/package.json +2 -2
- package/runtime/array.luau +36 -4
- package/runtime/console.luau +227 -0
- package/runtime/result.luau +122 -0
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) {
|
|
@@ -203,6 +275,18 @@ var Lowerer = class {
|
|
|
203
275
|
for (const binding of scopes.bindings.values()) {
|
|
204
276
|
if (binding.declarationNode) this.bindingByDeclaration.set(binding.declarationNode, binding);
|
|
205
277
|
}
|
|
278
|
+
const visit = (value) => {
|
|
279
|
+
if (!value || typeof value !== "object") return;
|
|
280
|
+
if (Array.isArray(value)) return void value.forEach(visit);
|
|
281
|
+
const node = value;
|
|
282
|
+
if (node.type === "VariableDeclaration" && node.name?.type === "IdentifierPattern" && node.name.name) {
|
|
283
|
+
let init = node.init;
|
|
284
|
+
while (init?.type === "ParenthesizedExpression") init = init.expression;
|
|
285
|
+
if (init?.type === "ClassExpression") this.classExpressionNames.set(init, node.name.name);
|
|
286
|
+
}
|
|
287
|
+
for (const [key, child] of Object.entries(value)) if (key !== "line" && key !== "column") visit(child);
|
|
288
|
+
};
|
|
289
|
+
visit(source.body);
|
|
206
290
|
}
|
|
207
291
|
source;
|
|
208
292
|
scopes;
|
|
@@ -224,6 +308,8 @@ var Lowerer = class {
|
|
|
224
308
|
lua51;
|
|
225
309
|
/** The source line each emitted statement came from. */
|
|
226
310
|
origins = /* @__PURE__ */ new WeakMap();
|
|
311
|
+
/** The name each class written as a value is known by. */
|
|
312
|
+
classExpressionNames = /* @__PURE__ */ new WeakMap();
|
|
227
313
|
run() {
|
|
228
314
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
229
315
|
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
@@ -277,6 +363,10 @@ var Lowerer = class {
|
|
|
277
363
|
}
|
|
278
364
|
return identifier(local2);
|
|
279
365
|
}
|
|
366
|
+
/** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
|
|
367
|
+
move() {
|
|
368
|
+
return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
|
|
369
|
+
}
|
|
280
370
|
helper(kind) {
|
|
281
371
|
let name = this.helpers.get(kind);
|
|
282
372
|
if (!name) {
|
|
@@ -377,7 +467,7 @@ var Lowerer = class {
|
|
|
377
467
|
const type = this.options.types?.typeOf.get(argument);
|
|
378
468
|
return {
|
|
379
469
|
type,
|
|
380
|
-
typeText: type ? (0,
|
|
470
|
+
typeText: type ? (0, import_parser5.formatType)(type) : void 0,
|
|
381
471
|
source: this.writtenSource(argument),
|
|
382
472
|
declaration: declaration ? this.writtenSource(declaration) : void 0,
|
|
383
473
|
spread: argument.type === "SpreadElement"
|
|
@@ -399,7 +489,11 @@ var Lowerer = class {
|
|
|
399
489
|
/** The language's own lowering, then each library's, in load order. A
|
|
400
490
|
* runtime is named by its lowering's place here (`loweringRuntime`). */
|
|
401
491
|
get lowerings() {
|
|
402
|
-
return this.allLowerings ??= [
|
|
492
|
+
return this.allLowerings ??= [
|
|
493
|
+
languageLowering,
|
|
494
|
+
...this.options.noLib ? [] : [standardLowering],
|
|
495
|
+
...this.options.lowerings ?? []
|
|
496
|
+
];
|
|
403
497
|
}
|
|
404
498
|
allLowerings;
|
|
405
499
|
/** What `receiver:method(...)` becomes, when Luau alone would not run it.
|
|
@@ -413,22 +507,28 @@ var Lowerer = class {
|
|
|
413
507
|
* (`console:log`), the last library loaded first. No answer leaves an
|
|
414
508
|
* ordinary Luau method call: `text:upper()` reaches Lua's own. */
|
|
415
509
|
loweredMethodCall(node) {
|
|
510
|
+
return this.loweredMethod(node.method.name, this.options.types?.methodSources.get(node), node.object, node.arguments, node.method, node);
|
|
511
|
+
}
|
|
512
|
+
/** `loweredMethodCall`, for a method named `method` of what `object`
|
|
513
|
+
* holds — called with `:` on it, or read off it with `.` and handed its
|
|
514
|
+
* `self` like any other argument. `args` are the arguments after the
|
|
515
|
+
* receiver; a read that is not called has none. */
|
|
516
|
+
loweredMethod(method, source, object2, args2, name, node) {
|
|
416
517
|
const lowerings = this.lowerings;
|
|
417
|
-
const source = this.options.types?.methodSources.get(node);
|
|
418
518
|
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();
|
|
419
519
|
if (!asked.some((i) => lowerings[i].plugin.methodCall)) return void 0;
|
|
420
|
-
const receiver = this.options.types?.typeOf.get(
|
|
421
|
-
const receiverGlobal = this.globalName(
|
|
422
|
-
const info = this.lazyArguments(
|
|
520
|
+
const receiver = this.options.types?.typeOf.get(object2);
|
|
521
|
+
const receiverGlobal = this.globalName(object2);
|
|
522
|
+
const info = this.lazyArguments(args2);
|
|
423
523
|
for (const i of asked) {
|
|
424
|
-
const
|
|
425
|
-
if (!
|
|
426
|
-
const answer =
|
|
427
|
-
method
|
|
524
|
+
const plugin3 = lowerings[i].plugin;
|
|
525
|
+
if (!plugin3.methodCall) continue;
|
|
526
|
+
const answer = plugin3.methodCall({
|
|
527
|
+
method,
|
|
428
528
|
receiver,
|
|
429
529
|
receiverGlobal,
|
|
430
|
-
argumentCount:
|
|
431
|
-
at: this.callSite(
|
|
530
|
+
argumentCount: args2.length,
|
|
531
|
+
at: this.callSite(name),
|
|
432
532
|
get arguments() {
|
|
433
533
|
return info();
|
|
434
534
|
},
|
|
@@ -452,9 +552,9 @@ var Lowerer = class {
|
|
|
452
552
|
if (name === void 0) return void 0;
|
|
453
553
|
const info = this.lazyArguments(node.arguments);
|
|
454
554
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
455
|
-
const
|
|
456
|
-
if (!
|
|
457
|
-
const answer =
|
|
555
|
+
const plugin3 = lowerings[i].plugin;
|
|
556
|
+
if (!plugin3.globalCall) continue;
|
|
557
|
+
const answer = plugin3.globalCall({
|
|
458
558
|
name,
|
|
459
559
|
at: this.callSite(node.callee),
|
|
460
560
|
get arguments() {
|
|
@@ -506,6 +606,31 @@ var Lowerer = class {
|
|
|
506
606
|
const out2 = [];
|
|
507
607
|
const runtimes = this.runtimeDefinitions();
|
|
508
608
|
if (this.helpers.has("concat")) this.helper("array");
|
|
609
|
+
if (this.helpers.has("concat")) this.move();
|
|
610
|
+
const move = this.helpers.get("move");
|
|
611
|
+
if (move) {
|
|
612
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
613
|
+
local function ${move}(from, first, last, at, to)
|
|
614
|
+
for i = first, last do
|
|
615
|
+
to[at + i - first] = from[i]
|
|
616
|
+
end
|
|
617
|
+
return to
|
|
618
|
+
end`).body.statements);
|
|
619
|
+
}
|
|
620
|
+
const each = this.helpers.get("each");
|
|
621
|
+
if (each) {
|
|
622
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
623
|
+
local function ${each}(source, ...)
|
|
624
|
+
if ${this.builtin("type").name}(source) == "function" then
|
|
625
|
+
return source, ...
|
|
626
|
+
end
|
|
627
|
+
local meta = ${this.builtin("getmetatable").name}(source)
|
|
628
|
+
if meta ~= nil and meta.__iter ~= nil then
|
|
629
|
+
return meta.__iter(source)
|
|
630
|
+
end
|
|
631
|
+
return ${this.builtin("next").name}, source
|
|
632
|
+
end`).body.statements);
|
|
633
|
+
}
|
|
509
634
|
const marker = this.helpers.get("array");
|
|
510
635
|
const typeOf = this.helpers.get("typeof");
|
|
511
636
|
if (marker || typeOf) {
|
|
@@ -544,19 +669,72 @@ local function ${typeOf}(value, known)
|
|
|
544
669
|
end`).body.statements);
|
|
545
670
|
}
|
|
546
671
|
}
|
|
672
|
+
const instanceOf = this.helpers.get("instanceof");
|
|
673
|
+
if (instanceOf) {
|
|
674
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
675
|
+
local function ${instanceOf}(value, class)
|
|
676
|
+
if ${this.builtin("type").name}(value) ~= "table" then
|
|
677
|
+
return false
|
|
678
|
+
end
|
|
679
|
+
local current = value.ClassObject
|
|
680
|
+
while current ~= nil do
|
|
681
|
+
if current == class then
|
|
682
|
+
return true
|
|
683
|
+
end
|
|
684
|
+
current = current.ParentClass
|
|
685
|
+
end
|
|
686
|
+
return false
|
|
687
|
+
end`).body.statements);
|
|
688
|
+
}
|
|
689
|
+
const accessors = this.helpers.get("accessors");
|
|
690
|
+
if (accessors) {
|
|
691
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
692
|
+
local function ${accessors}(target, getters, setters)
|
|
693
|
+
return ${this.builtin("setmetatable").name}(target, {
|
|
694
|
+
-- Read by a spread, which copies what the getters give.
|
|
695
|
+
__tiluaGetters = getters,
|
|
696
|
+
__index = function(self, key)
|
|
697
|
+
local get = getters[key]
|
|
698
|
+
if get ~= nil then
|
|
699
|
+
return get(self)
|
|
700
|
+
end
|
|
701
|
+
return nil
|
|
702
|
+
end,
|
|
703
|
+
__newindex = function(self, key, value)
|
|
704
|
+
local set = setters[key]
|
|
705
|
+
if set ~= nil then
|
|
706
|
+
set(self, value)
|
|
707
|
+
return
|
|
708
|
+
end
|
|
709
|
+
if getters[key] ~= nil then
|
|
710
|
+
${this.builtin("error").name}("Cannot assign to '" .. ${this.builtin("tostring").name}(key) .. "': it has a getter and no setter", 2)
|
|
711
|
+
end
|
|
712
|
+
${this.builtin("rawset").name}(self, key, value)
|
|
713
|
+
end,
|
|
714
|
+
})
|
|
715
|
+
end`).body.statements);
|
|
716
|
+
}
|
|
547
717
|
const assign2 = this.helpers.get("assign");
|
|
548
718
|
if (assign2) {
|
|
549
|
-
out2.push(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
719
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
720
|
+
local function ${assign2}(target, ...)
|
|
721
|
+
for i = 1, ${this.builtin("select").name}("#", ...) do
|
|
722
|
+
local source = (${this.builtin("select").name}(i, ...))
|
|
723
|
+
if source ~= nil then
|
|
724
|
+
for key, value in ${this.builtin("pairs").name}(source) do
|
|
725
|
+
target[key] = value
|
|
726
|
+
end
|
|
727
|
+
local meta = ${this.builtin("type").name}(source) == "table" and ${this.builtin("getmetatable").name}(source) or nil
|
|
728
|
+
local getters = ${this.builtin("type").name}(meta) == "table" and ${this.builtin("rawget").name}(meta, "__tiluaGetters") or nil
|
|
729
|
+
if getters ~= nil then
|
|
730
|
+
for key, get in ${this.builtin("pairs").name}(getters) do
|
|
731
|
+
target[key] = get(source)
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
return target
|
|
737
|
+
end`).body.statements);
|
|
560
738
|
}
|
|
561
739
|
out2.push(...runtimes);
|
|
562
740
|
const concat = this.helpers.get("concat");
|
|
@@ -565,7 +743,7 @@ end`).body.statements);
|
|
|
565
743
|
local(["result"], [this.array(table([]))]),
|
|
566
744
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
567
745
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
568
|
-
callStatement(call(
|
|
746
|
+
callStatement(call(this.move(), [
|
|
569
747
|
identifier("part"),
|
|
570
748
|
number(1),
|
|
571
749
|
unary("#", identifier("part")),
|
|
@@ -582,8 +760,8 @@ end`).body.statements);
|
|
|
582
760
|
runtimeDefinitions() {
|
|
583
761
|
const out2 = [];
|
|
584
762
|
for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
|
|
585
|
-
const { plugin:
|
|
586
|
-
const source =
|
|
763
|
+
const { plugin: plugin3, from } = this.lowerings[index2];
|
|
764
|
+
const source = plugin3.runtime?.[runtime2];
|
|
587
765
|
if (source === void 0) {
|
|
588
766
|
this.diagnostics.push({
|
|
589
767
|
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
@@ -616,7 +794,7 @@ end`).body.statements);
|
|
|
616
794
|
const requires = [];
|
|
617
795
|
const moduleLocals = /* @__PURE__ */ new Map();
|
|
618
796
|
const importedFrom = /* @__PURE__ */ new Map();
|
|
619
|
-
const
|
|
797
|
+
const resolve4 = (source) => {
|
|
620
798
|
const key = context.resolve(source.value);
|
|
621
799
|
if (key === void 0) this.report(source, `Cannot find module '${source.value}'`);
|
|
622
800
|
return key;
|
|
@@ -655,7 +833,7 @@ end`).body.statements);
|
|
|
655
833
|
];
|
|
656
834
|
const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references.length ?? 0) > 0);
|
|
657
835
|
if (!used.length) continue;
|
|
658
|
-
const key =
|
|
836
|
+
const key = resolve4(statement.source);
|
|
659
837
|
if (key === void 0) continue;
|
|
660
838
|
const local2 = moduleLocal(key);
|
|
661
839
|
for (const s of used) {
|
|
@@ -678,8 +856,9 @@ end`).body.statements);
|
|
|
678
856
|
names.push("default");
|
|
679
857
|
break;
|
|
680
858
|
case "ExportNamedStatement": {
|
|
859
|
+
if (statement.typeOnly) break;
|
|
681
860
|
if (statement.source) {
|
|
682
|
-
const key =
|
|
861
|
+
const key = resolve4(statement.source);
|
|
683
862
|
if (key === void 0) break;
|
|
684
863
|
load(key);
|
|
685
864
|
for (const s of statement.specifiers) links.push({ name: s.exported.name, module: key, imported: s.local.name });
|
|
@@ -695,7 +874,7 @@ end`).body.statements);
|
|
|
695
874
|
break;
|
|
696
875
|
}
|
|
697
876
|
case "ExportAllStatement": {
|
|
698
|
-
const key =
|
|
877
|
+
const key = resolve4(statement.source);
|
|
699
878
|
if (key === void 0) break;
|
|
700
879
|
load(key);
|
|
701
880
|
stars.push(key);
|
|
@@ -761,6 +940,26 @@ end`).body.statements);
|
|
|
761
940
|
for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
|
|
762
941
|
return [...declarations, ...hoisted, ...requires, ...body];
|
|
763
942
|
}
|
|
943
|
+
/** `import("./x")`: the bundle's `import`, called with the module's name.
|
|
944
|
+
* It answers the module's exports table — what `import * as` names. */
|
|
945
|
+
importExpression(node) {
|
|
946
|
+
const context = this.options.module;
|
|
947
|
+
if (!context) {
|
|
948
|
+
this.report(node, "import() needs a bundle: build the project with @tilua/compiler");
|
|
949
|
+
return nil();
|
|
950
|
+
}
|
|
951
|
+
const source = node.source;
|
|
952
|
+
if (source.type !== "StringLiteral") {
|
|
953
|
+
this.report(source, "import() takes a string literal: which module it loads is decided when the project is built");
|
|
954
|
+
return nil();
|
|
955
|
+
}
|
|
956
|
+
const key = context.resolve(source.value, true);
|
|
957
|
+
if (key === void 0) {
|
|
958
|
+
this.report(source, `Cannot find module '${source.value}'`);
|
|
959
|
+
return nil();
|
|
960
|
+
}
|
|
961
|
+
return call(context.import ?? context.require, [string(key)]);
|
|
962
|
+
}
|
|
764
963
|
isRewritten(node) {
|
|
765
964
|
const binding = this.bindingByDeclaration.get(node);
|
|
766
965
|
return binding !== void 0 && this.rewrites.has(binding.id);
|
|
@@ -907,7 +1106,7 @@ end`).body.statements);
|
|
|
907
1106
|
case "VariableDeclaration":
|
|
908
1107
|
return this.variableDeclaration(node, "declare");
|
|
909
1108
|
case "FunctionDeclaration":
|
|
910
|
-
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: node.attributes }];
|
|
1109
|
+
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
|
|
911
1110
|
case "FunctionDeclarationStatement":
|
|
912
1111
|
return [this.functionDeclarationStatement(node)];
|
|
913
1112
|
case "ClassDeclaration":
|
|
@@ -938,8 +1137,13 @@ end`).body.statements);
|
|
|
938
1137
|
// kept — Lua has no expression statement — and it cannot run
|
|
939
1138
|
// anything, since a call is a `CallStatement` instead, so dropping
|
|
940
1139
|
// it changes nothing but the noise.
|
|
941
|
-
case "ExpressionStatement":
|
|
942
|
-
|
|
1140
|
+
case "ExpressionStatement": {
|
|
1141
|
+
let expression = node.expression;
|
|
1142
|
+
while (expression.type === "ParenthesizedExpression") expression = expression.expression;
|
|
1143
|
+
if (expression.type !== "ImportExpression") return [];
|
|
1144
|
+
const call2 = this.importExpression(expression);
|
|
1145
|
+
return call2.type === "CallExpression" ? [this.from(callStatement(call2), node)] : [];
|
|
1146
|
+
}
|
|
943
1147
|
case "DoStatement":
|
|
944
1148
|
return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
|
|
945
1149
|
case "WhileStatement":
|
|
@@ -991,7 +1195,11 @@ end`).body.statements);
|
|
|
991
1195
|
let source = this.expression(node.iterator);
|
|
992
1196
|
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
993
1197
|
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
994
|
-
|
|
1198
|
+
let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
|
|
1199
|
+
if (this.lua51 && form.walks === "values") {
|
|
1200
|
+
const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
|
|
1201
|
+
iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
|
|
1202
|
+
}
|
|
995
1203
|
return [genericFor(
|
|
996
1204
|
variables,
|
|
997
1205
|
iterators,
|
|
@@ -1071,11 +1279,12 @@ end`).body.statements);
|
|
|
1071
1279
|
const { build, accessors } = this.classHelpers;
|
|
1072
1280
|
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
1073
1281
|
return (0, import_luau_parser.parse)(`
|
|
1074
|
-
local function ${build}(base)
|
|
1282
|
+
local function ${build}(base, name)
|
|
1075
1283
|
local class = { __getters = {}, __setters = {} }
|
|
1076
1284
|
class.__index = class
|
|
1077
1285
|
class.ClassObject = class
|
|
1078
1286
|
class.ParentClass = base
|
|
1287
|
+
class.name = name
|
|
1079
1288
|
if base ~= nil then
|
|
1080
1289
|
setmetatable(class, { __index = base })
|
|
1081
1290
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
@@ -1129,7 +1338,11 @@ end
|
|
|
1129
1338
|
* table is assigned to `self`, which a module has already declared. */
|
|
1130
1339
|
classStatements(node, self, declare) {
|
|
1131
1340
|
const { build, accessors } = this.classRuntime();
|
|
1132
|
-
const
|
|
1341
|
+
const called = node.name?.name ?? (node.type === "ClassExpression" ? this.classExpressionNames.get(node) : void 0) ?? "";
|
|
1342
|
+
const create = call(identifier(build), [
|
|
1343
|
+
node.superclass ? this.reference(node.superclass) : nil(),
|
|
1344
|
+
string(called)
|
|
1345
|
+
]);
|
|
1133
1346
|
const out2 = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
|
|
1134
1347
|
const previous = this.classContext;
|
|
1135
1348
|
this.classContext = node;
|
|
@@ -1244,7 +1457,8 @@ end
|
|
|
1244
1457
|
},
|
|
1245
1458
|
isMethod,
|
|
1246
1459
|
func,
|
|
1247
|
-
|
|
1460
|
+
// `@native` and the like are Luau's.
|
|
1461
|
+
attributes: this.lua51 ? void 0 : node.attributes,
|
|
1248
1462
|
...spanOf(node)
|
|
1249
1463
|
};
|
|
1250
1464
|
}
|
|
@@ -1345,7 +1559,7 @@ end
|
|
|
1345
1559
|
if (element) bind(element.value, index(source, number(i + 1)), element.default);
|
|
1346
1560
|
});
|
|
1347
1561
|
if (pattern.rest) {
|
|
1348
|
-
const elements = call(
|
|
1562
|
+
const elements = call(this.move(), [
|
|
1349
1563
|
source,
|
|
1350
1564
|
number(pattern.elements.length + 1),
|
|
1351
1565
|
unary("#", source),
|
|
@@ -1456,8 +1670,15 @@ end
|
|
|
1456
1670
|
case "ArrayExpression":
|
|
1457
1671
|
return this.arrayExpression(node);
|
|
1458
1672
|
case "BinaryExpression": {
|
|
1673
|
+
if (node.operator === "??") return this.nullish(node);
|
|
1459
1674
|
const left = this.expression(node.left);
|
|
1460
1675
|
const right = this.expression(node.right);
|
|
1676
|
+
if (node.operator === "in") {
|
|
1677
|
+
return parenthesized(binary("~=", index(this.keptToOne(right), this.keptToOne(left)), nil()));
|
|
1678
|
+
}
|
|
1679
|
+
if (node.operator === "instanceof") {
|
|
1680
|
+
return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1681
|
+
}
|
|
1461
1682
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1462
1683
|
return binary(node.operator, left, right);
|
|
1463
1684
|
}
|
|
@@ -1484,6 +1705,8 @@ end
|
|
|
1484
1705
|
}
|
|
1485
1706
|
case "ParenthesizedExpression":
|
|
1486
1707
|
return parenthesized(this.expression(node.expression));
|
|
1708
|
+
case "ImportExpression":
|
|
1709
|
+
return this.importExpression(node);
|
|
1487
1710
|
// Types only: the value is the expression itself.
|
|
1488
1711
|
case "TypeAssertionExpression":
|
|
1489
1712
|
case "SatisfiesExpression":
|
|
@@ -1537,7 +1760,7 @@ end
|
|
|
1537
1760
|
const last2 = list.length - 1;
|
|
1538
1761
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1539
1762
|
}
|
|
1540
|
-
const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
|
|
1763
|
+
const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
|
|
1541
1764
|
const last = list[list.length - 1];
|
|
1542
1765
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1543
1766
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -1551,11 +1774,21 @@ end
|
|
|
1551
1774
|
/** One link of an access chain, read from `object`. */
|
|
1552
1775
|
link(node, object2) {
|
|
1553
1776
|
switch (node.type) {
|
|
1554
|
-
case "MemberExpression":
|
|
1777
|
+
case "MemberExpression": {
|
|
1778
|
+
if (this.options.types?.lengths.has(node)) return unary("#", object2);
|
|
1779
|
+
const source = this.options.types?.methodReads.get(node);
|
|
1780
|
+
const lowered = source && this.loweredMethod(node.property.name, source, node.object, [], node.property, node);
|
|
1781
|
+
if (lowered) {
|
|
1782
|
+
const forwarded = call(calleePath(lowered.callee), [...lowered.prepend, vararg()]);
|
|
1783
|
+
return functionExpression(functionBody(lowered.passReceiver ? [] : ["_"], [returns([forwarded])], true));
|
|
1784
|
+
}
|
|
1555
1785
|
return member(object2, node.property.name);
|
|
1786
|
+
}
|
|
1556
1787
|
case "IndexExpression":
|
|
1557
1788
|
return index(object2, this.expression(node.index));
|
|
1558
1789
|
case "CallExpression": {
|
|
1790
|
+
const method = this.methodCalled(node);
|
|
1791
|
+
if (method) return method;
|
|
1559
1792
|
const lowered = this.loweredGlobalCall(node);
|
|
1560
1793
|
const args2 = this.arguments(node.arguments);
|
|
1561
1794
|
if (!lowered) return call(object2, args2);
|
|
@@ -1578,6 +1811,24 @@ end
|
|
|
1578
1811
|
}
|
|
1579
1812
|
}
|
|
1580
1813
|
}
|
|
1814
|
+
/** `names.filter(names, f)`: a method of the value's metatable, read with
|
|
1815
|
+
* `.` and handed its `self` as the first argument. Written as a call of
|
|
1816
|
+
* the function the method really is — `array.filter(names, f)` — where
|
|
1817
|
+
* whoever declared the metatable says so; `undefined` leaves an ordinary
|
|
1818
|
+
* call, which is right for a string's Lua methods (`text.upper(text)`
|
|
1819
|
+
* reaches Lua's own `string.upper`). */
|
|
1820
|
+
methodCalled(node) {
|
|
1821
|
+
const callee = node.callee;
|
|
1822
|
+
if (callee.type !== "MemberExpression") return void 0;
|
|
1823
|
+
const source = this.options.types?.methodReads.get(callee);
|
|
1824
|
+
if (!source) return void 0;
|
|
1825
|
+
const spreads = node.arguments.map((a, i) => a.type === "SpreadElement" ? i : -1).filter((i) => i >= 0);
|
|
1826
|
+
if (!node.arguments.length || spreads.includes(0) || spreads.some((i) => i !== node.arguments.length - 1)) return void 0;
|
|
1827
|
+
const lowered = this.loweredMethod(callee.property.name, source, callee.object, node.arguments.slice(1), callee.property, node);
|
|
1828
|
+
if (!lowered) return void 0;
|
|
1829
|
+
const [self, ...rest] = this.arguments(node.arguments);
|
|
1830
|
+
return call(calleePath(lowered.callee), [...lowered.prepend, ...lowered.passReceiver ? [self] : [], ...rest]);
|
|
1831
|
+
}
|
|
1581
1832
|
// --------------------------------------------------------
|
|
1582
1833
|
// Optional chains
|
|
1583
1834
|
// --------------------------------------------------------
|
|
@@ -1623,7 +1874,7 @@ end
|
|
|
1623
1874
|
const segments = this.segments(chain);
|
|
1624
1875
|
const base = this.expression(chain.base);
|
|
1625
1876
|
const last = chain.links[chain.links.length - 1];
|
|
1626
|
-
if (base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
1877
|
+
if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
1627
1878
|
return {
|
|
1628
1879
|
type: "IfElseExpression",
|
|
1629
1880
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -1644,6 +1895,32 @@ end
|
|
|
1644
1895
|
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1645
1896
|
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1646
1897
|
}
|
|
1898
|
+
/** `a ?? b`: `a`, or `b` where `a` is nil — read only then. A `false`
|
|
1899
|
+
* stays, which is what sets it apart from `a or b`. A name is tested
|
|
1900
|
+
* in place; anything else is read once, into a local. */
|
|
1901
|
+
nullish(node) {
|
|
1902
|
+
const left = this.keptToOne(this.expression(node.left));
|
|
1903
|
+
const right = this.keptToOne(this.expression(node.right));
|
|
1904
|
+
if (!this.lua51 && left.type === "Identifier") {
|
|
1905
|
+
return {
|
|
1906
|
+
type: "IfElseExpression",
|
|
1907
|
+
clauses: [{ condition: binary("~=", left, nil()), body: left }],
|
|
1908
|
+
alternate: right,
|
|
1909
|
+
line: left.line,
|
|
1910
|
+
column: left.column
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
const ref = this.names.fresh("value");
|
|
1914
|
+
const at = identifier(ref);
|
|
1915
|
+
const statements = [
|
|
1916
|
+
local([ref], [left]),
|
|
1917
|
+
ifThen(binary("~=", at, nil()), [returns([at])]),
|
|
1918
|
+
returns([right])
|
|
1919
|
+
];
|
|
1920
|
+
const passesVarargs = statements.some(usesVararg);
|
|
1921
|
+
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1922
|
+
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1923
|
+
}
|
|
1647
1924
|
/** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
|
|
1648
1925
|
interpolatedString(node) {
|
|
1649
1926
|
let format = "";
|
|
@@ -1725,12 +2002,12 @@ end
|
|
|
1725
2002
|
}));
|
|
1726
2003
|
const alternate = this.expression(node.alternate);
|
|
1727
2004
|
if (clauses.every((c) => alwaysTruthy(c.body))) {
|
|
1728
|
-
let
|
|
2005
|
+
let result2 = alternate;
|
|
1729
2006
|
for (let i = clauses.length - 1; i >= 0; i--) {
|
|
1730
2007
|
const { condition, body } = clauses[i];
|
|
1731
|
-
|
|
2008
|
+
result2 = binary("or", binary("and", condition, body), result2);
|
|
1732
2009
|
}
|
|
1733
|
-
return parenthesized(
|
|
2010
|
+
return parenthesized(result2);
|
|
1734
2011
|
}
|
|
1735
2012
|
const statement = {
|
|
1736
2013
|
type: "IfStatement",
|
|
@@ -1787,11 +2064,20 @@ end
|
|
|
1787
2064
|
current = [];
|
|
1788
2065
|
parts.push(this.expression(f.argument));
|
|
1789
2066
|
break;
|
|
2067
|
+
case "TableFieldAccessor":
|
|
2068
|
+
break;
|
|
1790
2069
|
}
|
|
1791
2070
|
}
|
|
1792
|
-
|
|
1793
|
-
if (
|
|
1794
|
-
|
|
2071
|
+
let made;
|
|
2072
|
+
if (parts.length === 0) made = table(current);
|
|
2073
|
+
else {
|
|
2074
|
+
if (current.length) parts.push(table(current));
|
|
2075
|
+
made = call(this.helper("assign"), [table([]), ...parts]);
|
|
2076
|
+
}
|
|
2077
|
+
const accessors = node.fields.filter((f) => f.type === "TableFieldAccessor");
|
|
2078
|
+
if (!accessors.length) return made;
|
|
2079
|
+
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)))));
|
|
2080
|
+
return call(this.helper("accessors"), [made, table2("get"), table2("set")]);
|
|
1795
2081
|
}
|
|
1796
2082
|
/** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
|
|
1797
2083
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
@@ -2048,7 +2334,7 @@ var CLASS_METAMETHODS = [
|
|
|
2048
2334
|
];
|
|
2049
2335
|
|
|
2050
2336
|
// src/linemap.ts
|
|
2051
|
-
var
|
|
2337
|
+
var import_parser6 = require("@tilua/parser");
|
|
2052
2338
|
function statementsOf(node) {
|
|
2053
2339
|
const out2 = [];
|
|
2054
2340
|
const visit = (value) => {
|
|
@@ -2081,7 +2367,7 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
2081
2367
|
}
|
|
2082
2368
|
function lineMapSource(map) {
|
|
2083
2369
|
const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2084
|
-
const quoted = (s) => (0,
|
|
2370
|
+
const quoted = (s) => (0, import_parser6.luauString)(s.replace(/\\/g, "/"));
|
|
2085
2371
|
return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
|
|
2086
2372
|
}
|
|
2087
2373
|
|
|
@@ -2093,15 +2379,15 @@ async function loadLowerings(modules) {
|
|
|
2093
2379
|
for (const module2 of modules) {
|
|
2094
2380
|
try {
|
|
2095
2381
|
const loaded = await import((0, import_node_url.pathToFileURL)(module2.file).href);
|
|
2096
|
-
const
|
|
2097
|
-
if (!
|
|
2382
|
+
const plugin3 = loaded.default;
|
|
2383
|
+
if (!plugin3 || typeof plugin3 !== "object") {
|
|
2098
2384
|
problems.push({
|
|
2099
2385
|
file: module2.file,
|
|
2100
2386
|
message: `'${module2.from}' lowering module has no default export`
|
|
2101
2387
|
});
|
|
2102
2388
|
continue;
|
|
2103
2389
|
}
|
|
2104
|
-
lowerings.push({ plugin:
|
|
2390
|
+
lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
|
|
2105
2391
|
} catch (error) {
|
|
2106
2392
|
problems.push({
|
|
2107
2393
|
file: module2.file,
|
|
@@ -2128,16 +2414,17 @@ async function bundle(options) {
|
|
|
2128
2414
|
const file = queue.shift();
|
|
2129
2415
|
if (sources.has(file)) continue;
|
|
2130
2416
|
const parsed = parseFile(file, diagnostics, directives);
|
|
2131
|
-
if (!parsed) return { modules: [], diagnostics };
|
|
2132
|
-
const { program:
|
|
2133
|
-
const scopes = (0,
|
|
2417
|
+
if (!parsed) return { files: [], modules: [], diagnostics };
|
|
2418
|
+
const { program: program3, text } = parsed;
|
|
2419
|
+
const scopes = (0, import_parser7.analyzeScopes)(program3, { noLib: config2?.noLib });
|
|
2134
2420
|
for (const d of scopes.diagnostics) {
|
|
2135
2421
|
diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
|
|
2136
2422
|
}
|
|
2137
|
-
sources.set(file, { file, name: nameOf(file), text, program:
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2423
|
+
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2424
|
+
const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
|
|
2425
|
+
for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
|
|
2426
|
+
const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
2427
|
+
if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
|
|
2141
2428
|
}
|
|
2142
2429
|
}
|
|
2143
2430
|
const analysis = analyzeModules([...sources.values()], config2);
|
|
@@ -2149,6 +2436,9 @@ async function bundle(options) {
|
|
|
2149
2436
|
const names = Names.from(...[...sources.values()].map((s) => s.program));
|
|
2150
2437
|
const G = names.fresh("G");
|
|
2151
2438
|
const requireExpression = member(identifier(G), "require");
|
|
2439
|
+
const importExpression = member(identifier(G), "import");
|
|
2440
|
+
const mode = options.output ?? config2?.output ?? "single";
|
|
2441
|
+
const target = options.target ?? config2?.target ?? "luau";
|
|
2152
2442
|
const modules = /* @__PURE__ */ new Map();
|
|
2153
2443
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
2154
2444
|
const pending = [entry2];
|
|
@@ -2157,104 +2447,228 @@ async function bundle(options) {
|
|
|
2157
2447
|
if (modules.has(file)) continue;
|
|
2158
2448
|
const source = sources.get(file);
|
|
2159
2449
|
const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
|
|
2160
|
-
const
|
|
2450
|
+
const imports = /* @__PURE__ */ new Set();
|
|
2451
|
+
const dynamicImports = /* @__PURE__ */ new Set();
|
|
2452
|
+
const lowered2 = lower(source.program, source.scopes, {
|
|
2161
2453
|
names,
|
|
2162
2454
|
types: analysis.types.get(file),
|
|
2163
2455
|
lowerings,
|
|
2164
|
-
target
|
|
2456
|
+
target,
|
|
2457
|
+
noLib: config2?.noLib,
|
|
2165
2458
|
source: source.text,
|
|
2166
2459
|
file: shown,
|
|
2167
2460
|
module: {
|
|
2168
2461
|
name: source.name,
|
|
2169
2462
|
require: requireExpression,
|
|
2170
|
-
|
|
2463
|
+
import: importExpression,
|
|
2464
|
+
// One file has one line map. Several have one each, and a
|
|
2465
|
+
// module reads the one of the file it is in.
|
|
2466
|
+
lines: mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
|
|
2171
2467
|
fail: `${G}.fail`,
|
|
2172
2468
|
arrays: `${G}.arrays`,
|
|
2173
|
-
resolve: (specifier) => {
|
|
2174
|
-
const
|
|
2175
|
-
if (!
|
|
2176
|
-
pending.push(
|
|
2177
|
-
|
|
2469
|
+
resolve: (specifier, dynamic) => {
|
|
2470
|
+
const found = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
2471
|
+
if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
|
|
2472
|
+
pending.push(found);
|
|
2473
|
+
const name = sources.get(found).name;
|
|
2474
|
+
(dynamic ? dynamicImports : imports).add(name);
|
|
2475
|
+
return name;
|
|
2178
2476
|
}
|
|
2179
2477
|
}
|
|
2180
2478
|
});
|
|
2181
|
-
for (const d of
|
|
2182
|
-
for (const statement of allStatements(
|
|
2183
|
-
const line =
|
|
2479
|
+
for (const d of lowered2.diagnostics) diagnostics.push({ file, ...d, category: "module" });
|
|
2480
|
+
for (const statement of allStatements(lowered2.statements)) {
|
|
2481
|
+
const line = lowered2.origins.get(statement);
|
|
2184
2482
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
2185
2483
|
}
|
|
2186
2484
|
modules.set(file, {
|
|
2187
2485
|
name: source.name,
|
|
2188
|
-
statements:
|
|
2189
|
-
exportsName:
|
|
2190
|
-
info:
|
|
2191
|
-
usesScriptArgs:
|
|
2486
|
+
statements: lowered2.statements,
|
|
2487
|
+
exportsName: lowered2.exportsName,
|
|
2488
|
+
info: lowered2.module,
|
|
2489
|
+
usesScriptArgs: lowered2.usesScriptArgs,
|
|
2490
|
+
imports,
|
|
2491
|
+
dynamicImports
|
|
2192
2492
|
});
|
|
2193
2493
|
}
|
|
2194
2494
|
for (const [file, fileDirectives] of directives) {
|
|
2195
2495
|
const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
|
|
2196
|
-
const { kept, unusedExpectErrors } = (0,
|
|
2496
|
+
const { kept, unusedExpectErrors } = (0, import_parser7.applyDirectives)(fileDirectives, semantic, (d) => d.line);
|
|
2197
2497
|
const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
|
|
2198
2498
|
const remaining = diagnostics.filter((d) => !suppressed.has(d));
|
|
2199
2499
|
diagnostics.length = 0;
|
|
2200
2500
|
diagnostics.push(...remaining);
|
|
2201
2501
|
if (options.typeCheck === false) continue;
|
|
2202
2502
|
for (const d of unusedExpectErrors) {
|
|
2203
|
-
diagnostics.push({ file, message:
|
|
2503
|
+
diagnostics.push({ file, message: import_parser7.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
|
|
2204
2504
|
}
|
|
2205
2505
|
}
|
|
2206
2506
|
const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2207
2507
|
const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2208
2508
|
diagnostics.length = 0;
|
|
2209
2509
|
diagnostics.push(...reported);
|
|
2210
|
-
const
|
|
2510
|
+
const lowered = [...modules.values()];
|
|
2511
|
+
const moduleNames = lowered.map((m) => m.name);
|
|
2211
2512
|
if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
|
|
2212
|
-
return { modules: moduleNames, diagnostics };
|
|
2213
|
-
}
|
|
2214
|
-
const
|
|
2215
|
-
const
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2513
|
+
return { files: [], modules: moduleNames, diagnostics };
|
|
2514
|
+
}
|
|
2515
|
+
const several = mode !== "single";
|
|
2516
|
+
const layout = layoutOf(mode, modules.get(entry2), lowered, options.outFile);
|
|
2517
|
+
const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
|
|
2518
|
+
const elsewhere = /* @__PURE__ */ new Map();
|
|
2519
|
+
for (const file of layout.slice(1)) {
|
|
2520
|
+
for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
|
|
2521
|
+
}
|
|
2522
|
+
const program2 = (0, import_luau_parser2.parse)(runtime(G, {
|
|
2523
|
+
several,
|
|
2524
|
+
target,
|
|
2525
|
+
files: elsewhere,
|
|
2526
|
+
imports: lowered.some((m) => m.dynamicImports.size > 0)
|
|
2527
|
+
}));
|
|
2528
|
+
const assignment = program2.body.statements[1];
|
|
2529
|
+
if (usesScriptArgs) {
|
|
2530
|
+
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2531
|
+
program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
|
|
2532
|
+
if (several) program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.scriptArgs = scriptArgs`).body.statements);
|
|
2219
2533
|
}
|
|
2220
2534
|
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;
|
|
2221
2535
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2222
|
-
for (const module2 of modules
|
|
2223
|
-
|
|
2224
|
-
const fields = [];
|
|
2225
|
-
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2226
|
-
if (links.length) {
|
|
2227
|
-
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2228
|
-
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2229
|
-
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2230
|
-
]))))));
|
|
2231
|
-
}
|
|
2232
|
-
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2233
|
-
fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
|
|
2234
|
-
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
|
|
2536
|
+
for (const module2 of layout[0].modules) {
|
|
2537
|
+
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
|
|
2235
2538
|
}
|
|
2236
2539
|
const ok = names.fresh("ok");
|
|
2237
|
-
const
|
|
2540
|
+
const result2 = names.fresh("result");
|
|
2238
2541
|
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
2239
|
-
local ${ok}, ${
|
|
2240
|
-
return ${G}.require(${(0,
|
|
2542
|
+
local ${ok}, ${result2} = xpcall(function()
|
|
2543
|
+
return ${G}.require(${(0, import_parser7.luauString)(sources.get(entry2).name)})
|
|
2241
2544
|
end, ${G}.fail)
|
|
2242
2545
|
if not ${ok} then
|
|
2243
|
-
error(${
|
|
2546
|
+
error(${result2}, 0)
|
|
2244
2547
|
end
|
|
2245
|
-
${modules.get(entry2).info.exports ? `return ${
|
|
2548
|
+
${modules.get(entry2).info.exports ? `return ${result2}` : ""}
|
|
2246
2549
|
`).body.statements;
|
|
2247
|
-
|
|
2248
|
-
const map = buildLineMap(
|
|
2249
|
-
if (map.size) {
|
|
2250
|
-
|
|
2550
|
+
program2.body.statements.push(...entryStatements);
|
|
2551
|
+
const map = buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins);
|
|
2552
|
+
if (map.size || several) {
|
|
2553
|
+
const mapStatements = [assign(
|
|
2251
2554
|
[member(identifier(G), "lines")],
|
|
2252
2555
|
[(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
|
|
2253
|
-
)
|
|
2556
|
+
)];
|
|
2557
|
+
if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
|
|
2558
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
|
|
2254
2559
|
}
|
|
2255
|
-
|
|
2560
|
+
const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
|
|
2561
|
+
for (const file of layout.slice(1)) {
|
|
2562
|
+
const body = [];
|
|
2563
|
+
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
2564
|
+
for (const module2 of file.modules) {
|
|
2565
|
+
body.push(assign(
|
|
2566
|
+
[index(member(identifier(G), "modules"), string(module2.name))],
|
|
2567
|
+
[moduleTable(module2)]
|
|
2568
|
+
));
|
|
2569
|
+
}
|
|
2570
|
+
const chunk = program([returns([functionExpression(functionBody([G], body))])]);
|
|
2571
|
+
const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
|
|
2572
|
+
body.push(...mapRegistration(G, file, lineMapSource(lines)));
|
|
2573
|
+
files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
|
|
2574
|
+
}
|
|
2575
|
+
return { code: files[0].code, files, modules: moduleNames, diagnostics };
|
|
2576
|
+
}
|
|
2577
|
+
function moduleTable(module2) {
|
|
2578
|
+
const { names: exported, links, stars } = module2.info;
|
|
2579
|
+
const fields = [];
|
|
2580
|
+
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2581
|
+
if (links.length) {
|
|
2582
|
+
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2583
|
+
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2584
|
+
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2585
|
+
]))))));
|
|
2586
|
+
}
|
|
2587
|
+
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2588
|
+
fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
|
|
2589
|
+
return table(fields);
|
|
2590
|
+
}
|
|
2591
|
+
function layoutOf(mode, entry2, modules, outFile) {
|
|
2592
|
+
const extension = outFile !== void 0 && (0, import_node_path2.extname)(outFile) || ".luau";
|
|
2593
|
+
const entryStem = outFile !== void 0 ? (0, import_node_path2.basename)(outFile, (0, import_node_path2.extname)(outFile)) : import_node_path2.posix.basename(entry2.name);
|
|
2594
|
+
const others = modules.filter((m) => m !== entry2);
|
|
2595
|
+
if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry2, ...others] }];
|
|
2596
|
+
if (mode === "modules") {
|
|
2597
|
+
const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
|
|
2598
|
+
return [entry2, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
|
|
2599
|
+
}
|
|
2600
|
+
const byName = new Map(modules.map((m) => [m.name, m]));
|
|
2601
|
+
const reach = (root) => {
|
|
2602
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2603
|
+
const stack = [root];
|
|
2604
|
+
while (stack.length) {
|
|
2605
|
+
const name = stack.pop();
|
|
2606
|
+
if (seen.has(name)) continue;
|
|
2607
|
+
seen.add(name);
|
|
2608
|
+
stack.push(...byName.get(name)?.imports ?? []);
|
|
2609
|
+
}
|
|
2610
|
+
return seen;
|
|
2611
|
+
};
|
|
2612
|
+
const inEntry = reach(entry2.name);
|
|
2613
|
+
const roots = [...new Set(modules.flatMap((m) => [...m.dynamicImports]))].filter((root) => !inEntry.has(root));
|
|
2614
|
+
const reached = new Map(roots.map((root) => [root, reach(root)]));
|
|
2615
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2616
|
+
for (const module2 of others) {
|
|
2617
|
+
if (inEntry.has(module2.name)) continue;
|
|
2618
|
+
const by = roots.filter((root) => reached.get(root).has(module2.name));
|
|
2619
|
+
const key = by.join("\n");
|
|
2620
|
+
if (!groups.has(key)) groups.set(key, { roots: by, modules: [] });
|
|
2621
|
+
groups.get(key).modules.push(module2);
|
|
2622
|
+
}
|
|
2623
|
+
const files = [{ path: `${entryStem}${extension}`, modules: modules.filter((m) => inEntry.has(m.name)) }];
|
|
2624
|
+
const taken = /* @__PURE__ */ new Set([entryStem]);
|
|
2625
|
+
for (const group of groups.values()) {
|
|
2626
|
+
const label = group.roots.length === 1 ? group.roots[0].replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "chunk" : "shared";
|
|
2627
|
+
let stem = `${entryStem}_${label}`;
|
|
2628
|
+
for (let n = 2; taken.has(stem); n++) stem = `${entryStem}_${label}_${n}`;
|
|
2629
|
+
taken.add(stem);
|
|
2630
|
+
files.push({ path: `${stem}${extension}`, modules: group.modules });
|
|
2631
|
+
}
|
|
2632
|
+
return files;
|
|
2633
|
+
}
|
|
2634
|
+
function loadPath(entryPath, file, target) {
|
|
2635
|
+
const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
|
|
2636
|
+
if (target === "lua51") return path;
|
|
2637
|
+
const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
|
|
2638
|
+
return bare.startsWith("../") ? bare : `./${bare}`;
|
|
2639
|
+
}
|
|
2640
|
+
function mapRegistration(G, file, map) {
|
|
2641
|
+
const extension = import_node_path2.posix.extname(file.path);
|
|
2642
|
+
const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
|
|
2643
|
+
const entry2 = `${G}.sources[#${G}.sources + 1]`;
|
|
2644
|
+
return (0, import_luau_parser2.parse)([
|
|
2645
|
+
`${entry2} = { { ${parts.map(import_parser7.luauString).join(", ")} }, ${map}, ${(0, import_parser7.luauString)(extension.slice(1))} }`,
|
|
2646
|
+
...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
|
|
2647
|
+
].join("\n")).body.statements;
|
|
2648
|
+
}
|
|
2649
|
+
function loader(G, target) {
|
|
2650
|
+
if (target === "lua51") {
|
|
2651
|
+
return `
|
|
2652
|
+
-- From beside this file, wherever that is: its own path says where.
|
|
2653
|
+
local folder = ""
|
|
2654
|
+
if debug ~= nil and debug.getinfo ~= nil then
|
|
2655
|
+
local at = debug.getinfo(${G}.load, "S").source
|
|
2656
|
+
if at:sub(1, 1) == "@" then
|
|
2657
|
+
folder = at:match("^@(.*[/\\\\])") or ""
|
|
2658
|
+
end
|
|
2659
|
+
end
|
|
2660
|
+
local chunk, problem = loadfile(folder .. file)
|
|
2661
|
+
if chunk == nil then
|
|
2662
|
+
error(problem, 0)
|
|
2663
|
+
end
|
|
2664
|
+
chunk()(${G})`;
|
|
2665
|
+
}
|
|
2666
|
+
return `
|
|
2667
|
+
require(file)(${G})`;
|
|
2256
2668
|
}
|
|
2257
|
-
function runtime(G) {
|
|
2669
|
+
function runtime(G, options) {
|
|
2670
|
+
const { several } = options;
|
|
2671
|
+
const files = [...options.files].map(([name, file]) => `[${(0, import_parser7.luauString)(name)}] = ${(0, import_parser7.luauString)(file)}`).join(", ");
|
|
2258
2672
|
return `
|
|
2259
2673
|
local ${G}
|
|
2260
2674
|
${G} = {
|
|
@@ -2302,7 +2716,7 @@ ${G} = {
|
|
|
2302
2716
|
return message
|
|
2303
2717
|
end
|
|
2304
2718
|
local lines = ${G}.lines
|
|
2305
|
-
if lines == nil then
|
|
2719
|
+
if lines == nil ${several ? `and #${G}.sources == 0 ` : ""}then
|
|
2306
2720
|
return message .. (trace or "")
|
|
2307
2721
|
end
|
|
2308
2722
|
-- The bundle's name, as a position in a message spells it: where this
|
|
@@ -2322,6 +2736,81 @@ ${G} = {
|
|
|
2322
2736
|
chunk = info.short_src
|
|
2323
2737
|
end
|
|
2324
2738
|
end
|
|
2739
|
+
${several ? `
|
|
2740
|
+
-- A position in this file is read by this file's map. One in another
|
|
2741
|
+
-- of the bundle's files is read by that file's: the one whose name
|
|
2742
|
+
-- the position ends with, by the most folders where several do. A
|
|
2743
|
+
-- message says nothing about which file it starts in, so nothing is
|
|
2744
|
+
-- guessed from it here.
|
|
2745
|
+
local prefix = nil
|
|
2746
|
+
if chunk ~= nil then
|
|
2747
|
+
prefix = chunk:gsub("%p", "%%%0")
|
|
2748
|
+
end
|
|
2749
|
+
local function place(line)
|
|
2750
|
+
local origin = lines ~= nil and lines[tonumber(line)] or nil
|
|
2751
|
+
if origin == nil then
|
|
2752
|
+
return nil
|
|
2753
|
+
end
|
|
2754
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2755
|
+
end
|
|
2756
|
+
local function elsewhere(at, line)
|
|
2757
|
+
-- Already a place in the project.
|
|
2758
|
+
if at:sub(-6) == ".tilua" then
|
|
2759
|
+
return nil
|
|
2760
|
+
end
|
|
2761
|
+
local parts = {}
|
|
2762
|
+
for part in at:gmatch("[^/\\\\%.]+") do
|
|
2763
|
+
parts[#parts + 1] = part
|
|
2764
|
+
end
|
|
2765
|
+
local map, most, tied = nil, 0, false
|
|
2766
|
+
for _, source in ipairs(${G}.sources) do
|
|
2767
|
+
local names = source[1]
|
|
2768
|
+
local last = #parts
|
|
2769
|
+
if parts[last] == source[3] then
|
|
2770
|
+
last = last - 1
|
|
2771
|
+
end
|
|
2772
|
+
local same = 0
|
|
2773
|
+
while same < #names and same < last and names[#names - same] == parts[last - same] do
|
|
2774
|
+
same = same + 1
|
|
2775
|
+
end
|
|
2776
|
+
if same > most then
|
|
2777
|
+
map, most, tied = source[2], same, false
|
|
2778
|
+
elseif same == most then
|
|
2779
|
+
tied = true
|
|
2780
|
+
end
|
|
2781
|
+
end
|
|
2782
|
+
-- Two files it could as well be: a wrong place is worse than none.
|
|
2783
|
+
local origin = map ~= nil and not tied and map[tonumber(line)] or nil
|
|
2784
|
+
if origin == nil then
|
|
2785
|
+
return nil
|
|
2786
|
+
end
|
|
2787
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2788
|
+
end
|
|
2789
|
+
local mapped = message
|
|
2790
|
+
if prefix ~= nil then
|
|
2791
|
+
mapped = mapped:gsub(prefix .. ":(%d+)", function(line)
|
|
2792
|
+
return place(line)
|
|
2793
|
+
end)
|
|
2794
|
+
end
|
|
2795
|
+
mapped = mapped:gsub("(%S+):(%d+)", elsewhere)
|
|
2796
|
+
local frames = {}
|
|
2797
|
+
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2798
|
+
local at = nil
|
|
2799
|
+
if prefix ~= nil then
|
|
2800
|
+
local line = frame:match(prefix .. ":(%d+)")
|
|
2801
|
+
if line ~= nil then
|
|
2802
|
+
at = place(line)
|
|
2803
|
+
end
|
|
2804
|
+
end
|
|
2805
|
+
if at == nil then
|
|
2806
|
+
for source, line in frame:gmatch("(%S+):(%d+)") do
|
|
2807
|
+
at = elsewhere(source, line)
|
|
2808
|
+
if at ~= nil then
|
|
2809
|
+
break
|
|
2810
|
+
end
|
|
2811
|
+
end
|
|
2812
|
+
end
|
|
2813
|
+
if at ~= nil then` : `
|
|
2325
2814
|
if chunk == nil then
|
|
2326
2815
|
chunk = message:match("^(.-):%d+:")
|
|
2327
2816
|
end
|
|
@@ -2343,8 +2832,9 @@ ${G} = {
|
|
|
2343
2832
|
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2344
2833
|
local line = frame:match(prefix .. ":(%d+)")
|
|
2345
2834
|
local at = line ~= nil and place(line) or nil
|
|
2346
|
-
if at ~= nil then
|
|
2347
|
-
|
|
2835
|
+
if at ~= nil then`}
|
|
2836
|
+
-- Luau writes \`function name\`; Lua 5.1, \`function 'name'\`.
|
|
2837
|
+
local name = frame:match("function ([%w_.:]+)") or frame:match("function '([^']+)'")
|
|
2348
2838
|
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2349
2839
|
end
|
|
2350
2840
|
end
|
|
@@ -2353,13 +2843,36 @@ ${G} = {
|
|
|
2353
2843
|
end
|
|
2354
2844
|
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2355
2845
|
end,
|
|
2356
|
-
records = {}
|
|
2846
|
+
records = {},${several ? `
|
|
2847
|
+
-- The file each module outside this one is in, as \`load\` is handed it.
|
|
2848
|
+
files = { ${files} },
|
|
2849
|
+
loaded = {},
|
|
2850
|
+
-- Every file's line map: by the module, and by the file's name.
|
|
2851
|
+
maps = {},
|
|
2852
|
+
sources = {},
|
|
2853
|
+
-- Another of the bundle's files: a function that adds its modules here.
|
|
2854
|
+
load = function(file)
|
|
2855
|
+
if ${G}.loaded[file] then
|
|
2856
|
+
return
|
|
2857
|
+
end
|
|
2858
|
+
${G}.loaded[file] = true${loader(G, options.target)}
|
|
2859
|
+
end,` : ""}${options.imports ? `
|
|
2860
|
+
-- \`import("./x")\`: the module's exports, asked for where the code runs
|
|
2861
|
+
-- rather than before its module does. \`require\` does the rest \u2014 and
|
|
2862
|
+
-- loads the file the module is in, if it is not this one.
|
|
2863
|
+
import = function(name)
|
|
2864
|
+
return ${G}.require(name)
|
|
2865
|
+
end,` : ""}
|
|
2357
2866
|
require = function(name)
|
|
2358
2867
|
local record = ${G}.records[name]
|
|
2359
2868
|
if record ~= nil then
|
|
2360
2869
|
return record.exports
|
|
2361
2870
|
end
|
|
2362
|
-
local module = ${G}.modules[name]
|
|
2871
|
+
local module = ${G}.modules[name]${several ? `
|
|
2872
|
+
if module == nil then
|
|
2873
|
+
${G}.load(${G}.files[name])
|
|
2874
|
+
module = ${G}.modules[name]
|
|
2875
|
+
end` : ""}
|
|
2363
2876
|
local initialized = {}
|
|
2364
2877
|
local exports = setmetatable({}, {
|
|
2365
2878
|
__index = function(_, key)
|
|
@@ -2378,7 +2891,7 @@ ${G} = {
|
|
|
2378
2891
|
return source[link[2]]
|
|
2379
2892
|
end
|
|
2380
2893
|
if module.stars ~= nil and key ~= "default" then
|
|
2381
|
-
for _, star in module.stars do
|
|
2894
|
+
for _, star in ipairs(module.stars) do
|
|
2382
2895
|
local value = ${G}.require(star)[key]
|
|
2383
2896
|
if value ~= nil then
|
|
2384
2897
|
return value
|
|
@@ -2403,7 +2916,7 @@ ${G} = {
|
|
|
2403
2916
|
}
|
|
2404
2917
|
function stampOf(file) {
|
|
2405
2918
|
try {
|
|
2406
|
-
const at = (0,
|
|
2919
|
+
const at = (0, import_node_fs3.statSync)(file);
|
|
2407
2920
|
return `${at.size}:${at.mtimeMs}`;
|
|
2408
2921
|
} catch {
|
|
2409
2922
|
return void 0;
|
|
@@ -2419,19 +2932,19 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2419
2932
|
}
|
|
2420
2933
|
let text;
|
|
2421
2934
|
try {
|
|
2422
|
-
text = (0,
|
|
2935
|
+
text = (0, import_node_fs3.readFileSync)(file, "utf8");
|
|
2423
2936
|
} catch {
|
|
2424
2937
|
diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
|
|
2425
2938
|
return void 0;
|
|
2426
2939
|
}
|
|
2427
2940
|
try {
|
|
2428
|
-
const
|
|
2429
|
-
const found = (0,
|
|
2941
|
+
const program2 = (0, import_parser7.parse)(text);
|
|
2942
|
+
const found = (0, import_parser7.directivesOf)(text);
|
|
2430
2943
|
directives.set(file, found);
|
|
2431
|
-
if (stamp !== void 0) parsedFiles.set(file, { stamp, program, text, directives: found });
|
|
2432
|
-
return { program, text };
|
|
2944
|
+
if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
|
|
2945
|
+
return { program: program2, text };
|
|
2433
2946
|
} catch (error) {
|
|
2434
|
-
if (error instanceof
|
|
2947
|
+
if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
|
|
2435
2948
|
const { line, column } = error;
|
|
2436
2949
|
diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
|
|
2437
2950
|
return void 0;
|
|
@@ -2439,31 +2952,33 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2439
2952
|
throw error;
|
|
2440
2953
|
}
|
|
2441
2954
|
}
|
|
2442
|
-
function importedSpecifiers(
|
|
2443
|
-
return
|
|
2955
|
+
function importedSpecifiers(program2) {
|
|
2956
|
+
return program2.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
|
|
2444
2957
|
}
|
|
2445
2958
|
var libraryCache = /* @__PURE__ */ new Map();
|
|
2446
2959
|
function parsedLibrary(file) {
|
|
2447
2960
|
let stamp;
|
|
2448
2961
|
try {
|
|
2449
|
-
const at = (0,
|
|
2962
|
+
const at = (0, import_node_fs3.statSync)(file);
|
|
2450
2963
|
stamp = `${at.size}:${at.mtimeMs}`;
|
|
2451
2964
|
} catch {
|
|
2452
2965
|
stamp = "";
|
|
2453
2966
|
}
|
|
2454
2967
|
const cached = libraryCache.get(file);
|
|
2455
2968
|
if (cached && cached.stamp === stamp) return cached.program;
|
|
2456
|
-
const
|
|
2457
|
-
libraryCache.set(file, { stamp, program });
|
|
2458
|
-
return
|
|
2969
|
+
const program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(file, "utf8"));
|
|
2970
|
+
libraryCache.set(file, { stamp, program: program2 });
|
|
2971
|
+
return program2;
|
|
2459
2972
|
}
|
|
2460
2973
|
var moduleAnalyses = /* @__PURE__ */ new Map();
|
|
2461
2974
|
function analyzeModules(modules, config2) {
|
|
2462
2975
|
const out2 = [];
|
|
2463
2976
|
const analyses = /* @__PURE__ */ new Map();
|
|
2464
|
-
const libraries = config2 ? (0,
|
|
2977
|
+
const libraries = config2 ? (0, import_parser7.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
|
|
2465
2978
|
for (const p of libraries.problems) out2.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
|
|
2466
2979
|
const libs = libraries.files.map(parsedLibrary);
|
|
2980
|
+
const noLib = config2?.noLib ?? false;
|
|
2981
|
+
const checked = libs.length > 0 || !noLib;
|
|
2467
2982
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
2468
2983
|
const byFile = new Map(modules.map((m) => [m.file, m]));
|
|
2469
2984
|
const exportsCache = /* @__PURE__ */ new Map();
|
|
@@ -2472,53 +2987,54 @@ function analyzeModules(modules, config2) {
|
|
|
2472
2987
|
let partials = 0;
|
|
2473
2988
|
const readWhile = /* @__PURE__ */ new Map();
|
|
2474
2989
|
const settingFor = `${libraries.files.join("|")}#${config2?.directory ?? ""}#${JSON.stringify(config2?.paths ?? {})}`;
|
|
2475
|
-
const analyzeModule = (file,
|
|
2990
|
+
const analyzeModule = (file, program2) => {
|
|
2476
2991
|
const done = analyzed.get(file);
|
|
2477
2992
|
if (done) return done;
|
|
2478
2993
|
const remembered = moduleAnalyses.get(file);
|
|
2479
|
-
if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program ===
|
|
2994
|
+
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)) {
|
|
2480
2995
|
analyzed.set(file, remembered.result);
|
|
2481
2996
|
readWhile.set(file, remembered.read);
|
|
2482
2997
|
return remembered.result;
|
|
2483
2998
|
}
|
|
2484
|
-
const scopes = (0,
|
|
2999
|
+
const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: checked, noLib });
|
|
2485
3000
|
const said = [];
|
|
2486
3001
|
for (const d of scopes.diagnostics) {
|
|
2487
3002
|
if (d.kind !== "undeclared") continue;
|
|
2488
3003
|
said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
|
|
2489
3004
|
}
|
|
2490
3005
|
const before = partials;
|
|
2491
|
-
const types = (0,
|
|
3006
|
+
const types = (0, import_parser7.analyzeTypes)(program2, scopes, {
|
|
2492
3007
|
libs,
|
|
2493
3008
|
resolveModule: resolverFor(file),
|
|
2494
|
-
|
|
3009
|
+
noLib,
|
|
3010
|
+
reportUnknownTypes: checked
|
|
2495
3011
|
});
|
|
2496
3012
|
for (const d of types.diagnostics) {
|
|
2497
3013
|
const at = d.node;
|
|
2498
3014
|
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
2499
3015
|
}
|
|
2500
|
-
const
|
|
3016
|
+
const result2 = { types, diagnostics: said };
|
|
2501
3017
|
if (partials === before) {
|
|
2502
|
-
analyzed.set(file,
|
|
2503
|
-
const
|
|
3018
|
+
analyzed.set(file, result2);
|
|
3019
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
2504
3020
|
const own = stampOf(file);
|
|
2505
|
-
if (own !== void 0)
|
|
2506
|
-
readWhile.set(file,
|
|
2507
|
-
if ([...
|
|
2508
|
-
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program, read:
|
|
3021
|
+
if (own !== void 0) read3.set(file, own);
|
|
3022
|
+
readWhile.set(file, read3);
|
|
3023
|
+
if ([...read3.values()].every((stamp) => stamp !== void 0)) {
|
|
3024
|
+
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read3, result: result2 });
|
|
2509
3025
|
}
|
|
2510
3026
|
}
|
|
2511
|
-
return
|
|
3027
|
+
return result2;
|
|
2512
3028
|
};
|
|
2513
3029
|
const resolverFor = (file) => (specifier) => {
|
|
2514
|
-
const target = (0,
|
|
3030
|
+
const target = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
2515
3031
|
if (!target) return void 0;
|
|
2516
3032
|
const noteRead = () => {
|
|
2517
|
-
const
|
|
2518
|
-
readWhile.set(file,
|
|
3033
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
3034
|
+
readWhile.set(file, read3);
|
|
2519
3035
|
const stamp = stampOf(target);
|
|
2520
|
-
if (stamp !== void 0)
|
|
2521
|
-
for (const [below, belowStamp] of readWhile.get(target) ?? [])
|
|
3036
|
+
if (stamp !== void 0) read3.set(target, stamp);
|
|
3037
|
+
for (const [below, belowStamp] of readWhile.get(target) ?? []) read3.set(below, belowStamp);
|
|
2522
3038
|
};
|
|
2523
3039
|
if (inProgress.has(target)) {
|
|
2524
3040
|
partials++;
|
|
@@ -2529,19 +3045,19 @@ function analyzeModules(modules, config2) {
|
|
|
2529
3045
|
noteRead();
|
|
2530
3046
|
return cached;
|
|
2531
3047
|
}
|
|
2532
|
-
let
|
|
2533
|
-
if (!
|
|
3048
|
+
let program2 = byFile.get(target)?.program;
|
|
3049
|
+
if (!program2) {
|
|
2534
3050
|
try {
|
|
2535
|
-
|
|
3051
|
+
program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(target, "utf8"));
|
|
2536
3052
|
} catch {
|
|
2537
3053
|
return void 0;
|
|
2538
3054
|
}
|
|
2539
3055
|
}
|
|
2540
3056
|
inProgress.add(target);
|
|
2541
3057
|
try {
|
|
2542
|
-
const scopes = (0,
|
|
2543
|
-
const types = byFile.has(target) ? analyzeModule(target,
|
|
2544
|
-
const exports2 = (0,
|
|
3058
|
+
const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, noLib });
|
|
3059
|
+
const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser7.analyzeTypes)(program2, scopes, { libs, noLib, resolveModule: resolverFor(target), diagnostics: false });
|
|
3060
|
+
const exports2 = (0, import_parser7.moduleExports)(program2, scopes, types, resolverFor(target));
|
|
2545
3061
|
exportsCache.set(target, exports2);
|
|
2546
3062
|
noteRead();
|
|
2547
3063
|
return exports2;
|
|
@@ -2574,13 +3090,14 @@ function allStatements(statements) {
|
|
|
2574
3090
|
}
|
|
2575
3091
|
|
|
2576
3092
|
// src/cli.ts
|
|
2577
|
-
var usage = "usage: tilua <entry> [--out <file>] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
|
|
3093
|
+
var usage = "usage: tilua <entry> [--out <file>] [--output single|chunks|modules] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
|
|
2578
3094
|
var args = process.argv.slice(2);
|
|
2579
3095
|
var entry;
|
|
2580
3096
|
var out;
|
|
2581
3097
|
var config;
|
|
2582
3098
|
var typeCheck = true;
|
|
2583
3099
|
var buildTarget;
|
|
3100
|
+
var output;
|
|
2584
3101
|
for (let i = 0; i < args.length; i++) {
|
|
2585
3102
|
const arg = args[i];
|
|
2586
3103
|
if (arg === "--out") out = args[++i];
|
|
@@ -2593,6 +3110,13 @@ for (let i = 0; i < args.length; i++) {
|
|
|
2593
3110
|
process.exit(2);
|
|
2594
3111
|
}
|
|
2595
3112
|
buildTarget = value;
|
|
3113
|
+
} else if (arg === "--output") {
|
|
3114
|
+
const value = args[++i];
|
|
3115
|
+
if (value !== "single" && value !== "chunks" && value !== "modules") {
|
|
3116
|
+
console.error(`--output must be 'single', 'chunks' or 'modules', not '${value}'`);
|
|
3117
|
+
process.exit(2);
|
|
3118
|
+
}
|
|
3119
|
+
output = value;
|
|
2596
3120
|
} else if (arg === "--help" || arg === "-h") {
|
|
2597
3121
|
console.log(usage);
|
|
2598
3122
|
process.exit(0);
|
|
@@ -2603,18 +3127,32 @@ if (!entry) {
|
|
|
2603
3127
|
process.exit(2);
|
|
2604
3128
|
}
|
|
2605
3129
|
async function main(from) {
|
|
2606
|
-
const
|
|
2607
|
-
|
|
3130
|
+
const found = resolveConfig(config, (0, import_node_path3.resolve)(from)).config;
|
|
3131
|
+
const mode = output ?? found?.output ?? "single";
|
|
3132
|
+
const entryFile = mode === "modules" ? void 0 : out ?? from.replace(/\.tilua$/, "") + ".luau";
|
|
3133
|
+
const folder = entryFile !== void 0 ? (0, import_node_path3.dirname)(entryFile) : out ?? found?.directory ?? (0, import_node_path3.dirname)(from);
|
|
3134
|
+
const result2 = await bundle({
|
|
3135
|
+
entry: from,
|
|
3136
|
+
config,
|
|
3137
|
+
typeCheck,
|
|
3138
|
+
target: buildTarget,
|
|
3139
|
+
output: mode,
|
|
3140
|
+
outFile: entryFile !== void 0 ? (0, import_node_path3.basename)(entryFile) : void 0
|
|
3141
|
+
});
|
|
3142
|
+
for (const d of result2.diagnostics) {
|
|
2608
3143
|
console.error(`${(0, import_node_path3.relative)(process.cwd(), d.file)}:${d.line}:${d.column} ${d.message}`);
|
|
2609
3144
|
}
|
|
2610
|
-
if (
|
|
3145
|
+
if (result2.code === void 0) {
|
|
2611
3146
|
console.error("no bundle written");
|
|
2612
3147
|
process.exit(1);
|
|
2613
3148
|
}
|
|
2614
|
-
const
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
3149
|
+
for (const file of result2.files) {
|
|
3150
|
+
const target = (0, import_node_path3.join)(folder, file.path);
|
|
3151
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(target), { recursive: true });
|
|
3152
|
+
(0, import_node_fs4.writeFileSync)(target, file.code);
|
|
3153
|
+
console.log(`${target}: ${file.modules.length} module${file.modules.length === 1 ? "" : "s"}`);
|
|
3154
|
+
}
|
|
3155
|
+
process.exit(result2.diagnostics.length ? 1 : 0);
|
|
2618
3156
|
}
|
|
2619
3157
|
main(entry).catch((error) => {
|
|
2620
3158
|
console.error(error instanceof Error ? error.message : error);
|