@tilua/compiler 4.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -10
- package/dist/{chunk-VZ7XC4BG.js → chunk-Y7QGXLCV.js} +643 -127
- package/dist/cli.cjs +709 -171
- package/dist/cli.js +32 -9
- package/dist/index.cjs +665 -148
- package/dist/index.d.cts +41 -6
- package/dist/index.d.ts +41 -6
- package/dist/index.js +6 -4
- package/package.json +2 -2
- package/runtime/array.luau +36 -4
- package/runtime/console.luau +227 -0
- package/runtime/result.luau +122 -0
package/dist/index.cjs
CHANGED
|
@@ -33,9 +33,9 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
33
33
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
34
34
|
|
|
35
35
|
// src/bundle.ts
|
|
36
|
-
var
|
|
36
|
+
var import_node_fs3 = require("fs");
|
|
37
37
|
var import_node_path2 = require("path");
|
|
38
|
-
var
|
|
38
|
+
var import_parser7 = require("@tilua/parser");
|
|
39
39
|
var import_luau_parser2 = require("luau-parser");
|
|
40
40
|
|
|
41
41
|
// src/config.ts
|
|
@@ -50,7 +50,7 @@ function resolveConfig(input, entry, cwd = process.cwd()) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// src/lower.ts
|
|
53
|
-
var
|
|
53
|
+
var import_parser5 = require("@tilua/parser");
|
|
54
54
|
var import_luau_parser = require("luau-parser");
|
|
55
55
|
|
|
56
56
|
// src/luau.ts
|
|
@@ -212,6 +212,77 @@ var plugin = {
|
|
|
212
212
|
};
|
|
213
213
|
var languageLowering = { plugin, from: "tilua" };
|
|
214
214
|
|
|
215
|
+
// src/standard.ts
|
|
216
|
+
var import_node_fs2 = require("fs");
|
|
217
|
+
var import_parser4 = require("@tilua/parser");
|
|
218
|
+
var read2 = (name) => (0, import_node_fs2.readFileSync)(new URL(`../runtime/${name}`, importMetaUrl), "utf8");
|
|
219
|
+
var console = read2("console.luau");
|
|
220
|
+
var result = read2("result.luau");
|
|
221
|
+
var place = (at) => (0, import_parser4.luauString)(at.file === void 0 ? `line ${at.line}` : `${at.file}:${at.line}`);
|
|
222
|
+
var tag = (at) => (0, import_parser4.luauString)(`[${at.file === void 0 ? `line ${at.line}` : `${at.file.replace(/\.tilua$/, "")}:${at.line}`}]:`);
|
|
223
|
+
function consoleMeta(args) {
|
|
224
|
+
const fields = [];
|
|
225
|
+
args.forEach((argument, index2) => {
|
|
226
|
+
if (argument.spread || argument.typeText === void 0 || !(0, import_parser4.isFunctionType)(argument.type)) return;
|
|
227
|
+
const written = argument.declaration ?? argument.source;
|
|
228
|
+
fields.push(`[${index2 + 1}] = { ${(0, import_parser4.luauString)(argument.typeText)}, ${(0, import_parser4.luauString)(written)} }`);
|
|
229
|
+
});
|
|
230
|
+
return fields.length ? `{ ${fields.join(", ")} }` : "nil";
|
|
231
|
+
}
|
|
232
|
+
var CONSOLE_METHODS = /* @__PURE__ */ new Set(["log", "warn", "error"]);
|
|
233
|
+
var REPLACED = /* @__PURE__ */ new Map([
|
|
234
|
+
["pcall", "pcall"],
|
|
235
|
+
["xpcall", "xpcall"],
|
|
236
|
+
["ypcall", "pcall"],
|
|
237
|
+
["coroutine.resume", "resume"],
|
|
238
|
+
["string.find", "find"],
|
|
239
|
+
["string.match", "match"],
|
|
240
|
+
["string.gsub", "gsub"],
|
|
241
|
+
["string.byte", "byte"],
|
|
242
|
+
["math.modf", "modf"],
|
|
243
|
+
["math.frexp", "frexp"],
|
|
244
|
+
["next", "next"],
|
|
245
|
+
["select", "select"],
|
|
246
|
+
["unpack", "unpack"],
|
|
247
|
+
["table.unpack", "unpack"],
|
|
248
|
+
["loadstring", "loadstring"],
|
|
249
|
+
["pairs", "pairs"],
|
|
250
|
+
["ipairs", "ipairs"],
|
|
251
|
+
["string.gmatch", "gmatch"]
|
|
252
|
+
]);
|
|
253
|
+
var plugin2 = {
|
|
254
|
+
runtime: { console, result },
|
|
255
|
+
methodCall({ method, receiverGlobal, at, arguments: args, use }) {
|
|
256
|
+
if (receiverGlobal === "console" && CONSOLE_METHODS.has(method)) {
|
|
257
|
+
return {
|
|
258
|
+
callee: `${use("console")}.${method}`,
|
|
259
|
+
prepend: [tag(at), consoleMeta(args)],
|
|
260
|
+
passReceiver: false
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return void 0;
|
|
264
|
+
},
|
|
265
|
+
// `print(x)` and `warn(x)` are left exactly as written: the place a line
|
|
266
|
+
// came from is what `console:log` / `console:warn` are for. `error` has to
|
|
267
|
+
// put the place inside the message it raises, so it goes through the
|
|
268
|
+
// runtime.
|
|
269
|
+
globalCall({ name, at, use }) {
|
|
270
|
+
if (name === "error") return { callee: `${use("console")}.raise`, prepend: [place(at)] };
|
|
271
|
+
const replaced = REPLACED.get(name);
|
|
272
|
+
if (replaced) return { callee: `${use("result")}.${replaced}` };
|
|
273
|
+
return void 0;
|
|
274
|
+
},
|
|
275
|
+
// Read as a value there is no call site to write down; the runtime finds
|
|
276
|
+
// one on the stack.
|
|
277
|
+
globalValue({ name, use }) {
|
|
278
|
+
if (name === "error") return `${use("console")}.raiseValue`;
|
|
279
|
+
const replaced = REPLACED.get(name);
|
|
280
|
+
if (replaced) return `${use("result")}.${replaced}`;
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
|
|
285
|
+
|
|
215
286
|
// src/lower.ts
|
|
216
287
|
function lower(program2, scopes, options = {}) {
|
|
217
288
|
return new Lowerer(program2, scopes, options).run();
|
|
@@ -227,6 +298,18 @@ var Lowerer = class {
|
|
|
227
298
|
for (const binding of scopes.bindings.values()) {
|
|
228
299
|
if (binding.declarationNode) this.bindingByDeclaration.set(binding.declarationNode, binding);
|
|
229
300
|
}
|
|
301
|
+
const visit = (value) => {
|
|
302
|
+
if (!value || typeof value !== "object") return;
|
|
303
|
+
if (Array.isArray(value)) return void value.forEach(visit);
|
|
304
|
+
const node = value;
|
|
305
|
+
if (node.type === "VariableDeclaration" && node.name?.type === "IdentifierPattern" && node.name.name) {
|
|
306
|
+
let init = node.init;
|
|
307
|
+
while (init?.type === "ParenthesizedExpression") init = init.expression;
|
|
308
|
+
if (init?.type === "ClassExpression") this.classExpressionNames.set(init, node.name.name);
|
|
309
|
+
}
|
|
310
|
+
for (const [key, child] of Object.entries(value)) if (key !== "line" && key !== "column") visit(child);
|
|
311
|
+
};
|
|
312
|
+
visit(source.body);
|
|
230
313
|
}
|
|
231
314
|
source;
|
|
232
315
|
scopes;
|
|
@@ -248,6 +331,8 @@ var Lowerer = class {
|
|
|
248
331
|
lua51;
|
|
249
332
|
/** The source line each emitted statement came from. */
|
|
250
333
|
origins = /* @__PURE__ */ new WeakMap();
|
|
334
|
+
/** The name each class written as a value is known by. */
|
|
335
|
+
classExpressionNames = /* @__PURE__ */ new WeakMap();
|
|
251
336
|
run() {
|
|
252
337
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
253
338
|
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
@@ -301,6 +386,10 @@ var Lowerer = class {
|
|
|
301
386
|
}
|
|
302
387
|
return identifier(local2);
|
|
303
388
|
}
|
|
389
|
+
/** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
|
|
390
|
+
move() {
|
|
391
|
+
return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
|
|
392
|
+
}
|
|
304
393
|
helper(kind) {
|
|
305
394
|
let name = this.helpers.get(kind);
|
|
306
395
|
if (!name) {
|
|
@@ -401,7 +490,7 @@ var Lowerer = class {
|
|
|
401
490
|
const type = this.options.types?.typeOf.get(argument);
|
|
402
491
|
return {
|
|
403
492
|
type,
|
|
404
|
-
typeText: type ? (0,
|
|
493
|
+
typeText: type ? (0, import_parser5.formatType)(type) : void 0,
|
|
405
494
|
source: this.writtenSource(argument),
|
|
406
495
|
declaration: declaration ? this.writtenSource(declaration) : void 0,
|
|
407
496
|
spread: argument.type === "SpreadElement"
|
|
@@ -423,7 +512,11 @@ var Lowerer = class {
|
|
|
423
512
|
/** The language's own lowering, then each library's, in load order. A
|
|
424
513
|
* runtime is named by its lowering's place here (`loweringRuntime`). */
|
|
425
514
|
get lowerings() {
|
|
426
|
-
return this.allLowerings ??= [
|
|
515
|
+
return this.allLowerings ??= [
|
|
516
|
+
languageLowering,
|
|
517
|
+
...this.options.noLib ? [] : [standardLowering],
|
|
518
|
+
...this.options.lowerings ?? []
|
|
519
|
+
];
|
|
427
520
|
}
|
|
428
521
|
allLowerings;
|
|
429
522
|
/** What `receiver:method(...)` becomes, when Luau alone would not run it.
|
|
@@ -437,22 +530,28 @@ var Lowerer = class {
|
|
|
437
530
|
* (`console:log`), the last library loaded first. No answer leaves an
|
|
438
531
|
* ordinary Luau method call: `text:upper()` reaches Lua's own. */
|
|
439
532
|
loweredMethodCall(node) {
|
|
533
|
+
return this.loweredMethod(node.method.name, this.options.types?.methodSources.get(node), node.object, node.arguments, node.method, node);
|
|
534
|
+
}
|
|
535
|
+
/** `loweredMethodCall`, for a method named `method` of what `object`
|
|
536
|
+
* holds — called with `:` on it, or read off it with `.` and handed its
|
|
537
|
+
* `self` like any other argument. `args` are the arguments after the
|
|
538
|
+
* receiver; a read that is not called has none. */
|
|
539
|
+
loweredMethod(method, source, object2, args, name, node) {
|
|
440
540
|
const lowerings = this.lowerings;
|
|
441
|
-
const source = this.options.types?.methodSources.get(node);
|
|
442
541
|
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();
|
|
443
542
|
if (!asked.some((i) => lowerings[i].plugin.methodCall)) return void 0;
|
|
444
|
-
const receiver = this.options.types?.typeOf.get(
|
|
445
|
-
const receiverGlobal = this.globalName(
|
|
446
|
-
const info = this.lazyArguments(
|
|
543
|
+
const receiver = this.options.types?.typeOf.get(object2);
|
|
544
|
+
const receiverGlobal = this.globalName(object2);
|
|
545
|
+
const info = this.lazyArguments(args);
|
|
447
546
|
for (const i of asked) {
|
|
448
|
-
const
|
|
449
|
-
if (!
|
|
450
|
-
const answer =
|
|
451
|
-
method
|
|
547
|
+
const plugin3 = lowerings[i].plugin;
|
|
548
|
+
if (!plugin3.methodCall) continue;
|
|
549
|
+
const answer = plugin3.methodCall({
|
|
550
|
+
method,
|
|
452
551
|
receiver,
|
|
453
552
|
receiverGlobal,
|
|
454
|
-
argumentCount:
|
|
455
|
-
at: this.callSite(
|
|
553
|
+
argumentCount: args.length,
|
|
554
|
+
at: this.callSite(name),
|
|
456
555
|
get arguments() {
|
|
457
556
|
return info();
|
|
458
557
|
},
|
|
@@ -476,9 +575,9 @@ var Lowerer = class {
|
|
|
476
575
|
if (name === void 0) return void 0;
|
|
477
576
|
const info = this.lazyArguments(node.arguments);
|
|
478
577
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
479
|
-
const
|
|
480
|
-
if (!
|
|
481
|
-
const answer =
|
|
578
|
+
const plugin3 = lowerings[i].plugin;
|
|
579
|
+
if (!plugin3.globalCall) continue;
|
|
580
|
+
const answer = plugin3.globalCall({
|
|
482
581
|
name,
|
|
483
582
|
at: this.callSite(node.callee),
|
|
484
583
|
get arguments() {
|
|
@@ -530,6 +629,31 @@ var Lowerer = class {
|
|
|
530
629
|
const out = [];
|
|
531
630
|
const runtimes = this.runtimeDefinitions();
|
|
532
631
|
if (this.helpers.has("concat")) this.helper("array");
|
|
632
|
+
if (this.helpers.has("concat")) this.move();
|
|
633
|
+
const move = this.helpers.get("move");
|
|
634
|
+
if (move) {
|
|
635
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
636
|
+
local function ${move}(from, first, last, at, to)
|
|
637
|
+
for i = first, last do
|
|
638
|
+
to[at + i - first] = from[i]
|
|
639
|
+
end
|
|
640
|
+
return to
|
|
641
|
+
end`).body.statements);
|
|
642
|
+
}
|
|
643
|
+
const each = this.helpers.get("each");
|
|
644
|
+
if (each) {
|
|
645
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
646
|
+
local function ${each}(source, ...)
|
|
647
|
+
if ${this.builtin("type").name}(source) == "function" then
|
|
648
|
+
return source, ...
|
|
649
|
+
end
|
|
650
|
+
local meta = ${this.builtin("getmetatable").name}(source)
|
|
651
|
+
if meta ~= nil and meta.__iter ~= nil then
|
|
652
|
+
return meta.__iter(source)
|
|
653
|
+
end
|
|
654
|
+
return ${this.builtin("next").name}, source
|
|
655
|
+
end`).body.statements);
|
|
656
|
+
}
|
|
533
657
|
const marker = this.helpers.get("array");
|
|
534
658
|
const typeOf = this.helpers.get("typeof");
|
|
535
659
|
if (marker || typeOf) {
|
|
@@ -568,19 +692,72 @@ local function ${typeOf}(value, known)
|
|
|
568
692
|
end`).body.statements);
|
|
569
693
|
}
|
|
570
694
|
}
|
|
695
|
+
const instanceOf = this.helpers.get("instanceof");
|
|
696
|
+
if (instanceOf) {
|
|
697
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
698
|
+
local function ${instanceOf}(value, class)
|
|
699
|
+
if ${this.builtin("type").name}(value) ~= "table" then
|
|
700
|
+
return false
|
|
701
|
+
end
|
|
702
|
+
local current = value.ClassObject
|
|
703
|
+
while current ~= nil do
|
|
704
|
+
if current == class then
|
|
705
|
+
return true
|
|
706
|
+
end
|
|
707
|
+
current = current.ParentClass
|
|
708
|
+
end
|
|
709
|
+
return false
|
|
710
|
+
end`).body.statements);
|
|
711
|
+
}
|
|
712
|
+
const accessors = this.helpers.get("accessors");
|
|
713
|
+
if (accessors) {
|
|
714
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
715
|
+
local function ${accessors}(target, getters, setters)
|
|
716
|
+
return ${this.builtin("setmetatable").name}(target, {
|
|
717
|
+
-- Read by a spread, which copies what the getters give.
|
|
718
|
+
__tiluaGetters = getters,
|
|
719
|
+
__index = function(self, key)
|
|
720
|
+
local get = getters[key]
|
|
721
|
+
if get ~= nil then
|
|
722
|
+
return get(self)
|
|
723
|
+
end
|
|
724
|
+
return nil
|
|
725
|
+
end,
|
|
726
|
+
__newindex = function(self, key, value)
|
|
727
|
+
local set = setters[key]
|
|
728
|
+
if set ~= nil then
|
|
729
|
+
set(self, value)
|
|
730
|
+
return
|
|
731
|
+
end
|
|
732
|
+
if getters[key] ~= nil then
|
|
733
|
+
${this.builtin("error").name}("Cannot assign to '" .. ${this.builtin("tostring").name}(key) .. "': it has a getter and no setter", 2)
|
|
734
|
+
end
|
|
735
|
+
${this.builtin("rawset").name}(self, key, value)
|
|
736
|
+
end,
|
|
737
|
+
})
|
|
738
|
+
end`).body.statements);
|
|
739
|
+
}
|
|
571
740
|
const assign2 = this.helpers.get("assign");
|
|
572
741
|
if (assign2) {
|
|
573
|
-
out.push(
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
742
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
743
|
+
local function ${assign2}(target, ...)
|
|
744
|
+
for i = 1, ${this.builtin("select").name}("#", ...) do
|
|
745
|
+
local source = (${this.builtin("select").name}(i, ...))
|
|
746
|
+
if source ~= nil then
|
|
747
|
+
for key, value in ${this.builtin("pairs").name}(source) do
|
|
748
|
+
target[key] = value
|
|
749
|
+
end
|
|
750
|
+
local meta = ${this.builtin("type").name}(source) == "table" and ${this.builtin("getmetatable").name}(source) or nil
|
|
751
|
+
local getters = ${this.builtin("type").name}(meta) == "table" and ${this.builtin("rawget").name}(meta, "__tiluaGetters") or nil
|
|
752
|
+
if getters ~= nil then
|
|
753
|
+
for key, get in ${this.builtin("pairs").name}(getters) do
|
|
754
|
+
target[key] = get(source)
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
end
|
|
758
|
+
end
|
|
759
|
+
return target
|
|
760
|
+
end`).body.statements);
|
|
584
761
|
}
|
|
585
762
|
out.push(...runtimes);
|
|
586
763
|
const concat = this.helpers.get("concat");
|
|
@@ -589,7 +766,7 @@ end`).body.statements);
|
|
|
589
766
|
local(["result"], [this.array(table([]))]),
|
|
590
767
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
591
768
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
592
|
-
callStatement(call(
|
|
769
|
+
callStatement(call(this.move(), [
|
|
593
770
|
identifier("part"),
|
|
594
771
|
number(1),
|
|
595
772
|
unary("#", identifier("part")),
|
|
@@ -606,8 +783,8 @@ end`).body.statements);
|
|
|
606
783
|
runtimeDefinitions() {
|
|
607
784
|
const out = [];
|
|
608
785
|
for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
|
|
609
|
-
const { plugin:
|
|
610
|
-
const source =
|
|
786
|
+
const { plugin: plugin3, from } = this.lowerings[index2];
|
|
787
|
+
const source = plugin3.runtime?.[runtime2];
|
|
611
788
|
if (source === void 0) {
|
|
612
789
|
this.diagnostics.push({
|
|
613
790
|
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
@@ -702,6 +879,7 @@ end`).body.statements);
|
|
|
702
879
|
names.push("default");
|
|
703
880
|
break;
|
|
704
881
|
case "ExportNamedStatement": {
|
|
882
|
+
if (statement.typeOnly) break;
|
|
705
883
|
if (statement.source) {
|
|
706
884
|
const key = resolve4(statement.source);
|
|
707
885
|
if (key === void 0) break;
|
|
@@ -785,6 +963,26 @@ end`).body.statements);
|
|
|
785
963
|
for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
|
|
786
964
|
return [...declarations, ...hoisted, ...requires, ...body];
|
|
787
965
|
}
|
|
966
|
+
/** `import("./x")`: the bundle's `import`, called with the module's name.
|
|
967
|
+
* It answers the module's exports table — what `import * as` names. */
|
|
968
|
+
importExpression(node) {
|
|
969
|
+
const context = this.options.module;
|
|
970
|
+
if (!context) {
|
|
971
|
+
this.report(node, "import() needs a bundle: build the project with @tilua/compiler");
|
|
972
|
+
return nil();
|
|
973
|
+
}
|
|
974
|
+
const source = node.source;
|
|
975
|
+
if (source.type !== "StringLiteral") {
|
|
976
|
+
this.report(source, "import() takes a string literal: which module it loads is decided when the project is built");
|
|
977
|
+
return nil();
|
|
978
|
+
}
|
|
979
|
+
const key = context.resolve(source.value, true);
|
|
980
|
+
if (key === void 0) {
|
|
981
|
+
this.report(source, `Cannot find module '${source.value}'`);
|
|
982
|
+
return nil();
|
|
983
|
+
}
|
|
984
|
+
return call(context.import ?? context.require, [string(key)]);
|
|
985
|
+
}
|
|
788
986
|
isRewritten(node) {
|
|
789
987
|
const binding = this.bindingByDeclaration.get(node);
|
|
790
988
|
return binding !== void 0 && this.rewrites.has(binding.id);
|
|
@@ -931,7 +1129,7 @@ end`).body.statements);
|
|
|
931
1129
|
case "VariableDeclaration":
|
|
932
1130
|
return this.variableDeclaration(node, "declare");
|
|
933
1131
|
case "FunctionDeclaration":
|
|
934
|
-
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: node.attributes }];
|
|
1132
|
+
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
|
|
935
1133
|
case "FunctionDeclarationStatement":
|
|
936
1134
|
return [this.functionDeclarationStatement(node)];
|
|
937
1135
|
case "ClassDeclaration":
|
|
@@ -962,8 +1160,13 @@ end`).body.statements);
|
|
|
962
1160
|
// kept — Lua has no expression statement — and it cannot run
|
|
963
1161
|
// anything, since a call is a `CallStatement` instead, so dropping
|
|
964
1162
|
// it changes nothing but the noise.
|
|
965
|
-
case "ExpressionStatement":
|
|
966
|
-
|
|
1163
|
+
case "ExpressionStatement": {
|
|
1164
|
+
let expression = node.expression;
|
|
1165
|
+
while (expression.type === "ParenthesizedExpression") expression = expression.expression;
|
|
1166
|
+
if (expression.type !== "ImportExpression") return [];
|
|
1167
|
+
const call2 = this.importExpression(expression);
|
|
1168
|
+
return call2.type === "CallExpression" ? [this.from(callStatement(call2), node)] : [];
|
|
1169
|
+
}
|
|
967
1170
|
case "DoStatement":
|
|
968
1171
|
return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
|
|
969
1172
|
case "WhileStatement":
|
|
@@ -1015,7 +1218,11 @@ end`).body.statements);
|
|
|
1015
1218
|
let source = this.expression(node.iterator);
|
|
1016
1219
|
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
1017
1220
|
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
1018
|
-
|
|
1221
|
+
let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
|
|
1222
|
+
if (this.lua51 && form.walks === "values") {
|
|
1223
|
+
const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
|
|
1224
|
+
iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
|
|
1225
|
+
}
|
|
1019
1226
|
return [genericFor(
|
|
1020
1227
|
variables,
|
|
1021
1228
|
iterators,
|
|
@@ -1095,11 +1302,12 @@ end`).body.statements);
|
|
|
1095
1302
|
const { build, accessors } = this.classHelpers;
|
|
1096
1303
|
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
1097
1304
|
return (0, import_luau_parser.parse)(`
|
|
1098
|
-
local function ${build}(base)
|
|
1305
|
+
local function ${build}(base, name)
|
|
1099
1306
|
local class = { __getters = {}, __setters = {} }
|
|
1100
1307
|
class.__index = class
|
|
1101
1308
|
class.ClassObject = class
|
|
1102
1309
|
class.ParentClass = base
|
|
1310
|
+
class.name = name
|
|
1103
1311
|
if base ~= nil then
|
|
1104
1312
|
setmetatable(class, { __index = base })
|
|
1105
1313
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
@@ -1153,7 +1361,11 @@ end
|
|
|
1153
1361
|
* table is assigned to `self`, which a module has already declared. */
|
|
1154
1362
|
classStatements(node, self, declare) {
|
|
1155
1363
|
const { build, accessors } = this.classRuntime();
|
|
1156
|
-
const
|
|
1364
|
+
const called = node.name?.name ?? (node.type === "ClassExpression" ? this.classExpressionNames.get(node) : void 0) ?? "";
|
|
1365
|
+
const create = call(identifier(build), [
|
|
1366
|
+
node.superclass ? this.reference(node.superclass) : nil(),
|
|
1367
|
+
string(called)
|
|
1368
|
+
]);
|
|
1157
1369
|
const out = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
|
|
1158
1370
|
const previous = this.classContext;
|
|
1159
1371
|
this.classContext = node;
|
|
@@ -1268,7 +1480,8 @@ end
|
|
|
1268
1480
|
},
|
|
1269
1481
|
isMethod,
|
|
1270
1482
|
func,
|
|
1271
|
-
|
|
1483
|
+
// `@native` and the like are Luau's.
|
|
1484
|
+
attributes: this.lua51 ? void 0 : node.attributes,
|
|
1272
1485
|
...spanOf(node)
|
|
1273
1486
|
};
|
|
1274
1487
|
}
|
|
@@ -1369,7 +1582,7 @@ end
|
|
|
1369
1582
|
if (element) bind(element.value, index(source, number(i + 1)), element.default);
|
|
1370
1583
|
});
|
|
1371
1584
|
if (pattern.rest) {
|
|
1372
|
-
const elements = call(
|
|
1585
|
+
const elements = call(this.move(), [
|
|
1373
1586
|
source,
|
|
1374
1587
|
number(pattern.elements.length + 1),
|
|
1375
1588
|
unary("#", source),
|
|
@@ -1480,8 +1693,15 @@ end
|
|
|
1480
1693
|
case "ArrayExpression":
|
|
1481
1694
|
return this.arrayExpression(node);
|
|
1482
1695
|
case "BinaryExpression": {
|
|
1696
|
+
if (node.operator === "??") return this.nullish(node);
|
|
1483
1697
|
const left = this.expression(node.left);
|
|
1484
1698
|
const right = this.expression(node.right);
|
|
1699
|
+
if (node.operator === "in") {
|
|
1700
|
+
return parenthesized(binary("~=", index(this.keptToOne(right), this.keptToOne(left)), nil()));
|
|
1701
|
+
}
|
|
1702
|
+
if (node.operator === "instanceof") {
|
|
1703
|
+
return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1704
|
+
}
|
|
1485
1705
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1486
1706
|
return binary(node.operator, left, right);
|
|
1487
1707
|
}
|
|
@@ -1508,6 +1728,8 @@ end
|
|
|
1508
1728
|
}
|
|
1509
1729
|
case "ParenthesizedExpression":
|
|
1510
1730
|
return parenthesized(this.expression(node.expression));
|
|
1731
|
+
case "ImportExpression":
|
|
1732
|
+
return this.importExpression(node);
|
|
1511
1733
|
// Types only: the value is the expression itself.
|
|
1512
1734
|
case "TypeAssertionExpression":
|
|
1513
1735
|
case "SatisfiesExpression":
|
|
@@ -1561,7 +1783,7 @@ end
|
|
|
1561
1783
|
const last2 = list.length - 1;
|
|
1562
1784
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1563
1785
|
}
|
|
1564
|
-
const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
|
|
1786
|
+
const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
|
|
1565
1787
|
const last = list[list.length - 1];
|
|
1566
1788
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1567
1789
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -1575,11 +1797,21 @@ end
|
|
|
1575
1797
|
/** One link of an access chain, read from `object`. */
|
|
1576
1798
|
link(node, object2) {
|
|
1577
1799
|
switch (node.type) {
|
|
1578
|
-
case "MemberExpression":
|
|
1800
|
+
case "MemberExpression": {
|
|
1801
|
+
if (this.options.types?.lengths.has(node)) return unary("#", object2);
|
|
1802
|
+
const source = this.options.types?.methodReads.get(node);
|
|
1803
|
+
const lowered = source && this.loweredMethod(node.property.name, source, node.object, [], node.property, node);
|
|
1804
|
+
if (lowered) {
|
|
1805
|
+
const forwarded = call(calleePath(lowered.callee), [...lowered.prepend, vararg()]);
|
|
1806
|
+
return functionExpression(functionBody(lowered.passReceiver ? [] : ["_"], [returns([forwarded])], true));
|
|
1807
|
+
}
|
|
1579
1808
|
return member(object2, node.property.name);
|
|
1809
|
+
}
|
|
1580
1810
|
case "IndexExpression":
|
|
1581
1811
|
return index(object2, this.expression(node.index));
|
|
1582
1812
|
case "CallExpression": {
|
|
1813
|
+
const method = this.methodCalled(node);
|
|
1814
|
+
if (method) return method;
|
|
1583
1815
|
const lowered = this.loweredGlobalCall(node);
|
|
1584
1816
|
const args = this.arguments(node.arguments);
|
|
1585
1817
|
if (!lowered) return call(object2, args);
|
|
@@ -1602,6 +1834,24 @@ end
|
|
|
1602
1834
|
}
|
|
1603
1835
|
}
|
|
1604
1836
|
}
|
|
1837
|
+
/** `names.filter(names, f)`: a method of the value's metatable, read with
|
|
1838
|
+
* `.` and handed its `self` as the first argument. Written as a call of
|
|
1839
|
+
* the function the method really is — `array.filter(names, f)` — where
|
|
1840
|
+
* whoever declared the metatable says so; `undefined` leaves an ordinary
|
|
1841
|
+
* call, which is right for a string's Lua methods (`text.upper(text)`
|
|
1842
|
+
* reaches Lua's own `string.upper`). */
|
|
1843
|
+
methodCalled(node) {
|
|
1844
|
+
const callee = node.callee;
|
|
1845
|
+
if (callee.type !== "MemberExpression") return void 0;
|
|
1846
|
+
const source = this.options.types?.methodReads.get(callee);
|
|
1847
|
+
if (!source) return void 0;
|
|
1848
|
+
const spreads = node.arguments.map((a, i) => a.type === "SpreadElement" ? i : -1).filter((i) => i >= 0);
|
|
1849
|
+
if (!node.arguments.length || spreads.includes(0) || spreads.some((i) => i !== node.arguments.length - 1)) return void 0;
|
|
1850
|
+
const lowered = this.loweredMethod(callee.property.name, source, callee.object, node.arguments.slice(1), callee.property, node);
|
|
1851
|
+
if (!lowered) return void 0;
|
|
1852
|
+
const [self, ...rest] = this.arguments(node.arguments);
|
|
1853
|
+
return call(calleePath(lowered.callee), [...lowered.prepend, ...lowered.passReceiver ? [self] : [], ...rest]);
|
|
1854
|
+
}
|
|
1605
1855
|
// --------------------------------------------------------
|
|
1606
1856
|
// Optional chains
|
|
1607
1857
|
// --------------------------------------------------------
|
|
@@ -1647,7 +1897,7 @@ end
|
|
|
1647
1897
|
const segments = this.segments(chain);
|
|
1648
1898
|
const base = this.expression(chain.base);
|
|
1649
1899
|
const last = chain.links[chain.links.length - 1];
|
|
1650
|
-
if (base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
1900
|
+
if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
1651
1901
|
return {
|
|
1652
1902
|
type: "IfElseExpression",
|
|
1653
1903
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -1668,6 +1918,32 @@ end
|
|
|
1668
1918
|
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1669
1919
|
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1670
1920
|
}
|
|
1921
|
+
/** `a ?? b`: `a`, or `b` where `a` is nil — read only then. A `false`
|
|
1922
|
+
* stays, which is what sets it apart from `a or b`. A name is tested
|
|
1923
|
+
* in place; anything else is read once, into a local. */
|
|
1924
|
+
nullish(node) {
|
|
1925
|
+
const left = this.keptToOne(this.expression(node.left));
|
|
1926
|
+
const right = this.keptToOne(this.expression(node.right));
|
|
1927
|
+
if (!this.lua51 && left.type === "Identifier") {
|
|
1928
|
+
return {
|
|
1929
|
+
type: "IfElseExpression",
|
|
1930
|
+
clauses: [{ condition: binary("~=", left, nil()), body: left }],
|
|
1931
|
+
alternate: right,
|
|
1932
|
+
line: left.line,
|
|
1933
|
+
column: left.column
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
const ref = this.names.fresh("value");
|
|
1937
|
+
const at = identifier(ref);
|
|
1938
|
+
const statements = [
|
|
1939
|
+
local([ref], [left]),
|
|
1940
|
+
ifThen(binary("~=", at, nil()), [returns([at])]),
|
|
1941
|
+
returns([right])
|
|
1942
|
+
];
|
|
1943
|
+
const passesVarargs = statements.some(usesVararg);
|
|
1944
|
+
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1945
|
+
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1946
|
+
}
|
|
1671
1947
|
/** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
|
|
1672
1948
|
interpolatedString(node) {
|
|
1673
1949
|
let format = "";
|
|
@@ -1749,12 +2025,12 @@ end
|
|
|
1749
2025
|
}));
|
|
1750
2026
|
const alternate = this.expression(node.alternate);
|
|
1751
2027
|
if (clauses.every((c) => alwaysTruthy(c.body))) {
|
|
1752
|
-
let
|
|
2028
|
+
let result2 = alternate;
|
|
1753
2029
|
for (let i = clauses.length - 1; i >= 0; i--) {
|
|
1754
2030
|
const { condition, body } = clauses[i];
|
|
1755
|
-
|
|
2031
|
+
result2 = binary("or", binary("and", condition, body), result2);
|
|
1756
2032
|
}
|
|
1757
|
-
return parenthesized(
|
|
2033
|
+
return parenthesized(result2);
|
|
1758
2034
|
}
|
|
1759
2035
|
const statement = {
|
|
1760
2036
|
type: "IfStatement",
|
|
@@ -1811,11 +2087,20 @@ end
|
|
|
1811
2087
|
current = [];
|
|
1812
2088
|
parts.push(this.expression(f.argument));
|
|
1813
2089
|
break;
|
|
2090
|
+
case "TableFieldAccessor":
|
|
2091
|
+
break;
|
|
1814
2092
|
}
|
|
1815
2093
|
}
|
|
1816
|
-
|
|
1817
|
-
if (
|
|
1818
|
-
|
|
2094
|
+
let made;
|
|
2095
|
+
if (parts.length === 0) made = table(current);
|
|
2096
|
+
else {
|
|
2097
|
+
if (current.length) parts.push(table(current));
|
|
2098
|
+
made = call(this.helper("assign"), [table([]), ...parts]);
|
|
2099
|
+
}
|
|
2100
|
+
const accessors = node.fields.filter((f) => f.type === "TableFieldAccessor");
|
|
2101
|
+
if (!accessors.length) return made;
|
|
2102
|
+
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)))));
|
|
2103
|
+
return call(this.helper("accessors"), [made, table2("get"), table2("set")]);
|
|
1819
2104
|
}
|
|
1820
2105
|
/** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
|
|
1821
2106
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
@@ -2072,7 +2357,7 @@ var CLASS_METAMETHODS = [
|
|
|
2072
2357
|
];
|
|
2073
2358
|
|
|
2074
2359
|
// src/linemap.ts
|
|
2075
|
-
var
|
|
2360
|
+
var import_parser6 = require("@tilua/parser");
|
|
2076
2361
|
function statementsOf(node) {
|
|
2077
2362
|
const out = [];
|
|
2078
2363
|
const visit = (value) => {
|
|
@@ -2105,7 +2390,7 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
2105
2390
|
}
|
|
2106
2391
|
function lineMapSource(map) {
|
|
2107
2392
|
const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2108
|
-
const quoted = (s) => (0,
|
|
2393
|
+
const quoted = (s) => (0, import_parser6.luauString)(s.replace(/\\/g, "/"));
|
|
2109
2394
|
return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
|
|
2110
2395
|
}
|
|
2111
2396
|
|
|
@@ -2117,15 +2402,15 @@ async function loadLowerings(modules) {
|
|
|
2117
2402
|
for (const module2 of modules) {
|
|
2118
2403
|
try {
|
|
2119
2404
|
const loaded = await import((0, import_node_url.pathToFileURL)(module2.file).href);
|
|
2120
|
-
const
|
|
2121
|
-
if (!
|
|
2405
|
+
const plugin3 = loaded.default;
|
|
2406
|
+
if (!plugin3 || typeof plugin3 !== "object") {
|
|
2122
2407
|
problems.push({
|
|
2123
2408
|
file: module2.file,
|
|
2124
2409
|
message: `'${module2.from}' lowering module has no default export`
|
|
2125
2410
|
});
|
|
2126
2411
|
continue;
|
|
2127
2412
|
}
|
|
2128
|
-
lowerings.push({ plugin:
|
|
2413
|
+
lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
|
|
2129
2414
|
} catch (error) {
|
|
2130
2415
|
problems.push({
|
|
2131
2416
|
file: module2.file,
|
|
@@ -2152,16 +2437,17 @@ async function bundle(options) {
|
|
|
2152
2437
|
const file = queue.shift();
|
|
2153
2438
|
if (sources.has(file)) continue;
|
|
2154
2439
|
const parsed = parseFile(file, diagnostics, directives);
|
|
2155
|
-
if (!parsed) return { modules: [], diagnostics };
|
|
2440
|
+
if (!parsed) return { files: [], modules: [], diagnostics };
|
|
2156
2441
|
const { program: program3, text } = parsed;
|
|
2157
|
-
const scopes = (0,
|
|
2442
|
+
const scopes = (0, import_parser7.analyzeScopes)(program3, { noLib: config?.noLib });
|
|
2158
2443
|
for (const d of scopes.diagnostics) {
|
|
2159
2444
|
diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
|
|
2160
2445
|
}
|
|
2161
2446
|
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2447
|
+
const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
|
|
2448
|
+
for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
|
|
2449
|
+
const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config);
|
|
2450
|
+
if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
|
|
2165
2451
|
}
|
|
2166
2452
|
}
|
|
2167
2453
|
const analysis = analyzeModules([...sources.values()], config);
|
|
@@ -2173,6 +2459,9 @@ async function bundle(options) {
|
|
|
2173
2459
|
const names = Names.from(...[...sources.values()].map((s) => s.program));
|
|
2174
2460
|
const G = names.fresh("G");
|
|
2175
2461
|
const requireExpression = member(identifier(G), "require");
|
|
2462
|
+
const importExpression = member(identifier(G), "import");
|
|
2463
|
+
const mode = options.output ?? config?.output ?? "single";
|
|
2464
|
+
const target = options.target ?? config?.target ?? "luau";
|
|
2176
2465
|
const modules = /* @__PURE__ */ new Map();
|
|
2177
2466
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
2178
2467
|
const pending = [entry];
|
|
@@ -2181,104 +2470,228 @@ async function bundle(options) {
|
|
|
2181
2470
|
if (modules.has(file)) continue;
|
|
2182
2471
|
const source = sources.get(file);
|
|
2183
2472
|
const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
|
|
2184
|
-
const
|
|
2473
|
+
const imports = /* @__PURE__ */ new Set();
|
|
2474
|
+
const dynamicImports = /* @__PURE__ */ new Set();
|
|
2475
|
+
const lowered2 = lower(source.program, source.scopes, {
|
|
2185
2476
|
names,
|
|
2186
2477
|
types: analysis.types.get(file),
|
|
2187
2478
|
lowerings,
|
|
2188
|
-
target
|
|
2479
|
+
target,
|
|
2480
|
+
noLib: config?.noLib,
|
|
2189
2481
|
source: source.text,
|
|
2190
2482
|
file: shown,
|
|
2191
2483
|
module: {
|
|
2192
2484
|
name: source.name,
|
|
2193
2485
|
require: requireExpression,
|
|
2194
|
-
|
|
2486
|
+
import: importExpression,
|
|
2487
|
+
// One file has one line map. Several have one each, and a
|
|
2488
|
+
// module reads the one of the file it is in.
|
|
2489
|
+
lines: mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
|
|
2195
2490
|
fail: `${G}.fail`,
|
|
2196
2491
|
arrays: `${G}.arrays`,
|
|
2197
|
-
resolve: (specifier) => {
|
|
2198
|
-
const
|
|
2199
|
-
if (!
|
|
2200
|
-
pending.push(
|
|
2201
|
-
|
|
2492
|
+
resolve: (specifier, dynamic) => {
|
|
2493
|
+
const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
|
|
2494
|
+
if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
|
|
2495
|
+
pending.push(found);
|
|
2496
|
+
const name = sources.get(found).name;
|
|
2497
|
+
(dynamic ? dynamicImports : imports).add(name);
|
|
2498
|
+
return name;
|
|
2202
2499
|
}
|
|
2203
2500
|
}
|
|
2204
2501
|
});
|
|
2205
|
-
for (const d of
|
|
2206
|
-
for (const statement of allStatements(
|
|
2207
|
-
const line =
|
|
2502
|
+
for (const d of lowered2.diagnostics) diagnostics.push({ file, ...d, category: "module" });
|
|
2503
|
+
for (const statement of allStatements(lowered2.statements)) {
|
|
2504
|
+
const line = lowered2.origins.get(statement);
|
|
2208
2505
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
2209
2506
|
}
|
|
2210
2507
|
modules.set(file, {
|
|
2211
2508
|
name: source.name,
|
|
2212
|
-
statements:
|
|
2213
|
-
exportsName:
|
|
2214
|
-
info:
|
|
2215
|
-
usesScriptArgs:
|
|
2509
|
+
statements: lowered2.statements,
|
|
2510
|
+
exportsName: lowered2.exportsName,
|
|
2511
|
+
info: lowered2.module,
|
|
2512
|
+
usesScriptArgs: lowered2.usesScriptArgs,
|
|
2513
|
+
imports,
|
|
2514
|
+
dynamicImports
|
|
2216
2515
|
});
|
|
2217
2516
|
}
|
|
2218
2517
|
for (const [file, fileDirectives] of directives) {
|
|
2219
2518
|
const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
|
|
2220
|
-
const { kept, unusedExpectErrors } = (0,
|
|
2519
|
+
const { kept, unusedExpectErrors } = (0, import_parser7.applyDirectives)(fileDirectives, semantic, (d) => d.line);
|
|
2221
2520
|
const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
|
|
2222
2521
|
const remaining = diagnostics.filter((d) => !suppressed.has(d));
|
|
2223
2522
|
diagnostics.length = 0;
|
|
2224
2523
|
diagnostics.push(...remaining);
|
|
2225
2524
|
if (options.typeCheck === false) continue;
|
|
2226
2525
|
for (const d of unusedExpectErrors) {
|
|
2227
|
-
diagnostics.push({ file, message:
|
|
2526
|
+
diagnostics.push({ file, message: import_parser7.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
|
|
2228
2527
|
}
|
|
2229
2528
|
}
|
|
2230
2529
|
const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2231
2530
|
const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2232
2531
|
diagnostics.length = 0;
|
|
2233
2532
|
diagnostics.push(...reported);
|
|
2234
|
-
const
|
|
2533
|
+
const lowered = [...modules.values()];
|
|
2534
|
+
const moduleNames = lowered.map((m) => m.name);
|
|
2235
2535
|
if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
|
|
2236
|
-
return { modules: moduleNames, diagnostics };
|
|
2237
|
-
}
|
|
2238
|
-
const
|
|
2536
|
+
return { files: [], modules: moduleNames, diagnostics };
|
|
2537
|
+
}
|
|
2538
|
+
const several = mode !== "single";
|
|
2539
|
+
const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
|
|
2540
|
+
const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
|
|
2541
|
+
const elsewhere = /* @__PURE__ */ new Map();
|
|
2542
|
+
for (const file of layout.slice(1)) {
|
|
2543
|
+
for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
|
|
2544
|
+
}
|
|
2545
|
+
const program2 = (0, import_luau_parser2.parse)(runtime(G, {
|
|
2546
|
+
several,
|
|
2547
|
+
target,
|
|
2548
|
+
files: elsewhere,
|
|
2549
|
+
imports: lowered.some((m) => m.dynamicImports.size > 0)
|
|
2550
|
+
}));
|
|
2239
2551
|
const assignment = program2.body.statements[1];
|
|
2240
|
-
if (
|
|
2552
|
+
if (usesScriptArgs) {
|
|
2241
2553
|
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2242
2554
|
program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
|
|
2555
|
+
if (several) program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.scriptArgs = scriptArgs`).body.statements);
|
|
2243
2556
|
}
|
|
2244
2557
|
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;
|
|
2245
2558
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2246
|
-
for (const module2 of modules
|
|
2247
|
-
|
|
2248
|
-
const fields = [];
|
|
2249
|
-
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2250
|
-
if (links.length) {
|
|
2251
|
-
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2252
|
-
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2253
|
-
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2254
|
-
]))))));
|
|
2255
|
-
}
|
|
2256
|
-
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2257
|
-
fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
|
|
2258
|
-
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
|
|
2559
|
+
for (const module2 of layout[0].modules) {
|
|
2560
|
+
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
|
|
2259
2561
|
}
|
|
2260
2562
|
const ok = names.fresh("ok");
|
|
2261
|
-
const
|
|
2563
|
+
const result2 = names.fresh("result");
|
|
2262
2564
|
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
2263
|
-
local ${ok}, ${
|
|
2264
|
-
return ${G}.require(${(0,
|
|
2565
|
+
local ${ok}, ${result2} = xpcall(function()
|
|
2566
|
+
return ${G}.require(${(0, import_parser7.luauString)(sources.get(entry).name)})
|
|
2265
2567
|
end, ${G}.fail)
|
|
2266
2568
|
if not ${ok} then
|
|
2267
|
-
error(${
|
|
2569
|
+
error(${result2}, 0)
|
|
2268
2570
|
end
|
|
2269
|
-
${modules.get(entry).info.exports ? `return ${
|
|
2571
|
+
${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
2270
2572
|
`).body.statements;
|
|
2271
2573
|
program2.body.statements.push(...entryStatements);
|
|
2272
2574
|
const map = buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins);
|
|
2273
|
-
if (map.size) {
|
|
2274
|
-
|
|
2575
|
+
if (map.size || several) {
|
|
2576
|
+
const mapStatements = [assign(
|
|
2275
2577
|
[member(identifier(G), "lines")],
|
|
2276
2578
|
[(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
|
|
2277
|
-
)
|
|
2579
|
+
)];
|
|
2580
|
+
if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
|
|
2581
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
|
|
2278
2582
|
}
|
|
2279
|
-
|
|
2583
|
+
const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
|
|
2584
|
+
for (const file of layout.slice(1)) {
|
|
2585
|
+
const body = [];
|
|
2586
|
+
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
2587
|
+
for (const module2 of file.modules) {
|
|
2588
|
+
body.push(assign(
|
|
2589
|
+
[index(member(identifier(G), "modules"), string(module2.name))],
|
|
2590
|
+
[moduleTable(module2)]
|
|
2591
|
+
));
|
|
2592
|
+
}
|
|
2593
|
+
const chunk = program([returns([functionExpression(functionBody([G], body))])]);
|
|
2594
|
+
const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
|
|
2595
|
+
body.push(...mapRegistration(G, file, lineMapSource(lines)));
|
|
2596
|
+
files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
|
|
2597
|
+
}
|
|
2598
|
+
return { code: files[0].code, files, modules: moduleNames, diagnostics };
|
|
2599
|
+
}
|
|
2600
|
+
function moduleTable(module2) {
|
|
2601
|
+
const { names: exported, links, stars } = module2.info;
|
|
2602
|
+
const fields = [];
|
|
2603
|
+
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2604
|
+
if (links.length) {
|
|
2605
|
+
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2606
|
+
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2607
|
+
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2608
|
+
]))))));
|
|
2609
|
+
}
|
|
2610
|
+
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2611
|
+
fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
|
|
2612
|
+
return table(fields);
|
|
2613
|
+
}
|
|
2614
|
+
function layoutOf(mode, entry, modules, outFile) {
|
|
2615
|
+
const extension = outFile !== void 0 && (0, import_node_path2.extname)(outFile) || ".luau";
|
|
2616
|
+
const entryStem = outFile !== void 0 ? (0, import_node_path2.basename)(outFile, (0, import_node_path2.extname)(outFile)) : import_node_path2.posix.basename(entry.name);
|
|
2617
|
+
const others = modules.filter((m) => m !== entry);
|
|
2618
|
+
if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry, ...others] }];
|
|
2619
|
+
if (mode === "modules") {
|
|
2620
|
+
const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
|
|
2621
|
+
return [entry, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
|
|
2622
|
+
}
|
|
2623
|
+
const byName = new Map(modules.map((m) => [m.name, m]));
|
|
2624
|
+
const reach = (root) => {
|
|
2625
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2626
|
+
const stack = [root];
|
|
2627
|
+
while (stack.length) {
|
|
2628
|
+
const name = stack.pop();
|
|
2629
|
+
if (seen.has(name)) continue;
|
|
2630
|
+
seen.add(name);
|
|
2631
|
+
stack.push(...byName.get(name)?.imports ?? []);
|
|
2632
|
+
}
|
|
2633
|
+
return seen;
|
|
2634
|
+
};
|
|
2635
|
+
const inEntry = reach(entry.name);
|
|
2636
|
+
const roots = [...new Set(modules.flatMap((m) => [...m.dynamicImports]))].filter((root) => !inEntry.has(root));
|
|
2637
|
+
const reached = new Map(roots.map((root) => [root, reach(root)]));
|
|
2638
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2639
|
+
for (const module2 of others) {
|
|
2640
|
+
if (inEntry.has(module2.name)) continue;
|
|
2641
|
+
const by = roots.filter((root) => reached.get(root).has(module2.name));
|
|
2642
|
+
const key = by.join("\n");
|
|
2643
|
+
if (!groups.has(key)) groups.set(key, { roots: by, modules: [] });
|
|
2644
|
+
groups.get(key).modules.push(module2);
|
|
2645
|
+
}
|
|
2646
|
+
const files = [{ path: `${entryStem}${extension}`, modules: modules.filter((m) => inEntry.has(m.name)) }];
|
|
2647
|
+
const taken = /* @__PURE__ */ new Set([entryStem]);
|
|
2648
|
+
for (const group of groups.values()) {
|
|
2649
|
+
const label = group.roots.length === 1 ? group.roots[0].replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "chunk" : "shared";
|
|
2650
|
+
let stem = `${entryStem}_${label}`;
|
|
2651
|
+
for (let n = 2; taken.has(stem); n++) stem = `${entryStem}_${label}_${n}`;
|
|
2652
|
+
taken.add(stem);
|
|
2653
|
+
files.push({ path: `${stem}${extension}`, modules: group.modules });
|
|
2654
|
+
}
|
|
2655
|
+
return files;
|
|
2656
|
+
}
|
|
2657
|
+
function loadPath(entryPath, file, target) {
|
|
2658
|
+
const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
|
|
2659
|
+
if (target === "lua51") return path;
|
|
2660
|
+
const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
|
|
2661
|
+
return bare.startsWith("../") ? bare : `./${bare}`;
|
|
2662
|
+
}
|
|
2663
|
+
function mapRegistration(G, file, map) {
|
|
2664
|
+
const extension = import_node_path2.posix.extname(file.path);
|
|
2665
|
+
const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
|
|
2666
|
+
const entry = `${G}.sources[#${G}.sources + 1]`;
|
|
2667
|
+
return (0, import_luau_parser2.parse)([
|
|
2668
|
+
`${entry} = { { ${parts.map(import_parser7.luauString).join(", ")} }, ${map}, ${(0, import_parser7.luauString)(extension.slice(1))} }`,
|
|
2669
|
+
...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
|
|
2670
|
+
].join("\n")).body.statements;
|
|
2671
|
+
}
|
|
2672
|
+
function loader(G, target) {
|
|
2673
|
+
if (target === "lua51") {
|
|
2674
|
+
return `
|
|
2675
|
+
-- From beside this file, wherever that is: its own path says where.
|
|
2676
|
+
local folder = ""
|
|
2677
|
+
if debug ~= nil and debug.getinfo ~= nil then
|
|
2678
|
+
local at = debug.getinfo(${G}.load, "S").source
|
|
2679
|
+
if at:sub(1, 1) == "@" then
|
|
2680
|
+
folder = at:match("^@(.*[/\\\\])") or ""
|
|
2681
|
+
end
|
|
2682
|
+
end
|
|
2683
|
+
local chunk, problem = loadfile(folder .. file)
|
|
2684
|
+
if chunk == nil then
|
|
2685
|
+
error(problem, 0)
|
|
2686
|
+
end
|
|
2687
|
+
chunk()(${G})`;
|
|
2688
|
+
}
|
|
2689
|
+
return `
|
|
2690
|
+
require(file)(${G})`;
|
|
2280
2691
|
}
|
|
2281
|
-
function runtime(G) {
|
|
2692
|
+
function runtime(G, options) {
|
|
2693
|
+
const { several } = options;
|
|
2694
|
+
const files = [...options.files].map(([name, file]) => `[${(0, import_parser7.luauString)(name)}] = ${(0, import_parser7.luauString)(file)}`).join(", ");
|
|
2282
2695
|
return `
|
|
2283
2696
|
local ${G}
|
|
2284
2697
|
${G} = {
|
|
@@ -2326,7 +2739,7 @@ ${G} = {
|
|
|
2326
2739
|
return message
|
|
2327
2740
|
end
|
|
2328
2741
|
local lines = ${G}.lines
|
|
2329
|
-
if lines == nil then
|
|
2742
|
+
if lines == nil ${several ? `and #${G}.sources == 0 ` : ""}then
|
|
2330
2743
|
return message .. (trace or "")
|
|
2331
2744
|
end
|
|
2332
2745
|
-- The bundle's name, as a position in a message spells it: where this
|
|
@@ -2346,6 +2759,81 @@ ${G} = {
|
|
|
2346
2759
|
chunk = info.short_src
|
|
2347
2760
|
end
|
|
2348
2761
|
end
|
|
2762
|
+
${several ? `
|
|
2763
|
+
-- A position in this file is read by this file's map. One in another
|
|
2764
|
+
-- of the bundle's files is read by that file's: the one whose name
|
|
2765
|
+
-- the position ends with, by the most folders where several do. A
|
|
2766
|
+
-- message says nothing about which file it starts in, so nothing is
|
|
2767
|
+
-- guessed from it here.
|
|
2768
|
+
local prefix = nil
|
|
2769
|
+
if chunk ~= nil then
|
|
2770
|
+
prefix = chunk:gsub("%p", "%%%0")
|
|
2771
|
+
end
|
|
2772
|
+
local function place(line)
|
|
2773
|
+
local origin = lines ~= nil and lines[tonumber(line)] or nil
|
|
2774
|
+
if origin == nil then
|
|
2775
|
+
return nil
|
|
2776
|
+
end
|
|
2777
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2778
|
+
end
|
|
2779
|
+
local function elsewhere(at, line)
|
|
2780
|
+
-- Already a place in the project.
|
|
2781
|
+
if at:sub(-6) == ".tilua" then
|
|
2782
|
+
return nil
|
|
2783
|
+
end
|
|
2784
|
+
local parts = {}
|
|
2785
|
+
for part in at:gmatch("[^/\\\\%.]+") do
|
|
2786
|
+
parts[#parts + 1] = part
|
|
2787
|
+
end
|
|
2788
|
+
local map, most, tied = nil, 0, false
|
|
2789
|
+
for _, source in ipairs(${G}.sources) do
|
|
2790
|
+
local names = source[1]
|
|
2791
|
+
local last = #parts
|
|
2792
|
+
if parts[last] == source[3] then
|
|
2793
|
+
last = last - 1
|
|
2794
|
+
end
|
|
2795
|
+
local same = 0
|
|
2796
|
+
while same < #names and same < last and names[#names - same] == parts[last - same] do
|
|
2797
|
+
same = same + 1
|
|
2798
|
+
end
|
|
2799
|
+
if same > most then
|
|
2800
|
+
map, most, tied = source[2], same, false
|
|
2801
|
+
elseif same == most then
|
|
2802
|
+
tied = true
|
|
2803
|
+
end
|
|
2804
|
+
end
|
|
2805
|
+
-- Two files it could as well be: a wrong place is worse than none.
|
|
2806
|
+
local origin = map ~= nil and not tied and map[tonumber(line)] or nil
|
|
2807
|
+
if origin == nil then
|
|
2808
|
+
return nil
|
|
2809
|
+
end
|
|
2810
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2811
|
+
end
|
|
2812
|
+
local mapped = message
|
|
2813
|
+
if prefix ~= nil then
|
|
2814
|
+
mapped = mapped:gsub(prefix .. ":(%d+)", function(line)
|
|
2815
|
+
return place(line)
|
|
2816
|
+
end)
|
|
2817
|
+
end
|
|
2818
|
+
mapped = mapped:gsub("(%S+):(%d+)", elsewhere)
|
|
2819
|
+
local frames = {}
|
|
2820
|
+
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2821
|
+
local at = nil
|
|
2822
|
+
if prefix ~= nil then
|
|
2823
|
+
local line = frame:match(prefix .. ":(%d+)")
|
|
2824
|
+
if line ~= nil then
|
|
2825
|
+
at = place(line)
|
|
2826
|
+
end
|
|
2827
|
+
end
|
|
2828
|
+
if at == nil then
|
|
2829
|
+
for source, line in frame:gmatch("(%S+):(%d+)") do
|
|
2830
|
+
at = elsewhere(source, line)
|
|
2831
|
+
if at ~= nil then
|
|
2832
|
+
break
|
|
2833
|
+
end
|
|
2834
|
+
end
|
|
2835
|
+
end
|
|
2836
|
+
if at ~= nil then` : `
|
|
2349
2837
|
if chunk == nil then
|
|
2350
2838
|
chunk = message:match("^(.-):%d+:")
|
|
2351
2839
|
end
|
|
@@ -2367,8 +2855,9 @@ ${G} = {
|
|
|
2367
2855
|
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2368
2856
|
local line = frame:match(prefix .. ":(%d+)")
|
|
2369
2857
|
local at = line ~= nil and place(line) or nil
|
|
2370
|
-
if at ~= nil then
|
|
2371
|
-
|
|
2858
|
+
if at ~= nil then`}
|
|
2859
|
+
-- Luau writes \`function name\`; Lua 5.1, \`function 'name'\`.
|
|
2860
|
+
local name = frame:match("function ([%w_.:]+)") or frame:match("function '([^']+)'")
|
|
2372
2861
|
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2373
2862
|
end
|
|
2374
2863
|
end
|
|
@@ -2377,13 +2866,36 @@ ${G} = {
|
|
|
2377
2866
|
end
|
|
2378
2867
|
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2379
2868
|
end,
|
|
2380
|
-
records = {}
|
|
2869
|
+
records = {},${several ? `
|
|
2870
|
+
-- The file each module outside this one is in, as \`load\` is handed it.
|
|
2871
|
+
files = { ${files} },
|
|
2872
|
+
loaded = {},
|
|
2873
|
+
-- Every file's line map: by the module, and by the file's name.
|
|
2874
|
+
maps = {},
|
|
2875
|
+
sources = {},
|
|
2876
|
+
-- Another of the bundle's files: a function that adds its modules here.
|
|
2877
|
+
load = function(file)
|
|
2878
|
+
if ${G}.loaded[file] then
|
|
2879
|
+
return
|
|
2880
|
+
end
|
|
2881
|
+
${G}.loaded[file] = true${loader(G, options.target)}
|
|
2882
|
+
end,` : ""}${options.imports ? `
|
|
2883
|
+
-- \`import("./x")\`: the module's exports, asked for where the code runs
|
|
2884
|
+
-- rather than before its module does. \`require\` does the rest \u2014 and
|
|
2885
|
+
-- loads the file the module is in, if it is not this one.
|
|
2886
|
+
import = function(name)
|
|
2887
|
+
return ${G}.require(name)
|
|
2888
|
+
end,` : ""}
|
|
2381
2889
|
require = function(name)
|
|
2382
2890
|
local record = ${G}.records[name]
|
|
2383
2891
|
if record ~= nil then
|
|
2384
2892
|
return record.exports
|
|
2385
2893
|
end
|
|
2386
|
-
local module = ${G}.modules[name]
|
|
2894
|
+
local module = ${G}.modules[name]${several ? `
|
|
2895
|
+
if module == nil then
|
|
2896
|
+
${G}.load(${G}.files[name])
|
|
2897
|
+
module = ${G}.modules[name]
|
|
2898
|
+
end` : ""}
|
|
2387
2899
|
local initialized = {}
|
|
2388
2900
|
local exports = setmetatable({}, {
|
|
2389
2901
|
__index = function(_, key)
|
|
@@ -2402,7 +2914,7 @@ ${G} = {
|
|
|
2402
2914
|
return source[link[2]]
|
|
2403
2915
|
end
|
|
2404
2916
|
if module.stars ~= nil and key ~= "default" then
|
|
2405
|
-
for _, star in module.stars do
|
|
2917
|
+
for _, star in ipairs(module.stars) do
|
|
2406
2918
|
local value = ${G}.require(star)[key]
|
|
2407
2919
|
if value ~= nil then
|
|
2408
2920
|
return value
|
|
@@ -2427,7 +2939,7 @@ ${G} = {
|
|
|
2427
2939
|
}
|
|
2428
2940
|
function stampOf(file) {
|
|
2429
2941
|
try {
|
|
2430
|
-
const at = (0,
|
|
2942
|
+
const at = (0, import_node_fs3.statSync)(file);
|
|
2431
2943
|
return `${at.size}:${at.mtimeMs}`;
|
|
2432
2944
|
} catch {
|
|
2433
2945
|
return void 0;
|
|
@@ -2443,19 +2955,19 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2443
2955
|
}
|
|
2444
2956
|
let text;
|
|
2445
2957
|
try {
|
|
2446
|
-
text = (0,
|
|
2958
|
+
text = (0, import_node_fs3.readFileSync)(file, "utf8");
|
|
2447
2959
|
} catch {
|
|
2448
2960
|
diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
|
|
2449
2961
|
return void 0;
|
|
2450
2962
|
}
|
|
2451
2963
|
try {
|
|
2452
|
-
const program2 = (0,
|
|
2453
|
-
const found = (0,
|
|
2964
|
+
const program2 = (0, import_parser7.parse)(text);
|
|
2965
|
+
const found = (0, import_parser7.directivesOf)(text);
|
|
2454
2966
|
directives.set(file, found);
|
|
2455
2967
|
if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
|
|
2456
2968
|
return { program: program2, text };
|
|
2457
2969
|
} catch (error) {
|
|
2458
|
-
if (error instanceof
|
|
2970
|
+
if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
|
|
2459
2971
|
const { line, column } = error;
|
|
2460
2972
|
diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
|
|
2461
2973
|
return void 0;
|
|
@@ -2470,14 +2982,14 @@ var libraryCache = /* @__PURE__ */ new Map();
|
|
|
2470
2982
|
function parsedLibrary(file) {
|
|
2471
2983
|
let stamp;
|
|
2472
2984
|
try {
|
|
2473
|
-
const at = (0,
|
|
2985
|
+
const at = (0, import_node_fs3.statSync)(file);
|
|
2474
2986
|
stamp = `${at.size}:${at.mtimeMs}`;
|
|
2475
2987
|
} catch {
|
|
2476
2988
|
stamp = "";
|
|
2477
2989
|
}
|
|
2478
2990
|
const cached = libraryCache.get(file);
|
|
2479
2991
|
if (cached && cached.stamp === stamp) return cached.program;
|
|
2480
|
-
const program2 = (0,
|
|
2992
|
+
const program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(file, "utf8"));
|
|
2481
2993
|
libraryCache.set(file, { stamp, program: program2 });
|
|
2482
2994
|
return program2;
|
|
2483
2995
|
}
|
|
@@ -2485,9 +2997,11 @@ var moduleAnalyses = /* @__PURE__ */ new Map();
|
|
|
2485
2997
|
function analyzeModules(modules, config) {
|
|
2486
2998
|
const out = [];
|
|
2487
2999
|
const analyses = /* @__PURE__ */ new Map();
|
|
2488
|
-
const libraries = config ? (0,
|
|
3000
|
+
const libraries = config ? (0, import_parser7.resolveTypeLibraries)(config) : { files: [], lowerings: [], problems: [] };
|
|
2489
3001
|
for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
|
|
2490
3002
|
const libs = libraries.files.map(parsedLibrary);
|
|
3003
|
+
const noLib = config?.noLib ?? false;
|
|
3004
|
+
const checked = libs.length > 0 || !noLib;
|
|
2491
3005
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
2492
3006
|
const byFile = new Map(modules.map((m) => [m.file, m]));
|
|
2493
3007
|
const exportsCache = /* @__PURE__ */ new Map();
|
|
@@ -2500,49 +3014,50 @@ function analyzeModules(modules, config) {
|
|
|
2500
3014
|
const done = analyzed.get(file);
|
|
2501
3015
|
if (done) return done;
|
|
2502
3016
|
const remembered = moduleAnalyses.get(file);
|
|
2503
|
-
if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([
|
|
3017
|
+
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)) {
|
|
2504
3018
|
analyzed.set(file, remembered.result);
|
|
2505
3019
|
readWhile.set(file, remembered.read);
|
|
2506
3020
|
return remembered.result;
|
|
2507
3021
|
}
|
|
2508
|
-
const scopes = (0,
|
|
3022
|
+
const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: checked, noLib });
|
|
2509
3023
|
const said = [];
|
|
2510
3024
|
for (const d of scopes.diagnostics) {
|
|
2511
3025
|
if (d.kind !== "undeclared") continue;
|
|
2512
3026
|
said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
|
|
2513
3027
|
}
|
|
2514
3028
|
const before = partials;
|
|
2515
|
-
const types = (0,
|
|
3029
|
+
const types = (0, import_parser7.analyzeTypes)(program2, scopes, {
|
|
2516
3030
|
libs,
|
|
2517
3031
|
resolveModule: resolverFor(file),
|
|
2518
|
-
|
|
3032
|
+
noLib,
|
|
3033
|
+
reportUnknownTypes: checked
|
|
2519
3034
|
});
|
|
2520
3035
|
for (const d of types.diagnostics) {
|
|
2521
3036
|
const at = d.node;
|
|
2522
3037
|
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
2523
3038
|
}
|
|
2524
|
-
const
|
|
3039
|
+
const result2 = { types, diagnostics: said };
|
|
2525
3040
|
if (partials === before) {
|
|
2526
|
-
analyzed.set(file,
|
|
2527
|
-
const
|
|
3041
|
+
analyzed.set(file, result2);
|
|
3042
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
2528
3043
|
const own = stampOf(file);
|
|
2529
|
-
if (own !== void 0)
|
|
2530
|
-
readWhile.set(file,
|
|
2531
|
-
if ([...
|
|
2532
|
-
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read:
|
|
3044
|
+
if (own !== void 0) read3.set(file, own);
|
|
3045
|
+
readWhile.set(file, read3);
|
|
3046
|
+
if ([...read3.values()].every((stamp) => stamp !== void 0)) {
|
|
3047
|
+
moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read3, result: result2 });
|
|
2533
3048
|
}
|
|
2534
3049
|
}
|
|
2535
|
-
return
|
|
3050
|
+
return result2;
|
|
2536
3051
|
};
|
|
2537
3052
|
const resolverFor = (file) => (specifier) => {
|
|
2538
|
-
const target = (0,
|
|
3053
|
+
const target = (0, import_parser7.resolveModulePath)(file, specifier, config);
|
|
2539
3054
|
if (!target) return void 0;
|
|
2540
3055
|
const noteRead = () => {
|
|
2541
|
-
const
|
|
2542
|
-
readWhile.set(file,
|
|
3056
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
3057
|
+
readWhile.set(file, read3);
|
|
2543
3058
|
const stamp = stampOf(target);
|
|
2544
|
-
if (stamp !== void 0)
|
|
2545
|
-
for (const [below, belowStamp] of readWhile.get(target) ?? [])
|
|
3059
|
+
if (stamp !== void 0) read3.set(target, stamp);
|
|
3060
|
+
for (const [below, belowStamp] of readWhile.get(target) ?? []) read3.set(below, belowStamp);
|
|
2546
3061
|
};
|
|
2547
3062
|
if (inProgress.has(target)) {
|
|
2548
3063
|
partials++;
|
|
@@ -2556,16 +3071,16 @@ function analyzeModules(modules, config) {
|
|
|
2556
3071
|
let program2 = byFile.get(target)?.program;
|
|
2557
3072
|
if (!program2) {
|
|
2558
3073
|
try {
|
|
2559
|
-
program2 = (0,
|
|
3074
|
+
program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(target, "utf8"));
|
|
2560
3075
|
} catch {
|
|
2561
3076
|
return void 0;
|
|
2562
3077
|
}
|
|
2563
3078
|
}
|
|
2564
3079
|
inProgress.add(target);
|
|
2565
3080
|
try {
|
|
2566
|
-
const scopes = (0,
|
|
2567
|
-
const types = byFile.has(target) ? analyzeModule(target, program2).types : (0,
|
|
2568
|
-
const exports2 = (0,
|
|
3081
|
+
const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, noLib });
|
|
3082
|
+
const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser7.analyzeTypes)(program2, scopes, { libs, noLib, resolveModule: resolverFor(target), diagnostics: false });
|
|
3083
|
+
const exports2 = (0, import_parser7.moduleExports)(program2, scopes, types, resolverFor(target));
|
|
2569
3084
|
exportsCache.set(target, exports2);
|
|
2570
3085
|
noteRead();
|
|
2571
3086
|
return exports2;
|
|
@@ -2598,37 +3113,39 @@ function allStatements(statements) {
|
|
|
2598
3113
|
}
|
|
2599
3114
|
|
|
2600
3115
|
// src/compile.ts
|
|
2601
|
-
var
|
|
3116
|
+
var import_node_fs4 = require("fs");
|
|
2602
3117
|
var import_node_path3 = require("path");
|
|
2603
|
-
var
|
|
3118
|
+
var import_parser8 = require("@tilua/parser");
|
|
2604
3119
|
var import_luau_parser3 = require("luau-parser");
|
|
2605
3120
|
async function compile(source, config) {
|
|
2606
3121
|
let program2;
|
|
2607
3122
|
try {
|
|
2608
|
-
program2 = (0,
|
|
3123
|
+
program2 = (0, import_parser8.parse)(source);
|
|
2609
3124
|
} catch (error) {
|
|
2610
|
-
if (error instanceof
|
|
3125
|
+
if (error instanceof import_parser8.ParseError || error instanceof import_parser8.LexError) {
|
|
2611
3126
|
const { line, column } = error;
|
|
2612
3127
|
return { diagnostics: [{ message: error.message, line, column }] };
|
|
2613
3128
|
}
|
|
2614
3129
|
throw error;
|
|
2615
3130
|
}
|
|
2616
|
-
const
|
|
3131
|
+
const { config: resolved, problems } = resolveConfig(config, (0, import_node_path3.resolve)("main.tilua"));
|
|
3132
|
+
const noLib = resolved?.noLib ?? false;
|
|
3133
|
+
const scopes = (0, import_parser8.analyzeScopes)(program2, { noLib });
|
|
2617
3134
|
const diagnostics = scopes.diagnostics.map((d) => ({
|
|
2618
3135
|
message: d.message,
|
|
2619
3136
|
line: d.node.line.start,
|
|
2620
3137
|
column: d.node.column.start
|
|
2621
3138
|
}));
|
|
2622
|
-
const
|
|
2623
|
-
const libraries = resolved ? (0, import_parser7.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
|
|
3139
|
+
const libraries = resolved ? (0, import_parser8.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
|
|
2624
3140
|
const { lowerings, problems: loweringProblems } = await loadLowerings(libraries.lowerings);
|
|
2625
3141
|
for (const p of [...problems, ...libraries.problems]) {
|
|
2626
3142
|
diagnostics.push({ message: p.message, line: p.line ?? 1, column: p.column ?? 1 });
|
|
2627
3143
|
}
|
|
2628
3144
|
for (const p of loweringProblems) diagnostics.push({ message: p.message, line: 1, column: 1 });
|
|
2629
|
-
const libs = libraries.files.map((file) => (0,
|
|
3145
|
+
const libs = libraries.files.map((file) => (0, import_parser8.parse)((0, import_node_fs4.readFileSync)(file, "utf8")));
|
|
2630
3146
|
const lowered = lower(program2, scopes, {
|
|
2631
|
-
types: (0,
|
|
3147
|
+
types: (0, import_parser8.analyzeTypes)(program2, scopes, { libs, noLib, diagnostics: false }),
|
|
3148
|
+
noLib,
|
|
2632
3149
|
lowerings,
|
|
2633
3150
|
target: resolved?.target,
|
|
2634
3151
|
source
|