@tilua/compiler 5.0.0 → 6.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 +13 -1
- package/dist/{chunk-3J2QMA4W.js → chunk-EQE22LX7.js} +288 -24
- package/dist/cli.cjs +290 -26
- package/dist/cli.js +3 -3
- package/dist/index.cjs +288 -24
- 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 +35 -0
package/dist/index.cjs
CHANGED
|
@@ -285,7 +285,29 @@ var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
|
|
|
285
285
|
|
|
286
286
|
// src/lower.ts
|
|
287
287
|
function lower(program2, scopes, options = {}) {
|
|
288
|
-
|
|
288
|
+
const result2 = new Lowerer(program2, scopes, options).run();
|
|
289
|
+
dropUnreachable(result2.statements);
|
|
290
|
+
return result2;
|
|
291
|
+
}
|
|
292
|
+
function dropUnreachable(statements) {
|
|
293
|
+
const exit = statements.findIndex((s) => s.type === "ReturnStatement" || s.type === "BreakStatement" || s.type === "ContinueStatement");
|
|
294
|
+
if (exit >= 0) statements.length = exit + 1;
|
|
295
|
+
for (const statement of statements) visitBlocks(statement);
|
|
296
|
+
}
|
|
297
|
+
function visitBlocks(node) {
|
|
298
|
+
if (!node || typeof node !== "object") return;
|
|
299
|
+
if (Array.isArray(node)) {
|
|
300
|
+
for (const item of node) visitBlocks(item);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const record = node;
|
|
304
|
+
if (record.type === "Block" && Array.isArray(record.statements)) {
|
|
305
|
+
dropUnreachable(record.statements);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
for (const key in record) {
|
|
309
|
+
if (key !== "line" && key !== "column") visitBlocks(record[key]);
|
|
310
|
+
}
|
|
289
311
|
}
|
|
290
312
|
var Lowerer = class {
|
|
291
313
|
constructor(source, scopes, options) {
|
|
@@ -298,6 +320,18 @@ var Lowerer = class {
|
|
|
298
320
|
for (const binding of scopes.bindings.values()) {
|
|
299
321
|
if (binding.declarationNode) this.bindingByDeclaration.set(binding.declarationNode, binding);
|
|
300
322
|
}
|
|
323
|
+
const visit = (value) => {
|
|
324
|
+
if (!value || typeof value !== "object") return;
|
|
325
|
+
if (Array.isArray(value)) return void value.forEach(visit);
|
|
326
|
+
const node = value;
|
|
327
|
+
if (node.type === "VariableDeclaration" && node.name?.type === "IdentifierPattern" && node.name.name) {
|
|
328
|
+
let init = node.init;
|
|
329
|
+
while (init?.type === "ParenthesizedExpression") init = init.expression;
|
|
330
|
+
if (init?.type === "ClassExpression") this.classExpressionNames.set(init, node.name.name);
|
|
331
|
+
}
|
|
332
|
+
for (const [key, child] of Object.entries(value)) if (key !== "line" && key !== "column") visit(child);
|
|
333
|
+
};
|
|
334
|
+
visit(source.body);
|
|
301
335
|
}
|
|
302
336
|
source;
|
|
303
337
|
scopes;
|
|
@@ -315,10 +349,17 @@ var Lowerer = class {
|
|
|
315
349
|
exportsName;
|
|
316
350
|
/** Globals generated code calls, renamed where the source shadows them. */
|
|
317
351
|
builtins = /* @__PURE__ */ new Map();
|
|
352
|
+
/** The `try` blocks being lowered in the current function, innermost
|
|
353
|
+
* last; see `tryStatement`. A function written inside one starts afresh. */
|
|
354
|
+
tryFrames = [];
|
|
355
|
+
/** The loops being lowered in the current function, innermost last. */
|
|
356
|
+
loops = [];
|
|
318
357
|
/** Whether the output has to be plain Lua 5.1 rather than Luau. */
|
|
319
358
|
lua51;
|
|
320
359
|
/** The source line each emitted statement came from. */
|
|
321
360
|
origins = /* @__PURE__ */ new WeakMap();
|
|
361
|
+
/** The name each class written as a value is known by. */
|
|
362
|
+
classExpressionNames = /* @__PURE__ */ new WeakMap();
|
|
322
363
|
run() {
|
|
323
364
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
324
365
|
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
@@ -678,6 +719,16 @@ local function ${typeOf}(value, known)
|
|
|
678
719
|
end`).body.statements);
|
|
679
720
|
}
|
|
680
721
|
}
|
|
722
|
+
const add = this.helpers.get("add");
|
|
723
|
+
if (add) {
|
|
724
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
725
|
+
local function ${add}(a, b)
|
|
726
|
+
if ${this.builtin("type").name}(a) == "string" or ${this.builtin("type").name}(b) == "string" then
|
|
727
|
+
return ${this.builtin("tostring").name}(a) .. ${this.builtin("tostring").name}(b)
|
|
728
|
+
end
|
|
729
|
+
return a + b
|
|
730
|
+
end`).body.statements);
|
|
731
|
+
}
|
|
681
732
|
const instanceOf = this.helpers.get("instanceof");
|
|
682
733
|
if (instanceOf) {
|
|
683
734
|
out.push(...(0, import_luau_parser.parse)(`
|
|
@@ -700,6 +751,8 @@ end`).body.statements);
|
|
|
700
751
|
out.push(...(0, import_luau_parser.parse)(`
|
|
701
752
|
local function ${accessors}(target, getters, setters)
|
|
702
753
|
return ${this.builtin("setmetatable").name}(target, {
|
|
754
|
+
-- Read by a spread, which copies what the getters give.
|
|
755
|
+
__tiluaGetters = getters,
|
|
703
756
|
__index = function(self, key)
|
|
704
757
|
local get = getters[key]
|
|
705
758
|
if get ~= nil then
|
|
@@ -723,17 +776,25 @@ end`).body.statements);
|
|
|
723
776
|
}
|
|
724
777
|
const assign2 = this.helpers.get("assign");
|
|
725
778
|
if (assign2) {
|
|
726
|
-
out.push(
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
779
|
+
out.push(...(0, import_luau_parser.parse)(`
|
|
780
|
+
local function ${assign2}(target, ...)
|
|
781
|
+
for i = 1, ${this.builtin("select").name}("#", ...) do
|
|
782
|
+
local source = (${this.builtin("select").name}(i, ...))
|
|
783
|
+
if source ~= nil then
|
|
784
|
+
for key, value in ${this.builtin("pairs").name}(source) do
|
|
785
|
+
target[key] = value
|
|
786
|
+
end
|
|
787
|
+
local meta = ${this.builtin("type").name}(source) == "table" and ${this.builtin("getmetatable").name}(source) or nil
|
|
788
|
+
local getters = ${this.builtin("type").name}(meta) == "table" and ${this.builtin("rawget").name}(meta, "__tiluaGetters") or nil
|
|
789
|
+
if getters ~= nil then
|
|
790
|
+
for key, get in ${this.builtin("pairs").name}(getters) do
|
|
791
|
+
target[key] = get(source)
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
return target
|
|
797
|
+
end`).body.statements);
|
|
737
798
|
}
|
|
738
799
|
out.push(...runtimes);
|
|
739
800
|
const concat = this.helpers.get("concat");
|
|
@@ -1092,7 +1153,49 @@ end`).body.statements);
|
|
|
1092
1153
|
if (!this.origins.has(statement)) this.origins.set(statement, node.line.start);
|
|
1093
1154
|
return statement;
|
|
1094
1155
|
}
|
|
1156
|
+
/** A statement; a loop with what its labels need around it.
|
|
1157
|
+
*
|
|
1158
|
+
* Luau has no labels, and no `goto` to stand in for them. `break outer`
|
|
1159
|
+
* in a loop inside `outer` sets a flag `outer` declares and breaks the
|
|
1160
|
+
* loop it is in; right after that loop, the flag is checked, and the
|
|
1161
|
+
* check breaks again — or, once it is `outer` that would be left, does
|
|
1162
|
+
* what was asked of `outer` itself. `continue outer` is the same, with
|
|
1163
|
+
* `continue` done there and the flag lowered for the next time round. */
|
|
1095
1164
|
statementOf(node) {
|
|
1165
|
+
if (!LOOPS.has(node.type)) return this.statementKind(node);
|
|
1166
|
+
const loop = {
|
|
1167
|
+
label: node.label?.name,
|
|
1168
|
+
tryDepth: this.tryFrames.length,
|
|
1169
|
+
leaving: /* @__PURE__ */ new Set()
|
|
1170
|
+
};
|
|
1171
|
+
this.loops.push(loop);
|
|
1172
|
+
let lowered;
|
|
1173
|
+
try {
|
|
1174
|
+
lowered = this.statementKind(node);
|
|
1175
|
+
} finally {
|
|
1176
|
+
this.loops.pop();
|
|
1177
|
+
}
|
|
1178
|
+
const flags = [loop.breakFlag, loop.continueFlag].filter((f) => f !== void 0);
|
|
1179
|
+
const out = [...flags.length ? [local(flags, flags.map(() => boolean(false)))] : [], ...lowered];
|
|
1180
|
+
const enclosing = this.loops[this.loops.length - 1];
|
|
1181
|
+
for (const target of loop.leaving) {
|
|
1182
|
+
if (target === enclosing) {
|
|
1183
|
+
if (target.breakFlag) out.push(ifThen(identifier(target.breakFlag), this.jump("break", void 0)));
|
|
1184
|
+
if (target.continueFlag) {
|
|
1185
|
+
out.push(ifThen(identifier(target.continueFlag), [
|
|
1186
|
+
assign([identifier(target.continueFlag)], [boolean(false)]),
|
|
1187
|
+
...this.jump("continue", void 0)
|
|
1188
|
+
]));
|
|
1189
|
+
}
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
enclosing?.leaving.add(target);
|
|
1193
|
+
const raised = [target.breakFlag, target.continueFlag].filter((f) => f !== void 0).map((f) => identifier(f)).reduce((a, b) => binary("or", a, b));
|
|
1194
|
+
out.push(ifThen(raised, this.jump("break", void 0)));
|
|
1195
|
+
}
|
|
1196
|
+
return out;
|
|
1197
|
+
}
|
|
1198
|
+
statementKind(node) {
|
|
1096
1199
|
switch (node.type) {
|
|
1097
1200
|
// Types, and what exists only for them.
|
|
1098
1201
|
case "TypeAliasStatement":
|
|
@@ -1112,15 +1215,20 @@ end`).body.statements);
|
|
|
1112
1215
|
return this.classDeclaration(node, "declare");
|
|
1113
1216
|
case "AssignmentStatement":
|
|
1114
1217
|
return this.assignment(node);
|
|
1115
|
-
case "CompoundAssignmentStatement":
|
|
1116
|
-
if (
|
|
1218
|
+
case "CompoundAssignmentStatement": {
|
|
1219
|
+
if (node.operator === "??=") return this.nullishAssignment(node);
|
|
1220
|
+
const join = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
|
|
1221
|
+
if (join === "dynamic") return this.dynamicAddition(node);
|
|
1222
|
+
const operator = join === "concat" ? "..=" : node.operator;
|
|
1223
|
+
if (this.lua51) return this.compoundAssignment51(node, operator);
|
|
1117
1224
|
return [{
|
|
1118
1225
|
type: "CompoundAssignmentStatement",
|
|
1119
|
-
operator
|
|
1226
|
+
operator,
|
|
1120
1227
|
target: this.expression(node.target),
|
|
1121
1228
|
value: this.expression(node.value),
|
|
1122
1229
|
...spanOf(node)
|
|
1123
1230
|
}];
|
|
1231
|
+
}
|
|
1124
1232
|
case "CallStatement": {
|
|
1125
1233
|
const chain = optionalChain(node.expression);
|
|
1126
1234
|
if (chain) return [this.optionalCallStatement(chain)];
|
|
@@ -1206,11 +1314,20 @@ end`).body.statements);
|
|
|
1206
1314
|
)];
|
|
1207
1315
|
}
|
|
1208
1316
|
case "ReturnStatement":
|
|
1209
|
-
return
|
|
1317
|
+
return this.jump("return", node.argument && this.keptToOne(this.expression(node.argument)), node);
|
|
1210
1318
|
case "BreakStatement":
|
|
1211
|
-
return
|
|
1319
|
+
return this.jump("break", void 0, node, node.label?.name);
|
|
1212
1320
|
case "ContinueStatement":
|
|
1213
|
-
return
|
|
1321
|
+
return this.jump("continue", void 0, node, node.label?.name);
|
|
1322
|
+
// Level 0: what is thrown is what a `catch` is handed, with no
|
|
1323
|
+
// position put in front of a string.
|
|
1324
|
+
case "ThrowStatement":
|
|
1325
|
+
return [callStatement(call(
|
|
1326
|
+
this.builtin("error"),
|
|
1327
|
+
[this.keptToOne(this.expression(node.argument)), number(0)]
|
|
1328
|
+
))];
|
|
1329
|
+
case "TryStatement":
|
|
1330
|
+
return this.tryStatement(node);
|
|
1214
1331
|
case "ImportStatement":
|
|
1215
1332
|
if (node.isTypeOnly) return [];
|
|
1216
1333
|
this.report(node, this.options.module ? "Imports and exports belong at the top level of a module" : "Imports and exports need a bundle: build the project with @tilua/compiler");
|
|
@@ -1223,6 +1340,90 @@ end`).body.statements);
|
|
|
1223
1340
|
return [];
|
|
1224
1341
|
}
|
|
1225
1342
|
}
|
|
1343
|
+
/** A `return`, `break` or `continue`. Inside a `try` that it leaves, it
|
|
1344
|
+
* is the `try`'s function returning a code saying which — and the value
|
|
1345
|
+
* — for the code after the `pcall` to do it there. */
|
|
1346
|
+
jump(kind, value, node, label) {
|
|
1347
|
+
const at = node ? spanOf(node) : {};
|
|
1348
|
+
const frame = this.tryFrames[this.tryFrames.length - 1];
|
|
1349
|
+
const target = kind === "return" ? void 0 : label === void 0 ? this.loops[this.loops.length - 1] : this.loops.findLast((loop) => loop.label === label);
|
|
1350
|
+
if (frame && (kind === "return" || target && target.tryDepth < this.tryFrames.length)) {
|
|
1351
|
+
const key = `${kind}:${label ?? ""}`;
|
|
1352
|
+
let exit = frame.exits.get(key);
|
|
1353
|
+
if (!exit) {
|
|
1354
|
+
exit = { kind, label, code: kind === "return" ? 1 : frame.exits.size + 2 };
|
|
1355
|
+
frame.exits.set(key, exit);
|
|
1356
|
+
}
|
|
1357
|
+
if (value) frame.returnsValue = true;
|
|
1358
|
+
return [returns([number(exit.code), ...value ? [value] : []])];
|
|
1359
|
+
}
|
|
1360
|
+
if (kind === "return") return [{ ...returns(value ? [value] : []), ...at }];
|
|
1361
|
+
const plain = () => kind === "break" ? { ...breakStatement(), ...at } : { ...breakStatement(), type: "ContinueStatement", ...at };
|
|
1362
|
+
const innermost = this.loops[this.loops.length - 1];
|
|
1363
|
+
if (!target || target === innermost) return [plain()];
|
|
1364
|
+
const flag = kind === "break" ? target.breakFlag ??= this.names.fresh(`${target.label}Broke`) : target.continueFlag ??= this.names.fresh(`${target.label}Continued`);
|
|
1365
|
+
innermost.leaving.add(target);
|
|
1366
|
+
return [assign([identifier(flag)], [boolean(true)]), { ...breakStatement(), ...at }];
|
|
1367
|
+
}
|
|
1368
|
+
/** `try { a } catch (e) { b } finally { c }`:
|
|
1369
|
+
*
|
|
1370
|
+
* local ok, result, value = pcall(function() a end)
|
|
1371
|
+
* if not ok then
|
|
1372
|
+
* ok, result, value = pcall(function(e) b end, result)
|
|
1373
|
+
* end
|
|
1374
|
+
* do c end
|
|
1375
|
+
* if not ok then error(result, 0) end
|
|
1376
|
+
* if result == 1 then return value elseif ... end
|
|
1377
|
+
*
|
|
1378
|
+
* Without a `finally`, the `catch` runs in place of the second `pcall`:
|
|
1379
|
+
* nothing has to run after it. A `return`, `break` or `continue` that
|
|
1380
|
+
* leaves the `try` cannot be written inside its function; it returns a
|
|
1381
|
+
* code instead (see `jump`), and the last `if` does what it says. */
|
|
1382
|
+
tryStatement(node) {
|
|
1383
|
+
const ok = this.names.fresh("ok");
|
|
1384
|
+
const result2 = this.names.fresh("result");
|
|
1385
|
+
const value = this.names.fresh("value");
|
|
1386
|
+
const frame = { exits: /* @__PURE__ */ new Map(), returnsValue: false };
|
|
1387
|
+
const guarded = (block2, params, args) => {
|
|
1388
|
+
this.tryFrames.push(frame);
|
|
1389
|
+
let statements;
|
|
1390
|
+
try {
|
|
1391
|
+
statements = this.block(block2).statements;
|
|
1392
|
+
} finally {
|
|
1393
|
+
this.tryFrames.pop();
|
|
1394
|
+
}
|
|
1395
|
+
const passesVarargs = statements.some(usesVararg);
|
|
1396
|
+
const func = functionExpression(functionBody(params, statements, passesVarargs));
|
|
1397
|
+
return call(this.builtin("pcall"), [func, ...args, ...passesVarargs ? [vararg()] : []]);
|
|
1398
|
+
};
|
|
1399
|
+
const attempt = guarded(node.block, [], []);
|
|
1400
|
+
const param = node.handler?.param && this.name(node.handler.param.name);
|
|
1401
|
+
const inPlace = node.handler && !node.finalizer ? this.block(node.handler.body, param ? [local([param], [identifier(result2)])] : []) : void 0;
|
|
1402
|
+
const caught = node.handler && node.finalizer ? guarded(node.handler.body, [param ?? "_"], [identifier(result2)]) : void 0;
|
|
1403
|
+
const finalizer = node.finalizer && this.block(node.finalizer);
|
|
1404
|
+
const returnsValue = frame.returnsValue;
|
|
1405
|
+
const usesResult = returnsValue || frame.exits.size > 0 || !!param || !!node.finalizer;
|
|
1406
|
+
const names = [ok, ...usesResult ? [result2] : [], ...returnsValue ? [value] : []];
|
|
1407
|
+
const failed = () => unary("not", identifier(ok));
|
|
1408
|
+
const out = [this.from(local(names, [attempt]), node)];
|
|
1409
|
+
const clauses = [];
|
|
1410
|
+
if (inPlace) clauses.push({ type: "IfClause", condition: failed(), body: inPlace, ...spanOf(node.handler) });
|
|
1411
|
+
if (caught) out.push(ifThen(failed(), [assign(names.map(identifier), [caught])]));
|
|
1412
|
+
if (finalizer) {
|
|
1413
|
+
out.push(doBlock(finalizer.statements));
|
|
1414
|
+
out.push(ifThen(failed(), [callStatement(call(this.builtin("error"), [identifier(result2), number(0)]))]));
|
|
1415
|
+
}
|
|
1416
|
+
for (const exit of [...frame.exits.values()].sort((a, b) => a.code - b.code)) {
|
|
1417
|
+
clauses.push({
|
|
1418
|
+
type: "IfClause",
|
|
1419
|
+
condition: binary("==", identifier(result2), number(exit.code)),
|
|
1420
|
+
body: block(this.jump(exit.kind, exit.kind === "return" && returnsValue ? identifier(value) : void 0, void 0, exit.label)),
|
|
1421
|
+
...spanOf(node)
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
if (clauses.length) out.push({ type: "IfStatement", clauses, ...spanOf(node) });
|
|
1425
|
+
return out;
|
|
1426
|
+
}
|
|
1226
1427
|
functionDeclarationStatement(node) {
|
|
1227
1428
|
const statement = this.functionStatement(this.reference(node.target.base), node, this.functionBody(node.func), node.isMethod, node.target.path.map((p) => p.name));
|
|
1228
1429
|
if (!node.target.method) return statement;
|
|
@@ -1278,11 +1479,12 @@ end`).body.statements);
|
|
|
1278
1479
|
const { build, accessors } = this.classHelpers;
|
|
1279
1480
|
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
1280
1481
|
return (0, import_luau_parser.parse)(`
|
|
1281
|
-
local function ${build}(base)
|
|
1482
|
+
local function ${build}(base, name)
|
|
1282
1483
|
local class = { __getters = {}, __setters = {} }
|
|
1283
1484
|
class.__index = class
|
|
1284
1485
|
class.ClassObject = class
|
|
1285
1486
|
class.ParentClass = base
|
|
1487
|
+
class.name = name
|
|
1286
1488
|
if base ~= nil then
|
|
1287
1489
|
setmetatable(class, { __index = base })
|
|
1288
1490
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
@@ -1336,7 +1538,11 @@ end
|
|
|
1336
1538
|
* table is assigned to `self`, which a module has already declared. */
|
|
1337
1539
|
classStatements(node, self, declare) {
|
|
1338
1540
|
const { build, accessors } = this.classRuntime();
|
|
1339
|
-
const
|
|
1541
|
+
const called = node.name?.name ?? (node.type === "ClassExpression" ? this.classExpressionNames.get(node) : void 0) ?? "";
|
|
1542
|
+
const create = call(identifier(build), [
|
|
1543
|
+
node.superclass ? this.reference(node.superclass) : nil(),
|
|
1544
|
+
string(called)
|
|
1545
|
+
]);
|
|
1340
1546
|
const out = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
|
|
1341
1547
|
const previous = this.classContext;
|
|
1342
1548
|
this.classContext = node;
|
|
@@ -1607,6 +1813,18 @@ end
|
|
|
1607
1813
|
// Functions
|
|
1608
1814
|
// --------------------------------------------------------
|
|
1609
1815
|
functionBody(func) {
|
|
1816
|
+
const frames = this.tryFrames;
|
|
1817
|
+
const loops = this.loops;
|
|
1818
|
+
this.tryFrames = [];
|
|
1819
|
+
this.loops = [];
|
|
1820
|
+
try {
|
|
1821
|
+
return this.functionBodyIn(func);
|
|
1822
|
+
} finally {
|
|
1823
|
+
this.tryFrames = frames;
|
|
1824
|
+
this.loops = loops;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
functionBodyIn(func) {
|
|
1610
1828
|
const params = func.isMethod ? func.params.slice(1) : func.params;
|
|
1611
1829
|
const prelude = [];
|
|
1612
1830
|
const names = [];
|
|
@@ -1657,8 +1875,10 @@ end
|
|
|
1657
1875
|
return nil();
|
|
1658
1876
|
case "InterpolatedStringExpression":
|
|
1659
1877
|
return this.interpolatedString(node);
|
|
1878
|
+
// An object's method, `{ m() { ... } }`, keeps its `this` as the
|
|
1879
|
+
// first parameter written out: a function value has no `:`.
|
|
1660
1880
|
case "FunctionExpression":
|
|
1661
|
-
return functionExpression(this.
|
|
1881
|
+
return functionExpression(this.classFunctionBody(node.func));
|
|
1662
1882
|
case "TableExpression":
|
|
1663
1883
|
return this.tableExpression(node);
|
|
1664
1884
|
case "ArrayExpression":
|
|
@@ -1673,6 +1893,11 @@ end
|
|
|
1673
1893
|
if (node.operator === "instanceof") {
|
|
1674
1894
|
return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1675
1895
|
}
|
|
1896
|
+
if (node.operator === "+") {
|
|
1897
|
+
const join = this.options.types?.joins.get(node);
|
|
1898
|
+
if (join === "concat") return binary("..", left, right);
|
|
1899
|
+
if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1900
|
+
}
|
|
1676
1901
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1677
1902
|
return binary(node.operator, left, right);
|
|
1678
1903
|
}
|
|
@@ -1932,14 +2157,52 @@ end
|
|
|
1932
2157
|
if (!args.length) return string(text);
|
|
1933
2158
|
return methodCall(parenthesized(string(format)), "format", args);
|
|
1934
2159
|
}
|
|
2160
|
+
/** `t ??= v`: `if t == nil then t = v end`, which no Luau has an
|
|
2161
|
+
* operator for. `v` is read only where `t` is nil. As in `a += b` for
|
|
2162
|
+
* Lua 5.1, what addresses the target is read once: an object or key
|
|
2163
|
+
* that is not a plain name goes into a local first. */
|
|
2164
|
+
nullishAssignment(node) {
|
|
2165
|
+
const { before, target } = this.targetOnce(node.target);
|
|
2166
|
+
const statement = ifThen(binary("==", target, nil()), [assign([target], [this.expression(node.value)])]);
|
|
2167
|
+
return before.length ? [doBlock([...before, statement])] : [statement];
|
|
2168
|
+
}
|
|
2169
|
+
/** `t += v` where only the values can say whether it joins or adds:
|
|
2170
|
+
* `t = tilua_add(t, v)`, with what addresses `t` read once. */
|
|
2171
|
+
dynamicAddition(node) {
|
|
2172
|
+
const { before, target } = this.targetOnce(node.target);
|
|
2173
|
+
const statement = assign([target], [call(this.helper("add"), [target, this.keptToOne(this.expression(node.value))])]);
|
|
2174
|
+
return before.length ? [doBlock([...before, statement])] : [statement];
|
|
2175
|
+
}
|
|
2176
|
+
/** An assignment's target, as an expression that may be read and written
|
|
2177
|
+
* freely: an object or key that is not a plain name goes into a local
|
|
2178
|
+
* first, so that `t[next()]` advances once. */
|
|
2179
|
+
targetOnce(node) {
|
|
2180
|
+
const before = [];
|
|
2181
|
+
const once = (expression, name) => {
|
|
2182
|
+
if (expression.type === "Identifier" || expression.type === "StringLiteral" || expression.type === "NumberLiteral") return expression;
|
|
2183
|
+
const local2 = this.names.fresh(name);
|
|
2184
|
+
before.push(local([local2], [expression]));
|
|
2185
|
+
return identifier(local2);
|
|
2186
|
+
};
|
|
2187
|
+
let target;
|
|
2188
|
+
if (node.type === "MemberExpression") {
|
|
2189
|
+
target = member(once(this.expression(node.object), "target"), node.property.name);
|
|
2190
|
+
} else if (node.type === "IndexExpression") {
|
|
2191
|
+
const object2 = once(this.expression(node.object), "target");
|
|
2192
|
+
target = index(object2, once(this.expression(node.index), "key"));
|
|
2193
|
+
} else {
|
|
2194
|
+
target = this.expression(node);
|
|
2195
|
+
}
|
|
2196
|
+
return { before, target };
|
|
2197
|
+
}
|
|
1935
2198
|
/** `a += b` for Lua 5.1, which has no compound assignment: `a = a + b`.
|
|
1936
2199
|
*
|
|
1937
2200
|
* The target has to be read and written, and writing it out twice would
|
|
1938
2201
|
* evaluate whatever addresses it twice — `t[next()] += 1` would advance
|
|
1939
2202
|
* twice and add to the wrong slot. So anything but a plain name has its
|
|
1940
2203
|
* object and key taken into locals first, and both halves use those. */
|
|
1941
|
-
compoundAssignment51(node) {
|
|
1942
|
-
const operator =
|
|
2204
|
+
compoundAssignment51(node, written = node.operator) {
|
|
2205
|
+
const operator = written.slice(0, -1);
|
|
1943
2206
|
const before = [];
|
|
1944
2207
|
let target;
|
|
1945
2208
|
if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
|
|
@@ -2138,6 +2401,7 @@ function optionalChain(node) {
|
|
|
2138
2401
|
if (first < 0) return void 0;
|
|
2139
2402
|
return { base: linkObject(spine[first]), links: spine.slice(first) };
|
|
2140
2403
|
}
|
|
2404
|
+
var LOOPS = /* @__PURE__ */ new Set(["WhileStatement", "RepeatStatement", "NumericForStatement", "GenericForStatement"]);
|
|
2141
2405
|
function usesVararg(node) {
|
|
2142
2406
|
if (Array.isArray(node)) return node.some(usesVararg);
|
|
2143
2407
|
if (!node || typeof node !== "object") return false;
|
|
@@ -3005,7 +3269,7 @@ function analyzeModules(modules, config) {
|
|
|
3005
3269
|
});
|
|
3006
3270
|
for (const d of types.diagnostics) {
|
|
3007
3271
|
const at = d.node;
|
|
3008
|
-
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
3272
|
+
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type", severity: d.severity });
|
|
3009
3273
|
}
|
|
3010
3274
|
const result2 = { types, diagnostics: said };
|
|
3011
3275
|
if (partials === before) {
|
package/dist/index.d.cts
CHANGED
|
@@ -256,6 +256,9 @@ interface BundleDiagnostic {
|
|
|
256
256
|
* `syntax`, `scope` (such as assigning to an import) and `module`
|
|
257
257
|
* problems leave no bundle. */
|
|
258
258
|
readonly category: "syntax" | "scope" | "module" | "type" | "config";
|
|
259
|
+
/** A test that always comes out the same, and the like: said, and no
|
|
260
|
+
* reason for the build to fail. */
|
|
261
|
+
readonly severity?: "warning";
|
|
259
262
|
}
|
|
260
263
|
interface BundleResult {
|
|
261
264
|
/** The bundle — the entry's file, when there are several — or `undefined`
|
package/dist/index.d.ts
CHANGED
|
@@ -256,6 +256,9 @@ interface BundleDiagnostic {
|
|
|
256
256
|
* `syntax`, `scope` (such as assigning to an import) and `module`
|
|
257
257
|
* problems leave no bundle. */
|
|
258
258
|
readonly category: "syntax" | "scope" | "module" | "type" | "config";
|
|
259
|
+
/** A test that always comes out the same, and the like: said, and no
|
|
260
|
+
* reason for the build to fail. */
|
|
261
|
+
readonly severity?: "warning";
|
|
259
262
|
}
|
|
260
263
|
interface BundleResult {
|
|
261
264
|
/** The bundle — the entry's file, when there are several — or `undefined`
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tilua/compiler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.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": "^6.0.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^22.20.2",
|
package/runtime/array.luau
CHANGED
|
@@ -130,6 +130,41 @@ function __NAME__.slice(t, start, stop)
|
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
-- One level, as JavaScript's `flat()` does by default.
|
|
133
|
+
function __NAME__.keys(t)
|
|
134
|
+
local out = __ARRAY__({})
|
|
135
|
+
for i = 1, #t do
|
|
136
|
+
out[i] = i
|
|
137
|
+
end
|
|
138
|
+
return out
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
function __NAME__.values(t)
|
|
142
|
+
local out = __ARRAY__({})
|
|
143
|
+
for i = 1, #t do
|
|
144
|
+
out[i] = t[i]
|
|
145
|
+
end
|
|
146
|
+
return out
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
function __NAME__.entries(t)
|
|
150
|
+
local out = __ARRAY__({})
|
|
151
|
+
for i = 1, #t do
|
|
152
|
+
out[i] = __ARRAY__({ i, t[i] })
|
|
153
|
+
end
|
|
154
|
+
return out
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
-- `Object.fromEntries`: each `[key, value]` pair a member of a new table,
|
|
158
|
+
-- a later pair over an earlier one of the same key.
|
|
159
|
+
function __NAME__.fromEntries(t)
|
|
160
|
+
local out = {}
|
|
161
|
+
for i = 1, #t do
|
|
162
|
+
local entry = t[i]
|
|
163
|
+
out[entry[1]] = entry[2]
|
|
164
|
+
end
|
|
165
|
+
return out
|
|
166
|
+
end
|
|
167
|
+
|
|
133
168
|
function __NAME__.flat(t)
|
|
134
169
|
local out = __ARRAY__({})
|
|
135
170
|
for i = 1, #t do
|