@tilua/compiler 4.0.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 +98 -9
- package/dist/{chunk-VZ7XC4BG.js → chunk-3J2QMA4W.js} +601 -114
- package/dist/cli.cjs +667 -158
- package/dist/cli.js +32 -9
- package/dist/index.cjs +623 -135
- 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 +12 -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) {
|
|
@@ -277,6 +349,10 @@ 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) {
|
|
@@ -377,7 +453,7 @@ var Lowerer = class {
|
|
|
377
453
|
const type = this.options.types?.typeOf.get(argument);
|
|
378
454
|
return {
|
|
379
455
|
type,
|
|
380
|
-
typeText: type ? (0,
|
|
456
|
+
typeText: type ? (0, import_parser5.formatType)(type) : void 0,
|
|
381
457
|
source: this.writtenSource(argument),
|
|
382
458
|
declaration: declaration ? this.writtenSource(declaration) : void 0,
|
|
383
459
|
spread: argument.type === "SpreadElement"
|
|
@@ -399,7 +475,11 @@ var Lowerer = class {
|
|
|
399
475
|
/** The language's own lowering, then each library's, in load order. A
|
|
400
476
|
* runtime is named by its lowering's place here (`loweringRuntime`). */
|
|
401
477
|
get lowerings() {
|
|
402
|
-
return this.allLowerings ??= [
|
|
478
|
+
return this.allLowerings ??= [
|
|
479
|
+
languageLowering,
|
|
480
|
+
...this.options.noLib ? [] : [standardLowering],
|
|
481
|
+
...this.options.lowerings ?? []
|
|
482
|
+
];
|
|
403
483
|
}
|
|
404
484
|
allLowerings;
|
|
405
485
|
/** What `receiver:method(...)` becomes, when Luau alone would not run it.
|
|
@@ -413,22 +493,28 @@ var Lowerer = class {
|
|
|
413
493
|
* (`console:log`), the last library loaded first. No answer leaves an
|
|
414
494
|
* ordinary Luau method call: `text:upper()` reaches Lua's own. */
|
|
415
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) {
|
|
416
503
|
const lowerings = this.lowerings;
|
|
417
|
-
const source = this.options.types?.methodSources.get(node);
|
|
418
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();
|
|
419
505
|
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(
|
|
506
|
+
const receiver = this.options.types?.typeOf.get(object2);
|
|
507
|
+
const receiverGlobal = this.globalName(object2);
|
|
508
|
+
const info = this.lazyArguments(args2);
|
|
423
509
|
for (const i of asked) {
|
|
424
|
-
const
|
|
425
|
-
if (!
|
|
426
|
-
const answer =
|
|
427
|
-
method
|
|
510
|
+
const plugin3 = lowerings[i].plugin;
|
|
511
|
+
if (!plugin3.methodCall) continue;
|
|
512
|
+
const answer = plugin3.methodCall({
|
|
513
|
+
method,
|
|
428
514
|
receiver,
|
|
429
515
|
receiverGlobal,
|
|
430
|
-
argumentCount:
|
|
431
|
-
at: this.callSite(
|
|
516
|
+
argumentCount: args2.length,
|
|
517
|
+
at: this.callSite(name),
|
|
432
518
|
get arguments() {
|
|
433
519
|
return info();
|
|
434
520
|
},
|
|
@@ -452,9 +538,9 @@ var Lowerer = class {
|
|
|
452
538
|
if (name === void 0) return void 0;
|
|
453
539
|
const info = this.lazyArguments(node.arguments);
|
|
454
540
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
455
|
-
const
|
|
456
|
-
if (!
|
|
457
|
-
const answer =
|
|
541
|
+
const plugin3 = lowerings[i].plugin;
|
|
542
|
+
if (!plugin3.globalCall) continue;
|
|
543
|
+
const answer = plugin3.globalCall({
|
|
458
544
|
name,
|
|
459
545
|
at: this.callSite(node.callee),
|
|
460
546
|
get arguments() {
|
|
@@ -506,6 +592,31 @@ var Lowerer = class {
|
|
|
506
592
|
const out2 = [];
|
|
507
593
|
const runtimes = this.runtimeDefinitions();
|
|
508
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
|
+
}
|
|
509
620
|
const marker = this.helpers.get("array");
|
|
510
621
|
const typeOf = this.helpers.get("typeof");
|
|
511
622
|
if (marker || typeOf) {
|
|
@@ -544,6 +655,49 @@ local function ${typeOf}(value, known)
|
|
|
544
655
|
end`).body.statements);
|
|
545
656
|
}
|
|
546
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
|
+
}
|
|
547
701
|
const assign2 = this.helpers.get("assign");
|
|
548
702
|
if (assign2) {
|
|
549
703
|
out2.push(localFunction(assign2, functionBody(["target"], [
|
|
@@ -565,7 +719,7 @@ end`).body.statements);
|
|
|
565
719
|
local(["result"], [this.array(table([]))]),
|
|
566
720
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
567
721
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
568
|
-
callStatement(call(
|
|
722
|
+
callStatement(call(this.move(), [
|
|
569
723
|
identifier("part"),
|
|
570
724
|
number(1),
|
|
571
725
|
unary("#", identifier("part")),
|
|
@@ -582,8 +736,8 @@ end`).body.statements);
|
|
|
582
736
|
runtimeDefinitions() {
|
|
583
737
|
const out2 = [];
|
|
584
738
|
for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
|
|
585
|
-
const { plugin:
|
|
586
|
-
const source =
|
|
739
|
+
const { plugin: plugin3, from } = this.lowerings[index2];
|
|
740
|
+
const source = plugin3.runtime?.[runtime2];
|
|
587
741
|
if (source === void 0) {
|
|
588
742
|
this.diagnostics.push({
|
|
589
743
|
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
@@ -616,7 +770,7 @@ end`).body.statements);
|
|
|
616
770
|
const requires = [];
|
|
617
771
|
const moduleLocals = /* @__PURE__ */ new Map();
|
|
618
772
|
const importedFrom = /* @__PURE__ */ new Map();
|
|
619
|
-
const
|
|
773
|
+
const resolve4 = (source) => {
|
|
620
774
|
const key = context.resolve(source.value);
|
|
621
775
|
if (key === void 0) this.report(source, `Cannot find module '${source.value}'`);
|
|
622
776
|
return key;
|
|
@@ -655,7 +809,7 @@ end`).body.statements);
|
|
|
655
809
|
];
|
|
656
810
|
const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references.length ?? 0) > 0);
|
|
657
811
|
if (!used.length) continue;
|
|
658
|
-
const key =
|
|
812
|
+
const key = resolve4(statement.source);
|
|
659
813
|
if (key === void 0) continue;
|
|
660
814
|
const local2 = moduleLocal(key);
|
|
661
815
|
for (const s of used) {
|
|
@@ -678,8 +832,9 @@ end`).body.statements);
|
|
|
678
832
|
names.push("default");
|
|
679
833
|
break;
|
|
680
834
|
case "ExportNamedStatement": {
|
|
835
|
+
if (statement.typeOnly) break;
|
|
681
836
|
if (statement.source) {
|
|
682
|
-
const key =
|
|
837
|
+
const key = resolve4(statement.source);
|
|
683
838
|
if (key === void 0) break;
|
|
684
839
|
load(key);
|
|
685
840
|
for (const s of statement.specifiers) links.push({ name: s.exported.name, module: key, imported: s.local.name });
|
|
@@ -695,7 +850,7 @@ end`).body.statements);
|
|
|
695
850
|
break;
|
|
696
851
|
}
|
|
697
852
|
case "ExportAllStatement": {
|
|
698
|
-
const key =
|
|
853
|
+
const key = resolve4(statement.source);
|
|
699
854
|
if (key === void 0) break;
|
|
700
855
|
load(key);
|
|
701
856
|
stars.push(key);
|
|
@@ -761,6 +916,26 @@ end`).body.statements);
|
|
|
761
916
|
for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
|
|
762
917
|
return [...declarations, ...hoisted, ...requires, ...body];
|
|
763
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
|
+
}
|
|
764
939
|
isRewritten(node) {
|
|
765
940
|
const binding = this.bindingByDeclaration.get(node);
|
|
766
941
|
return binding !== void 0 && this.rewrites.has(binding.id);
|
|
@@ -907,7 +1082,7 @@ end`).body.statements);
|
|
|
907
1082
|
case "VariableDeclaration":
|
|
908
1083
|
return this.variableDeclaration(node, "declare");
|
|
909
1084
|
case "FunctionDeclaration":
|
|
910
|
-
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 }];
|
|
911
1086
|
case "FunctionDeclarationStatement":
|
|
912
1087
|
return [this.functionDeclarationStatement(node)];
|
|
913
1088
|
case "ClassDeclaration":
|
|
@@ -938,8 +1113,13 @@ end`).body.statements);
|
|
|
938
1113
|
// kept — Lua has no expression statement — and it cannot run
|
|
939
1114
|
// anything, since a call is a `CallStatement` instead, so dropping
|
|
940
1115
|
// it changes nothing but the noise.
|
|
941
|
-
case "ExpressionStatement":
|
|
942
|
-
|
|
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
|
+
}
|
|
943
1123
|
case "DoStatement":
|
|
944
1124
|
return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
|
|
945
1125
|
case "WhileStatement":
|
|
@@ -991,7 +1171,11 @@ end`).body.statements);
|
|
|
991
1171
|
let source = this.expression(node.iterator);
|
|
992
1172
|
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
993
1173
|
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
994
|
-
|
|
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
|
+
}
|
|
995
1179
|
return [genericFor(
|
|
996
1180
|
variables,
|
|
997
1181
|
iterators,
|
|
@@ -1244,7 +1428,8 @@ end
|
|
|
1244
1428
|
},
|
|
1245
1429
|
isMethod,
|
|
1246
1430
|
func,
|
|
1247
|
-
|
|
1431
|
+
// `@native` and the like are Luau's.
|
|
1432
|
+
attributes: this.lua51 ? void 0 : node.attributes,
|
|
1248
1433
|
...spanOf(node)
|
|
1249
1434
|
};
|
|
1250
1435
|
}
|
|
@@ -1345,7 +1530,7 @@ end
|
|
|
1345
1530
|
if (element) bind(element.value, index(source, number(i + 1)), element.default);
|
|
1346
1531
|
});
|
|
1347
1532
|
if (pattern.rest) {
|
|
1348
|
-
const elements = call(
|
|
1533
|
+
const elements = call(this.move(), [
|
|
1349
1534
|
source,
|
|
1350
1535
|
number(pattern.elements.length + 1),
|
|
1351
1536
|
unary("#", source),
|
|
@@ -1456,8 +1641,15 @@ end
|
|
|
1456
1641
|
case "ArrayExpression":
|
|
1457
1642
|
return this.arrayExpression(node);
|
|
1458
1643
|
case "BinaryExpression": {
|
|
1644
|
+
if (node.operator === "??") return this.nullish(node);
|
|
1459
1645
|
const left = this.expression(node.left);
|
|
1460
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
|
+
}
|
|
1461
1653
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1462
1654
|
return binary(node.operator, left, right);
|
|
1463
1655
|
}
|
|
@@ -1484,6 +1676,8 @@ end
|
|
|
1484
1676
|
}
|
|
1485
1677
|
case "ParenthesizedExpression":
|
|
1486
1678
|
return parenthesized(this.expression(node.expression));
|
|
1679
|
+
case "ImportExpression":
|
|
1680
|
+
return this.importExpression(node);
|
|
1487
1681
|
// Types only: the value is the expression itself.
|
|
1488
1682
|
case "TypeAssertionExpression":
|
|
1489
1683
|
case "SatisfiesExpression":
|
|
@@ -1537,7 +1731,7 @@ end
|
|
|
1537
1731
|
const last2 = list.length - 1;
|
|
1538
1732
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1539
1733
|
}
|
|
1540
|
-
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]);
|
|
1541
1735
|
const last = list[list.length - 1];
|
|
1542
1736
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1543
1737
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -1551,11 +1745,21 @@ end
|
|
|
1551
1745
|
/** One link of an access chain, read from `object`. */
|
|
1552
1746
|
link(node, object2) {
|
|
1553
1747
|
switch (node.type) {
|
|
1554
|
-
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
|
+
}
|
|
1555
1756
|
return member(object2, node.property.name);
|
|
1757
|
+
}
|
|
1556
1758
|
case "IndexExpression":
|
|
1557
1759
|
return index(object2, this.expression(node.index));
|
|
1558
1760
|
case "CallExpression": {
|
|
1761
|
+
const method = this.methodCalled(node);
|
|
1762
|
+
if (method) return method;
|
|
1559
1763
|
const lowered = this.loweredGlobalCall(node);
|
|
1560
1764
|
const args2 = this.arguments(node.arguments);
|
|
1561
1765
|
if (!lowered) return call(object2, args2);
|
|
@@ -1578,6 +1782,24 @@ end
|
|
|
1578
1782
|
}
|
|
1579
1783
|
}
|
|
1580
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
|
+
}
|
|
1581
1803
|
// --------------------------------------------------------
|
|
1582
1804
|
// Optional chains
|
|
1583
1805
|
// --------------------------------------------------------
|
|
@@ -1623,7 +1845,7 @@ end
|
|
|
1623
1845
|
const segments = this.segments(chain);
|
|
1624
1846
|
const base = this.expression(chain.base);
|
|
1625
1847
|
const last = chain.links[chain.links.length - 1];
|
|
1626
|
-
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") {
|
|
1627
1849
|
return {
|
|
1628
1850
|
type: "IfElseExpression",
|
|
1629
1851
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -1644,6 +1866,32 @@ end
|
|
|
1644
1866
|
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1645
1867
|
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1646
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
|
+
}
|
|
1647
1895
|
/** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
|
|
1648
1896
|
interpolatedString(node) {
|
|
1649
1897
|
let format = "";
|
|
@@ -1725,12 +1973,12 @@ end
|
|
|
1725
1973
|
}));
|
|
1726
1974
|
const alternate = this.expression(node.alternate);
|
|
1727
1975
|
if (clauses.every((c) => alwaysTruthy(c.body))) {
|
|
1728
|
-
let
|
|
1976
|
+
let result2 = alternate;
|
|
1729
1977
|
for (let i = clauses.length - 1; i >= 0; i--) {
|
|
1730
1978
|
const { condition, body } = clauses[i];
|
|
1731
|
-
|
|
1979
|
+
result2 = binary("or", binary("and", condition, body), result2);
|
|
1732
1980
|
}
|
|
1733
|
-
return parenthesized(
|
|
1981
|
+
return parenthesized(result2);
|
|
1734
1982
|
}
|
|
1735
1983
|
const statement = {
|
|
1736
1984
|
type: "IfStatement",
|
|
@@ -1787,11 +2035,20 @@ end
|
|
|
1787
2035
|
current = [];
|
|
1788
2036
|
parts.push(this.expression(f.argument));
|
|
1789
2037
|
break;
|
|
2038
|
+
case "TableFieldAccessor":
|
|
2039
|
+
break;
|
|
1790
2040
|
}
|
|
1791
2041
|
}
|
|
1792
|
-
|
|
1793
|
-
if (
|
|
1794
|
-
|
|
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")]);
|
|
1795
2052
|
}
|
|
1796
2053
|
/** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
|
|
1797
2054
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
@@ -2048,7 +2305,7 @@ var CLASS_METAMETHODS = [
|
|
|
2048
2305
|
];
|
|
2049
2306
|
|
|
2050
2307
|
// src/linemap.ts
|
|
2051
|
-
var
|
|
2308
|
+
var import_parser6 = require("@tilua/parser");
|
|
2052
2309
|
function statementsOf(node) {
|
|
2053
2310
|
const out2 = [];
|
|
2054
2311
|
const visit = (value) => {
|
|
@@ -2081,7 +2338,7 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
2081
2338
|
}
|
|
2082
2339
|
function lineMapSource(map) {
|
|
2083
2340
|
const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2084
|
-
const quoted = (s) => (0,
|
|
2341
|
+
const quoted = (s) => (0, import_parser6.luauString)(s.replace(/\\/g, "/"));
|
|
2085
2342
|
return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
|
|
2086
2343
|
}
|
|
2087
2344
|
|
|
@@ -2093,15 +2350,15 @@ async function loadLowerings(modules) {
|
|
|
2093
2350
|
for (const module2 of modules) {
|
|
2094
2351
|
try {
|
|
2095
2352
|
const loaded = await import((0, import_node_url.pathToFileURL)(module2.file).href);
|
|
2096
|
-
const
|
|
2097
|
-
if (!
|
|
2353
|
+
const plugin3 = loaded.default;
|
|
2354
|
+
if (!plugin3 || typeof plugin3 !== "object") {
|
|
2098
2355
|
problems.push({
|
|
2099
2356
|
file: module2.file,
|
|
2100
2357
|
message: `'${module2.from}' lowering module has no default export`
|
|
2101
2358
|
});
|
|
2102
2359
|
continue;
|
|
2103
2360
|
}
|
|
2104
|
-
lowerings.push({ plugin:
|
|
2361
|
+
lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
|
|
2105
2362
|
} catch (error) {
|
|
2106
2363
|
problems.push({
|
|
2107
2364
|
file: module2.file,
|
|
@@ -2128,16 +2385,17 @@ async function bundle(options) {
|
|
|
2128
2385
|
const file = queue.shift();
|
|
2129
2386
|
if (sources.has(file)) continue;
|
|
2130
2387
|
const parsed = parseFile(file, diagnostics, directives);
|
|
2131
|
-
if (!parsed) return { modules: [], diagnostics };
|
|
2132
|
-
const { program:
|
|
2133
|
-
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 });
|
|
2134
2391
|
for (const d of scopes.diagnostics) {
|
|
2135
2392
|
diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
|
|
2136
2393
|
}
|
|
2137
|
-
sources.set(file, { file, name: nameOf(file), text, program:
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
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);
|
|
2141
2399
|
}
|
|
2142
2400
|
}
|
|
2143
2401
|
const analysis = analyzeModules([...sources.values()], config2);
|
|
@@ -2149,6 +2407,9 @@ async function bundle(options) {
|
|
|
2149
2407
|
const names = Names.from(...[...sources.values()].map((s) => s.program));
|
|
2150
2408
|
const G = names.fresh("G");
|
|
2151
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";
|
|
2152
2413
|
const modules = /* @__PURE__ */ new Map();
|
|
2153
2414
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
2154
2415
|
const pending = [entry2];
|
|
@@ -2157,104 +2418,228 @@ async function bundle(options) {
|
|
|
2157
2418
|
if (modules.has(file)) continue;
|
|
2158
2419
|
const source = sources.get(file);
|
|
2159
2420
|
const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
|
|
2160
|
-
const
|
|
2421
|
+
const imports = /* @__PURE__ */ new Set();
|
|
2422
|
+
const dynamicImports = /* @__PURE__ */ new Set();
|
|
2423
|
+
const lowered2 = lower(source.program, source.scopes, {
|
|
2161
2424
|
names,
|
|
2162
2425
|
types: analysis.types.get(file),
|
|
2163
2426
|
lowerings,
|
|
2164
|
-
target
|
|
2427
|
+
target,
|
|
2428
|
+
noLib: config2?.noLib,
|
|
2165
2429
|
source: source.text,
|
|
2166
2430
|
file: shown,
|
|
2167
2431
|
module: {
|
|
2168
2432
|
name: source.name,
|
|
2169
2433
|
require: requireExpression,
|
|
2170
|
-
|
|
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)}]`,
|
|
2171
2438
|
fail: `${G}.fail`,
|
|
2172
2439
|
arrays: `${G}.arrays`,
|
|
2173
|
-
resolve: (specifier) => {
|
|
2174
|
-
const
|
|
2175
|
-
if (!
|
|
2176
|
-
pending.push(
|
|
2177
|
-
|
|
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;
|
|
2178
2447
|
}
|
|
2179
2448
|
}
|
|
2180
2449
|
});
|
|
2181
|
-
for (const d of
|
|
2182
|
-
for (const statement of allStatements(
|
|
2183
|
-
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);
|
|
2184
2453
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
2185
2454
|
}
|
|
2186
2455
|
modules.set(file, {
|
|
2187
2456
|
name: source.name,
|
|
2188
|
-
statements:
|
|
2189
|
-
exportsName:
|
|
2190
|
-
info:
|
|
2191
|
-
usesScriptArgs:
|
|
2457
|
+
statements: lowered2.statements,
|
|
2458
|
+
exportsName: lowered2.exportsName,
|
|
2459
|
+
info: lowered2.module,
|
|
2460
|
+
usesScriptArgs: lowered2.usesScriptArgs,
|
|
2461
|
+
imports,
|
|
2462
|
+
dynamicImports
|
|
2192
2463
|
});
|
|
2193
2464
|
}
|
|
2194
2465
|
for (const [file, fileDirectives] of directives) {
|
|
2195
2466
|
const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
|
|
2196
|
-
const { kept, unusedExpectErrors } = (0,
|
|
2467
|
+
const { kept, unusedExpectErrors } = (0, import_parser7.applyDirectives)(fileDirectives, semantic, (d) => d.line);
|
|
2197
2468
|
const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
|
|
2198
2469
|
const remaining = diagnostics.filter((d) => !suppressed.has(d));
|
|
2199
2470
|
diagnostics.length = 0;
|
|
2200
2471
|
diagnostics.push(...remaining);
|
|
2201
2472
|
if (options.typeCheck === false) continue;
|
|
2202
2473
|
for (const d of unusedExpectErrors) {
|
|
2203
|
-
diagnostics.push({ file, message:
|
|
2474
|
+
diagnostics.push({ file, message: import_parser7.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
|
|
2204
2475
|
}
|
|
2205
2476
|
}
|
|
2206
2477
|
const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2207
2478
|
const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2208
2479
|
diagnostics.length = 0;
|
|
2209
2480
|
diagnostics.push(...reported);
|
|
2210
|
-
const
|
|
2481
|
+
const lowered = [...modules.values()];
|
|
2482
|
+
const moduleNames = lowered.map((m) => m.name);
|
|
2211
2483
|
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
|
-
|
|
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);
|
|
2219
2504
|
}
|
|
2220
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;
|
|
2221
2506
|
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) });
|
|
2507
|
+
for (const module2 of layout[0].modules) {
|
|
2508
|
+
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
|
|
2235
2509
|
}
|
|
2236
2510
|
const ok = names.fresh("ok");
|
|
2237
|
-
const
|
|
2511
|
+
const result2 = names.fresh("result");
|
|
2238
2512
|
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
2239
|
-
local ${ok}, ${
|
|
2240
|
-
return ${G}.require(${(0,
|
|
2513
|
+
local ${ok}, ${result2} = xpcall(function()
|
|
2514
|
+
return ${G}.require(${(0, import_parser7.luauString)(sources.get(entry2).name)})
|
|
2241
2515
|
end, ${G}.fail)
|
|
2242
2516
|
if not ${ok} then
|
|
2243
|
-
error(${
|
|
2517
|
+
error(${result2}, 0)
|
|
2244
2518
|
end
|
|
2245
|
-
${modules.get(entry2).info.exports ? `return ${
|
|
2519
|
+
${modules.get(entry2).info.exports ? `return ${result2}` : ""}
|
|
2246
2520
|
`).body.statements;
|
|
2247
|
-
|
|
2248
|
-
const map = buildLineMap(
|
|
2249
|
-
if (map.size) {
|
|
2250
|
-
|
|
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(
|
|
2251
2525
|
[member(identifier(G), "lines")],
|
|
2252
2526
|
[(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
|
|
2253
|
-
)
|
|
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);
|
|
2254
2530
|
}
|
|
2255
|
-
|
|
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;
|
|
2256
2604
|
}
|
|
2257
|
-
function
|
|
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})`;
|
|
2639
|
+
}
|
|
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(", ");
|
|
2258
2643
|
return `
|
|
2259
2644
|
local ${G}
|
|
2260
2645
|
${G} = {
|
|
@@ -2302,7 +2687,7 @@ ${G} = {
|
|
|
2302
2687
|
return message
|
|
2303
2688
|
end
|
|
2304
2689
|
local lines = ${G}.lines
|
|
2305
|
-
if lines == nil then
|
|
2690
|
+
if lines == nil ${several ? `and #${G}.sources == 0 ` : ""}then
|
|
2306
2691
|
return message .. (trace or "")
|
|
2307
2692
|
end
|
|
2308
2693
|
-- The bundle's name, as a position in a message spells it: where this
|
|
@@ -2322,6 +2707,81 @@ ${G} = {
|
|
|
2322
2707
|
chunk = info.short_src
|
|
2323
2708
|
end
|
|
2324
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` : `
|
|
2325
2785
|
if chunk == nil then
|
|
2326
2786
|
chunk = message:match("^(.-):%d+:")
|
|
2327
2787
|
end
|
|
@@ -2343,8 +2803,9 @@ ${G} = {
|
|
|
2343
2803
|
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2344
2804
|
local line = frame:match(prefix .. ":(%d+)")
|
|
2345
2805
|
local at = line ~= nil and place(line) or nil
|
|
2346
|
-
if at ~= nil then
|
|
2347
|
-
|
|
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 '([^']+)'")
|
|
2348
2809
|
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2349
2810
|
end
|
|
2350
2811
|
end
|
|
@@ -2353,13 +2814,36 @@ ${G} = {
|
|
|
2353
2814
|
end
|
|
2354
2815
|
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2355
2816
|
end,
|
|
2356
|
-
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,` : ""}
|
|
2357
2837
|
require = function(name)
|
|
2358
2838
|
local record = ${G}.records[name]
|
|
2359
2839
|
if record ~= nil then
|
|
2360
2840
|
return record.exports
|
|
2361
2841
|
end
|
|
2362
|
-
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` : ""}
|
|
2363
2847
|
local initialized = {}
|
|
2364
2848
|
local exports = setmetatable({}, {
|
|
2365
2849
|
__index = function(_, key)
|
|
@@ -2378,7 +2862,7 @@ ${G} = {
|
|
|
2378
2862
|
return source[link[2]]
|
|
2379
2863
|
end
|
|
2380
2864
|
if module.stars ~= nil and key ~= "default" then
|
|
2381
|
-
for _, star in module.stars do
|
|
2865
|
+
for _, star in ipairs(module.stars) do
|
|
2382
2866
|
local value = ${G}.require(star)[key]
|
|
2383
2867
|
if value ~= nil then
|
|
2384
2868
|
return value
|
|
@@ -2403,7 +2887,7 @@ ${G} = {
|
|
|
2403
2887
|
}
|
|
2404
2888
|
function stampOf(file) {
|
|
2405
2889
|
try {
|
|
2406
|
-
const at = (0,
|
|
2890
|
+
const at = (0, import_node_fs3.statSync)(file);
|
|
2407
2891
|
return `${at.size}:${at.mtimeMs}`;
|
|
2408
2892
|
} catch {
|
|
2409
2893
|
return void 0;
|
|
@@ -2419,19 +2903,19 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2419
2903
|
}
|
|
2420
2904
|
let text;
|
|
2421
2905
|
try {
|
|
2422
|
-
text = (0,
|
|
2906
|
+
text = (0, import_node_fs3.readFileSync)(file, "utf8");
|
|
2423
2907
|
} catch {
|
|
2424
2908
|
diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
|
|
2425
2909
|
return void 0;
|
|
2426
2910
|
}
|
|
2427
2911
|
try {
|
|
2428
|
-
const
|
|
2429
|
-
const found = (0,
|
|
2912
|
+
const program2 = (0, import_parser7.parse)(text);
|
|
2913
|
+
const found = (0, import_parser7.directivesOf)(text);
|
|
2430
2914
|
directives.set(file, found);
|
|
2431
|
-
if (stamp !== void 0) parsedFiles.set(file, { stamp, program, text, directives: found });
|
|
2432
|
-
return { program, text };
|
|
2915
|
+
if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
|
|
2916
|
+
return { program: program2, text };
|
|
2433
2917
|
} catch (error) {
|
|
2434
|
-
if (error instanceof
|
|
2918
|
+
if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
|
|
2435
2919
|
const { line, column } = error;
|
|
2436
2920
|
diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
|
|
2437
2921
|
return void 0;
|
|
@@ -2439,31 +2923,33 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2439
2923
|
throw error;
|
|
2440
2924
|
}
|
|
2441
2925
|
}
|
|
2442
|
-
function importedSpecifiers(
|
|
2443
|
-
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] : []);
|
|
2444
2928
|
}
|
|
2445
2929
|
var libraryCache = /* @__PURE__ */ new Map();
|
|
2446
2930
|
function parsedLibrary(file) {
|
|
2447
2931
|
let stamp;
|
|
2448
2932
|
try {
|
|
2449
|
-
const at = (0,
|
|
2933
|
+
const at = (0, import_node_fs3.statSync)(file);
|
|
2450
2934
|
stamp = `${at.size}:${at.mtimeMs}`;
|
|
2451
2935
|
} catch {
|
|
2452
2936
|
stamp = "";
|
|
2453
2937
|
}
|
|
2454
2938
|
const cached = libraryCache.get(file);
|
|
2455
2939
|
if (cached && cached.stamp === stamp) return cached.program;
|
|
2456
|
-
const
|
|
2457
|
-
libraryCache.set(file, { stamp, program });
|
|
2458
|
-
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;
|
|
2459
2943
|
}
|
|
2460
2944
|
var moduleAnalyses = /* @__PURE__ */ new Map();
|
|
2461
2945
|
function analyzeModules(modules, config2) {
|
|
2462
2946
|
const out2 = [];
|
|
2463
2947
|
const analyses = /* @__PURE__ */ new Map();
|
|
2464
|
-
const libraries = config2 ? (0,
|
|
2948
|
+
const libraries = config2 ? (0, import_parser7.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
|
|
2465
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" });
|
|
2466
2950
|
const libs = libraries.files.map(parsedLibrary);
|
|
2951
|
+
const noLib = config2?.noLib ?? false;
|
|
2952
|
+
const checked = libs.length > 0 || !noLib;
|
|
2467
2953
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
2468
2954
|
const byFile = new Map(modules.map((m) => [m.file, m]));
|
|
2469
2955
|
const exportsCache = /* @__PURE__ */ new Map();
|
|
@@ -2472,53 +2958,54 @@ function analyzeModules(modules, config2) {
|
|
|
2472
2958
|
let partials = 0;
|
|
2473
2959
|
const readWhile = /* @__PURE__ */ new Map();
|
|
2474
2960
|
const settingFor = `${libraries.files.join("|")}#${config2?.directory ?? ""}#${JSON.stringify(config2?.paths ?? {})}`;
|
|
2475
|
-
const analyzeModule = (file,
|
|
2961
|
+
const analyzeModule = (file, program2) => {
|
|
2476
2962
|
const done = analyzed.get(file);
|
|
2477
2963
|
if (done) return done;
|
|
2478
2964
|
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 ===
|
|
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)) {
|
|
2480
2966
|
analyzed.set(file, remembered.result);
|
|
2481
2967
|
readWhile.set(file, remembered.read);
|
|
2482
2968
|
return remembered.result;
|
|
2483
2969
|
}
|
|
2484
|
-
const scopes = (0,
|
|
2970
|
+
const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: checked, noLib });
|
|
2485
2971
|
const said = [];
|
|
2486
2972
|
for (const d of scopes.diagnostics) {
|
|
2487
2973
|
if (d.kind !== "undeclared") continue;
|
|
2488
2974
|
said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
|
|
2489
2975
|
}
|
|
2490
2976
|
const before = partials;
|
|
2491
|
-
const types = (0,
|
|
2977
|
+
const types = (0, import_parser7.analyzeTypes)(program2, scopes, {
|
|
2492
2978
|
libs,
|
|
2493
2979
|
resolveModule: resolverFor(file),
|
|
2494
|
-
|
|
2980
|
+
noLib,
|
|
2981
|
+
reportUnknownTypes: checked
|
|
2495
2982
|
});
|
|
2496
2983
|
for (const d of types.diagnostics) {
|
|
2497
2984
|
const at = d.node;
|
|
2498
2985
|
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
2499
2986
|
}
|
|
2500
|
-
const
|
|
2987
|
+
const result2 = { types, diagnostics: said };
|
|
2501
2988
|
if (partials === before) {
|
|
2502
|
-
analyzed.set(file,
|
|
2503
|
-
const
|
|
2989
|
+
analyzed.set(file, result2);
|
|
2990
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
2504
2991
|
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:
|
|
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 });
|
|
2509
2996
|
}
|
|
2510
2997
|
}
|
|
2511
|
-
return
|
|
2998
|
+
return result2;
|
|
2512
2999
|
};
|
|
2513
3000
|
const resolverFor = (file) => (specifier) => {
|
|
2514
|
-
const target = (0,
|
|
3001
|
+
const target = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
2515
3002
|
if (!target) return void 0;
|
|
2516
3003
|
const noteRead = () => {
|
|
2517
|
-
const
|
|
2518
|
-
readWhile.set(file,
|
|
3004
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
3005
|
+
readWhile.set(file, read3);
|
|
2519
3006
|
const stamp = stampOf(target);
|
|
2520
|
-
if (stamp !== void 0)
|
|
2521
|
-
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);
|
|
2522
3009
|
};
|
|
2523
3010
|
if (inProgress.has(target)) {
|
|
2524
3011
|
partials++;
|
|
@@ -2529,19 +3016,19 @@ function analyzeModules(modules, config2) {
|
|
|
2529
3016
|
noteRead();
|
|
2530
3017
|
return cached;
|
|
2531
3018
|
}
|
|
2532
|
-
let
|
|
2533
|
-
if (!
|
|
3019
|
+
let program2 = byFile.get(target)?.program;
|
|
3020
|
+
if (!program2) {
|
|
2534
3021
|
try {
|
|
2535
|
-
|
|
3022
|
+
program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(target, "utf8"));
|
|
2536
3023
|
} catch {
|
|
2537
3024
|
return void 0;
|
|
2538
3025
|
}
|
|
2539
3026
|
}
|
|
2540
3027
|
inProgress.add(target);
|
|
2541
3028
|
try {
|
|
2542
|
-
const scopes = (0,
|
|
2543
|
-
const types = byFile.has(target) ? analyzeModule(target,
|
|
2544
|
-
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));
|
|
2545
3032
|
exportsCache.set(target, exports2);
|
|
2546
3033
|
noteRead();
|
|
2547
3034
|
return exports2;
|
|
@@ -2574,13 +3061,14 @@ function allStatements(statements) {
|
|
|
2574
3061
|
}
|
|
2575
3062
|
|
|
2576
3063
|
// src/cli.ts
|
|
2577
|
-
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]";
|
|
2578
3065
|
var args = process.argv.slice(2);
|
|
2579
3066
|
var entry;
|
|
2580
3067
|
var out;
|
|
2581
3068
|
var config;
|
|
2582
3069
|
var typeCheck = true;
|
|
2583
3070
|
var buildTarget;
|
|
3071
|
+
var output;
|
|
2584
3072
|
for (let i = 0; i < args.length; i++) {
|
|
2585
3073
|
const arg = args[i];
|
|
2586
3074
|
if (arg === "--out") out = args[++i];
|
|
@@ -2593,6 +3081,13 @@ for (let i = 0; i < args.length; i++) {
|
|
|
2593
3081
|
process.exit(2);
|
|
2594
3082
|
}
|
|
2595
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;
|
|
2596
3091
|
} else if (arg === "--help" || arg === "-h") {
|
|
2597
3092
|
console.log(usage);
|
|
2598
3093
|
process.exit(0);
|
|
@@ -2603,18 +3098,32 @@ if (!entry) {
|
|
|
2603
3098
|
process.exit(2);
|
|
2604
3099
|
}
|
|
2605
3100
|
async function main(from) {
|
|
2606
|
-
const
|
|
2607
|
-
|
|
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) {
|
|
2608
3114
|
console.error(`${(0, import_node_path3.relative)(process.cwd(), d.file)}:${d.line}:${d.column} ${d.message}`);
|
|
2609
3115
|
}
|
|
2610
|
-
if (
|
|
3116
|
+
if (result2.code === void 0) {
|
|
2611
3117
|
console.error("no bundle written");
|
|
2612
3118
|
process.exit(1);
|
|
2613
3119
|
}
|
|
2614
|
-
const
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
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);
|
|
2618
3127
|
}
|
|
2619
3128
|
main(entry).catch((error) => {
|
|
2620
3129
|
console.error(error instanceof Error ? error.message : error);
|