@tilua/compiler 3.0.1 → 4.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 +24 -3
- package/dist/{chunk-X4DM2BX2.js → chunk-VZ7XC4BG.js} +162 -59
- package/dist/cli.cjs +185 -83
- package/dist/cli.js +1 -1
- package/dist/index.cjs +192 -90
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/runtime/array.luau +5 -5
- package/runtime/object.luau +43 -4
- package/runtime/string.luau +2 -2
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
35
35
|
// src/bundle.ts
|
|
36
36
|
var import_node_fs2 = require("fs");
|
|
37
37
|
var import_node_path2 = require("path");
|
|
38
|
-
var
|
|
38
|
+
var import_parser6 = require("@tilua/parser");
|
|
39
39
|
var import_luau_parser2 = require("luau-parser");
|
|
40
40
|
|
|
41
41
|
// src/config.ts
|
|
@@ -50,10 +50,11 @@ function resolveConfig(input, entry, cwd = process.cwd()) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// src/lower.ts
|
|
53
|
-
var
|
|
53
|
+
var import_parser4 = require("@tilua/parser");
|
|
54
54
|
var import_luau_parser = require("luau-parser");
|
|
55
55
|
|
|
56
56
|
// src/luau.ts
|
|
57
|
+
var import_parser2 = require("@tilua/parser");
|
|
57
58
|
var NO_SPAN = { line: { start: 0, end: 0 }, column: { start: 0, end: 0 } };
|
|
58
59
|
var span = () => ({ line: { ...NO_SPAN.line }, column: { ...NO_SPAN.column } });
|
|
59
60
|
var identifier = (name) => ({ type: "Identifier", name, ...span() });
|
|
@@ -61,7 +62,7 @@ var nil = () => ({ type: "NilLiteral", ...span() });
|
|
|
61
62
|
var boolean = (value) => ({ type: "BooleanLiteral", value, ...span() });
|
|
62
63
|
var number = (value, raw = String(value)) => ({ type: "NumberLiteral", value, raw, ...span() });
|
|
63
64
|
var string = (value) => ({ type: "StringLiteral", value, raw: "", ...span() });
|
|
64
|
-
var member = (object2, property) => isLuauName(property) ? { type: "MemberExpression", object: object2, property: identifier(property), ...span() } : index(object2, string(property));
|
|
65
|
+
var member = (object2, property) => (0, import_parser2.isLuauName)(property) ? { type: "MemberExpression", object: object2, property: identifier(property), ...span() } : index(object2, string(property));
|
|
65
66
|
var index = (object2, key) => ({ type: "IndexExpression", object: object2, index: key, ...span() });
|
|
66
67
|
var call = (callee, args) => ({ type: "CallExpression", callee, arguments: args, ...span() });
|
|
67
68
|
var methodCall = (object2, method, args) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args, ...span() });
|
|
@@ -70,7 +71,7 @@ var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, l
|
|
|
70
71
|
var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
|
|
71
72
|
var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
|
|
72
73
|
var table = (fields) => ({ type: "TableExpression", fields, ...span() });
|
|
73
|
-
var field = (key, value) => isLuauName(key) ? { type: "TableFieldNamed", name: identifier(key), value } : { type: "TableFieldComputed", key: string(key), value };
|
|
74
|
+
var field = (key, value) => (0, import_parser2.isLuauName)(key) ? { type: "TableFieldNamed", name: identifier(key), value } : { type: "TableFieldComputed", key: string(key), value };
|
|
74
75
|
var block = (statements) => ({ type: "Block", statements, ...span() });
|
|
75
76
|
var local = (names, init) => ({
|
|
76
77
|
type: "LocalStatement",
|
|
@@ -114,32 +115,6 @@ var functionBody = (params, statements, hasVarargs = false) => ({
|
|
|
114
115
|
});
|
|
115
116
|
var localFunction = (name, func) => ({ type: "LocalFunctionStatement", name: identifier(name), func, ...span() });
|
|
116
117
|
var program = (statements) => ({ type: "Program", body: block(statements), ...span() });
|
|
117
|
-
var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
|
|
118
|
-
"and",
|
|
119
|
-
"break",
|
|
120
|
-
"do",
|
|
121
|
-
"else",
|
|
122
|
-
"elseif",
|
|
123
|
-
"end",
|
|
124
|
-
"false",
|
|
125
|
-
"for",
|
|
126
|
-
"function",
|
|
127
|
-
"if",
|
|
128
|
-
"in",
|
|
129
|
-
"local",
|
|
130
|
-
"nil",
|
|
131
|
-
"not",
|
|
132
|
-
"or",
|
|
133
|
-
"repeat",
|
|
134
|
-
"return",
|
|
135
|
-
"then",
|
|
136
|
-
"true",
|
|
137
|
-
"until",
|
|
138
|
-
"while"
|
|
139
|
-
]);
|
|
140
|
-
function isLuauName(name) {
|
|
141
|
-
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
|
|
142
|
-
}
|
|
143
118
|
|
|
144
119
|
// src/names.ts
|
|
145
120
|
var Names = class _Names {
|
|
@@ -182,6 +157,7 @@ var Names = class _Names {
|
|
|
182
157
|
|
|
183
158
|
// src/language.ts
|
|
184
159
|
var import_node_fs = require("fs");
|
|
160
|
+
var import_parser3 = require("@tilua/parser");
|
|
185
161
|
var read = (name) => (0, import_node_fs.readFileSync)(new URL(`../runtime/${name}`, importMetaUrl), "utf8");
|
|
186
162
|
var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\w+)/g)].map((m) => m[1]));
|
|
187
163
|
var array = read("array.luau");
|
|
@@ -215,7 +191,7 @@ function objectKeys(type) {
|
|
|
215
191
|
orders.push(keys);
|
|
216
192
|
}
|
|
217
193
|
const order = JSON.stringify(orders[0] ?? []);
|
|
218
|
-
const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) =>
|
|
194
|
+
const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) => (0, import_parser3.luauString)(key)).join(", ")} }` : "nil";
|
|
219
195
|
return { known, open: String(open) };
|
|
220
196
|
}
|
|
221
197
|
var plugin = {
|
|
@@ -274,11 +250,11 @@ var Lowerer = class {
|
|
|
274
250
|
origins = /* @__PURE__ */ new WeakMap();
|
|
275
251
|
run() {
|
|
276
252
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
253
|
+
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
277
254
|
const helpers = this.helperDefinitions();
|
|
278
255
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
279
|
-
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
|
|
280
256
|
return {
|
|
281
|
-
statements: [...
|
|
257
|
+
statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
|
|
282
258
|
exportsName: this.exportsName,
|
|
283
259
|
module: this.info,
|
|
284
260
|
diagnostics: this.diagnostics,
|
|
@@ -296,7 +272,7 @@ var Lowerer = class {
|
|
|
296
272
|
// --------------------------------------------------------
|
|
297
273
|
/** A tilua name as Luau can write it: `local` is a keyword there. */
|
|
298
274
|
name(name) {
|
|
299
|
-
if (!LUAU_KEYWORDS.has(name)) return name;
|
|
275
|
+
if (!import_parser2.LUAU_KEYWORDS.has(name)) return name;
|
|
300
276
|
let renamed = this.renamed.get(name);
|
|
301
277
|
if (!renamed) {
|
|
302
278
|
renamed = this.names.fresh(`${name}_`);
|
|
@@ -328,11 +304,36 @@ var Lowerer = class {
|
|
|
328
304
|
helper(kind) {
|
|
329
305
|
let name = this.helpers.get(kind);
|
|
330
306
|
if (!name) {
|
|
331
|
-
name = this.names.fresh(`tilua_${kind}`);
|
|
307
|
+
name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
|
|
332
308
|
this.helpers.set(kind, name);
|
|
333
309
|
}
|
|
334
310
|
return identifier(name);
|
|
335
311
|
}
|
|
312
|
+
// `typeof v` tells an array from an object, which Luau cannot: both are
|
|
313
|
+
// tables. So every array the program makes — `[a, b]`, a rest parameter,
|
|
314
|
+
// what `:map` answers — is written down as one, in a table that holds its
|
|
315
|
+
// keys weakly, and `typeof` looks there. A table nothing wrote down (one a
|
|
316
|
+
// Lua library made) is told by its type where that says, and by its shape
|
|
317
|
+
// where it does not.
|
|
318
|
+
/** `value`, a new array, written down as one. */
|
|
319
|
+
array(value) {
|
|
320
|
+
const marked = call(this.helper("array"), [value]);
|
|
321
|
+
this.marked.add(marked);
|
|
322
|
+
return marked;
|
|
323
|
+
}
|
|
324
|
+
/** The calls `array` made: each answers the one table it was given. */
|
|
325
|
+
marked = /* @__PURE__ */ new WeakSet();
|
|
326
|
+
/** Luau reaching the record of arrays: the bundle's, or this file's own. */
|
|
327
|
+
arrays() {
|
|
328
|
+
return this.options.module?.arrays ?? this.helper("arrays").name;
|
|
329
|
+
}
|
|
330
|
+
typeofExpression(node) {
|
|
331
|
+
const known = this.options.types?.typeofTables.get(node);
|
|
332
|
+
let argument = node.argument;
|
|
333
|
+
while (argument.type === "ParenthesizedExpression") argument = argument.expression;
|
|
334
|
+
const value = this.expression(argument);
|
|
335
|
+
return call(this.helper("typeof"), known ? [value, string(known)] : [this.keptToOne(value)]);
|
|
336
|
+
}
|
|
336
337
|
/** The local a library's runtime table gets in this file, emitting it the
|
|
337
338
|
* first time something asks. Keyed by library and by the key the library
|
|
338
339
|
* itself chose, so two libraries' tables never collide. */
|
|
@@ -400,7 +401,7 @@ var Lowerer = class {
|
|
|
400
401
|
const type = this.options.types?.typeOf.get(argument);
|
|
401
402
|
return {
|
|
402
403
|
type,
|
|
403
|
-
typeText: type ? (0,
|
|
404
|
+
typeText: type ? (0, import_parser4.formatType)(type) : void 0,
|
|
404
405
|
source: this.writtenSource(argument),
|
|
405
406
|
declaration: declaration ? this.writtenSource(declaration) : void 0,
|
|
406
407
|
spread: argument.type === "SpreadElement"
|
|
@@ -527,6 +528,46 @@ var Lowerer = class {
|
|
|
527
528
|
}
|
|
528
529
|
helperDefinitions() {
|
|
529
530
|
const out = [];
|
|
531
|
+
const runtimes = this.runtimeDefinitions();
|
|
532
|
+
if (this.helpers.has("concat")) this.helper("array");
|
|
533
|
+
const marker = this.helpers.get("array");
|
|
534
|
+
const typeOf = this.helpers.get("typeof");
|
|
535
|
+
if (marker || typeOf) {
|
|
536
|
+
const arrays = this.arrays();
|
|
537
|
+
const own = this.helpers.get("arrays");
|
|
538
|
+
if (own) {
|
|
539
|
+
out.push(...(0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
|
|
540
|
+
}
|
|
541
|
+
if (marker) {
|
|
542
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
543
|
+
local function ${marker}(t)
|
|
544
|
+
${arrays}[t] = true
|
|
545
|
+
return t
|
|
546
|
+
end`).body.statements);
|
|
547
|
+
}
|
|
548
|
+
if (typeOf) {
|
|
549
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
550
|
+
local function ${typeOf}(value, known)
|
|
551
|
+
local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
|
|
552
|
+
if name ~= "table" then
|
|
553
|
+
return name
|
|
554
|
+
end
|
|
555
|
+
if known ~= nil then
|
|
556
|
+
return known
|
|
557
|
+
end
|
|
558
|
+
if ${arrays}[value] then
|
|
559
|
+
return "array"
|
|
560
|
+
end
|
|
561
|
+
if ${this.builtin("getmetatable").name}(value) ~= nil then
|
|
562
|
+
return "object"
|
|
563
|
+
end
|
|
564
|
+
if #value > 0 then
|
|
565
|
+
return "array"
|
|
566
|
+
end
|
|
567
|
+
return "object"
|
|
568
|
+
end`).body.statements);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
530
571
|
const assign2 = this.helpers.get("assign");
|
|
531
572
|
if (assign2) {
|
|
532
573
|
out.push(localFunction(assign2, functionBody(["target"], [
|
|
@@ -541,25 +582,11 @@ var Lowerer = class {
|
|
|
541
582
|
returns([identifier("target")])
|
|
542
583
|
], true)));
|
|
543
584
|
}
|
|
544
|
-
|
|
545
|
-
const { plugin: plugin2, from } = this.lowerings[index2];
|
|
546
|
-
const source = plugin2.runtime?.[runtime2];
|
|
547
|
-
if (source === void 0) {
|
|
548
|
-
this.diagnostics.push({
|
|
549
|
-
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
550
|
-
line: 1,
|
|
551
|
-
column: 1
|
|
552
|
-
});
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
const lines = this.options.module?.lines ?? "nil";
|
|
556
|
-
const fail = this.options.module?.fail ?? "nil";
|
|
557
|
-
out.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
|
|
558
|
-
}
|
|
585
|
+
out.push(...runtimes);
|
|
559
586
|
const concat = this.helpers.get("concat");
|
|
560
587
|
if (concat) {
|
|
561
588
|
out.push(localFunction(concat, functionBody([], [
|
|
562
|
-
local(["result"], [table([])]),
|
|
589
|
+
local(["result"], [this.array(table([]))]),
|
|
563
590
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
564
591
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
565
592
|
callStatement(call(member(this.builtin("table"), "move"), [
|
|
@@ -575,6 +602,26 @@ var Lowerer = class {
|
|
|
575
602
|
}
|
|
576
603
|
return out;
|
|
577
604
|
}
|
|
605
|
+
/** The runtime tables the file's libraries were asked for. */
|
|
606
|
+
runtimeDefinitions() {
|
|
607
|
+
const out = [];
|
|
608
|
+
for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
|
|
609
|
+
const { plugin: plugin2, from } = this.lowerings[index2];
|
|
610
|
+
const source = plugin2.runtime?.[runtime2];
|
|
611
|
+
if (source === void 0) {
|
|
612
|
+
this.diagnostics.push({
|
|
613
|
+
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
614
|
+
line: 1,
|
|
615
|
+
column: 1
|
|
616
|
+
});
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
const lines = this.options.module?.lines ?? "nil";
|
|
620
|
+
const fail = this.options.module?.fail ?? "nil";
|
|
621
|
+
out.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAY__/g, () => this.helper("array").name)).body.statements);
|
|
622
|
+
}
|
|
623
|
+
return out;
|
|
624
|
+
}
|
|
578
625
|
// --------------------------------------------------------
|
|
579
626
|
// Modules
|
|
580
627
|
// --------------------------------------------------------
|
|
@@ -1327,7 +1374,7 @@ end
|
|
|
1327
1374
|
number(pattern.elements.length + 1),
|
|
1328
1375
|
unary("#", source),
|
|
1329
1376
|
number(1),
|
|
1330
|
-
table([])
|
|
1377
|
+
this.array(table([]))
|
|
1331
1378
|
]);
|
|
1332
1379
|
const rest = this.restTarget(pattern.rest, elements, mode, after);
|
|
1333
1380
|
after.push(...rest.then);
|
|
@@ -1381,7 +1428,7 @@ end
|
|
|
1381
1428
|
const names = [];
|
|
1382
1429
|
for (const p of params) {
|
|
1383
1430
|
if (p.rest) {
|
|
1384
|
-
prelude.push(local([this.name(p.name)], [table([{ type: "TableFieldPositional", value: vararg() }])]));
|
|
1431
|
+
prelude.push(local([this.name(p.name)], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))]));
|
|
1385
1432
|
continue;
|
|
1386
1433
|
}
|
|
1387
1434
|
const name = p.pattern ? this.names.fresh("arg") : this.name(p.name);
|
|
@@ -1439,6 +1486,7 @@ end
|
|
|
1439
1486
|
return binary(node.operator, left, right);
|
|
1440
1487
|
}
|
|
1441
1488
|
case "UnaryExpression":
|
|
1489
|
+
if (node.operator === "typeof") return this.typeofExpression(node);
|
|
1442
1490
|
return unary(node.operator, this.expression(node.argument));
|
|
1443
1491
|
case "SuperExpression":
|
|
1444
1492
|
return this.superClassReference(node);
|
|
@@ -1479,6 +1527,9 @@ end
|
|
|
1479
1527
|
callValues(node) {
|
|
1480
1528
|
const chain = optionalChain(node);
|
|
1481
1529
|
if (chain) return this.optionalChainExpression(chain);
|
|
1530
|
+
if (node.type === "CallExpression" && (node.callee.type === "SuperExpression" || node.callee.type === "MemberExpression" && node.callee.object.type === "SuperExpression")) {
|
|
1531
|
+
return this.expression(node);
|
|
1532
|
+
}
|
|
1482
1533
|
const object2 = linkObject(node);
|
|
1483
1534
|
if (node.type === "CallExpression" && object2.type === "Identifier") {
|
|
1484
1535
|
return this.link(node, this.reference(object2));
|
|
@@ -1490,7 +1541,7 @@ end
|
|
|
1490
1541
|
* kept to one there, `f((g()))`. Every value is taken only where the
|
|
1491
1542
|
* program asks for them, as an array's last element: `[g()]`. */
|
|
1492
1543
|
keptToOne(value) {
|
|
1493
|
-
return expandsToMany(value) ? parenthesized(value) : value;
|
|
1544
|
+
return expandsToMany(value) && !this.marked.has(value) ? parenthesized(value) : value;
|
|
1494
1545
|
}
|
|
1495
1546
|
/** Is this `scriptArgs` the language's own, not a name the file declared? */
|
|
1496
1547
|
isLanguageGlobal(node) {
|
|
@@ -1544,7 +1595,7 @@ end
|
|
|
1544
1595
|
...args
|
|
1545
1596
|
]);
|
|
1546
1597
|
}
|
|
1547
|
-
if (!isLuauName(node.method.name)) {
|
|
1598
|
+
if (!import_parser2.isLuauName(node.method.name)) {
|
|
1548
1599
|
this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
|
|
1549
1600
|
}
|
|
1550
1601
|
return methodCall(object2, node.method.name, args);
|
|
@@ -1771,7 +1822,7 @@ end
|
|
|
1771
1822
|
arrayExpression(node) {
|
|
1772
1823
|
const elements = packs(node.elements).items;
|
|
1773
1824
|
if (!elements.some((e) => e.type === "SpreadElement")) {
|
|
1774
|
-
return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
|
|
1825
|
+
return this.array(table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) }))));
|
|
1775
1826
|
}
|
|
1776
1827
|
const parts = [];
|
|
1777
1828
|
let current = [];
|
|
@@ -1906,7 +1957,7 @@ function topLevelDeclaration(statements, name) {
|
|
|
1906
1957
|
function moduleName(key) {
|
|
1907
1958
|
const last = key.split("/").filter(Boolean).pop() ?? "module";
|
|
1908
1959
|
const cleaned = last.replace(/[^A-Za-z0-9_]/g, "_");
|
|
1909
|
-
return /^[A-Za-z_]/.test(cleaned) && !LUAU_KEYWORDS.has(cleaned) ? cleaned : `module_${cleaned}`;
|
|
1960
|
+
return /^[A-Za-z_]/.test(cleaned) && !import_parser2.LUAU_KEYWORDS.has(cleaned) ? cleaned : `module_${cleaned}`;
|
|
1910
1961
|
}
|
|
1911
1962
|
function alwaysTruthy(value) {
|
|
1912
1963
|
switch (value.type) {
|
|
@@ -2021,6 +2072,7 @@ var CLASS_METAMETHODS = [
|
|
|
2021
2072
|
];
|
|
2022
2073
|
|
|
2023
2074
|
// src/linemap.ts
|
|
2075
|
+
var import_parser5 = require("@tilua/parser");
|
|
2024
2076
|
function statementsOf(node) {
|
|
2025
2077
|
const out = [];
|
|
2026
2078
|
const visit = (value) => {
|
|
@@ -2053,7 +2105,7 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
2053
2105
|
}
|
|
2054
2106
|
function lineMapSource(map) {
|
|
2055
2107
|
const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2056
|
-
const quoted = (s) =>
|
|
2108
|
+
const quoted = (s) => (0, import_parser5.luauString)(s.replace(/\\/g, "/"));
|
|
2057
2109
|
return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
|
|
2058
2110
|
}
|
|
2059
2111
|
|
|
@@ -2102,13 +2154,13 @@ async function bundle(options) {
|
|
|
2102
2154
|
const parsed = parseFile(file, diagnostics, directives);
|
|
2103
2155
|
if (!parsed) return { modules: [], diagnostics };
|
|
2104
2156
|
const { program: program3, text } = parsed;
|
|
2105
|
-
const scopes = (0,
|
|
2157
|
+
const scopes = (0, import_parser6.analyzeScopes)(program3);
|
|
2106
2158
|
for (const d of scopes.diagnostics) {
|
|
2107
2159
|
diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
|
|
2108
2160
|
}
|
|
2109
2161
|
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2110
2162
|
for (const specifier of importedSpecifiers(program3)) {
|
|
2111
|
-
const target = (0,
|
|
2163
|
+
const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
|
|
2112
2164
|
if (target && !target.endsWith(".d.tilua")) queue.push(target);
|
|
2113
2165
|
}
|
|
2114
2166
|
}
|
|
@@ -2141,8 +2193,9 @@ async function bundle(options) {
|
|
|
2141
2193
|
require: requireExpression,
|
|
2142
2194
|
lines: `${G}.lines`,
|
|
2143
2195
|
fail: `${G}.fail`,
|
|
2196
|
+
arrays: `${G}.arrays`,
|
|
2144
2197
|
resolve: (specifier) => {
|
|
2145
|
-
const target = (0,
|
|
2198
|
+
const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
|
|
2146
2199
|
if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
|
|
2147
2200
|
pending.push(target);
|
|
2148
2201
|
return sources.get(target).name;
|
|
@@ -2164,14 +2217,14 @@ async function bundle(options) {
|
|
|
2164
2217
|
}
|
|
2165
2218
|
for (const [file, fileDirectives] of directives) {
|
|
2166
2219
|
const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
|
|
2167
|
-
const { kept, unusedExpectErrors } = (0,
|
|
2220
|
+
const { kept, unusedExpectErrors } = (0, import_parser6.applyDirectives)(fileDirectives, semantic, (d) => d.line);
|
|
2168
2221
|
const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
|
|
2169
2222
|
const remaining = diagnostics.filter((d) => !suppressed.has(d));
|
|
2170
2223
|
diagnostics.length = 0;
|
|
2171
2224
|
diagnostics.push(...remaining);
|
|
2172
2225
|
if (options.typeCheck === false) continue;
|
|
2173
2226
|
for (const d of unusedExpectErrors) {
|
|
2174
|
-
diagnostics.push({ file, message:
|
|
2227
|
+
diagnostics.push({ file, message: import_parser6.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
|
|
2175
2228
|
}
|
|
2176
2229
|
}
|
|
2177
2230
|
const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
|
|
@@ -2186,6 +2239,7 @@ async function bundle(options) {
|
|
|
2186
2239
|
const assignment = program2.body.statements[1];
|
|
2187
2240
|
if ([...modules.values()].some((m) => m.usesScriptArgs)) {
|
|
2188
2241
|
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2242
|
+
program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
|
|
2189
2243
|
}
|
|
2190
2244
|
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;
|
|
2191
2245
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
@@ -2207,7 +2261,7 @@ async function bundle(options) {
|
|
|
2207
2261
|
const result = names.fresh("result");
|
|
2208
2262
|
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
2209
2263
|
local ${ok}, ${result} = xpcall(function()
|
|
2210
|
-
return ${G}.require(${
|
|
2264
|
+
return ${G}.require(${(0, import_parser6.luauString)(sources.get(entry).name)})
|
|
2211
2265
|
end, ${G}.fail)
|
|
2212
2266
|
if not ${ok} then
|
|
2213
2267
|
error(${result}, 0)
|
|
@@ -2229,6 +2283,9 @@ function runtime(G) {
|
|
|
2229
2283
|
local ${G}
|
|
2230
2284
|
${G} = {
|
|
2231
2285
|
modules = {},
|
|
2286
|
+
-- Every array the program made, so \`typeof\` can tell one from an object.
|
|
2287
|
+
-- The keys are held weakly: being written down keeps no array alive.
|
|
2288
|
+
arrays = setmetatable({}, { __mode = "k" }),
|
|
2232
2289
|
-- Filled in just before the entry runs: bundle line -> { file, line }.
|
|
2233
2290
|
lines = nil,
|
|
2234
2291
|
-- What an error nothing caught says, in the project's files: the bundle's
|
|
@@ -2236,19 +2293,64 @@ ${G} = {
|
|
|
2236
2293
|
-- and a traceback of those places follows. A message that is not a string
|
|
2237
2294
|
-- is left as it is, and a position that is not this bundle's (a library
|
|
2238
2295
|
-- that raised its error with one already mapped) is left alone.
|
|
2296
|
+
--
|
|
2297
|
+
-- It is an \`xpcall\` handler, and a handler that fails loses the error
|
|
2298
|
+
-- altogether: Luau reports only "error in error handling". So the work is
|
|
2299
|
+
-- done under \`pcall\`, leaning on nothing it has not checked is there (an
|
|
2300
|
+
-- executor may lack \`debug.info\`, or refuse it), and an error it cannot
|
|
2301
|
+
-- map is still answered as Luau raised it, with why.
|
|
2239
2302
|
fail = function(message)
|
|
2303
|
+
-- Taken first, before anything that can fail. Level 3 is the frame
|
|
2304
|
+
-- that raised: past \`pcall\` and this handler.
|
|
2305
|
+
local trace
|
|
2306
|
+
if debug ~= nil and debug.traceback ~= nil then
|
|
2307
|
+
local ok, text = pcall(debug.traceback, "", 3)
|
|
2308
|
+
if ok and type(text) == "string" then
|
|
2309
|
+
trace = text
|
|
2310
|
+
end
|
|
2311
|
+
end
|
|
2312
|
+
local ok, described = pcall(${G}.describe, message, trace)
|
|
2313
|
+
if ok then
|
|
2314
|
+
return described
|
|
2315
|
+
end
|
|
2316
|
+
if type(message) ~= "string" then
|
|
2317
|
+
return message
|
|
2318
|
+
end
|
|
2319
|
+
local why = type(described) == "string" and described or "unknown"
|
|
2320
|
+
return message .. "\\n(tilua could not map this error to the project's files: " .. why .. ")" .. (trace or "")
|
|
2321
|
+
end,
|
|
2322
|
+
-- \`fail\`'s work. \`trace\` is a traceback from where the error was
|
|
2323
|
+
-- raised, or nil when none could be taken.
|
|
2324
|
+
describe = function(message, trace)
|
|
2240
2325
|
if type(message) ~= "string" then
|
|
2241
2326
|
return message
|
|
2242
2327
|
end
|
|
2243
2328
|
local lines = ${G}.lines
|
|
2244
2329
|
if lines == nil then
|
|
2245
|
-
return
|
|
2330
|
+
return message .. (trace or "")
|
|
2246
2331
|
end
|
|
2332
|
+
-- The bundle's name, as a position in a message spells it: where this
|
|
2333
|
+
-- function is, asked of the debug library by the function rather
|
|
2334
|
+
-- than by a level, which \`pcall\` would shift. Failing that, a message
|
|
2335
|
+
-- starts with the position it was raised at.
|
|
2247
2336
|
local chunk
|
|
2248
|
-
if debug.info ~= nil then
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2337
|
+
if debug ~= nil and debug.info ~= nil then
|
|
2338
|
+
local found, source = pcall(debug.info, ${G}.describe, "s")
|
|
2339
|
+
if found and type(source) == "string" then
|
|
2340
|
+
chunk = source
|
|
2341
|
+
end
|
|
2342
|
+
end
|
|
2343
|
+
if chunk == nil and debug ~= nil and debug.getinfo ~= nil then
|
|
2344
|
+
local found, info = pcall(debug.getinfo, ${G}.describe, "S")
|
|
2345
|
+
if found and type(info) == "table" and type(info.short_src) == "string" then
|
|
2346
|
+
chunk = info.short_src
|
|
2347
|
+
end
|
|
2348
|
+
end
|
|
2349
|
+
if chunk == nil then
|
|
2350
|
+
chunk = message:match("^(.-):%d+:")
|
|
2351
|
+
end
|
|
2352
|
+
if chunk == nil then
|
|
2353
|
+
return message .. (trace or "")
|
|
2252
2354
|
end
|
|
2253
2355
|
local prefix = chunk:gsub("%p", "%%%0")
|
|
2254
2356
|
local function place(line)
|
|
@@ -2262,7 +2364,7 @@ ${G} = {
|
|
|
2262
2364
|
return place(line)
|
|
2263
2365
|
end)
|
|
2264
2366
|
local frames = {}
|
|
2265
|
-
for frame in
|
|
2367
|
+
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2266
2368
|
local line = frame:match(prefix .. ":(%d+)")
|
|
2267
2369
|
local at = line ~= nil and place(line) or nil
|
|
2268
2370
|
if at ~= nil then
|
|
@@ -2347,13 +2449,13 @@ function parseFile(file, diagnostics, directives) {
|
|
|
2347
2449
|
return void 0;
|
|
2348
2450
|
}
|
|
2349
2451
|
try {
|
|
2350
|
-
const program2 = (0,
|
|
2351
|
-
const found = (0,
|
|
2452
|
+
const program2 = (0, import_parser6.parse)(text);
|
|
2453
|
+
const found = (0, import_parser6.directivesOf)(text);
|
|
2352
2454
|
directives.set(file, found);
|
|
2353
2455
|
if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
|
|
2354
2456
|
return { program: program2, text };
|
|
2355
2457
|
} catch (error) {
|
|
2356
|
-
if (error instanceof
|
|
2458
|
+
if (error instanceof import_parser6.ParseError || error instanceof import_parser6.LexError) {
|
|
2357
2459
|
const { line, column } = error;
|
|
2358
2460
|
diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
|
|
2359
2461
|
return void 0;
|
|
@@ -2375,7 +2477,7 @@ function parsedLibrary(file) {
|
|
|
2375
2477
|
}
|
|
2376
2478
|
const cached = libraryCache.get(file);
|
|
2377
2479
|
if (cached && cached.stamp === stamp) return cached.program;
|
|
2378
|
-
const program2 = (0,
|
|
2480
|
+
const program2 = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(file, "utf8"));
|
|
2379
2481
|
libraryCache.set(file, { stamp, program: program2 });
|
|
2380
2482
|
return program2;
|
|
2381
2483
|
}
|
|
@@ -2383,7 +2485,7 @@ var moduleAnalyses = /* @__PURE__ */ new Map();
|
|
|
2383
2485
|
function analyzeModules(modules, config) {
|
|
2384
2486
|
const out = [];
|
|
2385
2487
|
const analyses = /* @__PURE__ */ new Map();
|
|
2386
|
-
const libraries = config ? (0,
|
|
2488
|
+
const libraries = config ? (0, import_parser6.resolveTypeLibraries)(config) : { files: [], lowerings: [], problems: [] };
|
|
2387
2489
|
for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
|
|
2388
2490
|
const libs = libraries.files.map(parsedLibrary);
|
|
2389
2491
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
@@ -2403,14 +2505,14 @@ function analyzeModules(modules, config) {
|
|
|
2403
2505
|
readWhile.set(file, remembered.read);
|
|
2404
2506
|
return remembered.result;
|
|
2405
2507
|
}
|
|
2406
|
-
const scopes = (0,
|
|
2508
|
+
const scopes = (0, import_parser6.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
|
|
2407
2509
|
const said = [];
|
|
2408
2510
|
for (const d of scopes.diagnostics) {
|
|
2409
2511
|
if (d.kind !== "undeclared") continue;
|
|
2410
2512
|
said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
|
|
2411
2513
|
}
|
|
2412
2514
|
const before = partials;
|
|
2413
|
-
const types = (0,
|
|
2515
|
+
const types = (0, import_parser6.analyzeTypes)(program2, scopes, {
|
|
2414
2516
|
libs,
|
|
2415
2517
|
resolveModule: resolverFor(file),
|
|
2416
2518
|
reportUnknownTypes: libs.length > 0
|
|
@@ -2433,7 +2535,7 @@ function analyzeModules(modules, config) {
|
|
|
2433
2535
|
return result;
|
|
2434
2536
|
};
|
|
2435
2537
|
const resolverFor = (file) => (specifier) => {
|
|
2436
|
-
const target = (0,
|
|
2538
|
+
const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
|
|
2437
2539
|
if (!target) return void 0;
|
|
2438
2540
|
const noteRead = () => {
|
|
2439
2541
|
const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
@@ -2454,16 +2556,16 @@ function analyzeModules(modules, config) {
|
|
|
2454
2556
|
let program2 = byFile.get(target)?.program;
|
|
2455
2557
|
if (!program2) {
|
|
2456
2558
|
try {
|
|
2457
|
-
program2 = (0,
|
|
2559
|
+
program2 = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(target, "utf8"));
|
|
2458
2560
|
} catch {
|
|
2459
2561
|
return void 0;
|
|
2460
2562
|
}
|
|
2461
2563
|
}
|
|
2462
2564
|
inProgress.add(target);
|
|
2463
2565
|
try {
|
|
2464
|
-
const scopes = (0,
|
|
2465
|
-
const types = byFile.has(target) ? analyzeModule(target, program2).types : (0,
|
|
2466
|
-
const exports2 = (0,
|
|
2566
|
+
const scopes = (0, import_parser6.analyzeScopes)(program2, { builtinGlobals: globals });
|
|
2567
|
+
const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser6.analyzeTypes)(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
|
|
2568
|
+
const exports2 = (0, import_parser6.moduleExports)(program2, scopes, types, resolverFor(target));
|
|
2467
2569
|
exportsCache.set(target, exports2);
|
|
2468
2570
|
noteRead();
|
|
2469
2571
|
return exports2;
|
|
@@ -2498,35 +2600,35 @@ function allStatements(statements) {
|
|
|
2498
2600
|
// src/compile.ts
|
|
2499
2601
|
var import_node_fs3 = require("fs");
|
|
2500
2602
|
var import_node_path3 = require("path");
|
|
2501
|
-
var
|
|
2603
|
+
var import_parser7 = require("@tilua/parser");
|
|
2502
2604
|
var import_luau_parser3 = require("luau-parser");
|
|
2503
2605
|
async function compile(source, config) {
|
|
2504
2606
|
let program2;
|
|
2505
2607
|
try {
|
|
2506
|
-
program2 = (0,
|
|
2608
|
+
program2 = (0, import_parser7.parse)(source);
|
|
2507
2609
|
} catch (error) {
|
|
2508
|
-
if (error instanceof
|
|
2610
|
+
if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
|
|
2509
2611
|
const { line, column } = error;
|
|
2510
2612
|
return { diagnostics: [{ message: error.message, line, column }] };
|
|
2511
2613
|
}
|
|
2512
2614
|
throw error;
|
|
2513
2615
|
}
|
|
2514
|
-
const scopes = (0,
|
|
2616
|
+
const scopes = (0, import_parser7.analyzeScopes)(program2);
|
|
2515
2617
|
const diagnostics = scopes.diagnostics.map((d) => ({
|
|
2516
2618
|
message: d.message,
|
|
2517
2619
|
line: d.node.line.start,
|
|
2518
2620
|
column: d.node.column.start
|
|
2519
2621
|
}));
|
|
2520
2622
|
const { config: resolved, problems } = resolveConfig(config, (0, import_node_path3.resolve)("main.tilua"));
|
|
2521
|
-
const libraries = resolved ? (0,
|
|
2623
|
+
const libraries = resolved ? (0, import_parser7.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
|
|
2522
2624
|
const { lowerings, problems: loweringProblems } = await loadLowerings(libraries.lowerings);
|
|
2523
2625
|
for (const p of [...problems, ...libraries.problems]) {
|
|
2524
2626
|
diagnostics.push({ message: p.message, line: p.line ?? 1, column: p.column ?? 1 });
|
|
2525
2627
|
}
|
|
2526
2628
|
for (const p of loweringProblems) diagnostics.push({ message: p.message, line: 1, column: 1 });
|
|
2527
|
-
const libs = libraries.files.map((file) => (0,
|
|
2629
|
+
const libs = libraries.files.map((file) => (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(file, "utf8")));
|
|
2528
2630
|
const lowered = lower(program2, scopes, {
|
|
2529
|
-
types: (0,
|
|
2631
|
+
types: (0, import_parser7.analyzeTypes)(program2, scopes, { libs, diagnostics: false }),
|
|
2530
2632
|
lowerings,
|
|
2531
2633
|
target: resolved?.target,
|
|
2532
2634
|
source
|
package/dist/index.d.cts
CHANGED
|
@@ -100,6 +100,9 @@ interface ModuleContext {
|
|
|
100
100
|
* reads, as `__FAIL__`, to report an error from a callback the way an
|
|
101
101
|
* error that reached the entry is reported. */
|
|
102
102
|
readonly fail?: string;
|
|
103
|
+
/** Luau reaching the bundle's record of which tables are arrays, so that
|
|
104
|
+
* every module marks and reads the same one (`typeof`). */
|
|
105
|
+
readonly arrays?: string;
|
|
103
106
|
}
|
|
104
107
|
interface LowerOptions {
|
|
105
108
|
/** Lower the file as a module of a bundle. Without it, `import` and
|
package/dist/index.d.ts
CHANGED
|
@@ -100,6 +100,9 @@ interface ModuleContext {
|
|
|
100
100
|
* reads, as `__FAIL__`, to report an error from a callback the way an
|
|
101
101
|
* error that reached the entry is reported. */
|
|
102
102
|
readonly fail?: string;
|
|
103
|
+
/** Luau reaching the bundle's record of which tables are arrays, so that
|
|
104
|
+
* every module marks and reads the same one (`typeof`). */
|
|
105
|
+
readonly arrays?: string;
|
|
103
106
|
}
|
|
104
107
|
interface LowerOptions {
|
|
105
108
|
/** Lower the file as a module of a bundle. Without it, `import` and
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tilua/compiler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Compiles tilua to Luau: lowers the @tilua/parser AST to a luau-parser AST and prints it",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"type": "module",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"luau-parser": "^1.0.3",
|
|
41
|
-
"@tilua/parser": "^
|
|
41
|
+
"@tilua/parser": "^4.0.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^22.20.2",
|