@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
|
@@ -172,6 +172,77 @@ var plugin = {
|
|
|
172
172
|
};
|
|
173
173
|
var languageLowering = { plugin, from: "tilua" };
|
|
174
174
|
|
|
175
|
+
// src/standard.ts
|
|
176
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
177
|
+
import { isFunctionType, luauString as luauString2 } from "@tilua/parser";
|
|
178
|
+
var read2 = (name) => readFileSync2(new URL(`../runtime/${name}`, import.meta.url), "utf8");
|
|
179
|
+
var console = read2("console.luau");
|
|
180
|
+
var result = read2("result.luau");
|
|
181
|
+
var place = (at) => luauString2(at.file === void 0 ? `line ${at.line}` : `${at.file}:${at.line}`);
|
|
182
|
+
var tag = (at) => luauString2(`[${at.file === void 0 ? `line ${at.line}` : `${at.file.replace(/\.tilua$/, "")}:${at.line}`}]:`);
|
|
183
|
+
function consoleMeta(args) {
|
|
184
|
+
const fields = [];
|
|
185
|
+
args.forEach((argument, index2) => {
|
|
186
|
+
if (argument.spread || argument.typeText === void 0 || !isFunctionType(argument.type)) return;
|
|
187
|
+
const written = argument.declaration ?? argument.source;
|
|
188
|
+
fields.push(`[${index2 + 1}] = { ${luauString2(argument.typeText)}, ${luauString2(written)} }`);
|
|
189
|
+
});
|
|
190
|
+
return fields.length ? `{ ${fields.join(", ")} }` : "nil";
|
|
191
|
+
}
|
|
192
|
+
var CONSOLE_METHODS = /* @__PURE__ */ new Set(["log", "warn", "error"]);
|
|
193
|
+
var REPLACED = /* @__PURE__ */ new Map([
|
|
194
|
+
["pcall", "pcall"],
|
|
195
|
+
["xpcall", "xpcall"],
|
|
196
|
+
["ypcall", "pcall"],
|
|
197
|
+
["coroutine.resume", "resume"],
|
|
198
|
+
["string.find", "find"],
|
|
199
|
+
["string.match", "match"],
|
|
200
|
+
["string.gsub", "gsub"],
|
|
201
|
+
["string.byte", "byte"],
|
|
202
|
+
["math.modf", "modf"],
|
|
203
|
+
["math.frexp", "frexp"],
|
|
204
|
+
["next", "next"],
|
|
205
|
+
["select", "select"],
|
|
206
|
+
["unpack", "unpack"],
|
|
207
|
+
["table.unpack", "unpack"],
|
|
208
|
+
["loadstring", "loadstring"],
|
|
209
|
+
["pairs", "pairs"],
|
|
210
|
+
["ipairs", "ipairs"],
|
|
211
|
+
["string.gmatch", "gmatch"]
|
|
212
|
+
]);
|
|
213
|
+
var plugin2 = {
|
|
214
|
+
runtime: { console, result },
|
|
215
|
+
methodCall({ method, receiverGlobal, at, arguments: args, use }) {
|
|
216
|
+
if (receiverGlobal === "console" && CONSOLE_METHODS.has(method)) {
|
|
217
|
+
return {
|
|
218
|
+
callee: `${use("console")}.${method}`,
|
|
219
|
+
prepend: [tag(at), consoleMeta(args)],
|
|
220
|
+
passReceiver: false
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return void 0;
|
|
224
|
+
},
|
|
225
|
+
// `print(x)` and `warn(x)` are left exactly as written: the place a line
|
|
226
|
+
// came from is what `console:log` / `console:warn` are for. `error` has to
|
|
227
|
+
// put the place inside the message it raises, so it goes through the
|
|
228
|
+
// runtime.
|
|
229
|
+
globalCall({ name, at, use }) {
|
|
230
|
+
if (name === "error") return { callee: `${use("console")}.raise`, prepend: [place(at)] };
|
|
231
|
+
const replaced = REPLACED.get(name);
|
|
232
|
+
if (replaced) return { callee: `${use("result")}.${replaced}` };
|
|
233
|
+
return void 0;
|
|
234
|
+
},
|
|
235
|
+
// Read as a value there is no call site to write down; the runtime finds
|
|
236
|
+
// one on the stack.
|
|
237
|
+
globalValue({ name, use }) {
|
|
238
|
+
if (name === "error") return `${use("console")}.raiseValue`;
|
|
239
|
+
const replaced = REPLACED.get(name);
|
|
240
|
+
if (replaced) return `${use("result")}.${replaced}`;
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
|
|
245
|
+
|
|
175
246
|
// src/lower.ts
|
|
176
247
|
function lower(program2, scopes, options = {}) {
|
|
177
248
|
return new Lowerer(program2, scopes, options).run();
|
|
@@ -261,6 +332,10 @@ var Lowerer = class {
|
|
|
261
332
|
}
|
|
262
333
|
return identifier(local2);
|
|
263
334
|
}
|
|
335
|
+
/** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
|
|
336
|
+
move() {
|
|
337
|
+
return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
|
|
338
|
+
}
|
|
264
339
|
helper(kind) {
|
|
265
340
|
let name = this.helpers.get(kind);
|
|
266
341
|
if (!name) {
|
|
@@ -383,7 +458,11 @@ var Lowerer = class {
|
|
|
383
458
|
/** The language's own lowering, then each library's, in load order. A
|
|
384
459
|
* runtime is named by its lowering's place here (`loweringRuntime`). */
|
|
385
460
|
get lowerings() {
|
|
386
|
-
return this.allLowerings ??= [
|
|
461
|
+
return this.allLowerings ??= [
|
|
462
|
+
languageLowering,
|
|
463
|
+
...this.options.noLib ? [] : [standardLowering],
|
|
464
|
+
...this.options.lowerings ?? []
|
|
465
|
+
];
|
|
387
466
|
}
|
|
388
467
|
allLowerings;
|
|
389
468
|
/** What `receiver:method(...)` becomes, when Luau alone would not run it.
|
|
@@ -397,22 +476,28 @@ var Lowerer = class {
|
|
|
397
476
|
* (`console:log`), the last library loaded first. No answer leaves an
|
|
398
477
|
* ordinary Luau method call: `text:upper()` reaches Lua's own. */
|
|
399
478
|
loweredMethodCall(node) {
|
|
479
|
+
return this.loweredMethod(node.method.name, this.options.types?.methodSources.get(node), node.object, node.arguments, node.method, node);
|
|
480
|
+
}
|
|
481
|
+
/** `loweredMethodCall`, for a method named `method` of what `object`
|
|
482
|
+
* holds — called with `:` on it, or read off it with `.` and handed its
|
|
483
|
+
* `self` like any other argument. `args` are the arguments after the
|
|
484
|
+
* receiver; a read that is not called has none. */
|
|
485
|
+
loweredMethod(method, source, object2, args, name, node) {
|
|
400
486
|
const lowerings = this.lowerings;
|
|
401
|
-
const source = this.options.types?.methodSources.get(node);
|
|
402
487
|
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();
|
|
403
488
|
if (!asked.some((i) => lowerings[i].plugin.methodCall)) return void 0;
|
|
404
|
-
const receiver = this.options.types?.typeOf.get(
|
|
405
|
-
const receiverGlobal = this.globalName(
|
|
406
|
-
const info = this.lazyArguments(
|
|
489
|
+
const receiver = this.options.types?.typeOf.get(object2);
|
|
490
|
+
const receiverGlobal = this.globalName(object2);
|
|
491
|
+
const info = this.lazyArguments(args);
|
|
407
492
|
for (const i of asked) {
|
|
408
|
-
const
|
|
409
|
-
if (!
|
|
410
|
-
const answer =
|
|
411
|
-
method
|
|
493
|
+
const plugin3 = lowerings[i].plugin;
|
|
494
|
+
if (!plugin3.methodCall) continue;
|
|
495
|
+
const answer = plugin3.methodCall({
|
|
496
|
+
method,
|
|
412
497
|
receiver,
|
|
413
498
|
receiverGlobal,
|
|
414
|
-
argumentCount:
|
|
415
|
-
at: this.callSite(
|
|
499
|
+
argumentCount: args.length,
|
|
500
|
+
at: this.callSite(name),
|
|
416
501
|
get arguments() {
|
|
417
502
|
return info();
|
|
418
503
|
},
|
|
@@ -436,9 +521,9 @@ var Lowerer = class {
|
|
|
436
521
|
if (name === void 0) return void 0;
|
|
437
522
|
const info = this.lazyArguments(node.arguments);
|
|
438
523
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
439
|
-
const
|
|
440
|
-
if (!
|
|
441
|
-
const answer =
|
|
524
|
+
const plugin3 = lowerings[i].plugin;
|
|
525
|
+
if (!plugin3.globalCall) continue;
|
|
526
|
+
const answer = plugin3.globalCall({
|
|
442
527
|
name,
|
|
443
528
|
at: this.callSite(node.callee),
|
|
444
529
|
get arguments() {
|
|
@@ -490,6 +575,31 @@ var Lowerer = class {
|
|
|
490
575
|
const out = [];
|
|
491
576
|
const runtimes = this.runtimeDefinitions();
|
|
492
577
|
if (this.helpers.has("concat")) this.helper("array");
|
|
578
|
+
if (this.helpers.has("concat")) this.move();
|
|
579
|
+
const move = this.helpers.get("move");
|
|
580
|
+
if (move) {
|
|
581
|
+
out.push(...parseLuau(`
|
|
582
|
+
local function ${move}(from, first, last, at, to)
|
|
583
|
+
for i = first, last do
|
|
584
|
+
to[at + i - first] = from[i]
|
|
585
|
+
end
|
|
586
|
+
return to
|
|
587
|
+
end`).body.statements);
|
|
588
|
+
}
|
|
589
|
+
const each = this.helpers.get("each");
|
|
590
|
+
if (each) {
|
|
591
|
+
out.push(...parseLuau(`
|
|
592
|
+
local function ${each}(source, ...)
|
|
593
|
+
if ${this.builtin("type").name}(source) == "function" then
|
|
594
|
+
return source, ...
|
|
595
|
+
end
|
|
596
|
+
local meta = ${this.builtin("getmetatable").name}(source)
|
|
597
|
+
if meta ~= nil and meta.__iter ~= nil then
|
|
598
|
+
return meta.__iter(source)
|
|
599
|
+
end
|
|
600
|
+
return ${this.builtin("next").name}, source
|
|
601
|
+
end`).body.statements);
|
|
602
|
+
}
|
|
493
603
|
const marker = this.helpers.get("array");
|
|
494
604
|
const typeOf = this.helpers.get("typeof");
|
|
495
605
|
if (marker || typeOf) {
|
|
@@ -528,6 +638,49 @@ local function ${typeOf}(value, known)
|
|
|
528
638
|
end`).body.statements);
|
|
529
639
|
}
|
|
530
640
|
}
|
|
641
|
+
const instanceOf = this.helpers.get("instanceof");
|
|
642
|
+
if (instanceOf) {
|
|
643
|
+
out.push(...parseLuau(`
|
|
644
|
+
local function ${instanceOf}(value, class)
|
|
645
|
+
if ${this.builtin("type").name}(value) ~= "table" then
|
|
646
|
+
return false
|
|
647
|
+
end
|
|
648
|
+
local current = value.ClassObject
|
|
649
|
+
while current ~= nil do
|
|
650
|
+
if current == class then
|
|
651
|
+
return true
|
|
652
|
+
end
|
|
653
|
+
current = current.ParentClass
|
|
654
|
+
end
|
|
655
|
+
return false
|
|
656
|
+
end`).body.statements);
|
|
657
|
+
}
|
|
658
|
+
const accessors = this.helpers.get("accessors");
|
|
659
|
+
if (accessors) {
|
|
660
|
+
out.push(...parseLuau(`
|
|
661
|
+
local function ${accessors}(target, getters, setters)
|
|
662
|
+
return ${this.builtin("setmetatable").name}(target, {
|
|
663
|
+
__index = function(self, key)
|
|
664
|
+
local get = getters[key]
|
|
665
|
+
if get ~= nil then
|
|
666
|
+
return get(self)
|
|
667
|
+
end
|
|
668
|
+
return nil
|
|
669
|
+
end,
|
|
670
|
+
__newindex = function(self, key, value)
|
|
671
|
+
local set = setters[key]
|
|
672
|
+
if set ~= nil then
|
|
673
|
+
set(self, value)
|
|
674
|
+
return
|
|
675
|
+
end
|
|
676
|
+
if getters[key] ~= nil then
|
|
677
|
+
${this.builtin("error").name}("Cannot assign to '" .. ${this.builtin("tostring").name}(key) .. "': it has a getter and no setter", 2)
|
|
678
|
+
end
|
|
679
|
+
${this.builtin("rawset").name}(self, key, value)
|
|
680
|
+
end,
|
|
681
|
+
})
|
|
682
|
+
end`).body.statements);
|
|
683
|
+
}
|
|
531
684
|
const assign2 = this.helpers.get("assign");
|
|
532
685
|
if (assign2) {
|
|
533
686
|
out.push(localFunction(assign2, functionBody(["target"], [
|
|
@@ -549,7 +702,7 @@ end`).body.statements);
|
|
|
549
702
|
local(["result"], [this.array(table([]))]),
|
|
550
703
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
551
704
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
552
|
-
callStatement(call(
|
|
705
|
+
callStatement(call(this.move(), [
|
|
553
706
|
identifier("part"),
|
|
554
707
|
number(1),
|
|
555
708
|
unary("#", identifier("part")),
|
|
@@ -566,8 +719,8 @@ end`).body.statements);
|
|
|
566
719
|
runtimeDefinitions() {
|
|
567
720
|
const out = [];
|
|
568
721
|
for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
|
|
569
|
-
const { plugin:
|
|
570
|
-
const source =
|
|
722
|
+
const { plugin: plugin3, from } = this.lowerings[index2];
|
|
723
|
+
const source = plugin3.runtime?.[runtime2];
|
|
571
724
|
if (source === void 0) {
|
|
572
725
|
this.diagnostics.push({
|
|
573
726
|
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
@@ -662,6 +815,7 @@ end`).body.statements);
|
|
|
662
815
|
names.push("default");
|
|
663
816
|
break;
|
|
664
817
|
case "ExportNamedStatement": {
|
|
818
|
+
if (statement.typeOnly) break;
|
|
665
819
|
if (statement.source) {
|
|
666
820
|
const key = resolve3(statement.source);
|
|
667
821
|
if (key === void 0) break;
|
|
@@ -745,6 +899,26 @@ end`).body.statements);
|
|
|
745
899
|
for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
|
|
746
900
|
return [...declarations, ...hoisted, ...requires, ...body];
|
|
747
901
|
}
|
|
902
|
+
/** `import("./x")`: the bundle's `import`, called with the module's name.
|
|
903
|
+
* It answers the module's exports table — what `import * as` names. */
|
|
904
|
+
importExpression(node) {
|
|
905
|
+
const context = this.options.module;
|
|
906
|
+
if (!context) {
|
|
907
|
+
this.report(node, "import() needs a bundle: build the project with @tilua/compiler");
|
|
908
|
+
return nil();
|
|
909
|
+
}
|
|
910
|
+
const source = node.source;
|
|
911
|
+
if (source.type !== "StringLiteral") {
|
|
912
|
+
this.report(source, "import() takes a string literal: which module it loads is decided when the project is built");
|
|
913
|
+
return nil();
|
|
914
|
+
}
|
|
915
|
+
const key = context.resolve(source.value, true);
|
|
916
|
+
if (key === void 0) {
|
|
917
|
+
this.report(source, `Cannot find module '${source.value}'`);
|
|
918
|
+
return nil();
|
|
919
|
+
}
|
|
920
|
+
return call(context.import ?? context.require, [string(key)]);
|
|
921
|
+
}
|
|
748
922
|
isRewritten(node) {
|
|
749
923
|
const binding = this.bindingByDeclaration.get(node);
|
|
750
924
|
return binding !== void 0 && this.rewrites.has(binding.id);
|
|
@@ -891,7 +1065,7 @@ end`).body.statements);
|
|
|
891
1065
|
case "VariableDeclaration":
|
|
892
1066
|
return this.variableDeclaration(node, "declare");
|
|
893
1067
|
case "FunctionDeclaration":
|
|
894
|
-
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: node.attributes }];
|
|
1068
|
+
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
|
|
895
1069
|
case "FunctionDeclarationStatement":
|
|
896
1070
|
return [this.functionDeclarationStatement(node)];
|
|
897
1071
|
case "ClassDeclaration":
|
|
@@ -922,8 +1096,13 @@ end`).body.statements);
|
|
|
922
1096
|
// kept — Lua has no expression statement — and it cannot run
|
|
923
1097
|
// anything, since a call is a `CallStatement` instead, so dropping
|
|
924
1098
|
// it changes nothing but the noise.
|
|
925
|
-
case "ExpressionStatement":
|
|
926
|
-
|
|
1099
|
+
case "ExpressionStatement": {
|
|
1100
|
+
let expression = node.expression;
|
|
1101
|
+
while (expression.type === "ParenthesizedExpression") expression = expression.expression;
|
|
1102
|
+
if (expression.type !== "ImportExpression") return [];
|
|
1103
|
+
const call2 = this.importExpression(expression);
|
|
1104
|
+
return call2.type === "CallExpression" ? [this.from(callStatement(call2), node)] : [];
|
|
1105
|
+
}
|
|
927
1106
|
case "DoStatement":
|
|
928
1107
|
return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
|
|
929
1108
|
case "WhileStatement":
|
|
@@ -975,7 +1154,11 @@ end`).body.statements);
|
|
|
975
1154
|
let source = this.expression(node.iterator);
|
|
976
1155
|
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
977
1156
|
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
978
|
-
|
|
1157
|
+
let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
|
|
1158
|
+
if (this.lua51 && form.walks === "values") {
|
|
1159
|
+
const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
|
|
1160
|
+
iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
|
|
1161
|
+
}
|
|
979
1162
|
return [genericFor(
|
|
980
1163
|
variables,
|
|
981
1164
|
iterators,
|
|
@@ -1228,7 +1411,8 @@ end
|
|
|
1228
1411
|
},
|
|
1229
1412
|
isMethod,
|
|
1230
1413
|
func,
|
|
1231
|
-
|
|
1414
|
+
// `@native` and the like are Luau's.
|
|
1415
|
+
attributes: this.lua51 ? void 0 : node.attributes,
|
|
1232
1416
|
...spanOf(node)
|
|
1233
1417
|
};
|
|
1234
1418
|
}
|
|
@@ -1329,7 +1513,7 @@ end
|
|
|
1329
1513
|
if (element) bind(element.value, index(source, number(i + 1)), element.default);
|
|
1330
1514
|
});
|
|
1331
1515
|
if (pattern.rest) {
|
|
1332
|
-
const elements = call(
|
|
1516
|
+
const elements = call(this.move(), [
|
|
1333
1517
|
source,
|
|
1334
1518
|
number(pattern.elements.length + 1),
|
|
1335
1519
|
unary("#", source),
|
|
@@ -1440,8 +1624,15 @@ end
|
|
|
1440
1624
|
case "ArrayExpression":
|
|
1441
1625
|
return this.arrayExpression(node);
|
|
1442
1626
|
case "BinaryExpression": {
|
|
1627
|
+
if (node.operator === "??") return this.nullish(node);
|
|
1443
1628
|
const left = this.expression(node.left);
|
|
1444
1629
|
const right = this.expression(node.right);
|
|
1630
|
+
if (node.operator === "in") {
|
|
1631
|
+
return parenthesized(binary("~=", index(this.keptToOne(right), this.keptToOne(left)), nil()));
|
|
1632
|
+
}
|
|
1633
|
+
if (node.operator === "instanceof") {
|
|
1634
|
+
return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1635
|
+
}
|
|
1445
1636
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1446
1637
|
return binary(node.operator, left, right);
|
|
1447
1638
|
}
|
|
@@ -1468,6 +1659,8 @@ end
|
|
|
1468
1659
|
}
|
|
1469
1660
|
case "ParenthesizedExpression":
|
|
1470
1661
|
return parenthesized(this.expression(node.expression));
|
|
1662
|
+
case "ImportExpression":
|
|
1663
|
+
return this.importExpression(node);
|
|
1471
1664
|
// Types only: the value is the expression itself.
|
|
1472
1665
|
case "TypeAssertionExpression":
|
|
1473
1666
|
case "SatisfiesExpression":
|
|
@@ -1521,7 +1714,7 @@ end
|
|
|
1521
1714
|
const last2 = list.length - 1;
|
|
1522
1715
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1523
1716
|
}
|
|
1524
|
-
const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
|
|
1717
|
+
const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
|
|
1525
1718
|
const last = list[list.length - 1];
|
|
1526
1719
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1527
1720
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -1535,11 +1728,21 @@ end
|
|
|
1535
1728
|
/** One link of an access chain, read from `object`. */
|
|
1536
1729
|
link(node, object2) {
|
|
1537
1730
|
switch (node.type) {
|
|
1538
|
-
case "MemberExpression":
|
|
1731
|
+
case "MemberExpression": {
|
|
1732
|
+
if (this.options.types?.lengths.has(node)) return unary("#", object2);
|
|
1733
|
+
const source = this.options.types?.methodReads.get(node);
|
|
1734
|
+
const lowered = source && this.loweredMethod(node.property.name, source, node.object, [], node.property, node);
|
|
1735
|
+
if (lowered) {
|
|
1736
|
+
const forwarded = call(calleePath(lowered.callee), [...lowered.prepend, vararg()]);
|
|
1737
|
+
return functionExpression(functionBody(lowered.passReceiver ? [] : ["_"], [returns([forwarded])], true));
|
|
1738
|
+
}
|
|
1539
1739
|
return member(object2, node.property.name);
|
|
1740
|
+
}
|
|
1540
1741
|
case "IndexExpression":
|
|
1541
1742
|
return index(object2, this.expression(node.index));
|
|
1542
1743
|
case "CallExpression": {
|
|
1744
|
+
const method = this.methodCalled(node);
|
|
1745
|
+
if (method) return method;
|
|
1543
1746
|
const lowered = this.loweredGlobalCall(node);
|
|
1544
1747
|
const args = this.arguments(node.arguments);
|
|
1545
1748
|
if (!lowered) return call(object2, args);
|
|
@@ -1562,6 +1765,24 @@ end
|
|
|
1562
1765
|
}
|
|
1563
1766
|
}
|
|
1564
1767
|
}
|
|
1768
|
+
/** `names.filter(names, f)`: a method of the value's metatable, read with
|
|
1769
|
+
* `.` and handed its `self` as the first argument. Written as a call of
|
|
1770
|
+
* the function the method really is — `array.filter(names, f)` — where
|
|
1771
|
+
* whoever declared the metatable says so; `undefined` leaves an ordinary
|
|
1772
|
+
* call, which is right for a string's Lua methods (`text.upper(text)`
|
|
1773
|
+
* reaches Lua's own `string.upper`). */
|
|
1774
|
+
methodCalled(node) {
|
|
1775
|
+
const callee = node.callee;
|
|
1776
|
+
if (callee.type !== "MemberExpression") return void 0;
|
|
1777
|
+
const source = this.options.types?.methodReads.get(callee);
|
|
1778
|
+
if (!source) return void 0;
|
|
1779
|
+
const spreads = node.arguments.map((a, i) => a.type === "SpreadElement" ? i : -1).filter((i) => i >= 0);
|
|
1780
|
+
if (!node.arguments.length || spreads.includes(0) || spreads.some((i) => i !== node.arguments.length - 1)) return void 0;
|
|
1781
|
+
const lowered = this.loweredMethod(callee.property.name, source, callee.object, node.arguments.slice(1), callee.property, node);
|
|
1782
|
+
if (!lowered) return void 0;
|
|
1783
|
+
const [self, ...rest] = this.arguments(node.arguments);
|
|
1784
|
+
return call(calleePath(lowered.callee), [...lowered.prepend, ...lowered.passReceiver ? [self] : [], ...rest]);
|
|
1785
|
+
}
|
|
1565
1786
|
// --------------------------------------------------------
|
|
1566
1787
|
// Optional chains
|
|
1567
1788
|
// --------------------------------------------------------
|
|
@@ -1607,7 +1828,7 @@ end
|
|
|
1607
1828
|
const segments = this.segments(chain);
|
|
1608
1829
|
const base = this.expression(chain.base);
|
|
1609
1830
|
const last = chain.links[chain.links.length - 1];
|
|
1610
|
-
if (base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
1831
|
+
if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
1611
1832
|
return {
|
|
1612
1833
|
type: "IfElseExpression",
|
|
1613
1834
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -1628,6 +1849,32 @@ end
|
|
|
1628
1849
|
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1629
1850
|
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1630
1851
|
}
|
|
1852
|
+
/** `a ?? b`: `a`, or `b` where `a` is nil — read only then. A `false`
|
|
1853
|
+
* stays, which is what sets it apart from `a or b`. A name is tested
|
|
1854
|
+
* in place; anything else is read once, into a local. */
|
|
1855
|
+
nullish(node) {
|
|
1856
|
+
const left = this.keptToOne(this.expression(node.left));
|
|
1857
|
+
const right = this.keptToOne(this.expression(node.right));
|
|
1858
|
+
if (!this.lua51 && left.type === "Identifier") {
|
|
1859
|
+
return {
|
|
1860
|
+
type: "IfElseExpression",
|
|
1861
|
+
clauses: [{ condition: binary("~=", left, nil()), body: left }],
|
|
1862
|
+
alternate: right,
|
|
1863
|
+
line: left.line,
|
|
1864
|
+
column: left.column
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
const ref = this.names.fresh("value");
|
|
1868
|
+
const at = identifier(ref);
|
|
1869
|
+
const statements = [
|
|
1870
|
+
local([ref], [left]),
|
|
1871
|
+
ifThen(binary("~=", at, nil()), [returns([at])]),
|
|
1872
|
+
returns([right])
|
|
1873
|
+
];
|
|
1874
|
+
const passesVarargs = statements.some(usesVararg);
|
|
1875
|
+
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1876
|
+
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1877
|
+
}
|
|
1631
1878
|
/** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
|
|
1632
1879
|
interpolatedString(node) {
|
|
1633
1880
|
let format = "";
|
|
@@ -1709,12 +1956,12 @@ end
|
|
|
1709
1956
|
}));
|
|
1710
1957
|
const alternate = this.expression(node.alternate);
|
|
1711
1958
|
if (clauses.every((c) => alwaysTruthy(c.body))) {
|
|
1712
|
-
let
|
|
1959
|
+
let result2 = alternate;
|
|
1713
1960
|
for (let i = clauses.length - 1; i >= 0; i--) {
|
|
1714
1961
|
const { condition, body } = clauses[i];
|
|
1715
|
-
|
|
1962
|
+
result2 = binary("or", binary("and", condition, body), result2);
|
|
1716
1963
|
}
|
|
1717
|
-
return parenthesized(
|
|
1964
|
+
return parenthesized(result2);
|
|
1718
1965
|
}
|
|
1719
1966
|
const statement = {
|
|
1720
1967
|
type: "IfStatement",
|
|
@@ -1771,11 +2018,20 @@ end
|
|
|
1771
2018
|
current = [];
|
|
1772
2019
|
parts.push(this.expression(f.argument));
|
|
1773
2020
|
break;
|
|
2021
|
+
case "TableFieldAccessor":
|
|
2022
|
+
break;
|
|
1774
2023
|
}
|
|
1775
2024
|
}
|
|
1776
|
-
|
|
1777
|
-
if (
|
|
1778
|
-
|
|
2025
|
+
let made;
|
|
2026
|
+
if (parts.length === 0) made = table(current);
|
|
2027
|
+
else {
|
|
2028
|
+
if (current.length) parts.push(table(current));
|
|
2029
|
+
made = call(this.helper("assign"), [table([]), ...parts]);
|
|
2030
|
+
}
|
|
2031
|
+
const accessors = node.fields.filter((f) => f.type === "TableFieldAccessor");
|
|
2032
|
+
if (!accessors.length) return made;
|
|
2033
|
+
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)))));
|
|
2034
|
+
return call(this.helper("accessors"), [made, table2("get"), table2("set")]);
|
|
1779
2035
|
}
|
|
1780
2036
|
/** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
|
|
1781
2037
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
@@ -2039,15 +2295,15 @@ async function loadLowerings(modules) {
|
|
|
2039
2295
|
for (const module of modules) {
|
|
2040
2296
|
try {
|
|
2041
2297
|
const loaded = await import(pathToFileURL(module.file).href);
|
|
2042
|
-
const
|
|
2043
|
-
if (!
|
|
2298
|
+
const plugin3 = loaded.default;
|
|
2299
|
+
if (!plugin3 || typeof plugin3 !== "object") {
|
|
2044
2300
|
problems.push({
|
|
2045
2301
|
file: module.file,
|
|
2046
2302
|
message: `'${module.from}' lowering module has no default export`
|
|
2047
2303
|
});
|
|
2048
2304
|
continue;
|
|
2049
2305
|
}
|
|
2050
|
-
lowerings.push({ plugin:
|
|
2306
|
+
lowerings.push({ plugin: plugin3, from: module.from, library: module.library });
|
|
2051
2307
|
} catch (error) {
|
|
2052
2308
|
problems.push({
|
|
2053
2309
|
file: module.file,
|
|
@@ -2059,8 +2315,8 @@ async function loadLowerings(modules) {
|
|
|
2059
2315
|
}
|
|
2060
2316
|
|
|
2061
2317
|
// src/bundle.ts
|
|
2062
|
-
import { readFileSync as
|
|
2063
|
-
import { dirname, relative, resolve as resolve2 } from "path";
|
|
2318
|
+
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
2319
|
+
import { basename, dirname, extname, posix, relative, resolve as resolve2 } from "path";
|
|
2064
2320
|
import {
|
|
2065
2321
|
parse,
|
|
2066
2322
|
analyzeScopes,
|
|
@@ -2068,17 +2324,18 @@ import {
|
|
|
2068
2324
|
moduleExports,
|
|
2069
2325
|
resolveModulePath,
|
|
2070
2326
|
resolveTypeLibraries,
|
|
2327
|
+
importExpressions,
|
|
2071
2328
|
directivesOf,
|
|
2072
2329
|
applyDirectives,
|
|
2073
2330
|
UNUSED_EXPECT_ERROR,
|
|
2074
2331
|
ParseError,
|
|
2075
2332
|
LexError,
|
|
2076
|
-
luauString as
|
|
2333
|
+
luauString as luauString4
|
|
2077
2334
|
} from "@tilua/parser";
|
|
2078
2335
|
import { parse as parseLuau2, parseExpressionFromSource as parseExpressionFromSource2, print } from "luau-parser";
|
|
2079
2336
|
|
|
2080
2337
|
// src/linemap.ts
|
|
2081
|
-
import { luauString as
|
|
2338
|
+
import { luauString as luauString3 } from "@tilua/parser";
|
|
2082
2339
|
function statementsOf(node) {
|
|
2083
2340
|
const out = [];
|
|
2084
2341
|
const visit = (value) => {
|
|
@@ -2111,7 +2368,7 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
2111
2368
|
}
|
|
2112
2369
|
function lineMapSource(map) {
|
|
2113
2370
|
const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2114
|
-
const quoted = (s) =>
|
|
2371
|
+
const quoted = (s) => luauString3(s.replace(/\\/g, "/"));
|
|
2115
2372
|
return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
|
|
2116
2373
|
}
|
|
2117
2374
|
|
|
@@ -2131,16 +2388,17 @@ async function bundle(options) {
|
|
|
2131
2388
|
const file = queue.shift();
|
|
2132
2389
|
if (sources.has(file)) continue;
|
|
2133
2390
|
const parsed = parseFile(file, diagnostics, directives);
|
|
2134
|
-
if (!parsed) return { modules: [], diagnostics };
|
|
2391
|
+
if (!parsed) return { files: [], modules: [], diagnostics };
|
|
2135
2392
|
const { program: program3, text } = parsed;
|
|
2136
|
-
const scopes = analyzeScopes(program3);
|
|
2393
|
+
const scopes = analyzeScopes(program3, { noLib: config?.noLib });
|
|
2137
2394
|
for (const d of scopes.diagnostics) {
|
|
2138
2395
|
diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
|
|
2139
2396
|
}
|
|
2140
2397
|
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2398
|
+
const dynamic = importExpressions(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
|
|
2399
|
+
for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
|
|
2400
|
+
const target2 = resolveModulePath(file, specifier, config);
|
|
2401
|
+
if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
|
|
2144
2402
|
}
|
|
2145
2403
|
}
|
|
2146
2404
|
const analysis = analyzeModules([...sources.values()], config);
|
|
@@ -2152,6 +2410,9 @@ async function bundle(options) {
|
|
|
2152
2410
|
const names = Names.from(...[...sources.values()].map((s) => s.program));
|
|
2153
2411
|
const G = names.fresh("G");
|
|
2154
2412
|
const requireExpression = member(identifier(G), "require");
|
|
2413
|
+
const importExpression = member(identifier(G), "import");
|
|
2414
|
+
const mode = options.output ?? config?.output ?? "single";
|
|
2415
|
+
const target = options.target ?? config?.target ?? "luau";
|
|
2155
2416
|
const modules = /* @__PURE__ */ new Map();
|
|
2156
2417
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
2157
2418
|
const pending = [entry];
|
|
@@ -2160,38 +2421,48 @@ async function bundle(options) {
|
|
|
2160
2421
|
if (modules.has(file)) continue;
|
|
2161
2422
|
const source = sources.get(file);
|
|
2162
2423
|
const shown = relative(root, file).replace(/\\/g, "/");
|
|
2163
|
-
const
|
|
2424
|
+
const imports = /* @__PURE__ */ new Set();
|
|
2425
|
+
const dynamicImports = /* @__PURE__ */ new Set();
|
|
2426
|
+
const lowered2 = lower(source.program, source.scopes, {
|
|
2164
2427
|
names,
|
|
2165
2428
|
types: analysis.types.get(file),
|
|
2166
2429
|
lowerings,
|
|
2167
|
-
target
|
|
2430
|
+
target,
|
|
2431
|
+
noLib: config?.noLib,
|
|
2168
2432
|
source: source.text,
|
|
2169
2433
|
file: shown,
|
|
2170
2434
|
module: {
|
|
2171
2435
|
name: source.name,
|
|
2172
2436
|
require: requireExpression,
|
|
2173
|
-
|
|
2437
|
+
import: importExpression,
|
|
2438
|
+
// One file has one line map. Several have one each, and a
|
|
2439
|
+
// module reads the one of the file it is in.
|
|
2440
|
+
lines: mode === "single" ? `${G}.lines` : `${G}.maps[${luauString4(source.name)}]`,
|
|
2174
2441
|
fail: `${G}.fail`,
|
|
2175
2442
|
arrays: `${G}.arrays`,
|
|
2176
|
-
resolve: (specifier) => {
|
|
2177
|
-
const
|
|
2178
|
-
if (!
|
|
2179
|
-
pending.push(
|
|
2180
|
-
|
|
2443
|
+
resolve: (specifier, dynamic) => {
|
|
2444
|
+
const found = resolveModulePath(file, specifier, config);
|
|
2445
|
+
if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
|
|
2446
|
+
pending.push(found);
|
|
2447
|
+
const name = sources.get(found).name;
|
|
2448
|
+
(dynamic ? dynamicImports : imports).add(name);
|
|
2449
|
+
return name;
|
|
2181
2450
|
}
|
|
2182
2451
|
}
|
|
2183
2452
|
});
|
|
2184
|
-
for (const d of
|
|
2185
|
-
for (const statement of allStatements(
|
|
2186
|
-
const line =
|
|
2453
|
+
for (const d of lowered2.diagnostics) diagnostics.push({ file, ...d, category: "module" });
|
|
2454
|
+
for (const statement of allStatements(lowered2.statements)) {
|
|
2455
|
+
const line = lowered2.origins.get(statement);
|
|
2187
2456
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
2188
2457
|
}
|
|
2189
2458
|
modules.set(file, {
|
|
2190
2459
|
name: source.name,
|
|
2191
|
-
statements:
|
|
2192
|
-
exportsName:
|
|
2193
|
-
info:
|
|
2194
|
-
usesScriptArgs:
|
|
2460
|
+
statements: lowered2.statements,
|
|
2461
|
+
exportsName: lowered2.exportsName,
|
|
2462
|
+
info: lowered2.module,
|
|
2463
|
+
usesScriptArgs: lowered2.usesScriptArgs,
|
|
2464
|
+
imports,
|
|
2465
|
+
dynamicImports
|
|
2195
2466
|
});
|
|
2196
2467
|
}
|
|
2197
2468
|
for (const [file, fileDirectives] of directives) {
|
|
@@ -2210,54 +2481,168 @@ async function bundle(options) {
|
|
|
2210
2481
|
const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2211
2482
|
diagnostics.length = 0;
|
|
2212
2483
|
diagnostics.push(...reported);
|
|
2213
|
-
const
|
|
2484
|
+
const lowered = [...modules.values()];
|
|
2485
|
+
const moduleNames = lowered.map((m) => m.name);
|
|
2214
2486
|
if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
|
|
2215
|
-
return { modules: moduleNames, diagnostics };
|
|
2216
|
-
}
|
|
2217
|
-
const
|
|
2487
|
+
return { files: [], modules: moduleNames, diagnostics };
|
|
2488
|
+
}
|
|
2489
|
+
const several = mode !== "single";
|
|
2490
|
+
const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
|
|
2491
|
+
const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
|
|
2492
|
+
const elsewhere = /* @__PURE__ */ new Map();
|
|
2493
|
+
for (const file of layout.slice(1)) {
|
|
2494
|
+
for (const module of file.modules) elsewhere.set(module.name, loadPath(layout[0].path, file.path, target));
|
|
2495
|
+
}
|
|
2496
|
+
const program2 = parseLuau2(runtime(G, {
|
|
2497
|
+
several,
|
|
2498
|
+
target,
|
|
2499
|
+
files: elsewhere,
|
|
2500
|
+
imports: lowered.some((m) => m.dynamicImports.size > 0)
|
|
2501
|
+
}));
|
|
2218
2502
|
const assignment = program2.body.statements[1];
|
|
2219
|
-
if (
|
|
2503
|
+
if (usesScriptArgs) {
|
|
2220
2504
|
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2221
2505
|
program2.body.statements.push(...parseLuau2(`${G}.arrays[scriptArgs] = true`).body.statements);
|
|
2506
|
+
if (several) program2.body.statements.push(...parseLuau2(`${G}.scriptArgs = scriptArgs`).body.statements);
|
|
2222
2507
|
}
|
|
2223
2508
|
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;
|
|
2224
2509
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2225
|
-
for (const module of modules
|
|
2226
|
-
|
|
2227
|
-
const fields = [];
|
|
2228
|
-
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2229
|
-
if (links.length) {
|
|
2230
|
-
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2231
|
-
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2232
|
-
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2233
|
-
]))))));
|
|
2234
|
-
}
|
|
2235
|
-
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2236
|
-
fields.push(field("load", functionExpression(functionBody([module.exportsName], module.statements, true))));
|
|
2237
|
-
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module.name), value: table(fields) });
|
|
2510
|
+
for (const module of layout[0].modules) {
|
|
2511
|
+
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module.name), value: moduleTable(module) });
|
|
2238
2512
|
}
|
|
2239
2513
|
const ok = names.fresh("ok");
|
|
2240
|
-
const
|
|
2514
|
+
const result2 = names.fresh("result");
|
|
2241
2515
|
const entryStatements = parseLuau2(`
|
|
2242
|
-
local ${ok}, ${
|
|
2243
|
-
return ${G}.require(${
|
|
2516
|
+
local ${ok}, ${result2} = xpcall(function()
|
|
2517
|
+
return ${G}.require(${luauString4(sources.get(entry).name)})
|
|
2244
2518
|
end, ${G}.fail)
|
|
2245
2519
|
if not ${ok} then
|
|
2246
|
-
error(${
|
|
2520
|
+
error(${result2}, 0)
|
|
2247
2521
|
end
|
|
2248
|
-
${modules.get(entry).info.exports ? `return ${
|
|
2522
|
+
${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
2249
2523
|
`).body.statements;
|
|
2250
2524
|
program2.body.statements.push(...entryStatements);
|
|
2251
2525
|
const map = buildLineMap(program2, parseLuau2(print(program2)), origins);
|
|
2252
|
-
if (map.size) {
|
|
2253
|
-
|
|
2526
|
+
if (map.size || several) {
|
|
2527
|
+
const mapStatements = [assign(
|
|
2254
2528
|
[member(identifier(G), "lines")],
|
|
2255
2529
|
[parseExpressionFromSource2(lineMapSource(map))]
|
|
2256
|
-
)
|
|
2530
|
+
)];
|
|
2531
|
+
if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
|
|
2532
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
|
|
2257
2533
|
}
|
|
2258
|
-
|
|
2534
|
+
const files = [{ path: layout[0].path, code: print(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
|
|
2535
|
+
for (const file of layout.slice(1)) {
|
|
2536
|
+
const body = [];
|
|
2537
|
+
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...parseLuau2(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
2538
|
+
for (const module of file.modules) {
|
|
2539
|
+
body.push(assign(
|
|
2540
|
+
[index(member(identifier(G), "modules"), string(module.name))],
|
|
2541
|
+
[moduleTable(module)]
|
|
2542
|
+
));
|
|
2543
|
+
}
|
|
2544
|
+
const chunk = program([returns([functionExpression(functionBody([G], body))])]);
|
|
2545
|
+
const lines = buildLineMap(chunk, parseLuau2(print(chunk)), origins);
|
|
2546
|
+
body.push(...mapRegistration(G, file, lineMapSource(lines)));
|
|
2547
|
+
files.push({ path: file.path, code: print(chunk) + "\n", modules: file.modules.map((m) => m.name) });
|
|
2548
|
+
}
|
|
2549
|
+
return { code: files[0].code, files, modules: moduleNames, diagnostics };
|
|
2550
|
+
}
|
|
2551
|
+
function moduleTable(module) {
|
|
2552
|
+
const { names: exported, links, stars } = module.info;
|
|
2553
|
+
const fields = [];
|
|
2554
|
+
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2555
|
+
if (links.length) {
|
|
2556
|
+
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2557
|
+
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2558
|
+
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2559
|
+
]))))));
|
|
2560
|
+
}
|
|
2561
|
+
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2562
|
+
fields.push(field("load", functionExpression(functionBody([module.exportsName], module.statements, true))));
|
|
2563
|
+
return table(fields);
|
|
2564
|
+
}
|
|
2565
|
+
function layoutOf(mode, entry, modules, outFile) {
|
|
2566
|
+
const extension = outFile !== void 0 && extname(outFile) || ".luau";
|
|
2567
|
+
const entryStem = outFile !== void 0 ? basename(outFile, extname(outFile)) : posix.basename(entry.name);
|
|
2568
|
+
const others = modules.filter((m) => m !== entry);
|
|
2569
|
+
if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry, ...others] }];
|
|
2570
|
+
if (mode === "modules") {
|
|
2571
|
+
const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
|
|
2572
|
+
return [entry, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
|
|
2573
|
+
}
|
|
2574
|
+
const byName = new Map(modules.map((m) => [m.name, m]));
|
|
2575
|
+
const reach = (root) => {
|
|
2576
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2577
|
+
const stack = [root];
|
|
2578
|
+
while (stack.length) {
|
|
2579
|
+
const name = stack.pop();
|
|
2580
|
+
if (seen.has(name)) continue;
|
|
2581
|
+
seen.add(name);
|
|
2582
|
+
stack.push(...byName.get(name)?.imports ?? []);
|
|
2583
|
+
}
|
|
2584
|
+
return seen;
|
|
2585
|
+
};
|
|
2586
|
+
const inEntry = reach(entry.name);
|
|
2587
|
+
const roots = [...new Set(modules.flatMap((m) => [...m.dynamicImports]))].filter((root) => !inEntry.has(root));
|
|
2588
|
+
const reached = new Map(roots.map((root) => [root, reach(root)]));
|
|
2589
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2590
|
+
for (const module of others) {
|
|
2591
|
+
if (inEntry.has(module.name)) continue;
|
|
2592
|
+
const by = roots.filter((root) => reached.get(root).has(module.name));
|
|
2593
|
+
const key = by.join("\n");
|
|
2594
|
+
if (!groups.has(key)) groups.set(key, { roots: by, modules: [] });
|
|
2595
|
+
groups.get(key).modules.push(module);
|
|
2596
|
+
}
|
|
2597
|
+
const files = [{ path: `${entryStem}${extension}`, modules: modules.filter((m) => inEntry.has(m.name)) }];
|
|
2598
|
+
const taken = /* @__PURE__ */ new Set([entryStem]);
|
|
2599
|
+
for (const group of groups.values()) {
|
|
2600
|
+
const label = group.roots.length === 1 ? group.roots[0].replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "chunk" : "shared";
|
|
2601
|
+
let stem = `${entryStem}_${label}`;
|
|
2602
|
+
for (let n = 2; taken.has(stem); n++) stem = `${entryStem}_${label}_${n}`;
|
|
2603
|
+
taken.add(stem);
|
|
2604
|
+
files.push({ path: `${stem}${extension}`, modules: group.modules });
|
|
2605
|
+
}
|
|
2606
|
+
return files;
|
|
2607
|
+
}
|
|
2608
|
+
function loadPath(entryPath, file, target) {
|
|
2609
|
+
const path = posix.relative(posix.dirname(entryPath), file);
|
|
2610
|
+
if (target === "lua51") return path;
|
|
2611
|
+
const bare = path.slice(0, path.length - posix.extname(path).length);
|
|
2612
|
+
return bare.startsWith("../") ? bare : `./${bare}`;
|
|
2259
2613
|
}
|
|
2260
|
-
function
|
|
2614
|
+
function mapRegistration(G, file, map) {
|
|
2615
|
+
const extension = posix.extname(file.path);
|
|
2616
|
+
const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
|
|
2617
|
+
const entry = `${G}.sources[#${G}.sources + 1]`;
|
|
2618
|
+
return parseLuau2([
|
|
2619
|
+
`${entry} = { { ${parts.map(luauString4).join(", ")} }, ${map}, ${luauString4(extension.slice(1))} }`,
|
|
2620
|
+
...file.modules.map((module) => `${G}.maps[${luauString4(module.name)}] = ${G}.sources[#${G}.sources][2]`)
|
|
2621
|
+
].join("\n")).body.statements;
|
|
2622
|
+
}
|
|
2623
|
+
function loader(G, target) {
|
|
2624
|
+
if (target === "lua51") {
|
|
2625
|
+
return `
|
|
2626
|
+
-- From beside this file, wherever that is: its own path says where.
|
|
2627
|
+
local folder = ""
|
|
2628
|
+
if debug ~= nil and debug.getinfo ~= nil then
|
|
2629
|
+
local at = debug.getinfo(${G}.load, "S").source
|
|
2630
|
+
if at:sub(1, 1) == "@" then
|
|
2631
|
+
folder = at:match("^@(.*[/\\\\])") or ""
|
|
2632
|
+
end
|
|
2633
|
+
end
|
|
2634
|
+
local chunk, problem = loadfile(folder .. file)
|
|
2635
|
+
if chunk == nil then
|
|
2636
|
+
error(problem, 0)
|
|
2637
|
+
end
|
|
2638
|
+
chunk()(${G})`;
|
|
2639
|
+
}
|
|
2640
|
+
return `
|
|
2641
|
+
require(file)(${G})`;
|
|
2642
|
+
}
|
|
2643
|
+
function runtime(G, options) {
|
|
2644
|
+
const { several } = options;
|
|
2645
|
+
const files = [...options.files].map(([name, file]) => `[${luauString4(name)}] = ${luauString4(file)}`).join(", ");
|
|
2261
2646
|
return `
|
|
2262
2647
|
local ${G}
|
|
2263
2648
|
${G} = {
|
|
@@ -2305,7 +2690,7 @@ ${G} = {
|
|
|
2305
2690
|
return message
|
|
2306
2691
|
end
|
|
2307
2692
|
local lines = ${G}.lines
|
|
2308
|
-
if lines == nil then
|
|
2693
|
+
if lines == nil ${several ? `and #${G}.sources == 0 ` : ""}then
|
|
2309
2694
|
return message .. (trace or "")
|
|
2310
2695
|
end
|
|
2311
2696
|
-- The bundle's name, as a position in a message spells it: where this
|
|
@@ -2325,6 +2710,81 @@ ${G} = {
|
|
|
2325
2710
|
chunk = info.short_src
|
|
2326
2711
|
end
|
|
2327
2712
|
end
|
|
2713
|
+
${several ? `
|
|
2714
|
+
-- A position in this file is read by this file's map. One in another
|
|
2715
|
+
-- of the bundle's files is read by that file's: the one whose name
|
|
2716
|
+
-- the position ends with, by the most folders where several do. A
|
|
2717
|
+
-- message says nothing about which file it starts in, so nothing is
|
|
2718
|
+
-- guessed from it here.
|
|
2719
|
+
local prefix = nil
|
|
2720
|
+
if chunk ~= nil then
|
|
2721
|
+
prefix = chunk:gsub("%p", "%%%0")
|
|
2722
|
+
end
|
|
2723
|
+
local function place(line)
|
|
2724
|
+
local origin = lines ~= nil and lines[tonumber(line)] or nil
|
|
2725
|
+
if origin == nil then
|
|
2726
|
+
return nil
|
|
2727
|
+
end
|
|
2728
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2729
|
+
end
|
|
2730
|
+
local function elsewhere(at, line)
|
|
2731
|
+
-- Already a place in the project.
|
|
2732
|
+
if at:sub(-6) == ".tilua" then
|
|
2733
|
+
return nil
|
|
2734
|
+
end
|
|
2735
|
+
local parts = {}
|
|
2736
|
+
for part in at:gmatch("[^/\\\\%.]+") do
|
|
2737
|
+
parts[#parts + 1] = part
|
|
2738
|
+
end
|
|
2739
|
+
local map, most, tied = nil, 0, false
|
|
2740
|
+
for _, source in ipairs(${G}.sources) do
|
|
2741
|
+
local names = source[1]
|
|
2742
|
+
local last = #parts
|
|
2743
|
+
if parts[last] == source[3] then
|
|
2744
|
+
last = last - 1
|
|
2745
|
+
end
|
|
2746
|
+
local same = 0
|
|
2747
|
+
while same < #names and same < last and names[#names - same] == parts[last - same] do
|
|
2748
|
+
same = same + 1
|
|
2749
|
+
end
|
|
2750
|
+
if same > most then
|
|
2751
|
+
map, most, tied = source[2], same, false
|
|
2752
|
+
elseif same == most then
|
|
2753
|
+
tied = true
|
|
2754
|
+
end
|
|
2755
|
+
end
|
|
2756
|
+
-- Two files it could as well be: a wrong place is worse than none.
|
|
2757
|
+
local origin = map ~= nil and not tied and map[tonumber(line)] or nil
|
|
2758
|
+
if origin == nil then
|
|
2759
|
+
return nil
|
|
2760
|
+
end
|
|
2761
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2762
|
+
end
|
|
2763
|
+
local mapped = message
|
|
2764
|
+
if prefix ~= nil then
|
|
2765
|
+
mapped = mapped:gsub(prefix .. ":(%d+)", function(line)
|
|
2766
|
+
return place(line)
|
|
2767
|
+
end)
|
|
2768
|
+
end
|
|
2769
|
+
mapped = mapped:gsub("(%S+):(%d+)", elsewhere)
|
|
2770
|
+
local frames = {}
|
|
2771
|
+
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2772
|
+
local at = nil
|
|
2773
|
+
if prefix ~= nil then
|
|
2774
|
+
local line = frame:match(prefix .. ":(%d+)")
|
|
2775
|
+
if line ~= nil then
|
|
2776
|
+
at = place(line)
|
|
2777
|
+
end
|
|
2778
|
+
end
|
|
2779
|
+
if at == nil then
|
|
2780
|
+
for source, line in frame:gmatch("(%S+):(%d+)") do
|
|
2781
|
+
at = elsewhere(source, line)
|
|
2782
|
+
if at ~= nil then
|
|
2783
|
+
break
|
|
2784
|
+
end
|
|
2785
|
+
end
|
|
2786
|
+
end
|
|
2787
|
+
if at ~= nil then` : `
|
|
2328
2788
|
if chunk == nil then
|
|
2329
2789
|
chunk = message:match("^(.-):%d+:")
|
|
2330
2790
|
end
|
|
@@ -2346,8 +2806,9 @@ ${G} = {
|
|
|
2346
2806
|
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2347
2807
|
local line = frame:match(prefix .. ":(%d+)")
|
|
2348
2808
|
local at = line ~= nil and place(line) or nil
|
|
2349
|
-
if at ~= nil then
|
|
2350
|
-
|
|
2809
|
+
if at ~= nil then`}
|
|
2810
|
+
-- Luau writes \`function name\`; Lua 5.1, \`function 'name'\`.
|
|
2811
|
+
local name = frame:match("function ([%w_.:]+)") or frame:match("function '([^']+)'")
|
|
2351
2812
|
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2352
2813
|
end
|
|
2353
2814
|
end
|
|
@@ -2356,13 +2817,36 @@ ${G} = {
|
|
|
2356
2817
|
end
|
|
2357
2818
|
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2358
2819
|
end,
|
|
2359
|
-
records = {}
|
|
2820
|
+
records = {},${several ? `
|
|
2821
|
+
-- The file each module outside this one is in, as \`load\` is handed it.
|
|
2822
|
+
files = { ${files} },
|
|
2823
|
+
loaded = {},
|
|
2824
|
+
-- Every file's line map: by the module, and by the file's name.
|
|
2825
|
+
maps = {},
|
|
2826
|
+
sources = {},
|
|
2827
|
+
-- Another of the bundle's files: a function that adds its modules here.
|
|
2828
|
+
load = function(file)
|
|
2829
|
+
if ${G}.loaded[file] then
|
|
2830
|
+
return
|
|
2831
|
+
end
|
|
2832
|
+
${G}.loaded[file] = true${loader(G, options.target)}
|
|
2833
|
+
end,` : ""}${options.imports ? `
|
|
2834
|
+
-- \`import("./x")\`: the module's exports, asked for where the code runs
|
|
2835
|
+
-- rather than before its module does. \`require\` does the rest \u2014 and
|
|
2836
|
+
-- loads the file the module is in, if it is not this one.
|
|
2837
|
+
import = function(name)
|
|
2838
|
+
return ${G}.require(name)
|
|
2839
|
+
end,` : ""}
|
|
2360
2840
|
require = function(name)
|
|
2361
2841
|
local record = ${G}.records[name]
|
|
2362
2842
|
if record ~= nil then
|
|
2363
2843
|
return record.exports
|
|
2364
2844
|
end
|
|
2365
|
-
local module = ${G}.modules[name]
|
|
2845
|
+
local module = ${G}.modules[name]${several ? `
|
|
2846
|
+
if module == nil then
|
|
2847
|
+
${G}.load(${G}.files[name])
|
|
2848
|
+
module = ${G}.modules[name]
|
|
2849
|
+
end` : ""}
|
|
2366
2850
|
local initialized = {}
|
|
2367
2851
|
local exports = setmetatable({}, {
|
|
2368
2852
|
__index = function(_, key)
|
|
@@ -2381,7 +2865,7 @@ ${G} = {
|
|
|
2381
2865
|
return source[link[2]]
|
|
2382
2866
|
end
|
|
2383
2867
|
if module.stars ~= nil and key ~= "default" then
|
|
2384
|
-
for _, star in module.stars do
|
|
2868
|
+
for _, star in ipairs(module.stars) do
|
|
2385
2869
|
local value = ${G}.require(star)[key]
|
|
2386
2870
|
if value ~= nil then
|
|
2387
2871
|
return value
|
|
@@ -2422,7 +2906,7 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2422
2906
|
}
|
|
2423
2907
|
let text;
|
|
2424
2908
|
try {
|
|
2425
|
-
text =
|
|
2909
|
+
text = readFileSync3(file, "utf8");
|
|
2426
2910
|
} catch {
|
|
2427
2911
|
diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
|
|
2428
2912
|
return void 0;
|
|
@@ -2456,7 +2940,7 @@ function parsedLibrary(file) {
|
|
|
2456
2940
|
}
|
|
2457
2941
|
const cached = libraryCache.get(file);
|
|
2458
2942
|
if (cached && cached.stamp === stamp) return cached.program;
|
|
2459
|
-
const program2 = parse(
|
|
2943
|
+
const program2 = parse(readFileSync3(file, "utf8"));
|
|
2460
2944
|
libraryCache.set(file, { stamp, program: program2 });
|
|
2461
2945
|
return program2;
|
|
2462
2946
|
}
|
|
@@ -2467,6 +2951,8 @@ function analyzeModules(modules, config) {
|
|
|
2467
2951
|
const libraries = config ? resolveTypeLibraries(config) : { files: [], lowerings: [], problems: [] };
|
|
2468
2952
|
for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
|
|
2469
2953
|
const libs = libraries.files.map(parsedLibrary);
|
|
2954
|
+
const noLib = config?.noLib ?? false;
|
|
2955
|
+
const checked = libs.length > 0 || !noLib;
|
|
2470
2956
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
2471
2957
|
const byFile = new Map(modules.map((m) => [m.file, m]));
|
|
2472
2958
|
const exportsCache = /* @__PURE__ */ new Map();
|
|
@@ -2479,12 +2965,12 @@ function analyzeModules(modules, config) {
|
|
|
2479
2965
|
const done = analyzed.get(file);
|
|
2480
2966
|
if (done) return done;
|
|
2481
2967
|
const remembered = moduleAnalyses.get(file);
|
|
2482
|
-
if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([
|
|
2968
|
+
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)) {
|
|
2483
2969
|
analyzed.set(file, remembered.result);
|
|
2484
2970
|
readWhile.set(file, remembered.read);
|
|
2485
2971
|
return remembered.result;
|
|
2486
2972
|
}
|
|
2487
|
-
const scopes = analyzeScopes(program2, { builtinGlobals: globals, reportUndeclared:
|
|
2973
|
+
const scopes = analyzeScopes(program2, { builtinGlobals: globals, reportUndeclared: checked, noLib });
|
|
2488
2974
|
const said = [];
|
|
2489
2975
|
for (const d of scopes.diagnostics) {
|
|
2490
2976
|
if (d.kind !== "undeclared") continue;
|
|
@@ -2494,34 +2980,35 @@ function analyzeModules(modules, config) {
|
|
|
2494
2980
|
const types = analyzeTypes(program2, scopes, {
|
|
2495
2981
|
libs,
|
|
2496
2982
|
resolveModule: resolverFor(file),
|
|
2497
|
-
|
|
2983
|
+
noLib,
|
|
2984
|
+
reportUnknownTypes: checked
|
|
2498
2985
|
});
|
|
2499
2986
|
for (const d of types.diagnostics) {
|
|
2500
2987
|
const at = d.node;
|
|
2501
2988
|
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
2502
2989
|
}
|
|
2503
|
-
const
|
|
2990
|
+
const result2 = { types, diagnostics: said };
|
|
2504
2991
|
if (partials === before) {
|
|
2505
|
-
analyzed.set(file,
|
|
2506
|
-
const
|
|
2992
|
+
analyzed.set(file, result2);
|
|
2993
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
2507
2994
|
const own = stampOf(file);
|
|
2508
|
-
if (own !== void 0)
|
|
2509
|
-
readWhile.set(file,
|
|
2510
|
-
if ([...
|
|
2511
|
-
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read:
|
|
2995
|
+
if (own !== void 0) read3.set(file, own);
|
|
2996
|
+
readWhile.set(file, read3);
|
|
2997
|
+
if ([...read3.values()].every((stamp) => stamp !== void 0)) {
|
|
2998
|
+
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read3, result: result2 });
|
|
2512
2999
|
}
|
|
2513
3000
|
}
|
|
2514
|
-
return
|
|
3001
|
+
return result2;
|
|
2515
3002
|
};
|
|
2516
3003
|
const resolverFor = (file) => (specifier) => {
|
|
2517
3004
|
const target = resolveModulePath(file, specifier, config);
|
|
2518
3005
|
if (!target) return void 0;
|
|
2519
3006
|
const noteRead = () => {
|
|
2520
|
-
const
|
|
2521
|
-
readWhile.set(file,
|
|
3007
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
3008
|
+
readWhile.set(file, read3);
|
|
2522
3009
|
const stamp = stampOf(target);
|
|
2523
|
-
if (stamp !== void 0)
|
|
2524
|
-
for (const [below, belowStamp] of readWhile.get(target) ?? [])
|
|
3010
|
+
if (stamp !== void 0) read3.set(target, stamp);
|
|
3011
|
+
for (const [below, belowStamp] of readWhile.get(target) ?? []) read3.set(below, belowStamp);
|
|
2525
3012
|
};
|
|
2526
3013
|
if (inProgress.has(target)) {
|
|
2527
3014
|
partials++;
|
|
@@ -2535,15 +3022,15 @@ function analyzeModules(modules, config) {
|
|
|
2535
3022
|
let program2 = byFile.get(target)?.program;
|
|
2536
3023
|
if (!program2) {
|
|
2537
3024
|
try {
|
|
2538
|
-
program2 = parse(
|
|
3025
|
+
program2 = parse(readFileSync3(target, "utf8"));
|
|
2539
3026
|
} catch {
|
|
2540
3027
|
return void 0;
|
|
2541
3028
|
}
|
|
2542
3029
|
}
|
|
2543
3030
|
inProgress.add(target);
|
|
2544
3031
|
try {
|
|
2545
|
-
const scopes = analyzeScopes(program2, { builtinGlobals: globals });
|
|
2546
|
-
const types = byFile.has(target) ? analyzeModule(target, program2).types : analyzeTypes(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
|
|
3032
|
+
const scopes = analyzeScopes(program2, { builtinGlobals: globals, noLib });
|
|
3033
|
+
const types = byFile.has(target) ? analyzeModule(target, program2).types : analyzeTypes(program2, scopes, { libs, noLib, resolveModule: resolverFor(target), diagnostics: false });
|
|
2547
3034
|
const exports = moduleExports(program2, scopes, types, resolverFor(target));
|
|
2548
3035
|
exportsCache.set(target, exports);
|
|
2549
3036
|
noteRead();
|