@tilua/compiler 1.1.0 → 2.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 +17 -5
- package/dist/{chunk-2IPPO4V3.js → chunk-KG6W5XKA.js} +108 -40
- package/dist/cli.cjs +108 -40
- package/dist/cli.js +1 -1
- package/dist/index.cjs +108 -40
- 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/README.md
CHANGED
|
@@ -65,10 +65,14 @@ JavaScript module the build loads.
|
|
|
65
65
|
| `function f(n = 1, { x })` | `function f(n, arg) if n == nil then n = 1 end local x = arg.x ...` |
|
|
66
66
|
| `const` / `let` | `local` |
|
|
67
67
|
| `class C { … }` | one table for the class, one per instance — see below |
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
68
|
+
| `return [a, b]` | `return { a, b }` — several results are an array |
|
|
69
|
+
| `for (const item in xs)` | `for _, item in xs do … end` — an array or a table walked for its values |
|
|
70
|
+
| `for (const item in step)` | `for item in step do … end` — an iterator function |
|
|
71
|
+
| `for (const [k, v] in pairs(t))` | `for item in unpack(pairs(t), 1, 3) do local k, v = item[1], item[2] … end` — an iteration is `[step, state, first]` |
|
|
72
|
+
| `pcall(f)`, and other globals Luau answers several values from | the type library's own function, answering one — see below |
|
|
70
73
|
| `function f(a, ...rest)` | `function f(a, ...) local rest = {...}` |
|
|
71
|
-
| `
|
|
74
|
+
| `scriptArgs` | the chunk's own `...`, as `local scriptArgs = { ... }` |
|
|
75
|
+
| `f(a, ...xs)` | `table.unpack(xs)` in the last position |
|
|
72
76
|
| `f(...xs, a)` | the list built first, then `table.unpack` of it |
|
|
73
77
|
| `x as T`, `x satisfies T`, types, `declare` | removed |
|
|
74
78
|
| `names:filter(f)` | whatever the type library that declared `filter` says — see below |
|
|
@@ -113,8 +117,16 @@ tilua_accessors(Dog)
|
|
|
113
117
|
|
|
114
118
|
`__init` is what `super(...)` calls: it runs the constructor on an instance
|
|
115
119
|
that already exists, so a derived class builds one table, not one per level.
|
|
116
|
-
`new` is a real function on the class table
|
|
117
|
-
|
|
120
|
+
`new` is a real function on the class table, and `Dog.new(x)` is how an
|
|
121
|
+
instance is built — the way Luau builds one. An `abstract class` has no `new`,
|
|
122
|
+
and an `abstract` method no function: both exist only to be checked.
|
|
123
|
+
|
|
124
|
+
A method named for a metamethod — `__add`, `__tostring`, `__call`, … — is an
|
|
125
|
+
ordinary function on the class table, and since the class table is its
|
|
126
|
+
instances' metatable, it works as one. Luau reads metamethods off the
|
|
127
|
+
metatable with `rawget`, never through its `__index`, so a class would not
|
|
128
|
+
inherit its base's: `tilua_class` copies them into each class that extends
|
|
129
|
+
another, and one the class writes itself replaces the copy.
|
|
118
130
|
|
|
119
131
|
Two links come with every class, and they cost an instance nothing because
|
|
120
132
|
they live on the class table: `ClassObject`, which an instance reads through
|
|
@@ -180,14 +180,18 @@ var Lowerer = class {
|
|
|
180
180
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
181
181
|
const helpers = this.helperDefinitions();
|
|
182
182
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
183
|
+
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
|
|
183
184
|
return {
|
|
184
|
-
statements: [...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
|
|
185
|
+
statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
|
|
185
186
|
exportsName: this.exportsName,
|
|
186
187
|
module: this.info,
|
|
187
188
|
diagnostics: this.diagnostics,
|
|
188
|
-
origins: this.origins
|
|
189
|
+
origins: this.origins,
|
|
190
|
+
usesScriptArgs: this.usesScriptArgs
|
|
189
191
|
};
|
|
190
192
|
}
|
|
193
|
+
/** Whether the file reads `scriptArgs`, the language's own global. */
|
|
194
|
+
usesScriptArgs = false;
|
|
191
195
|
report(node, message) {
|
|
192
196
|
this.diagnostics.push({ message, line: node.line.start, column: node.column.start });
|
|
193
197
|
}
|
|
@@ -312,6 +316,10 @@ var Lowerer = class {
|
|
|
312
316
|
/** The name a global is read by at `node`, or `undefined` when `node` is
|
|
313
317
|
* not a global — a local of the same name is the author's. */
|
|
314
318
|
globalName(node) {
|
|
319
|
+
if (node.type === "MemberExpression" && !node.optional) {
|
|
320
|
+
const base = this.globalName(node.object);
|
|
321
|
+
return base === void 0 ? void 0 : `${base}.${node.property.name}`;
|
|
322
|
+
}
|
|
315
323
|
if (node.type !== "Identifier") return void 0;
|
|
316
324
|
const id = this.scopes.bindingOf.get(node);
|
|
317
325
|
if (id === void 0) return node.name;
|
|
@@ -608,7 +616,7 @@ var Lowerer = class {
|
|
|
608
616
|
break;
|
|
609
617
|
}
|
|
610
618
|
case "ReturnStatement":
|
|
611
|
-
if (declaration.
|
|
619
|
+
if (declaration.argument) this.report(declaration, "A module cannot return a value; export it instead");
|
|
612
620
|
body.push(returns([]));
|
|
613
621
|
break;
|
|
614
622
|
case "ImportStatement":
|
|
@@ -787,7 +795,7 @@ var Lowerer = class {
|
|
|
787
795
|
case "CallStatement": {
|
|
788
796
|
const chain = optionalChain(node.expression);
|
|
789
797
|
if (chain) return [this.optionalCallStatement(chain)];
|
|
790
|
-
const expression = this.
|
|
798
|
+
const expression = this.callValues(node.expression);
|
|
791
799
|
if (expression.type !== "CallExpression" && expression.type !== "MethodCallExpression") {
|
|
792
800
|
this.report(node, "A statement must be a call");
|
|
793
801
|
return [];
|
|
@@ -840,23 +848,26 @@ var Lowerer = class {
|
|
|
840
848
|
}];
|
|
841
849
|
case "GenericForStatement": {
|
|
842
850
|
const prelude = [];
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
if (variables.length === 1 && this.options.types?.iteratesValues.has(node)) {
|
|
850
|
-
variables.unshift(this.names.fresh("_"));
|
|
851
|
+
let item;
|
|
852
|
+
if (node.variable.type === "IdentifierPattern") {
|
|
853
|
+
item = this.name(node.variable.name);
|
|
854
|
+
} else {
|
|
855
|
+
item = this.names.fresh("item");
|
|
856
|
+
prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
|
|
851
857
|
}
|
|
858
|
+
const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
|
|
859
|
+
let source = this.expression(node.iterator);
|
|
860
|
+
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
861
|
+
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
862
|
+
const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
|
|
852
863
|
return [genericFor(
|
|
853
864
|
variables,
|
|
854
|
-
|
|
865
|
+
iterators,
|
|
855
866
|
this.loopBody51(this.block(node.body, prelude)).statements
|
|
856
867
|
)];
|
|
857
868
|
}
|
|
858
869
|
case "ReturnStatement":
|
|
859
|
-
return [returns(this.
|
|
870
|
+
return [returns(node.argument ? [this.expression(node.argument)] : [])];
|
|
860
871
|
case "BreakStatement":
|
|
861
872
|
return [{ type: "BreakStatement", ...spanOf(node) }];
|
|
862
873
|
case "ContinueStatement":
|
|
@@ -900,7 +911,7 @@ var Lowerer = class {
|
|
|
900
911
|
// end
|
|
901
912
|
// function Dog.speak(this) ... end
|
|
902
913
|
//
|
|
903
|
-
// `new
|
|
914
|
+
// `Dog.new(x)` builds one, `dog:speak()` passes the instance
|
|
904
915
|
// as `this`, and `super.speak()` is `Animal.speak(this)` — the
|
|
905
916
|
// base's own function, run on this instance.
|
|
906
917
|
// ============================================================
|
|
@@ -926,6 +937,7 @@ var Lowerer = class {
|
|
|
926
937
|
classRuntimeStatements() {
|
|
927
938
|
if (!this.classHelpers) return [];
|
|
928
939
|
const { build, accessors } = this.classHelpers;
|
|
940
|
+
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
929
941
|
return parseLuau(`
|
|
930
942
|
local function ${build}(base)
|
|
931
943
|
local class = { __getters = {}, __setters = {} }
|
|
@@ -936,6 +948,9 @@ local function ${build}(base)
|
|
|
936
948
|
setmetatable(class, { __index = base })
|
|
937
949
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
938
950
|
setmetatable(class.__setters, { __index = base.__setters })
|
|
951
|
+
for _, key in ipairs(${metamethods}) do
|
|
952
|
+
class[key] = base[key]
|
|
953
|
+
end
|
|
939
954
|
end
|
|
940
955
|
return class
|
|
941
956
|
end
|
|
@@ -998,6 +1013,7 @@ end
|
|
|
998
1013
|
}
|
|
999
1014
|
break;
|
|
1000
1015
|
case "ClassMethod":
|
|
1016
|
+
if (member2.isAbstract) break;
|
|
1001
1017
|
out.push(this.functionStatement(
|
|
1002
1018
|
member(self, member2.name.name),
|
|
1003
1019
|
member2,
|
|
@@ -1019,7 +1035,7 @@ end
|
|
|
1019
1035
|
}
|
|
1020
1036
|
}
|
|
1021
1037
|
out.push(this.classInit(node, self));
|
|
1022
|
-
out.push(this.classNew(node, self));
|
|
1038
|
+
if (!node.isAbstract) out.push(this.classNew(node, self));
|
|
1023
1039
|
out.push(callStatement(call(identifier(accessors), [self])));
|
|
1024
1040
|
} finally {
|
|
1025
1041
|
this.classContext = previous;
|
|
@@ -1297,6 +1313,10 @@ end
|
|
|
1297
1313
|
}
|
|
1298
1314
|
switch (node.type) {
|
|
1299
1315
|
case "Identifier":
|
|
1316
|
+
if (node.name === "scriptArgs" && this.isLanguageGlobal(node)) {
|
|
1317
|
+
this.usesScriptArgs = true;
|
|
1318
|
+
return identifier("scriptArgs");
|
|
1319
|
+
}
|
|
1300
1320
|
return this.loweredGlobalValue(node) ?? this.reference(node);
|
|
1301
1321
|
case "NilLiteral":
|
|
1302
1322
|
return nil();
|
|
@@ -1306,8 +1326,6 @@ end
|
|
|
1306
1326
|
return number(node.value, this.numberRaw(node));
|
|
1307
1327
|
case "StringLiteral":
|
|
1308
1328
|
return string(node.value);
|
|
1309
|
-
case "VarargExpression":
|
|
1310
|
-
return vararg();
|
|
1311
1329
|
// Only a recovering parse makes one, and a syntax error stops the build.
|
|
1312
1330
|
case "ErrorExpression":
|
|
1313
1331
|
this.report(node, "Syntax error");
|
|
@@ -1328,11 +1346,6 @@ end
|
|
|
1328
1346
|
}
|
|
1329
1347
|
case "UnaryExpression":
|
|
1330
1348
|
return unary(node.operator, this.expression(node.argument));
|
|
1331
|
-
case "NewExpression":
|
|
1332
|
-
return call(
|
|
1333
|
-
member(this.expression(node.callee), "new"),
|
|
1334
|
-
this.values(node.arguments)
|
|
1335
|
-
);
|
|
1336
1349
|
case "SuperExpression":
|
|
1337
1350
|
return this.superClassReference(node);
|
|
1338
1351
|
case "SpreadElement":
|
|
@@ -1340,17 +1353,16 @@ end
|
|
|
1340
1353
|
return nil();
|
|
1341
1354
|
case "ClassExpression":
|
|
1342
1355
|
return this.classExpression(node);
|
|
1343
|
-
case "MemberExpression":
|
|
1344
|
-
case "IndexExpression":
|
|
1345
1356
|
case "CallExpression":
|
|
1346
|
-
case "MethodCallExpression":
|
|
1357
|
+
case "MethodCallExpression":
|
|
1358
|
+
return this.callValues(node);
|
|
1359
|
+
case "MemberExpression":
|
|
1360
|
+
case "IndexExpression": {
|
|
1347
1361
|
const chain = optionalChain(node);
|
|
1348
1362
|
if (chain) return this.optionalChainExpression(chain);
|
|
1349
|
-
const
|
|
1350
|
-
if (
|
|
1351
|
-
|
|
1352
|
-
}
|
|
1353
|
-
return this.link(node, this.expression(object));
|
|
1363
|
+
const global = node.type === "MemberExpression" ? this.loweredGlobalValue(node) : void 0;
|
|
1364
|
+
if (global) return global;
|
|
1365
|
+
return this.link(node, this.expression(linkObject(node)));
|
|
1354
1366
|
}
|
|
1355
1367
|
case "ParenthesizedExpression":
|
|
1356
1368
|
return parenthesized(this.expression(node.expression));
|
|
@@ -1369,14 +1381,47 @@ end
|
|
|
1369
1381
|
};
|
|
1370
1382
|
}
|
|
1371
1383
|
}
|
|
1384
|
+
/** A call. */
|
|
1385
|
+
callValues(node) {
|
|
1386
|
+
const chain = optionalChain(node);
|
|
1387
|
+
if (chain) return this.optionalChainExpression(chain);
|
|
1388
|
+
const object = linkObject(node);
|
|
1389
|
+
if (node.type === "CallExpression" && object.type === "Identifier") {
|
|
1390
|
+
return this.link(node, this.reference(object));
|
|
1391
|
+
}
|
|
1392
|
+
return this.link(node, this.expression(object));
|
|
1393
|
+
}
|
|
1394
|
+
/** A call is one value in tilua, and Luau would hand on every value one
|
|
1395
|
+
* in the last place of an argument list or an array returns. Types say
|
|
1396
|
+
* how many a call returns; where a type says nothing — `any`, `unknown`
|
|
1397
|
+
* — the call is kept to one: `f((g()))`. */
|
|
1398
|
+
keptToOne(nodes, values) {
|
|
1399
|
+
const node = nodes[nodes.length - 1];
|
|
1400
|
+
const last = values[values.length - 1];
|
|
1401
|
+
if (!node || !last || !expandsToMany(last)) return values;
|
|
1402
|
+
const type = this.options.types?.typeOf.get(node);
|
|
1403
|
+
if (type && type.kind !== "any" && type.kind !== "unknown") return values;
|
|
1404
|
+
return [...values.slice(0, -1), parenthesized(last)];
|
|
1405
|
+
}
|
|
1406
|
+
/** Is this `scriptArgs` the language's own, not a name the file declared? */
|
|
1407
|
+
isLanguageGlobal(node) {
|
|
1408
|
+
const id = this.bindingIdOf(node);
|
|
1409
|
+
return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
|
|
1410
|
+
}
|
|
1372
1411
|
/** A list of values — a call's arguments, a `return`'s, a declaration's,
|
|
1373
1412
|
* an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
|
|
1374
1413
|
* `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
|
|
1375
1414
|
* the last value of a list expands, so the whole list is built as an
|
|
1376
|
-
* array first and that is what expands.
|
|
1377
|
-
|
|
1415
|
+
* array first and that is what expands.
|
|
1416
|
+
*
|
|
1417
|
+
* `arguments` is set for a call's arguments, where Luau hands on every
|
|
1418
|
+
* value the last one returns — see `keptToOne`. */
|
|
1419
|
+
values(list, asArguments = false) {
|
|
1378
1420
|
const spreads = list.filter((a) => a.type === "SpreadElement");
|
|
1379
|
-
if (!spreads.length)
|
|
1421
|
+
if (!spreads.length) {
|
|
1422
|
+
const lowered = list.map((a) => this.expression(a));
|
|
1423
|
+
return asArguments ? this.keptToOne(list, lowered) : lowered;
|
|
1424
|
+
}
|
|
1380
1425
|
const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
|
|
1381
1426
|
const last = list[list.length - 1];
|
|
1382
1427
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
@@ -1397,13 +1442,13 @@ end
|
|
|
1397
1442
|
return index(object, this.expression(node.index));
|
|
1398
1443
|
case "CallExpression": {
|
|
1399
1444
|
const lowered = this.loweredGlobalCall(node);
|
|
1400
|
-
const args = this.values(node.arguments);
|
|
1401
|
-
if (lowered) return call(
|
|
1402
|
-
return call(
|
|
1445
|
+
const args = this.values(node.arguments, true);
|
|
1446
|
+
if (!lowered) return call(object, args);
|
|
1447
|
+
return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
|
|
1403
1448
|
}
|
|
1404
1449
|
case "MethodCallExpression": {
|
|
1405
1450
|
const lowered = this.loweredMethodCall(node);
|
|
1406
|
-
const args = this.values(node.arguments);
|
|
1451
|
+
const args = this.values(node.arguments, true);
|
|
1407
1452
|
if (lowered) {
|
|
1408
1453
|
return call(calleePath(lowered.callee), [
|
|
1409
1454
|
...lowered.prepend,
|
|
@@ -1637,7 +1682,8 @@ end
|
|
|
1637
1682
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
1638
1683
|
arrayExpression(node) {
|
|
1639
1684
|
if (!node.elements.some((e) => e.type === "SpreadElement")) {
|
|
1640
|
-
|
|
1685
|
+
const elements = node.elements;
|
|
1686
|
+
return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
|
|
1641
1687
|
}
|
|
1642
1688
|
const parts = [];
|
|
1643
1689
|
let current = [];
|
|
@@ -1836,6 +1882,24 @@ function allNodes(root) {
|
|
|
1836
1882
|
visit(root);
|
|
1837
1883
|
return out;
|
|
1838
1884
|
}
|
|
1885
|
+
var CLASS_METAMETHODS = [
|
|
1886
|
+
"__add",
|
|
1887
|
+
"__sub",
|
|
1888
|
+
"__mul",
|
|
1889
|
+
"__div",
|
|
1890
|
+
"__idiv",
|
|
1891
|
+
"__mod",
|
|
1892
|
+
"__pow",
|
|
1893
|
+
"__unm",
|
|
1894
|
+
"__concat",
|
|
1895
|
+
"__len",
|
|
1896
|
+
"__eq",
|
|
1897
|
+
"__lt",
|
|
1898
|
+
"__le",
|
|
1899
|
+
"__call",
|
|
1900
|
+
"__tostring",
|
|
1901
|
+
"__iter"
|
|
1902
|
+
];
|
|
1839
1903
|
|
|
1840
1904
|
// src/lowering.ts
|
|
1841
1905
|
import { pathToFileURL } from "url";
|
|
@@ -1993,7 +2057,8 @@ async function bundle(options) {
|
|
|
1993
2057
|
name: source.name,
|
|
1994
2058
|
statements: lowered.statements,
|
|
1995
2059
|
exportsName: lowered.exportsName,
|
|
1996
|
-
info: lowered.module
|
|
2060
|
+
info: lowered.module,
|
|
2061
|
+
usesScriptArgs: lowered.usesScriptArgs
|
|
1997
2062
|
});
|
|
1998
2063
|
}
|
|
1999
2064
|
for (const [file, fileDirectives] of directives) {
|
|
@@ -2018,6 +2083,9 @@ async function bundle(options) {
|
|
|
2018
2083
|
}
|
|
2019
2084
|
const program2 = parseLuau2(runtime(G));
|
|
2020
2085
|
const assignment = program2.body.statements[1];
|
|
2086
|
+
if ([...modules.values()].some((m) => m.usesScriptArgs)) {
|
|
2087
|
+
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2088
|
+
}
|
|
2021
2089
|
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;
|
|
2022
2090
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2023
2091
|
for (const module of modules.values()) {
|
package/dist/cli.cjs
CHANGED
|
@@ -192,14 +192,18 @@ var Lowerer = class {
|
|
|
192
192
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
193
193
|
const helpers = this.helperDefinitions();
|
|
194
194
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
195
|
+
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
|
|
195
196
|
return {
|
|
196
|
-
statements: [...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
|
|
197
|
+
statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
|
|
197
198
|
exportsName: this.exportsName,
|
|
198
199
|
module: this.info,
|
|
199
200
|
diagnostics: this.diagnostics,
|
|
200
|
-
origins: this.origins
|
|
201
|
+
origins: this.origins,
|
|
202
|
+
usesScriptArgs: this.usesScriptArgs
|
|
201
203
|
};
|
|
202
204
|
}
|
|
205
|
+
/** Whether the file reads `scriptArgs`, the language's own global. */
|
|
206
|
+
usesScriptArgs = false;
|
|
203
207
|
report(node, message) {
|
|
204
208
|
this.diagnostics.push({ message, line: node.line.start, column: node.column.start });
|
|
205
209
|
}
|
|
@@ -324,6 +328,10 @@ var Lowerer = class {
|
|
|
324
328
|
/** The name a global is read by at `node`, or `undefined` when `node` is
|
|
325
329
|
* not a global — a local of the same name is the author's. */
|
|
326
330
|
globalName(node) {
|
|
331
|
+
if (node.type === "MemberExpression" && !node.optional) {
|
|
332
|
+
const base = this.globalName(node.object);
|
|
333
|
+
return base === void 0 ? void 0 : `${base}.${node.property.name}`;
|
|
334
|
+
}
|
|
327
335
|
if (node.type !== "Identifier") return void 0;
|
|
328
336
|
const id = this.scopes.bindingOf.get(node);
|
|
329
337
|
if (id === void 0) return node.name;
|
|
@@ -620,7 +628,7 @@ var Lowerer = class {
|
|
|
620
628
|
break;
|
|
621
629
|
}
|
|
622
630
|
case "ReturnStatement":
|
|
623
|
-
if (declaration.
|
|
631
|
+
if (declaration.argument) this.report(declaration, "A module cannot return a value; export it instead");
|
|
624
632
|
body.push(returns([]));
|
|
625
633
|
break;
|
|
626
634
|
case "ImportStatement":
|
|
@@ -799,7 +807,7 @@ var Lowerer = class {
|
|
|
799
807
|
case "CallStatement": {
|
|
800
808
|
const chain = optionalChain(node.expression);
|
|
801
809
|
if (chain) return [this.optionalCallStatement(chain)];
|
|
802
|
-
const expression = this.
|
|
810
|
+
const expression = this.callValues(node.expression);
|
|
803
811
|
if (expression.type !== "CallExpression" && expression.type !== "MethodCallExpression") {
|
|
804
812
|
this.report(node, "A statement must be a call");
|
|
805
813
|
return [];
|
|
@@ -852,23 +860,26 @@ var Lowerer = class {
|
|
|
852
860
|
}];
|
|
853
861
|
case "GenericForStatement": {
|
|
854
862
|
const prelude = [];
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
if (variables.length === 1 && this.options.types?.iteratesValues.has(node)) {
|
|
862
|
-
variables.unshift(this.names.fresh("_"));
|
|
863
|
+
let item;
|
|
864
|
+
if (node.variable.type === "IdentifierPattern") {
|
|
865
|
+
item = this.name(node.variable.name);
|
|
866
|
+
} else {
|
|
867
|
+
item = this.names.fresh("item");
|
|
868
|
+
prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
|
|
863
869
|
}
|
|
870
|
+
const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
|
|
871
|
+
let source = this.expression(node.iterator);
|
|
872
|
+
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
873
|
+
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
874
|
+
const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
|
|
864
875
|
return [genericFor(
|
|
865
876
|
variables,
|
|
866
|
-
|
|
877
|
+
iterators,
|
|
867
878
|
this.loopBody51(this.block(node.body, prelude)).statements
|
|
868
879
|
)];
|
|
869
880
|
}
|
|
870
881
|
case "ReturnStatement":
|
|
871
|
-
return [returns(this.
|
|
882
|
+
return [returns(node.argument ? [this.expression(node.argument)] : [])];
|
|
872
883
|
case "BreakStatement":
|
|
873
884
|
return [{ type: "BreakStatement", ...spanOf(node) }];
|
|
874
885
|
case "ContinueStatement":
|
|
@@ -912,7 +923,7 @@ var Lowerer = class {
|
|
|
912
923
|
// end
|
|
913
924
|
// function Dog.speak(this) ... end
|
|
914
925
|
//
|
|
915
|
-
// `new
|
|
926
|
+
// `Dog.new(x)` builds one, `dog:speak()` passes the instance
|
|
916
927
|
// as `this`, and `super.speak()` is `Animal.speak(this)` — the
|
|
917
928
|
// base's own function, run on this instance.
|
|
918
929
|
// ============================================================
|
|
@@ -938,6 +949,7 @@ var Lowerer = class {
|
|
|
938
949
|
classRuntimeStatements() {
|
|
939
950
|
if (!this.classHelpers) return [];
|
|
940
951
|
const { build, accessors } = this.classHelpers;
|
|
952
|
+
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
941
953
|
return (0, import_luau_parser.parse)(`
|
|
942
954
|
local function ${build}(base)
|
|
943
955
|
local class = { __getters = {}, __setters = {} }
|
|
@@ -948,6 +960,9 @@ local function ${build}(base)
|
|
|
948
960
|
setmetatable(class, { __index = base })
|
|
949
961
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
950
962
|
setmetatable(class.__setters, { __index = base.__setters })
|
|
963
|
+
for _, key in ipairs(${metamethods}) do
|
|
964
|
+
class[key] = base[key]
|
|
965
|
+
end
|
|
951
966
|
end
|
|
952
967
|
return class
|
|
953
968
|
end
|
|
@@ -1010,6 +1025,7 @@ end
|
|
|
1010
1025
|
}
|
|
1011
1026
|
break;
|
|
1012
1027
|
case "ClassMethod":
|
|
1028
|
+
if (member2.isAbstract) break;
|
|
1013
1029
|
out2.push(this.functionStatement(
|
|
1014
1030
|
member(self, member2.name.name),
|
|
1015
1031
|
member2,
|
|
@@ -1031,7 +1047,7 @@ end
|
|
|
1031
1047
|
}
|
|
1032
1048
|
}
|
|
1033
1049
|
out2.push(this.classInit(node, self));
|
|
1034
|
-
out2.push(this.classNew(node, self));
|
|
1050
|
+
if (!node.isAbstract) out2.push(this.classNew(node, self));
|
|
1035
1051
|
out2.push(callStatement(call(identifier(accessors), [self])));
|
|
1036
1052
|
} finally {
|
|
1037
1053
|
this.classContext = previous;
|
|
@@ -1309,6 +1325,10 @@ end
|
|
|
1309
1325
|
}
|
|
1310
1326
|
switch (node.type) {
|
|
1311
1327
|
case "Identifier":
|
|
1328
|
+
if (node.name === "scriptArgs" && this.isLanguageGlobal(node)) {
|
|
1329
|
+
this.usesScriptArgs = true;
|
|
1330
|
+
return identifier("scriptArgs");
|
|
1331
|
+
}
|
|
1312
1332
|
return this.loweredGlobalValue(node) ?? this.reference(node);
|
|
1313
1333
|
case "NilLiteral":
|
|
1314
1334
|
return nil();
|
|
@@ -1318,8 +1338,6 @@ end
|
|
|
1318
1338
|
return number(node.value, this.numberRaw(node));
|
|
1319
1339
|
case "StringLiteral":
|
|
1320
1340
|
return string(node.value);
|
|
1321
|
-
case "VarargExpression":
|
|
1322
|
-
return vararg();
|
|
1323
1341
|
// Only a recovering parse makes one, and a syntax error stops the build.
|
|
1324
1342
|
case "ErrorExpression":
|
|
1325
1343
|
this.report(node, "Syntax error");
|
|
@@ -1340,11 +1358,6 @@ end
|
|
|
1340
1358
|
}
|
|
1341
1359
|
case "UnaryExpression":
|
|
1342
1360
|
return unary(node.operator, this.expression(node.argument));
|
|
1343
|
-
case "NewExpression":
|
|
1344
|
-
return call(
|
|
1345
|
-
member(this.expression(node.callee), "new"),
|
|
1346
|
-
this.values(node.arguments)
|
|
1347
|
-
);
|
|
1348
1361
|
case "SuperExpression":
|
|
1349
1362
|
return this.superClassReference(node);
|
|
1350
1363
|
case "SpreadElement":
|
|
@@ -1352,17 +1365,16 @@ end
|
|
|
1352
1365
|
return nil();
|
|
1353
1366
|
case "ClassExpression":
|
|
1354
1367
|
return this.classExpression(node);
|
|
1355
|
-
case "MemberExpression":
|
|
1356
|
-
case "IndexExpression":
|
|
1357
1368
|
case "CallExpression":
|
|
1358
|
-
case "MethodCallExpression":
|
|
1369
|
+
case "MethodCallExpression":
|
|
1370
|
+
return this.callValues(node);
|
|
1371
|
+
case "MemberExpression":
|
|
1372
|
+
case "IndexExpression": {
|
|
1359
1373
|
const chain = optionalChain(node);
|
|
1360
1374
|
if (chain) return this.optionalChainExpression(chain);
|
|
1361
|
-
const
|
|
1362
|
-
if (
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
return this.link(node, this.expression(object));
|
|
1375
|
+
const global = node.type === "MemberExpression" ? this.loweredGlobalValue(node) : void 0;
|
|
1376
|
+
if (global) return global;
|
|
1377
|
+
return this.link(node, this.expression(linkObject(node)));
|
|
1366
1378
|
}
|
|
1367
1379
|
case "ParenthesizedExpression":
|
|
1368
1380
|
return parenthesized(this.expression(node.expression));
|
|
@@ -1381,14 +1393,47 @@ end
|
|
|
1381
1393
|
};
|
|
1382
1394
|
}
|
|
1383
1395
|
}
|
|
1396
|
+
/** A call. */
|
|
1397
|
+
callValues(node) {
|
|
1398
|
+
const chain = optionalChain(node);
|
|
1399
|
+
if (chain) return this.optionalChainExpression(chain);
|
|
1400
|
+
const object = linkObject(node);
|
|
1401
|
+
if (node.type === "CallExpression" && object.type === "Identifier") {
|
|
1402
|
+
return this.link(node, this.reference(object));
|
|
1403
|
+
}
|
|
1404
|
+
return this.link(node, this.expression(object));
|
|
1405
|
+
}
|
|
1406
|
+
/** A call is one value in tilua, and Luau would hand on every value one
|
|
1407
|
+
* in the last place of an argument list or an array returns. Types say
|
|
1408
|
+
* how many a call returns; where a type says nothing — `any`, `unknown`
|
|
1409
|
+
* — the call is kept to one: `f((g()))`. */
|
|
1410
|
+
keptToOne(nodes, values) {
|
|
1411
|
+
const node = nodes[nodes.length - 1];
|
|
1412
|
+
const last = values[values.length - 1];
|
|
1413
|
+
if (!node || !last || !expandsToMany(last)) return values;
|
|
1414
|
+
const type = this.options.types?.typeOf.get(node);
|
|
1415
|
+
if (type && type.kind !== "any" && type.kind !== "unknown") return values;
|
|
1416
|
+
return [...values.slice(0, -1), parenthesized(last)];
|
|
1417
|
+
}
|
|
1418
|
+
/** Is this `scriptArgs` the language's own, not a name the file declared? */
|
|
1419
|
+
isLanguageGlobal(node) {
|
|
1420
|
+
const id = this.bindingIdOf(node);
|
|
1421
|
+
return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
|
|
1422
|
+
}
|
|
1384
1423
|
/** A list of values — a call's arguments, a `return`'s, a declaration's,
|
|
1385
1424
|
* an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
|
|
1386
1425
|
* `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
|
|
1387
1426
|
* the last value of a list expands, so the whole list is built as an
|
|
1388
|
-
* array first and that is what expands.
|
|
1389
|
-
|
|
1427
|
+
* array first and that is what expands.
|
|
1428
|
+
*
|
|
1429
|
+
* `arguments` is set for a call's arguments, where Luau hands on every
|
|
1430
|
+
* value the last one returns — see `keptToOne`. */
|
|
1431
|
+
values(list, asArguments = false) {
|
|
1390
1432
|
const spreads = list.filter((a) => a.type === "SpreadElement");
|
|
1391
|
-
if (!spreads.length)
|
|
1433
|
+
if (!spreads.length) {
|
|
1434
|
+
const lowered = list.map((a) => this.expression(a));
|
|
1435
|
+
return asArguments ? this.keptToOne(list, lowered) : lowered;
|
|
1436
|
+
}
|
|
1392
1437
|
const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
|
|
1393
1438
|
const last = list[list.length - 1];
|
|
1394
1439
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
@@ -1409,13 +1454,13 @@ end
|
|
|
1409
1454
|
return index(object, this.expression(node.index));
|
|
1410
1455
|
case "CallExpression": {
|
|
1411
1456
|
const lowered = this.loweredGlobalCall(node);
|
|
1412
|
-
const args2 = this.values(node.arguments);
|
|
1413
|
-
if (lowered) return call(
|
|
1414
|
-
return call(
|
|
1457
|
+
const args2 = this.values(node.arguments, true);
|
|
1458
|
+
if (!lowered) return call(object, args2);
|
|
1459
|
+
return call(calleePath(lowered.callee), [...lowered.prepend, ...args2]);
|
|
1415
1460
|
}
|
|
1416
1461
|
case "MethodCallExpression": {
|
|
1417
1462
|
const lowered = this.loweredMethodCall(node);
|
|
1418
|
-
const args2 = this.values(node.arguments);
|
|
1463
|
+
const args2 = this.values(node.arguments, true);
|
|
1419
1464
|
if (lowered) {
|
|
1420
1465
|
return call(calleePath(lowered.callee), [
|
|
1421
1466
|
...lowered.prepend,
|
|
@@ -1649,7 +1694,8 @@ end
|
|
|
1649
1694
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
1650
1695
|
arrayExpression(node) {
|
|
1651
1696
|
if (!node.elements.some((e) => e.type === "SpreadElement")) {
|
|
1652
|
-
|
|
1697
|
+
const elements = node.elements;
|
|
1698
|
+
return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
|
|
1653
1699
|
}
|
|
1654
1700
|
const parts = [];
|
|
1655
1701
|
let current = [];
|
|
@@ -1848,6 +1894,24 @@ function allNodes(root) {
|
|
|
1848
1894
|
visit(root);
|
|
1849
1895
|
return out2;
|
|
1850
1896
|
}
|
|
1897
|
+
var CLASS_METAMETHODS = [
|
|
1898
|
+
"__add",
|
|
1899
|
+
"__sub",
|
|
1900
|
+
"__mul",
|
|
1901
|
+
"__div",
|
|
1902
|
+
"__idiv",
|
|
1903
|
+
"__mod",
|
|
1904
|
+
"__pow",
|
|
1905
|
+
"__unm",
|
|
1906
|
+
"__concat",
|
|
1907
|
+
"__len",
|
|
1908
|
+
"__eq",
|
|
1909
|
+
"__lt",
|
|
1910
|
+
"__le",
|
|
1911
|
+
"__call",
|
|
1912
|
+
"__tostring",
|
|
1913
|
+
"__iter"
|
|
1914
|
+
];
|
|
1851
1915
|
|
|
1852
1916
|
// src/linemap.ts
|
|
1853
1917
|
function statementsOf(node) {
|
|
@@ -1987,7 +2051,8 @@ async function bundle(options) {
|
|
|
1987
2051
|
name: source.name,
|
|
1988
2052
|
statements: lowered.statements,
|
|
1989
2053
|
exportsName: lowered.exportsName,
|
|
1990
|
-
info: lowered.module
|
|
2054
|
+
info: lowered.module,
|
|
2055
|
+
usesScriptArgs: lowered.usesScriptArgs
|
|
1991
2056
|
});
|
|
1992
2057
|
}
|
|
1993
2058
|
for (const [file, fileDirectives] of directives) {
|
|
@@ -2012,6 +2077,9 @@ async function bundle(options) {
|
|
|
2012
2077
|
}
|
|
2013
2078
|
const program = (0, import_luau_parser2.parse)(runtime(G));
|
|
2014
2079
|
const assignment = program.body.statements[1];
|
|
2080
|
+
if ([...modules.values()].some((m) => m.usesScriptArgs)) {
|
|
2081
|
+
program.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2082
|
+
}
|
|
2015
2083
|
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;
|
|
2016
2084
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2017
2085
|
for (const module2 of modules.values()) {
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -216,14 +216,18 @@ var Lowerer = class {
|
|
|
216
216
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
217
217
|
const helpers = this.helperDefinitions();
|
|
218
218
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
219
|
+
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
|
|
219
220
|
return {
|
|
220
|
-
statements: [...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
|
|
221
|
+
statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
|
|
221
222
|
exportsName: this.exportsName,
|
|
222
223
|
module: this.info,
|
|
223
224
|
diagnostics: this.diagnostics,
|
|
224
|
-
origins: this.origins
|
|
225
|
+
origins: this.origins,
|
|
226
|
+
usesScriptArgs: this.usesScriptArgs
|
|
225
227
|
};
|
|
226
228
|
}
|
|
229
|
+
/** Whether the file reads `scriptArgs`, the language's own global. */
|
|
230
|
+
usesScriptArgs = false;
|
|
227
231
|
report(node, message) {
|
|
228
232
|
this.diagnostics.push({ message, line: node.line.start, column: node.column.start });
|
|
229
233
|
}
|
|
@@ -348,6 +352,10 @@ var Lowerer = class {
|
|
|
348
352
|
/** The name a global is read by at `node`, or `undefined` when `node` is
|
|
349
353
|
* not a global — a local of the same name is the author's. */
|
|
350
354
|
globalName(node) {
|
|
355
|
+
if (node.type === "MemberExpression" && !node.optional) {
|
|
356
|
+
const base = this.globalName(node.object);
|
|
357
|
+
return base === void 0 ? void 0 : `${base}.${node.property.name}`;
|
|
358
|
+
}
|
|
351
359
|
if (node.type !== "Identifier") return void 0;
|
|
352
360
|
const id = this.scopes.bindingOf.get(node);
|
|
353
361
|
if (id === void 0) return node.name;
|
|
@@ -644,7 +652,7 @@ var Lowerer = class {
|
|
|
644
652
|
break;
|
|
645
653
|
}
|
|
646
654
|
case "ReturnStatement":
|
|
647
|
-
if (declaration.
|
|
655
|
+
if (declaration.argument) this.report(declaration, "A module cannot return a value; export it instead");
|
|
648
656
|
body.push(returns([]));
|
|
649
657
|
break;
|
|
650
658
|
case "ImportStatement":
|
|
@@ -823,7 +831,7 @@ var Lowerer = class {
|
|
|
823
831
|
case "CallStatement": {
|
|
824
832
|
const chain = optionalChain(node.expression);
|
|
825
833
|
if (chain) return [this.optionalCallStatement(chain)];
|
|
826
|
-
const expression = this.
|
|
834
|
+
const expression = this.callValues(node.expression);
|
|
827
835
|
if (expression.type !== "CallExpression" && expression.type !== "MethodCallExpression") {
|
|
828
836
|
this.report(node, "A statement must be a call");
|
|
829
837
|
return [];
|
|
@@ -876,23 +884,26 @@ var Lowerer = class {
|
|
|
876
884
|
}];
|
|
877
885
|
case "GenericForStatement": {
|
|
878
886
|
const prelude = [];
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
if (variables.length === 1 && this.options.types?.iteratesValues.has(node)) {
|
|
886
|
-
variables.unshift(this.names.fresh("_"));
|
|
887
|
+
let item;
|
|
888
|
+
if (node.variable.type === "IdentifierPattern") {
|
|
889
|
+
item = this.name(node.variable.name);
|
|
890
|
+
} else {
|
|
891
|
+
item = this.names.fresh("item");
|
|
892
|
+
prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
|
|
887
893
|
}
|
|
894
|
+
const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
|
|
895
|
+
let source = this.expression(node.iterator);
|
|
896
|
+
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
897
|
+
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
898
|
+
const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
|
|
888
899
|
return [genericFor(
|
|
889
900
|
variables,
|
|
890
|
-
|
|
901
|
+
iterators,
|
|
891
902
|
this.loopBody51(this.block(node.body, prelude)).statements
|
|
892
903
|
)];
|
|
893
904
|
}
|
|
894
905
|
case "ReturnStatement":
|
|
895
|
-
return [returns(this.
|
|
906
|
+
return [returns(node.argument ? [this.expression(node.argument)] : [])];
|
|
896
907
|
case "BreakStatement":
|
|
897
908
|
return [{ type: "BreakStatement", ...spanOf(node) }];
|
|
898
909
|
case "ContinueStatement":
|
|
@@ -936,7 +947,7 @@ var Lowerer = class {
|
|
|
936
947
|
// end
|
|
937
948
|
// function Dog.speak(this) ... end
|
|
938
949
|
//
|
|
939
|
-
// `new
|
|
950
|
+
// `Dog.new(x)` builds one, `dog:speak()` passes the instance
|
|
940
951
|
// as `this`, and `super.speak()` is `Animal.speak(this)` — the
|
|
941
952
|
// base's own function, run on this instance.
|
|
942
953
|
// ============================================================
|
|
@@ -962,6 +973,7 @@ var Lowerer = class {
|
|
|
962
973
|
classRuntimeStatements() {
|
|
963
974
|
if (!this.classHelpers) return [];
|
|
964
975
|
const { build, accessors } = this.classHelpers;
|
|
976
|
+
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
965
977
|
return (0, import_luau_parser.parse)(`
|
|
966
978
|
local function ${build}(base)
|
|
967
979
|
local class = { __getters = {}, __setters = {} }
|
|
@@ -972,6 +984,9 @@ local function ${build}(base)
|
|
|
972
984
|
setmetatable(class, { __index = base })
|
|
973
985
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
974
986
|
setmetatable(class.__setters, { __index = base.__setters })
|
|
987
|
+
for _, key in ipairs(${metamethods}) do
|
|
988
|
+
class[key] = base[key]
|
|
989
|
+
end
|
|
975
990
|
end
|
|
976
991
|
return class
|
|
977
992
|
end
|
|
@@ -1034,6 +1049,7 @@ end
|
|
|
1034
1049
|
}
|
|
1035
1050
|
break;
|
|
1036
1051
|
case "ClassMethod":
|
|
1052
|
+
if (member2.isAbstract) break;
|
|
1037
1053
|
out.push(this.functionStatement(
|
|
1038
1054
|
member(self, member2.name.name),
|
|
1039
1055
|
member2,
|
|
@@ -1055,7 +1071,7 @@ end
|
|
|
1055
1071
|
}
|
|
1056
1072
|
}
|
|
1057
1073
|
out.push(this.classInit(node, self));
|
|
1058
|
-
out.push(this.classNew(node, self));
|
|
1074
|
+
if (!node.isAbstract) out.push(this.classNew(node, self));
|
|
1059
1075
|
out.push(callStatement(call(identifier(accessors), [self])));
|
|
1060
1076
|
} finally {
|
|
1061
1077
|
this.classContext = previous;
|
|
@@ -1333,6 +1349,10 @@ end
|
|
|
1333
1349
|
}
|
|
1334
1350
|
switch (node.type) {
|
|
1335
1351
|
case "Identifier":
|
|
1352
|
+
if (node.name === "scriptArgs" && this.isLanguageGlobal(node)) {
|
|
1353
|
+
this.usesScriptArgs = true;
|
|
1354
|
+
return identifier("scriptArgs");
|
|
1355
|
+
}
|
|
1336
1356
|
return this.loweredGlobalValue(node) ?? this.reference(node);
|
|
1337
1357
|
case "NilLiteral":
|
|
1338
1358
|
return nil();
|
|
@@ -1342,8 +1362,6 @@ end
|
|
|
1342
1362
|
return number(node.value, this.numberRaw(node));
|
|
1343
1363
|
case "StringLiteral":
|
|
1344
1364
|
return string(node.value);
|
|
1345
|
-
case "VarargExpression":
|
|
1346
|
-
return vararg();
|
|
1347
1365
|
// Only a recovering parse makes one, and a syntax error stops the build.
|
|
1348
1366
|
case "ErrorExpression":
|
|
1349
1367
|
this.report(node, "Syntax error");
|
|
@@ -1364,11 +1382,6 @@ end
|
|
|
1364
1382
|
}
|
|
1365
1383
|
case "UnaryExpression":
|
|
1366
1384
|
return unary(node.operator, this.expression(node.argument));
|
|
1367
|
-
case "NewExpression":
|
|
1368
|
-
return call(
|
|
1369
|
-
member(this.expression(node.callee), "new"),
|
|
1370
|
-
this.values(node.arguments)
|
|
1371
|
-
);
|
|
1372
1385
|
case "SuperExpression":
|
|
1373
1386
|
return this.superClassReference(node);
|
|
1374
1387
|
case "SpreadElement":
|
|
@@ -1376,17 +1389,16 @@ end
|
|
|
1376
1389
|
return nil();
|
|
1377
1390
|
case "ClassExpression":
|
|
1378
1391
|
return this.classExpression(node);
|
|
1379
|
-
case "MemberExpression":
|
|
1380
|
-
case "IndexExpression":
|
|
1381
1392
|
case "CallExpression":
|
|
1382
|
-
case "MethodCallExpression":
|
|
1393
|
+
case "MethodCallExpression":
|
|
1394
|
+
return this.callValues(node);
|
|
1395
|
+
case "MemberExpression":
|
|
1396
|
+
case "IndexExpression": {
|
|
1383
1397
|
const chain = optionalChain(node);
|
|
1384
1398
|
if (chain) return this.optionalChainExpression(chain);
|
|
1385
|
-
const
|
|
1386
|
-
if (
|
|
1387
|
-
|
|
1388
|
-
}
|
|
1389
|
-
return this.link(node, this.expression(object));
|
|
1399
|
+
const global = node.type === "MemberExpression" ? this.loweredGlobalValue(node) : void 0;
|
|
1400
|
+
if (global) return global;
|
|
1401
|
+
return this.link(node, this.expression(linkObject(node)));
|
|
1390
1402
|
}
|
|
1391
1403
|
case "ParenthesizedExpression":
|
|
1392
1404
|
return parenthesized(this.expression(node.expression));
|
|
@@ -1405,14 +1417,47 @@ end
|
|
|
1405
1417
|
};
|
|
1406
1418
|
}
|
|
1407
1419
|
}
|
|
1420
|
+
/** A call. */
|
|
1421
|
+
callValues(node) {
|
|
1422
|
+
const chain = optionalChain(node);
|
|
1423
|
+
if (chain) return this.optionalChainExpression(chain);
|
|
1424
|
+
const object = linkObject(node);
|
|
1425
|
+
if (node.type === "CallExpression" && object.type === "Identifier") {
|
|
1426
|
+
return this.link(node, this.reference(object));
|
|
1427
|
+
}
|
|
1428
|
+
return this.link(node, this.expression(object));
|
|
1429
|
+
}
|
|
1430
|
+
/** A call is one value in tilua, and Luau would hand on every value one
|
|
1431
|
+
* in the last place of an argument list or an array returns. Types say
|
|
1432
|
+
* how many a call returns; where a type says nothing — `any`, `unknown`
|
|
1433
|
+
* — the call is kept to one: `f((g()))`. */
|
|
1434
|
+
keptToOne(nodes, values) {
|
|
1435
|
+
const node = nodes[nodes.length - 1];
|
|
1436
|
+
const last = values[values.length - 1];
|
|
1437
|
+
if (!node || !last || !expandsToMany(last)) return values;
|
|
1438
|
+
const type = this.options.types?.typeOf.get(node);
|
|
1439
|
+
if (type && type.kind !== "any" && type.kind !== "unknown") return values;
|
|
1440
|
+
return [...values.slice(0, -1), parenthesized(last)];
|
|
1441
|
+
}
|
|
1442
|
+
/** Is this `scriptArgs` the language's own, not a name the file declared? */
|
|
1443
|
+
isLanguageGlobal(node) {
|
|
1444
|
+
const id = this.bindingIdOf(node);
|
|
1445
|
+
return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
|
|
1446
|
+
}
|
|
1408
1447
|
/** A list of values — a call's arguments, a `return`'s, a declaration's,
|
|
1409
1448
|
* an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
|
|
1410
1449
|
* `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
|
|
1411
1450
|
* the last value of a list expands, so the whole list is built as an
|
|
1412
|
-
* array first and that is what expands.
|
|
1413
|
-
|
|
1451
|
+
* array first and that is what expands.
|
|
1452
|
+
*
|
|
1453
|
+
* `arguments` is set for a call's arguments, where Luau hands on every
|
|
1454
|
+
* value the last one returns — see `keptToOne`. */
|
|
1455
|
+
values(list, asArguments = false) {
|
|
1414
1456
|
const spreads = list.filter((a) => a.type === "SpreadElement");
|
|
1415
|
-
if (!spreads.length)
|
|
1457
|
+
if (!spreads.length) {
|
|
1458
|
+
const lowered = list.map((a) => this.expression(a));
|
|
1459
|
+
return asArguments ? this.keptToOne(list, lowered) : lowered;
|
|
1460
|
+
}
|
|
1416
1461
|
const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
|
|
1417
1462
|
const last = list[list.length - 1];
|
|
1418
1463
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
@@ -1433,13 +1478,13 @@ end
|
|
|
1433
1478
|
return index(object, this.expression(node.index));
|
|
1434
1479
|
case "CallExpression": {
|
|
1435
1480
|
const lowered = this.loweredGlobalCall(node);
|
|
1436
|
-
const args = this.values(node.arguments);
|
|
1437
|
-
if (lowered) return call(
|
|
1438
|
-
return call(
|
|
1481
|
+
const args = this.values(node.arguments, true);
|
|
1482
|
+
if (!lowered) return call(object, args);
|
|
1483
|
+
return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
|
|
1439
1484
|
}
|
|
1440
1485
|
case "MethodCallExpression": {
|
|
1441
1486
|
const lowered = this.loweredMethodCall(node);
|
|
1442
|
-
const args = this.values(node.arguments);
|
|
1487
|
+
const args = this.values(node.arguments, true);
|
|
1443
1488
|
if (lowered) {
|
|
1444
1489
|
return call(calleePath(lowered.callee), [
|
|
1445
1490
|
...lowered.prepend,
|
|
@@ -1673,7 +1718,8 @@ end
|
|
|
1673
1718
|
* runs of plain elements and the spread arrays are joined by `concat`. */
|
|
1674
1719
|
arrayExpression(node) {
|
|
1675
1720
|
if (!node.elements.some((e) => e.type === "SpreadElement")) {
|
|
1676
|
-
|
|
1721
|
+
const elements = node.elements;
|
|
1722
|
+
return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
|
|
1677
1723
|
}
|
|
1678
1724
|
const parts = [];
|
|
1679
1725
|
let current = [];
|
|
@@ -1872,6 +1918,24 @@ function allNodes(root) {
|
|
|
1872
1918
|
visit(root);
|
|
1873
1919
|
return out;
|
|
1874
1920
|
}
|
|
1921
|
+
var CLASS_METAMETHODS = [
|
|
1922
|
+
"__add",
|
|
1923
|
+
"__sub",
|
|
1924
|
+
"__mul",
|
|
1925
|
+
"__div",
|
|
1926
|
+
"__idiv",
|
|
1927
|
+
"__mod",
|
|
1928
|
+
"__pow",
|
|
1929
|
+
"__unm",
|
|
1930
|
+
"__concat",
|
|
1931
|
+
"__len",
|
|
1932
|
+
"__eq",
|
|
1933
|
+
"__lt",
|
|
1934
|
+
"__le",
|
|
1935
|
+
"__call",
|
|
1936
|
+
"__tostring",
|
|
1937
|
+
"__iter"
|
|
1938
|
+
];
|
|
1875
1939
|
|
|
1876
1940
|
// src/linemap.ts
|
|
1877
1941
|
function statementsOf(node) {
|
|
@@ -2011,7 +2075,8 @@ async function bundle(options) {
|
|
|
2011
2075
|
name: source.name,
|
|
2012
2076
|
statements: lowered.statements,
|
|
2013
2077
|
exportsName: lowered.exportsName,
|
|
2014
|
-
info: lowered.module
|
|
2078
|
+
info: lowered.module,
|
|
2079
|
+
usesScriptArgs: lowered.usesScriptArgs
|
|
2015
2080
|
});
|
|
2016
2081
|
}
|
|
2017
2082
|
for (const [file, fileDirectives] of directives) {
|
|
@@ -2036,6 +2101,9 @@ async function bundle(options) {
|
|
|
2036
2101
|
}
|
|
2037
2102
|
const program2 = (0, import_luau_parser2.parse)(runtime(G));
|
|
2038
2103
|
const assignment = program2.body.statements[1];
|
|
2104
|
+
if ([...modules.values()].some((m) => m.usesScriptArgs)) {
|
|
2105
|
+
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2106
|
+
}
|
|
2039
2107
|
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;
|
|
2040
2108
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
2041
2109
|
for (const module2 of modules.values()) {
|
package/dist/index.d.cts
CHANGED
|
@@ -142,6 +142,9 @@ interface LowerResult {
|
|
|
142
142
|
* file it was lowered from, this is what turns a line of the bundle back
|
|
143
143
|
* into a place someone can open. */
|
|
144
144
|
readonly origins: WeakMap<L.Statement, number>;
|
|
145
|
+
/** The file reads `scriptArgs`: whatever runs it has to set it up, from
|
|
146
|
+
* the chunk's own `...` — this file, compiled alone, does it itself. */
|
|
147
|
+
readonly usesScriptArgs: boolean;
|
|
145
148
|
}
|
|
146
149
|
/** How a module's exports behave at runtime, beyond what its code assigns. */
|
|
147
150
|
interface ModuleInfo {
|
package/dist/index.d.ts
CHANGED
|
@@ -142,6 +142,9 @@ interface LowerResult {
|
|
|
142
142
|
* file it was lowered from, this is what turns a line of the bundle back
|
|
143
143
|
* into a place someone can open. */
|
|
144
144
|
readonly origins: WeakMap<L.Statement, number>;
|
|
145
|
+
/** The file reads `scriptArgs`: whatever runs it has to set it up, from
|
|
146
|
+
* the chunk's own `...` — this file, compiled alone, does it itself. */
|
|
147
|
+
readonly usesScriptArgs: boolean;
|
|
145
148
|
}
|
|
146
149
|
/** How a module's exports behave at runtime, beyond what its code assigns. */
|
|
147
150
|
interface ModuleInfo {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tilua/compiler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.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",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"type": "module",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"luau-parser": "^1.0.3",
|
|
40
|
-
"@tilua/parser": "^
|
|
40
|
+
"@tilua/parser": "^2.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^22.20.2",
|