@tilua/compiler 3.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -12
- package/dist/{chunk-VANZBLCL.js → chunk-3J2QMA4W.js} +699 -135
- package/dist/cli.cjs +764 -178
- package/dist/cli.js +32 -9
- package/dist/index.cjs +721 -156
- package/dist/index.d.cts +44 -6
- package/dist/index.d.ts +44 -6
- package/dist/index.js +6 -4
- package/package.json +2 -2
- package/runtime/array.luau +17 -9
- package/runtime/console.luau +227 -0
- package/runtime/object.luau +4 -4
- package/runtime/result.luau +122 -0
- package/runtime/string.luau +2 -2
|
@@ -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();
|
|
@@ -210,11 +281,11 @@ var Lowerer = class {
|
|
|
210
281
|
origins = /* @__PURE__ */ new WeakMap();
|
|
211
282
|
run() {
|
|
212
283
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
284
|
+
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
213
285
|
const helpers = this.helperDefinitions();
|
|
214
286
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
215
|
-
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
|
|
216
287
|
return {
|
|
217
|
-
statements: [...
|
|
288
|
+
statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
|
|
218
289
|
exportsName: this.exportsName,
|
|
219
290
|
module: this.info,
|
|
220
291
|
diagnostics: this.diagnostics,
|
|
@@ -261,14 +332,43 @@ 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) {
|
|
267
|
-
name = this.names.fresh(`tilua_${kind}`);
|
|
342
|
+
name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
|
|
268
343
|
this.helpers.set(kind, name);
|
|
269
344
|
}
|
|
270
345
|
return identifier(name);
|
|
271
346
|
}
|
|
347
|
+
// `typeof v` tells an array from an object, which Luau cannot: both are
|
|
348
|
+
// tables. So every array the program makes — `[a, b]`, a rest parameter,
|
|
349
|
+
// what `:map` answers — is written down as one, in a table that holds its
|
|
350
|
+
// keys weakly, and `typeof` looks there. A table nothing wrote down (one a
|
|
351
|
+
// Lua library made) is told by its type where that says, and by its shape
|
|
352
|
+
// where it does not.
|
|
353
|
+
/** `value`, a new array, written down as one. */
|
|
354
|
+
array(value) {
|
|
355
|
+
const marked = call(this.helper("array"), [value]);
|
|
356
|
+
this.marked.add(marked);
|
|
357
|
+
return marked;
|
|
358
|
+
}
|
|
359
|
+
/** The calls `array` made: each answers the one table it was given. */
|
|
360
|
+
marked = /* @__PURE__ */ new WeakSet();
|
|
361
|
+
/** Luau reaching the record of arrays: the bundle's, or this file's own. */
|
|
362
|
+
arrays() {
|
|
363
|
+
return this.options.module?.arrays ?? this.helper("arrays").name;
|
|
364
|
+
}
|
|
365
|
+
typeofExpression(node) {
|
|
366
|
+
const known = this.options.types?.typeofTables.get(node);
|
|
367
|
+
let argument = node.argument;
|
|
368
|
+
while (argument.type === "ParenthesizedExpression") argument = argument.expression;
|
|
369
|
+
const value = this.expression(argument);
|
|
370
|
+
return call(this.helper("typeof"), known ? [value, string(known)] : [this.keptToOne(value)]);
|
|
371
|
+
}
|
|
272
372
|
/** The local a library's runtime table gets in this file, emitting it the
|
|
273
373
|
* first time something asks. Keyed by library and by the key the library
|
|
274
374
|
* itself chose, so two libraries' tables never collide. */
|
|
@@ -358,7 +458,11 @@ var Lowerer = class {
|
|
|
358
458
|
/** The language's own lowering, then each library's, in load order. A
|
|
359
459
|
* runtime is named by its lowering's place here (`loweringRuntime`). */
|
|
360
460
|
get lowerings() {
|
|
361
|
-
return this.allLowerings ??= [
|
|
461
|
+
return this.allLowerings ??= [
|
|
462
|
+
languageLowering,
|
|
463
|
+
...this.options.noLib ? [] : [standardLowering],
|
|
464
|
+
...this.options.lowerings ?? []
|
|
465
|
+
];
|
|
362
466
|
}
|
|
363
467
|
allLowerings;
|
|
364
468
|
/** What `receiver:method(...)` becomes, when Luau alone would not run it.
|
|
@@ -372,22 +476,28 @@ var Lowerer = class {
|
|
|
372
476
|
* (`console:log`), the last library loaded first. No answer leaves an
|
|
373
477
|
* ordinary Luau method call: `text:upper()` reaches Lua's own. */
|
|
374
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) {
|
|
375
486
|
const lowerings = this.lowerings;
|
|
376
|
-
const source = this.options.types?.methodSources.get(node);
|
|
377
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();
|
|
378
488
|
if (!asked.some((i) => lowerings[i].plugin.methodCall)) return void 0;
|
|
379
|
-
const receiver = this.options.types?.typeOf.get(
|
|
380
|
-
const receiverGlobal = this.globalName(
|
|
381
|
-
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);
|
|
382
492
|
for (const i of asked) {
|
|
383
|
-
const
|
|
384
|
-
if (!
|
|
385
|
-
const answer =
|
|
386
|
-
method
|
|
493
|
+
const plugin3 = lowerings[i].plugin;
|
|
494
|
+
if (!plugin3.methodCall) continue;
|
|
495
|
+
const answer = plugin3.methodCall({
|
|
496
|
+
method,
|
|
387
497
|
receiver,
|
|
388
498
|
receiverGlobal,
|
|
389
|
-
argumentCount:
|
|
390
|
-
at: this.callSite(
|
|
499
|
+
argumentCount: args.length,
|
|
500
|
+
at: this.callSite(name),
|
|
391
501
|
get arguments() {
|
|
392
502
|
return info();
|
|
393
503
|
},
|
|
@@ -411,9 +521,9 @@ var Lowerer = class {
|
|
|
411
521
|
if (name === void 0) return void 0;
|
|
412
522
|
const info = this.lazyArguments(node.arguments);
|
|
413
523
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
414
|
-
const
|
|
415
|
-
if (!
|
|
416
|
-
const answer =
|
|
524
|
+
const plugin3 = lowerings[i].plugin;
|
|
525
|
+
if (!plugin3.globalCall) continue;
|
|
526
|
+
const answer = plugin3.globalCall({
|
|
417
527
|
name,
|
|
418
528
|
at: this.callSite(node.callee),
|
|
419
529
|
get arguments() {
|
|
@@ -463,6 +573,114 @@ var Lowerer = class {
|
|
|
463
573
|
}
|
|
464
574
|
helperDefinitions() {
|
|
465
575
|
const out = [];
|
|
576
|
+
const runtimes = this.runtimeDefinitions();
|
|
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
|
+
}
|
|
603
|
+
const marker = this.helpers.get("array");
|
|
604
|
+
const typeOf = this.helpers.get("typeof");
|
|
605
|
+
if (marker || typeOf) {
|
|
606
|
+
const arrays = this.arrays();
|
|
607
|
+
const own = this.helpers.get("arrays");
|
|
608
|
+
if (own) {
|
|
609
|
+
out.push(...parseLuau(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
|
|
610
|
+
}
|
|
611
|
+
if (marker) {
|
|
612
|
+
out.push(...parseLuau(`
|
|
613
|
+
local function ${marker}(t)
|
|
614
|
+
${arrays}[t] = true
|
|
615
|
+
return t
|
|
616
|
+
end`).body.statements);
|
|
617
|
+
}
|
|
618
|
+
if (typeOf) {
|
|
619
|
+
out.push(...parseLuau(`
|
|
620
|
+
local function ${typeOf}(value, known)
|
|
621
|
+
local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
|
|
622
|
+
if name ~= "table" then
|
|
623
|
+
return name
|
|
624
|
+
end
|
|
625
|
+
if known ~= nil then
|
|
626
|
+
return known
|
|
627
|
+
end
|
|
628
|
+
if ${arrays}[value] then
|
|
629
|
+
return "array"
|
|
630
|
+
end
|
|
631
|
+
if ${this.builtin("getmetatable").name}(value) ~= nil then
|
|
632
|
+
return "object"
|
|
633
|
+
end
|
|
634
|
+
if #value > 0 then
|
|
635
|
+
return "array"
|
|
636
|
+
end
|
|
637
|
+
return "object"
|
|
638
|
+
end`).body.statements);
|
|
639
|
+
}
|
|
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
|
+
}
|
|
466
684
|
const assign2 = this.helpers.get("assign");
|
|
467
685
|
if (assign2) {
|
|
468
686
|
out.push(localFunction(assign2, functionBody(["target"], [
|
|
@@ -477,28 +695,14 @@ var Lowerer = class {
|
|
|
477
695
|
returns([identifier("target")])
|
|
478
696
|
], true)));
|
|
479
697
|
}
|
|
480
|
-
|
|
481
|
-
const { plugin: plugin2, from } = this.lowerings[index2];
|
|
482
|
-
const source = plugin2.runtime?.[runtime2];
|
|
483
|
-
if (source === void 0) {
|
|
484
|
-
this.diagnostics.push({
|
|
485
|
-
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
486
|
-
line: 1,
|
|
487
|
-
column: 1
|
|
488
|
-
});
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
const lines = this.options.module?.lines ?? "nil";
|
|
492
|
-
const fail = this.options.module?.fail ?? "nil";
|
|
493
|
-
out.push(...parseLuau(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
|
|
494
|
-
}
|
|
698
|
+
out.push(...runtimes);
|
|
495
699
|
const concat = this.helpers.get("concat");
|
|
496
700
|
if (concat) {
|
|
497
701
|
out.push(localFunction(concat, functionBody([], [
|
|
498
|
-
local(["result"], [table([])]),
|
|
702
|
+
local(["result"], [this.array(table([]))]),
|
|
499
703
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
500
704
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
501
|
-
callStatement(call(
|
|
705
|
+
callStatement(call(this.move(), [
|
|
502
706
|
identifier("part"),
|
|
503
707
|
number(1),
|
|
504
708
|
unary("#", identifier("part")),
|
|
@@ -511,6 +715,26 @@ var Lowerer = class {
|
|
|
511
715
|
}
|
|
512
716
|
return out;
|
|
513
717
|
}
|
|
718
|
+
/** The runtime tables the file's libraries were asked for. */
|
|
719
|
+
runtimeDefinitions() {
|
|
720
|
+
const out = [];
|
|
721
|
+
for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
|
|
722
|
+
const { plugin: plugin3, from } = this.lowerings[index2];
|
|
723
|
+
const source = plugin3.runtime?.[runtime2];
|
|
724
|
+
if (source === void 0) {
|
|
725
|
+
this.diagnostics.push({
|
|
726
|
+
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
727
|
+
line: 1,
|
|
728
|
+
column: 1
|
|
729
|
+
});
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const lines = this.options.module?.lines ?? "nil";
|
|
733
|
+
const fail = this.options.module?.fail ?? "nil";
|
|
734
|
+
out.push(...parseLuau(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAY__/g, () => this.helper("array").name)).body.statements);
|
|
735
|
+
}
|
|
736
|
+
return out;
|
|
737
|
+
}
|
|
514
738
|
// --------------------------------------------------------
|
|
515
739
|
// Modules
|
|
516
740
|
// --------------------------------------------------------
|
|
@@ -591,6 +815,7 @@ var Lowerer = class {
|
|
|
591
815
|
names.push("default");
|
|
592
816
|
break;
|
|
593
817
|
case "ExportNamedStatement": {
|
|
818
|
+
if (statement.typeOnly) break;
|
|
594
819
|
if (statement.source) {
|
|
595
820
|
const key = resolve3(statement.source);
|
|
596
821
|
if (key === void 0) break;
|
|
@@ -674,6 +899,26 @@ var Lowerer = class {
|
|
|
674
899
|
for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
|
|
675
900
|
return [...declarations, ...hoisted, ...requires, ...body];
|
|
676
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
|
+
}
|
|
677
922
|
isRewritten(node) {
|
|
678
923
|
const binding = this.bindingByDeclaration.get(node);
|
|
679
924
|
return binding !== void 0 && this.rewrites.has(binding.id);
|
|
@@ -820,7 +1065,7 @@ var Lowerer = class {
|
|
|
820
1065
|
case "VariableDeclaration":
|
|
821
1066
|
return this.variableDeclaration(node, "declare");
|
|
822
1067
|
case "FunctionDeclaration":
|
|
823
|
-
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 }];
|
|
824
1069
|
case "FunctionDeclarationStatement":
|
|
825
1070
|
return [this.functionDeclarationStatement(node)];
|
|
826
1071
|
case "ClassDeclaration":
|
|
@@ -851,8 +1096,13 @@ var Lowerer = class {
|
|
|
851
1096
|
// kept — Lua has no expression statement — and it cannot run
|
|
852
1097
|
// anything, since a call is a `CallStatement` instead, so dropping
|
|
853
1098
|
// it changes nothing but the noise.
|
|
854
|
-
case "ExpressionStatement":
|
|
855
|
-
|
|
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
|
+
}
|
|
856
1106
|
case "DoStatement":
|
|
857
1107
|
return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
|
|
858
1108
|
case "WhileStatement":
|
|
@@ -904,7 +1154,11 @@ var Lowerer = class {
|
|
|
904
1154
|
let source = this.expression(node.iterator);
|
|
905
1155
|
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
906
1156
|
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
907
|
-
|
|
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
|
+
}
|
|
908
1162
|
return [genericFor(
|
|
909
1163
|
variables,
|
|
910
1164
|
iterators,
|
|
@@ -1157,7 +1411,8 @@ end
|
|
|
1157
1411
|
},
|
|
1158
1412
|
isMethod,
|
|
1159
1413
|
func,
|
|
1160
|
-
|
|
1414
|
+
// `@native` and the like are Luau's.
|
|
1415
|
+
attributes: this.lua51 ? void 0 : node.attributes,
|
|
1161
1416
|
...spanOf(node)
|
|
1162
1417
|
};
|
|
1163
1418
|
}
|
|
@@ -1258,12 +1513,12 @@ end
|
|
|
1258
1513
|
if (element) bind(element.value, index(source, number(i + 1)), element.default);
|
|
1259
1514
|
});
|
|
1260
1515
|
if (pattern.rest) {
|
|
1261
|
-
const elements = call(
|
|
1516
|
+
const elements = call(this.move(), [
|
|
1262
1517
|
source,
|
|
1263
1518
|
number(pattern.elements.length + 1),
|
|
1264
1519
|
unary("#", source),
|
|
1265
1520
|
number(1),
|
|
1266
|
-
table([])
|
|
1521
|
+
this.array(table([]))
|
|
1267
1522
|
]);
|
|
1268
1523
|
const rest = this.restTarget(pattern.rest, elements, mode, after);
|
|
1269
1524
|
after.push(...rest.then);
|
|
@@ -1317,7 +1572,7 @@ end
|
|
|
1317
1572
|
const names = [];
|
|
1318
1573
|
for (const p of params) {
|
|
1319
1574
|
if (p.rest) {
|
|
1320
|
-
prelude.push(local([this.name(p.name)], [table([{ type: "TableFieldPositional", value: vararg() }])]));
|
|
1575
|
+
prelude.push(local([this.name(p.name)], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))]));
|
|
1321
1576
|
continue;
|
|
1322
1577
|
}
|
|
1323
1578
|
const name = p.pattern ? this.names.fresh("arg") : this.name(p.name);
|
|
@@ -1369,12 +1624,20 @@ end
|
|
|
1369
1624
|
case "ArrayExpression":
|
|
1370
1625
|
return this.arrayExpression(node);
|
|
1371
1626
|
case "BinaryExpression": {
|
|
1627
|
+
if (node.operator === "??") return this.nullish(node);
|
|
1372
1628
|
const left = this.expression(node.left);
|
|
1373
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
|
+
}
|
|
1374
1636
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1375
1637
|
return binary(node.operator, left, right);
|
|
1376
1638
|
}
|
|
1377
1639
|
case "UnaryExpression":
|
|
1640
|
+
if (node.operator === "typeof") return this.typeofExpression(node);
|
|
1378
1641
|
return unary(node.operator, this.expression(node.argument));
|
|
1379
1642
|
case "SuperExpression":
|
|
1380
1643
|
return this.superClassReference(node);
|
|
@@ -1396,6 +1659,8 @@ end
|
|
|
1396
1659
|
}
|
|
1397
1660
|
case "ParenthesizedExpression":
|
|
1398
1661
|
return parenthesized(this.expression(node.expression));
|
|
1662
|
+
case "ImportExpression":
|
|
1663
|
+
return this.importExpression(node);
|
|
1399
1664
|
// Types only: the value is the expression itself.
|
|
1400
1665
|
case "TypeAssertionExpression":
|
|
1401
1666
|
case "SatisfiesExpression":
|
|
@@ -1429,7 +1694,7 @@ end
|
|
|
1429
1694
|
* kept to one there, `f((g()))`. Every value is taken only where the
|
|
1430
1695
|
* program asks for them, as an array's last element: `[g()]`. */
|
|
1431
1696
|
keptToOne(value) {
|
|
1432
|
-
return expandsToMany(value) ? parenthesized(value) : value;
|
|
1697
|
+
return expandsToMany(value) && !this.marked.has(value) ? parenthesized(value) : value;
|
|
1433
1698
|
}
|
|
1434
1699
|
/** Is this `scriptArgs` the language's own, not a name the file declared? */
|
|
1435
1700
|
isLanguageGlobal(node) {
|
|
@@ -1449,7 +1714,7 @@ end
|
|
|
1449
1714
|
const last2 = list.length - 1;
|
|
1450
1715
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1451
1716
|
}
|
|
1452
|
-
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]);
|
|
1453
1718
|
const last = list[list.length - 1];
|
|
1454
1719
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1455
1720
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -1463,11 +1728,21 @@ end
|
|
|
1463
1728
|
/** One link of an access chain, read from `object`. */
|
|
1464
1729
|
link(node, object2) {
|
|
1465
1730
|
switch (node.type) {
|
|
1466
|
-
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
|
+
}
|
|
1467
1739
|
return member(object2, node.property.name);
|
|
1740
|
+
}
|
|
1468
1741
|
case "IndexExpression":
|
|
1469
1742
|
return index(object2, this.expression(node.index));
|
|
1470
1743
|
case "CallExpression": {
|
|
1744
|
+
const method = this.methodCalled(node);
|
|
1745
|
+
if (method) return method;
|
|
1471
1746
|
const lowered = this.loweredGlobalCall(node);
|
|
1472
1747
|
const args = this.arguments(node.arguments);
|
|
1473
1748
|
if (!lowered) return call(object2, args);
|
|
@@ -1490,6 +1765,24 @@ end
|
|
|
1490
1765
|
}
|
|
1491
1766
|
}
|
|
1492
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
|
+
}
|
|
1493
1786
|
// --------------------------------------------------------
|
|
1494
1787
|
// Optional chains
|
|
1495
1788
|
// --------------------------------------------------------
|
|
@@ -1535,7 +1828,7 @@ end
|
|
|
1535
1828
|
const segments = this.segments(chain);
|
|
1536
1829
|
const base = this.expression(chain.base);
|
|
1537
1830
|
const last = chain.links[chain.links.length - 1];
|
|
1538
|
-
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") {
|
|
1539
1832
|
return {
|
|
1540
1833
|
type: "IfElseExpression",
|
|
1541
1834
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -1556,6 +1849,32 @@ end
|
|
|
1556
1849
|
const func = functionExpression(functionBody([], statements, passesVarargs));
|
|
1557
1850
|
return call(parenthesized(func), passesVarargs ? [vararg()] : []);
|
|
1558
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
|
+
}
|
|
1559
1878
|
/** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
|
|
1560
1879
|
interpolatedString(node) {
|
|
1561
1880
|
let format = "";
|
|
@@ -1637,12 +1956,12 @@ end
|
|
|
1637
1956
|
}));
|
|
1638
1957
|
const alternate = this.expression(node.alternate);
|
|
1639
1958
|
if (clauses.every((c) => alwaysTruthy(c.body))) {
|
|
1640
|
-
let
|
|
1959
|
+
let result2 = alternate;
|
|
1641
1960
|
for (let i = clauses.length - 1; i >= 0; i--) {
|
|
1642
1961
|
const { condition, body } = clauses[i];
|
|
1643
|
-
|
|
1962
|
+
result2 = binary("or", binary("and", condition, body), result2);
|
|
1644
1963
|
}
|
|
1645
|
-
return parenthesized(
|
|
1964
|
+
return parenthesized(result2);
|
|
1646
1965
|
}
|
|
1647
1966
|
const statement = {
|
|
1648
1967
|
type: "IfStatement",
|
|
@@ -1699,18 +2018,27 @@ end
|
|
|
1699
2018
|
current = [];
|
|
1700
2019
|
parts.push(this.expression(f.argument));
|
|
1701
2020
|
break;
|
|
2021
|
+
case "TableFieldAccessor":
|
|
2022
|
+
break;
|
|
1702
2023
|
}
|
|
1703
2024
|
}
|
|
1704
|
-
|
|
1705
|
-
if (
|
|
1706
|
-
|
|
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")]);
|
|
1707
2035
|
}
|
|
1708
2036
|
/** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
|
|
1709
2037
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
1710
2038
|
arrayExpression(node) {
|
|
1711
2039
|
const elements = packs(node.elements).items;
|
|
1712
2040
|
if (!elements.some((e) => e.type === "SpreadElement")) {
|
|
1713
|
-
return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
|
|
2041
|
+
return this.array(table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) }))));
|
|
1714
2042
|
}
|
|
1715
2043
|
const parts = [];
|
|
1716
2044
|
let current = [];
|
|
@@ -1967,15 +2295,15 @@ async function loadLowerings(modules) {
|
|
|
1967
2295
|
for (const module of modules) {
|
|
1968
2296
|
try {
|
|
1969
2297
|
const loaded = await import(pathToFileURL(module.file).href);
|
|
1970
|
-
const
|
|
1971
|
-
if (!
|
|
2298
|
+
const plugin3 = loaded.default;
|
|
2299
|
+
if (!plugin3 || typeof plugin3 !== "object") {
|
|
1972
2300
|
problems.push({
|
|
1973
2301
|
file: module.file,
|
|
1974
2302
|
message: `'${module.from}' lowering module has no default export`
|
|
1975
2303
|
});
|
|
1976
2304
|
continue;
|
|
1977
2305
|
}
|
|
1978
|
-
lowerings.push({ plugin:
|
|
2306
|
+
lowerings.push({ plugin: plugin3, from: module.from, library: module.library });
|
|
1979
2307
|
} catch (error) {
|
|
1980
2308
|
problems.push({
|
|
1981
2309
|
file: module.file,
|
|
@@ -1987,8 +2315,8 @@ async function loadLowerings(modules) {
|
|
|
1987
2315
|
}
|
|
1988
2316
|
|
|
1989
2317
|
// src/bundle.ts
|
|
1990
|
-
import { readFileSync as
|
|
1991
|
-
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";
|
|
1992
2320
|
import {
|
|
1993
2321
|
parse,
|
|
1994
2322
|
analyzeScopes,
|
|
@@ -1996,17 +2324,18 @@ import {
|
|
|
1996
2324
|
moduleExports,
|
|
1997
2325
|
resolveModulePath,
|
|
1998
2326
|
resolveTypeLibraries,
|
|
2327
|
+
importExpressions,
|
|
1999
2328
|
directivesOf,
|
|
2000
2329
|
applyDirectives,
|
|
2001
2330
|
UNUSED_EXPECT_ERROR,
|
|
2002
2331
|
ParseError,
|
|
2003
2332
|
LexError,
|
|
2004
|
-
luauString as
|
|
2333
|
+
luauString as luauString4
|
|
2005
2334
|
} from "@tilua/parser";
|
|
2006
2335
|
import { parse as parseLuau2, parseExpressionFromSource as parseExpressionFromSource2, print } from "luau-parser";
|
|
2007
2336
|
|
|
2008
2337
|
// src/linemap.ts
|
|
2009
|
-
import { luauString as
|
|
2338
|
+
import { luauString as luauString3 } from "@tilua/parser";
|
|
2010
2339
|
function statementsOf(node) {
|
|
2011
2340
|
const out = [];
|
|
2012
2341
|
const visit = (value) => {
|
|
@@ -2039,7 +2368,7 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
2039
2368
|
}
|
|
2040
2369
|
function lineMapSource(map) {
|
|
2041
2370
|
const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2042
|
-
const quoted = (s) =>
|
|
2371
|
+
const quoted = (s) => luauString3(s.replace(/\\/g, "/"));
|
|
2043
2372
|
return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
|
|
2044
2373
|
}
|
|
2045
2374
|
|
|
@@ -2059,16 +2388,17 @@ async function bundle(options) {
|
|
|
2059
2388
|
const file = queue.shift();
|
|
2060
2389
|
if (sources.has(file)) continue;
|
|
2061
2390
|
const parsed = parseFile(file, diagnostics, directives);
|
|
2062
|
-
if (!parsed) return { modules: [], diagnostics };
|
|
2391
|
+
if (!parsed) return { files: [], modules: [], diagnostics };
|
|
2063
2392
|
const { program: program3, text } = parsed;
|
|
2064
|
-
const scopes = analyzeScopes(program3);
|
|
2393
|
+
const scopes = analyzeScopes(program3, { noLib: config?.noLib });
|
|
2065
2394
|
for (const d of scopes.diagnostics) {
|
|
2066
2395
|
diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
|
|
2067
2396
|
}
|
|
2068
2397
|
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
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);
|
|
2072
2402
|
}
|
|
2073
2403
|
}
|
|
2074
2404
|
const analysis = analyzeModules([...sources.values()], config);
|
|
@@ -2080,6 +2410,9 @@ async function bundle(options) {
|
|
|
2080
2410
|
const names = Names.from(...[...sources.values()].map((s) => s.program));
|
|
2081
2411
|
const G = names.fresh("G");
|
|
2082
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";
|
|
2083
2416
|
const modules = /* @__PURE__ */ new Map();
|
|
2084
2417
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
2085
2418
|
const pending = [entry];
|
|
@@ -2088,37 +2421,48 @@ async function bundle(options) {
|
|
|
2088
2421
|
if (modules.has(file)) continue;
|
|
2089
2422
|
const source = sources.get(file);
|
|
2090
2423
|
const shown = relative(root, file).replace(/\\/g, "/");
|
|
2091
|
-
const
|
|
2424
|
+
const imports = /* @__PURE__ */ new Set();
|
|
2425
|
+
const dynamicImports = /* @__PURE__ */ new Set();
|
|
2426
|
+
const lowered2 = lower(source.program, source.scopes, {
|
|
2092
2427
|
names,
|
|
2093
2428
|
types: analysis.types.get(file),
|
|
2094
2429
|
lowerings,
|
|
2095
|
-
target
|
|
2430
|
+
target,
|
|
2431
|
+
noLib: config?.noLib,
|
|
2096
2432
|
source: source.text,
|
|
2097
2433
|
file: shown,
|
|
2098
2434
|
module: {
|
|
2099
2435
|
name: source.name,
|
|
2100
2436
|
require: requireExpression,
|
|
2101
|
-
|
|
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)}]`,
|
|
2102
2441
|
fail: `${G}.fail`,
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2442
|
+
arrays: `${G}.arrays`,
|
|
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;
|
|
2108
2450
|
}
|
|
2109
2451
|
}
|
|
2110
2452
|
});
|
|
2111
|
-
for (const d of
|
|
2112
|
-
for (const statement of allStatements(
|
|
2113
|
-
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);
|
|
2114
2456
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
2115
2457
|
}
|
|
2116
2458
|
modules.set(file, {
|
|
2117
2459
|
name: source.name,
|
|
2118
|
-
statements:
|
|
2119
|
-
exportsName:
|
|
2120
|
-
info:
|
|
2121
|
-
usesScriptArgs:
|
|
2460
|
+
statements: lowered2.statements,
|
|
2461
|
+
exportsName: lowered2.exportsName,
|
|
2462
|
+
info: lowered2.module,
|
|
2463
|
+
usesScriptArgs: lowered2.usesScriptArgs,
|
|
2464
|
+
imports,
|
|
2465
|
+
dynamicImports
|
|
2122
2466
|
});
|
|
2123
2467
|
}
|
|
2124
2468
|
for (const [file, fileDirectives] of directives) {
|
|
@@ -2137,57 +2481,175 @@ async function bundle(options) {
|
|
|
2137
2481
|
const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
2138
2482
|
diagnostics.length = 0;
|
|
2139
2483
|
diagnostics.push(...reported);
|
|
2140
|
-
const
|
|
2484
|
+
const lowered = [...modules.values()];
|
|
2485
|
+
const moduleNames = lowered.map((m) => m.name);
|
|
2141
2486
|
if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
|
|
2142
|
-
return { modules: moduleNames, diagnostics };
|
|
2143
|
-
}
|
|
2144
|
-
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
|
+
}));
|
|
2145
2502
|
const assignment = program2.body.statements[1];
|
|
2146
|
-
if (
|
|
2503
|
+
if (usesScriptArgs) {
|
|
2147
2504
|
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
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);
|
|
2148
2507
|
}
|
|
2149
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;
|
|
2150
2509
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2151
|
-
for (const module of modules
|
|
2152
|
-
|
|
2153
|
-
const fields = [];
|
|
2154
|
-
if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
|
|
2155
|
-
if (links.length) {
|
|
2156
|
-
fields.push(field("links", table(links.map((link) => field(link.name, table([
|
|
2157
|
-
{ type: "TableFieldPositional", value: string(link.module) },
|
|
2158
|
-
...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
|
|
2159
|
-
]))))));
|
|
2160
|
-
}
|
|
2161
|
-
if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
|
|
2162
|
-
fields.push(field("load", functionExpression(functionBody([module.exportsName], module.statements, true))));
|
|
2163
|
-
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) });
|
|
2164
2512
|
}
|
|
2165
2513
|
const ok = names.fresh("ok");
|
|
2166
|
-
const
|
|
2514
|
+
const result2 = names.fresh("result");
|
|
2167
2515
|
const entryStatements = parseLuau2(`
|
|
2168
|
-
local ${ok}, ${
|
|
2169
|
-
return ${G}.require(${
|
|
2516
|
+
local ${ok}, ${result2} = xpcall(function()
|
|
2517
|
+
return ${G}.require(${luauString4(sources.get(entry).name)})
|
|
2170
2518
|
end, ${G}.fail)
|
|
2171
2519
|
if not ${ok} then
|
|
2172
|
-
error(${
|
|
2520
|
+
error(${result2}, 0)
|
|
2173
2521
|
end
|
|
2174
|
-
${modules.get(entry).info.exports ? `return ${
|
|
2522
|
+
${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
2175
2523
|
`).body.statements;
|
|
2176
2524
|
program2.body.statements.push(...entryStatements);
|
|
2177
2525
|
const map = buildLineMap(program2, parseLuau2(print(program2)), origins);
|
|
2178
|
-
if (map.size) {
|
|
2179
|
-
|
|
2526
|
+
if (map.size || several) {
|
|
2527
|
+
const mapStatements = [assign(
|
|
2180
2528
|
[member(identifier(G), "lines")],
|
|
2181
2529
|
[parseExpressionFromSource2(lineMapSource(map))]
|
|
2182
|
-
)
|
|
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);
|
|
2533
|
+
}
|
|
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}`;
|
|
2613
|
+
}
|
|
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})`;
|
|
2183
2639
|
}
|
|
2184
|
-
return
|
|
2640
|
+
return `
|
|
2641
|
+
require(file)(${G})`;
|
|
2185
2642
|
}
|
|
2186
|
-
function runtime(G) {
|
|
2643
|
+
function runtime(G, options) {
|
|
2644
|
+
const { several } = options;
|
|
2645
|
+
const files = [...options.files].map(([name, file]) => `[${luauString4(name)}] = ${luauString4(file)}`).join(", ");
|
|
2187
2646
|
return `
|
|
2188
2647
|
local ${G}
|
|
2189
2648
|
${G} = {
|
|
2190
2649
|
modules = {},
|
|
2650
|
+
-- Every array the program made, so \`typeof\` can tell one from an object.
|
|
2651
|
+
-- The keys are held weakly: being written down keeps no array alive.
|
|
2652
|
+
arrays = setmetatable({}, { __mode = "k" }),
|
|
2191
2653
|
-- Filled in just before the entry runs: bundle line -> { file, line }.
|
|
2192
2654
|
lines = nil,
|
|
2193
2655
|
-- What an error nothing caught says, in the project's files: the bundle's
|
|
@@ -2228,7 +2690,7 @@ ${G} = {
|
|
|
2228
2690
|
return message
|
|
2229
2691
|
end
|
|
2230
2692
|
local lines = ${G}.lines
|
|
2231
|
-
if lines == nil then
|
|
2693
|
+
if lines == nil ${several ? `and #${G}.sources == 0 ` : ""}then
|
|
2232
2694
|
return message .. (trace or "")
|
|
2233
2695
|
end
|
|
2234
2696
|
-- The bundle's name, as a position in a message spells it: where this
|
|
@@ -2248,6 +2710,81 @@ ${G} = {
|
|
|
2248
2710
|
chunk = info.short_src
|
|
2249
2711
|
end
|
|
2250
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` : `
|
|
2251
2788
|
if chunk == nil then
|
|
2252
2789
|
chunk = message:match("^(.-):%d+:")
|
|
2253
2790
|
end
|
|
@@ -2269,8 +2806,9 @@ ${G} = {
|
|
|
2269
2806
|
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2270
2807
|
local line = frame:match(prefix .. ":(%d+)")
|
|
2271
2808
|
local at = line ~= nil and place(line) or nil
|
|
2272
|
-
if at ~= nil then
|
|
2273
|
-
|
|
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 '([^']+)'")
|
|
2274
2812
|
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2275
2813
|
end
|
|
2276
2814
|
end
|
|
@@ -2279,13 +2817,36 @@ ${G} = {
|
|
|
2279
2817
|
end
|
|
2280
2818
|
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2281
2819
|
end,
|
|
2282
|
-
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,` : ""}
|
|
2283
2840
|
require = function(name)
|
|
2284
2841
|
local record = ${G}.records[name]
|
|
2285
2842
|
if record ~= nil then
|
|
2286
2843
|
return record.exports
|
|
2287
2844
|
end
|
|
2288
|
-
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` : ""}
|
|
2289
2850
|
local initialized = {}
|
|
2290
2851
|
local exports = setmetatable({}, {
|
|
2291
2852
|
__index = function(_, key)
|
|
@@ -2304,7 +2865,7 @@ ${G} = {
|
|
|
2304
2865
|
return source[link[2]]
|
|
2305
2866
|
end
|
|
2306
2867
|
if module.stars ~= nil and key ~= "default" then
|
|
2307
|
-
for _, star in module.stars do
|
|
2868
|
+
for _, star in ipairs(module.stars) do
|
|
2308
2869
|
local value = ${G}.require(star)[key]
|
|
2309
2870
|
if value ~= nil then
|
|
2310
2871
|
return value
|
|
@@ -2345,7 +2906,7 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2345
2906
|
}
|
|
2346
2907
|
let text;
|
|
2347
2908
|
try {
|
|
2348
|
-
text =
|
|
2909
|
+
text = readFileSync3(file, "utf8");
|
|
2349
2910
|
} catch {
|
|
2350
2911
|
diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
|
|
2351
2912
|
return void 0;
|
|
@@ -2379,7 +2940,7 @@ function parsedLibrary(file) {
|
|
|
2379
2940
|
}
|
|
2380
2941
|
const cached = libraryCache.get(file);
|
|
2381
2942
|
if (cached && cached.stamp === stamp) return cached.program;
|
|
2382
|
-
const program2 = parse(
|
|
2943
|
+
const program2 = parse(readFileSync3(file, "utf8"));
|
|
2383
2944
|
libraryCache.set(file, { stamp, program: program2 });
|
|
2384
2945
|
return program2;
|
|
2385
2946
|
}
|
|
@@ -2390,6 +2951,8 @@ function analyzeModules(modules, config) {
|
|
|
2390
2951
|
const libraries = config ? resolveTypeLibraries(config) : { files: [], lowerings: [], problems: [] };
|
|
2391
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" });
|
|
2392
2953
|
const libs = libraries.files.map(parsedLibrary);
|
|
2954
|
+
const noLib = config?.noLib ?? false;
|
|
2955
|
+
const checked = libs.length > 0 || !noLib;
|
|
2393
2956
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
2394
2957
|
const byFile = new Map(modules.map((m) => [m.file, m]));
|
|
2395
2958
|
const exportsCache = /* @__PURE__ */ new Map();
|
|
@@ -2402,12 +2965,12 @@ function analyzeModules(modules, config) {
|
|
|
2402
2965
|
const done = analyzed.get(file);
|
|
2403
2966
|
if (done) return done;
|
|
2404
2967
|
const remembered = moduleAnalyses.get(file);
|
|
2405
|
-
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)) {
|
|
2406
2969
|
analyzed.set(file, remembered.result);
|
|
2407
2970
|
readWhile.set(file, remembered.read);
|
|
2408
2971
|
return remembered.result;
|
|
2409
2972
|
}
|
|
2410
|
-
const scopes = analyzeScopes(program2, { builtinGlobals: globals, reportUndeclared:
|
|
2973
|
+
const scopes = analyzeScopes(program2, { builtinGlobals: globals, reportUndeclared: checked, noLib });
|
|
2411
2974
|
const said = [];
|
|
2412
2975
|
for (const d of scopes.diagnostics) {
|
|
2413
2976
|
if (d.kind !== "undeclared") continue;
|
|
@@ -2417,34 +2980,35 @@ function analyzeModules(modules, config) {
|
|
|
2417
2980
|
const types = analyzeTypes(program2, scopes, {
|
|
2418
2981
|
libs,
|
|
2419
2982
|
resolveModule: resolverFor(file),
|
|
2420
|
-
|
|
2983
|
+
noLib,
|
|
2984
|
+
reportUnknownTypes: checked
|
|
2421
2985
|
});
|
|
2422
2986
|
for (const d of types.diagnostics) {
|
|
2423
2987
|
const at = d.node;
|
|
2424
2988
|
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
2425
2989
|
}
|
|
2426
|
-
const
|
|
2990
|
+
const result2 = { types, diagnostics: said };
|
|
2427
2991
|
if (partials === before) {
|
|
2428
|
-
analyzed.set(file,
|
|
2429
|
-
const
|
|
2992
|
+
analyzed.set(file, result2);
|
|
2993
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
2430
2994
|
const own = stampOf(file);
|
|
2431
|
-
if (own !== void 0)
|
|
2432
|
-
readWhile.set(file,
|
|
2433
|
-
if ([...
|
|
2434
|
-
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 });
|
|
2435
2999
|
}
|
|
2436
3000
|
}
|
|
2437
|
-
return
|
|
3001
|
+
return result2;
|
|
2438
3002
|
};
|
|
2439
3003
|
const resolverFor = (file) => (specifier) => {
|
|
2440
3004
|
const target = resolveModulePath(file, specifier, config);
|
|
2441
3005
|
if (!target) return void 0;
|
|
2442
3006
|
const noteRead = () => {
|
|
2443
|
-
const
|
|
2444
|
-
readWhile.set(file,
|
|
3007
|
+
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
3008
|
+
readWhile.set(file, read3);
|
|
2445
3009
|
const stamp = stampOf(target);
|
|
2446
|
-
if (stamp !== void 0)
|
|
2447
|
-
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);
|
|
2448
3012
|
};
|
|
2449
3013
|
if (inProgress.has(target)) {
|
|
2450
3014
|
partials++;
|
|
@@ -2458,15 +3022,15 @@ function analyzeModules(modules, config) {
|
|
|
2458
3022
|
let program2 = byFile.get(target)?.program;
|
|
2459
3023
|
if (!program2) {
|
|
2460
3024
|
try {
|
|
2461
|
-
program2 = parse(
|
|
3025
|
+
program2 = parse(readFileSync3(target, "utf8"));
|
|
2462
3026
|
} catch {
|
|
2463
3027
|
return void 0;
|
|
2464
3028
|
}
|
|
2465
3029
|
}
|
|
2466
3030
|
inProgress.add(target);
|
|
2467
3031
|
try {
|
|
2468
|
-
const scopes = analyzeScopes(program2, { builtinGlobals: globals });
|
|
2469
|
-
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 });
|
|
2470
3034
|
const exports = moduleExports(program2, scopes, types, resolverFor(target));
|
|
2471
3035
|
exportsCache.set(target, exports);
|
|
2472
3036
|
noteRead();
|