@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/cli.cjs
CHANGED
|
@@ -262,7 +262,29 @@ var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
|
|
|
262
262
|
|
|
263
263
|
// src/lower.ts
|
|
264
264
|
function lower(program2, scopes, options = {}) {
|
|
265
|
-
|
|
265
|
+
const result2 = new Lowerer(program2, scopes, options).run();
|
|
266
|
+
dropUnreachable(result2.statements);
|
|
267
|
+
return result2;
|
|
268
|
+
}
|
|
269
|
+
function dropUnreachable(statements) {
|
|
270
|
+
const exit = statements.findIndex((s) => s.type === "ReturnStatement" || s.type === "BreakStatement" || s.type === "ContinueStatement");
|
|
271
|
+
if (exit >= 0) statements.length = exit + 1;
|
|
272
|
+
for (const statement of statements) visitBlocks(statement);
|
|
273
|
+
}
|
|
274
|
+
function visitBlocks(node) {
|
|
275
|
+
if (!node || typeof node !== "object") return;
|
|
276
|
+
if (Array.isArray(node)) {
|
|
277
|
+
for (const item of node) visitBlocks(item);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const record = node;
|
|
281
|
+
if (record.type === "Block" && Array.isArray(record.statements)) {
|
|
282
|
+
dropUnreachable(record.statements);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
for (const key in record) {
|
|
286
|
+
if (key !== "line" && key !== "column") visitBlocks(record[key]);
|
|
287
|
+
}
|
|
266
288
|
}
|
|
267
289
|
var Lowerer = class {
|
|
268
290
|
constructor(source, scopes, options) {
|
|
@@ -275,6 +297,18 @@ var Lowerer = class {
|
|
|
275
297
|
for (const binding of scopes.bindings.values()) {
|
|
276
298
|
if (binding.declarationNode) this.bindingByDeclaration.set(binding.declarationNode, binding);
|
|
277
299
|
}
|
|
300
|
+
const visit = (value) => {
|
|
301
|
+
if (!value || typeof value !== "object") return;
|
|
302
|
+
if (Array.isArray(value)) return void value.forEach(visit);
|
|
303
|
+
const node = value;
|
|
304
|
+
if (node.type === "VariableDeclaration" && node.name?.type === "IdentifierPattern" && node.name.name) {
|
|
305
|
+
let init = node.init;
|
|
306
|
+
while (init?.type === "ParenthesizedExpression") init = init.expression;
|
|
307
|
+
if (init?.type === "ClassExpression") this.classExpressionNames.set(init, node.name.name);
|
|
308
|
+
}
|
|
309
|
+
for (const [key, child] of Object.entries(value)) if (key !== "line" && key !== "column") visit(child);
|
|
310
|
+
};
|
|
311
|
+
visit(source.body);
|
|
278
312
|
}
|
|
279
313
|
source;
|
|
280
314
|
scopes;
|
|
@@ -292,10 +326,17 @@ var Lowerer = class {
|
|
|
292
326
|
exportsName;
|
|
293
327
|
/** Globals generated code calls, renamed where the source shadows them. */
|
|
294
328
|
builtins = /* @__PURE__ */ new Map();
|
|
329
|
+
/** The `try` blocks being lowered in the current function, innermost
|
|
330
|
+
* last; see `tryStatement`. A function written inside one starts afresh. */
|
|
331
|
+
tryFrames = [];
|
|
332
|
+
/** The loops being lowered in the current function, innermost last. */
|
|
333
|
+
loops = [];
|
|
295
334
|
/** Whether the output has to be plain Lua 5.1 rather than Luau. */
|
|
296
335
|
lua51;
|
|
297
336
|
/** The source line each emitted statement came from. */
|
|
298
337
|
origins = /* @__PURE__ */ new WeakMap();
|
|
338
|
+
/** The name each class written as a value is known by. */
|
|
339
|
+
classExpressionNames = /* @__PURE__ */ new WeakMap();
|
|
299
340
|
run() {
|
|
300
341
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
301
342
|
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
@@ -655,6 +696,16 @@ local function ${typeOf}(value, known)
|
|
|
655
696
|
end`).body.statements);
|
|
656
697
|
}
|
|
657
698
|
}
|
|
699
|
+
const add = this.helpers.get("add");
|
|
700
|
+
if (add) {
|
|
701
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
702
|
+
local function ${add}(a, b)
|
|
703
|
+
if ${this.builtin("type").name}(a) == "string" or ${this.builtin("type").name}(b) == "string" then
|
|
704
|
+
return ${this.builtin("tostring").name}(a) .. ${this.builtin("tostring").name}(b)
|
|
705
|
+
end
|
|
706
|
+
return a + b
|
|
707
|
+
end`).body.statements);
|
|
708
|
+
}
|
|
658
709
|
const instanceOf = this.helpers.get("instanceof");
|
|
659
710
|
if (instanceOf) {
|
|
660
711
|
out2.push(...(0, import_luau_parser.parse)(`
|
|
@@ -677,6 +728,8 @@ end`).body.statements);
|
|
|
677
728
|
out2.push(...(0, import_luau_parser.parse)(`
|
|
678
729
|
local function ${accessors}(target, getters, setters)
|
|
679
730
|
return ${this.builtin("setmetatable").name}(target, {
|
|
731
|
+
-- Read by a spread, which copies what the getters give.
|
|
732
|
+
__tiluaGetters = getters,
|
|
680
733
|
__index = function(self, key)
|
|
681
734
|
local get = getters[key]
|
|
682
735
|
if get ~= nil then
|
|
@@ -700,17 +753,25 @@ end`).body.statements);
|
|
|
700
753
|
}
|
|
701
754
|
const assign2 = this.helpers.get("assign");
|
|
702
755
|
if (assign2) {
|
|
703
|
-
out2.push(
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
756
|
+
out2.push(...(0, import_luau_parser.parse)(`
|
|
757
|
+
local function ${assign2}(target, ...)
|
|
758
|
+
for i = 1, ${this.builtin("select").name}("#", ...) do
|
|
759
|
+
local source = (${this.builtin("select").name}(i, ...))
|
|
760
|
+
if source ~= nil then
|
|
761
|
+
for key, value in ${this.builtin("pairs").name}(source) do
|
|
762
|
+
target[key] = value
|
|
763
|
+
end
|
|
764
|
+
local meta = ${this.builtin("type").name}(source) == "table" and ${this.builtin("getmetatable").name}(source) or nil
|
|
765
|
+
local getters = ${this.builtin("type").name}(meta) == "table" and ${this.builtin("rawget").name}(meta, "__tiluaGetters") or nil
|
|
766
|
+
if getters ~= nil then
|
|
767
|
+
for key, get in ${this.builtin("pairs").name}(getters) do
|
|
768
|
+
target[key] = get(source)
|
|
769
|
+
end
|
|
770
|
+
end
|
|
771
|
+
end
|
|
772
|
+
end
|
|
773
|
+
return target
|
|
774
|
+
end`).body.statements);
|
|
714
775
|
}
|
|
715
776
|
out2.push(...runtimes);
|
|
716
777
|
const concat = this.helpers.get("concat");
|
|
@@ -1069,7 +1130,49 @@ end`).body.statements);
|
|
|
1069
1130
|
if (!this.origins.has(statement)) this.origins.set(statement, node.line.start);
|
|
1070
1131
|
return statement;
|
|
1071
1132
|
}
|
|
1133
|
+
/** A statement; a loop with what its labels need around it.
|
|
1134
|
+
*
|
|
1135
|
+
* Luau has no labels, and no `goto` to stand in for them. `break outer`
|
|
1136
|
+
* in a loop inside `outer` sets a flag `outer` declares and breaks the
|
|
1137
|
+
* loop it is in; right after that loop, the flag is checked, and the
|
|
1138
|
+
* check breaks again — or, once it is `outer` that would be left, does
|
|
1139
|
+
* what was asked of `outer` itself. `continue outer` is the same, with
|
|
1140
|
+
* `continue` done there and the flag lowered for the next time round. */
|
|
1072
1141
|
statementOf(node) {
|
|
1142
|
+
if (!LOOPS.has(node.type)) return this.statementKind(node);
|
|
1143
|
+
const loop = {
|
|
1144
|
+
label: node.label?.name,
|
|
1145
|
+
tryDepth: this.tryFrames.length,
|
|
1146
|
+
leaving: /* @__PURE__ */ new Set()
|
|
1147
|
+
};
|
|
1148
|
+
this.loops.push(loop);
|
|
1149
|
+
let lowered;
|
|
1150
|
+
try {
|
|
1151
|
+
lowered = this.statementKind(node);
|
|
1152
|
+
} finally {
|
|
1153
|
+
this.loops.pop();
|
|
1154
|
+
}
|
|
1155
|
+
const flags = [loop.breakFlag, loop.continueFlag].filter((f) => f !== void 0);
|
|
1156
|
+
const out2 = [...flags.length ? [local(flags, flags.map(() => boolean(false)))] : [], ...lowered];
|
|
1157
|
+
const enclosing = this.loops[this.loops.length - 1];
|
|
1158
|
+
for (const target of loop.leaving) {
|
|
1159
|
+
if (target === enclosing) {
|
|
1160
|
+
if (target.breakFlag) out2.push(ifThen(identifier(target.breakFlag), this.jump("break", void 0)));
|
|
1161
|
+
if (target.continueFlag) {
|
|
1162
|
+
out2.push(ifThen(identifier(target.continueFlag), [
|
|
1163
|
+
assign([identifier(target.continueFlag)], [boolean(false)]),
|
|
1164
|
+
...this.jump("continue", void 0)
|
|
1165
|
+
]));
|
|
1166
|
+
}
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
enclosing?.leaving.add(target);
|
|
1170
|
+
const raised = [target.breakFlag, target.continueFlag].filter((f) => f !== void 0).map((f) => identifier(f)).reduce((a, b) => binary("or", a, b));
|
|
1171
|
+
out2.push(ifThen(raised, this.jump("break", void 0)));
|
|
1172
|
+
}
|
|
1173
|
+
return out2;
|
|
1174
|
+
}
|
|
1175
|
+
statementKind(node) {
|
|
1073
1176
|
switch (node.type) {
|
|
1074
1177
|
// Types, and what exists only for them.
|
|
1075
1178
|
case "TypeAliasStatement":
|
|
@@ -1089,15 +1192,20 @@ end`).body.statements);
|
|
|
1089
1192
|
return this.classDeclaration(node, "declare");
|
|
1090
1193
|
case "AssignmentStatement":
|
|
1091
1194
|
return this.assignment(node);
|
|
1092
|
-
case "CompoundAssignmentStatement":
|
|
1093
|
-
if (
|
|
1195
|
+
case "CompoundAssignmentStatement": {
|
|
1196
|
+
if (node.operator === "??=") return this.nullishAssignment(node);
|
|
1197
|
+
const join2 = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
|
|
1198
|
+
if (join2 === "dynamic") return this.dynamicAddition(node);
|
|
1199
|
+
const operator = join2 === "concat" ? "..=" : node.operator;
|
|
1200
|
+
if (this.lua51) return this.compoundAssignment51(node, operator);
|
|
1094
1201
|
return [{
|
|
1095
1202
|
type: "CompoundAssignmentStatement",
|
|
1096
|
-
operator
|
|
1203
|
+
operator,
|
|
1097
1204
|
target: this.expression(node.target),
|
|
1098
1205
|
value: this.expression(node.value),
|
|
1099
1206
|
...spanOf(node)
|
|
1100
1207
|
}];
|
|
1208
|
+
}
|
|
1101
1209
|
case "CallStatement": {
|
|
1102
1210
|
const chain = optionalChain(node.expression);
|
|
1103
1211
|
if (chain) return [this.optionalCallStatement(chain)];
|
|
@@ -1183,11 +1291,20 @@ end`).body.statements);
|
|
|
1183
1291
|
)];
|
|
1184
1292
|
}
|
|
1185
1293
|
case "ReturnStatement":
|
|
1186
|
-
return
|
|
1294
|
+
return this.jump("return", node.argument && this.keptToOne(this.expression(node.argument)), node);
|
|
1187
1295
|
case "BreakStatement":
|
|
1188
|
-
return
|
|
1296
|
+
return this.jump("break", void 0, node, node.label?.name);
|
|
1189
1297
|
case "ContinueStatement":
|
|
1190
|
-
return
|
|
1298
|
+
return this.jump("continue", void 0, node, node.label?.name);
|
|
1299
|
+
// Level 0: what is thrown is what a `catch` is handed, with no
|
|
1300
|
+
// position put in front of a string.
|
|
1301
|
+
case "ThrowStatement":
|
|
1302
|
+
return [callStatement(call(
|
|
1303
|
+
this.builtin("error"),
|
|
1304
|
+
[this.keptToOne(this.expression(node.argument)), number(0)]
|
|
1305
|
+
))];
|
|
1306
|
+
case "TryStatement":
|
|
1307
|
+
return this.tryStatement(node);
|
|
1191
1308
|
case "ImportStatement":
|
|
1192
1309
|
if (node.isTypeOnly) return [];
|
|
1193
1310
|
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");
|
|
@@ -1200,6 +1317,90 @@ end`).body.statements);
|
|
|
1200
1317
|
return [];
|
|
1201
1318
|
}
|
|
1202
1319
|
}
|
|
1320
|
+
/** A `return`, `break` or `continue`. Inside a `try` that it leaves, it
|
|
1321
|
+
* is the `try`'s function returning a code saying which — and the value
|
|
1322
|
+
* — for the code after the `pcall` to do it there. */
|
|
1323
|
+
jump(kind, value, node, label) {
|
|
1324
|
+
const at = node ? spanOf(node) : {};
|
|
1325
|
+
const frame = this.tryFrames[this.tryFrames.length - 1];
|
|
1326
|
+
const target = kind === "return" ? void 0 : label === void 0 ? this.loops[this.loops.length - 1] : this.loops.findLast((loop) => loop.label === label);
|
|
1327
|
+
if (frame && (kind === "return" || target && target.tryDepth < this.tryFrames.length)) {
|
|
1328
|
+
const key = `${kind}:${label ?? ""}`;
|
|
1329
|
+
let exit = frame.exits.get(key);
|
|
1330
|
+
if (!exit) {
|
|
1331
|
+
exit = { kind, label, code: kind === "return" ? 1 : frame.exits.size + 2 };
|
|
1332
|
+
frame.exits.set(key, exit);
|
|
1333
|
+
}
|
|
1334
|
+
if (value) frame.returnsValue = true;
|
|
1335
|
+
return [returns([number(exit.code), ...value ? [value] : []])];
|
|
1336
|
+
}
|
|
1337
|
+
if (kind === "return") return [{ ...returns(value ? [value] : []), ...at }];
|
|
1338
|
+
const plain = () => kind === "break" ? { ...breakStatement(), ...at } : { ...breakStatement(), type: "ContinueStatement", ...at };
|
|
1339
|
+
const innermost = this.loops[this.loops.length - 1];
|
|
1340
|
+
if (!target || target === innermost) return [plain()];
|
|
1341
|
+
const flag = kind === "break" ? target.breakFlag ??= this.names.fresh(`${target.label}Broke`) : target.continueFlag ??= this.names.fresh(`${target.label}Continued`);
|
|
1342
|
+
innermost.leaving.add(target);
|
|
1343
|
+
return [assign([identifier(flag)], [boolean(true)]), { ...breakStatement(), ...at }];
|
|
1344
|
+
}
|
|
1345
|
+
/** `try { a } catch (e) { b } finally { c }`:
|
|
1346
|
+
*
|
|
1347
|
+
* local ok, result, value = pcall(function() a end)
|
|
1348
|
+
* if not ok then
|
|
1349
|
+
* ok, result, value = pcall(function(e) b end, result)
|
|
1350
|
+
* end
|
|
1351
|
+
* do c end
|
|
1352
|
+
* if not ok then error(result, 0) end
|
|
1353
|
+
* if result == 1 then return value elseif ... end
|
|
1354
|
+
*
|
|
1355
|
+
* Without a `finally`, the `catch` runs in place of the second `pcall`:
|
|
1356
|
+
* nothing has to run after it. A `return`, `break` or `continue` that
|
|
1357
|
+
* leaves the `try` cannot be written inside its function; it returns a
|
|
1358
|
+
* code instead (see `jump`), and the last `if` does what it says. */
|
|
1359
|
+
tryStatement(node) {
|
|
1360
|
+
const ok = this.names.fresh("ok");
|
|
1361
|
+
const result2 = this.names.fresh("result");
|
|
1362
|
+
const value = this.names.fresh("value");
|
|
1363
|
+
const frame = { exits: /* @__PURE__ */ new Map(), returnsValue: false };
|
|
1364
|
+
const guarded = (block2, params, args2) => {
|
|
1365
|
+
this.tryFrames.push(frame);
|
|
1366
|
+
let statements;
|
|
1367
|
+
try {
|
|
1368
|
+
statements = this.block(block2).statements;
|
|
1369
|
+
} finally {
|
|
1370
|
+
this.tryFrames.pop();
|
|
1371
|
+
}
|
|
1372
|
+
const passesVarargs = statements.some(usesVararg);
|
|
1373
|
+
const func = functionExpression(functionBody(params, statements, passesVarargs));
|
|
1374
|
+
return call(this.builtin("pcall"), [func, ...args2, ...passesVarargs ? [vararg()] : []]);
|
|
1375
|
+
};
|
|
1376
|
+
const attempt = guarded(node.block, [], []);
|
|
1377
|
+
const param = node.handler?.param && this.name(node.handler.param.name);
|
|
1378
|
+
const inPlace = node.handler && !node.finalizer ? this.block(node.handler.body, param ? [local([param], [identifier(result2)])] : []) : void 0;
|
|
1379
|
+
const caught = node.handler && node.finalizer ? guarded(node.handler.body, [param ?? "_"], [identifier(result2)]) : void 0;
|
|
1380
|
+
const finalizer = node.finalizer && this.block(node.finalizer);
|
|
1381
|
+
const returnsValue = frame.returnsValue;
|
|
1382
|
+
const usesResult = returnsValue || frame.exits.size > 0 || !!param || !!node.finalizer;
|
|
1383
|
+
const names = [ok, ...usesResult ? [result2] : [], ...returnsValue ? [value] : []];
|
|
1384
|
+
const failed = () => unary("not", identifier(ok));
|
|
1385
|
+
const out2 = [this.from(local(names, [attempt]), node)];
|
|
1386
|
+
const clauses = [];
|
|
1387
|
+
if (inPlace) clauses.push({ type: "IfClause", condition: failed(), body: inPlace, ...spanOf(node.handler) });
|
|
1388
|
+
if (caught) out2.push(ifThen(failed(), [assign(names.map(identifier), [caught])]));
|
|
1389
|
+
if (finalizer) {
|
|
1390
|
+
out2.push(doBlock(finalizer.statements));
|
|
1391
|
+
out2.push(ifThen(failed(), [callStatement(call(this.builtin("error"), [identifier(result2), number(0)]))]));
|
|
1392
|
+
}
|
|
1393
|
+
for (const exit of [...frame.exits.values()].sort((a, b) => a.code - b.code)) {
|
|
1394
|
+
clauses.push({
|
|
1395
|
+
type: "IfClause",
|
|
1396
|
+
condition: binary("==", identifier(result2), number(exit.code)),
|
|
1397
|
+
body: block(this.jump(exit.kind, exit.kind === "return" && returnsValue ? identifier(value) : void 0, void 0, exit.label)),
|
|
1398
|
+
...spanOf(node)
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
if (clauses.length) out2.push({ type: "IfStatement", clauses, ...spanOf(node) });
|
|
1402
|
+
return out2;
|
|
1403
|
+
}
|
|
1203
1404
|
functionDeclarationStatement(node) {
|
|
1204
1405
|
const statement = this.functionStatement(this.reference(node.target.base), node, this.functionBody(node.func), node.isMethod, node.target.path.map((p) => p.name));
|
|
1205
1406
|
if (!node.target.method) return statement;
|
|
@@ -1255,11 +1456,12 @@ end`).body.statements);
|
|
|
1255
1456
|
const { build, accessors } = this.classHelpers;
|
|
1256
1457
|
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
1257
1458
|
return (0, import_luau_parser.parse)(`
|
|
1258
|
-
local function ${build}(base)
|
|
1459
|
+
local function ${build}(base, name)
|
|
1259
1460
|
local class = { __getters = {}, __setters = {} }
|
|
1260
1461
|
class.__index = class
|
|
1261
1462
|
class.ClassObject = class
|
|
1262
1463
|
class.ParentClass = base
|
|
1464
|
+
class.name = name
|
|
1263
1465
|
if base ~= nil then
|
|
1264
1466
|
setmetatable(class, { __index = base })
|
|
1265
1467
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
@@ -1313,7 +1515,11 @@ end
|
|
|
1313
1515
|
* table is assigned to `self`, which a module has already declared. */
|
|
1314
1516
|
classStatements(node, self, declare) {
|
|
1315
1517
|
const { build, accessors } = this.classRuntime();
|
|
1316
|
-
const
|
|
1518
|
+
const called = node.name?.name ?? (node.type === "ClassExpression" ? this.classExpressionNames.get(node) : void 0) ?? "";
|
|
1519
|
+
const create = call(identifier(build), [
|
|
1520
|
+
node.superclass ? this.reference(node.superclass) : nil(),
|
|
1521
|
+
string(called)
|
|
1522
|
+
]);
|
|
1317
1523
|
const out2 = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
|
|
1318
1524
|
const previous = this.classContext;
|
|
1319
1525
|
this.classContext = node;
|
|
@@ -1584,6 +1790,18 @@ end
|
|
|
1584
1790
|
// Functions
|
|
1585
1791
|
// --------------------------------------------------------
|
|
1586
1792
|
functionBody(func) {
|
|
1793
|
+
const frames = this.tryFrames;
|
|
1794
|
+
const loops = this.loops;
|
|
1795
|
+
this.tryFrames = [];
|
|
1796
|
+
this.loops = [];
|
|
1797
|
+
try {
|
|
1798
|
+
return this.functionBodyIn(func);
|
|
1799
|
+
} finally {
|
|
1800
|
+
this.tryFrames = frames;
|
|
1801
|
+
this.loops = loops;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
functionBodyIn(func) {
|
|
1587
1805
|
const params = func.isMethod ? func.params.slice(1) : func.params;
|
|
1588
1806
|
const prelude = [];
|
|
1589
1807
|
const names = [];
|
|
@@ -1634,8 +1852,10 @@ end
|
|
|
1634
1852
|
return nil();
|
|
1635
1853
|
case "InterpolatedStringExpression":
|
|
1636
1854
|
return this.interpolatedString(node);
|
|
1855
|
+
// An object's method, `{ m() { ... } }`, keeps its `this` as the
|
|
1856
|
+
// first parameter written out: a function value has no `:`.
|
|
1637
1857
|
case "FunctionExpression":
|
|
1638
|
-
return functionExpression(this.
|
|
1858
|
+
return functionExpression(this.classFunctionBody(node.func));
|
|
1639
1859
|
case "TableExpression":
|
|
1640
1860
|
return this.tableExpression(node);
|
|
1641
1861
|
case "ArrayExpression":
|
|
@@ -1650,6 +1870,11 @@ end
|
|
|
1650
1870
|
if (node.operator === "instanceof") {
|
|
1651
1871
|
return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1652
1872
|
}
|
|
1873
|
+
if (node.operator === "+") {
|
|
1874
|
+
const join2 = this.options.types?.joins.get(node);
|
|
1875
|
+
if (join2 === "concat") return binary("..", left, right);
|
|
1876
|
+
if (join2 === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1877
|
+
}
|
|
1653
1878
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1654
1879
|
return binary(node.operator, left, right);
|
|
1655
1880
|
}
|
|
@@ -1909,14 +2134,52 @@ end
|
|
|
1909
2134
|
if (!args2.length) return string(text);
|
|
1910
2135
|
return methodCall(parenthesized(string(format)), "format", args2);
|
|
1911
2136
|
}
|
|
2137
|
+
/** `t ??= v`: `if t == nil then t = v end`, which no Luau has an
|
|
2138
|
+
* operator for. `v` is read only where `t` is nil. As in `a += b` for
|
|
2139
|
+
* Lua 5.1, what addresses the target is read once: an object or key
|
|
2140
|
+
* that is not a plain name goes into a local first. */
|
|
2141
|
+
nullishAssignment(node) {
|
|
2142
|
+
const { before, target } = this.targetOnce(node.target);
|
|
2143
|
+
const statement = ifThen(binary("==", target, nil()), [assign([target], [this.expression(node.value)])]);
|
|
2144
|
+
return before.length ? [doBlock([...before, statement])] : [statement];
|
|
2145
|
+
}
|
|
2146
|
+
/** `t += v` where only the values can say whether it joins or adds:
|
|
2147
|
+
* `t = tilua_add(t, v)`, with what addresses `t` read once. */
|
|
2148
|
+
dynamicAddition(node) {
|
|
2149
|
+
const { before, target } = this.targetOnce(node.target);
|
|
2150
|
+
const statement = assign([target], [call(this.helper("add"), [target, this.keptToOne(this.expression(node.value))])]);
|
|
2151
|
+
return before.length ? [doBlock([...before, statement])] : [statement];
|
|
2152
|
+
}
|
|
2153
|
+
/** An assignment's target, as an expression that may be read and written
|
|
2154
|
+
* freely: an object or key that is not a plain name goes into a local
|
|
2155
|
+
* first, so that `t[next()]` advances once. */
|
|
2156
|
+
targetOnce(node) {
|
|
2157
|
+
const before = [];
|
|
2158
|
+
const once = (expression, name) => {
|
|
2159
|
+
if (expression.type === "Identifier" || expression.type === "StringLiteral" || expression.type === "NumberLiteral") return expression;
|
|
2160
|
+
const local2 = this.names.fresh(name);
|
|
2161
|
+
before.push(local([local2], [expression]));
|
|
2162
|
+
return identifier(local2);
|
|
2163
|
+
};
|
|
2164
|
+
let target;
|
|
2165
|
+
if (node.type === "MemberExpression") {
|
|
2166
|
+
target = member(once(this.expression(node.object), "target"), node.property.name);
|
|
2167
|
+
} else if (node.type === "IndexExpression") {
|
|
2168
|
+
const object2 = once(this.expression(node.object), "target");
|
|
2169
|
+
target = index(object2, once(this.expression(node.index), "key"));
|
|
2170
|
+
} else {
|
|
2171
|
+
target = this.expression(node);
|
|
2172
|
+
}
|
|
2173
|
+
return { before, target };
|
|
2174
|
+
}
|
|
1912
2175
|
/** `a += b` for Lua 5.1, which has no compound assignment: `a = a + b`.
|
|
1913
2176
|
*
|
|
1914
2177
|
* The target has to be read and written, and writing it out twice would
|
|
1915
2178
|
* evaluate whatever addresses it twice — `t[next()] += 1` would advance
|
|
1916
2179
|
* twice and add to the wrong slot. So anything but a plain name has its
|
|
1917
2180
|
* object and key taken into locals first, and both halves use those. */
|
|
1918
|
-
compoundAssignment51(node) {
|
|
1919
|
-
const operator =
|
|
2181
|
+
compoundAssignment51(node, written = node.operator) {
|
|
2182
|
+
const operator = written.slice(0, -1);
|
|
1920
2183
|
const before = [];
|
|
1921
2184
|
let target;
|
|
1922
2185
|
if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
|
|
@@ -2115,6 +2378,7 @@ function optionalChain(node) {
|
|
|
2115
2378
|
if (first < 0) return void 0;
|
|
2116
2379
|
return { base: linkObject(spine[first]), links: spine.slice(first) };
|
|
2117
2380
|
}
|
|
2381
|
+
var LOOPS = /* @__PURE__ */ new Set(["WhileStatement", "RepeatStatement", "NumericForStatement", "GenericForStatement"]);
|
|
2118
2382
|
function usesVararg(node) {
|
|
2119
2383
|
if (Array.isArray(node)) return node.some(usesVararg);
|
|
2120
2384
|
if (!node || typeof node !== "object") return false;
|
|
@@ -2982,7 +3246,7 @@ function analyzeModules(modules, config2) {
|
|
|
2982
3246
|
});
|
|
2983
3247
|
for (const d of types.diagnostics) {
|
|
2984
3248
|
const at = d.node;
|
|
2985
|
-
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
3249
|
+
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type", severity: d.severity });
|
|
2986
3250
|
}
|
|
2987
3251
|
const result2 = { types, diagnostics: said };
|
|
2988
3252
|
if (partials === before) {
|
|
@@ -3111,7 +3375,7 @@ async function main(from) {
|
|
|
3111
3375
|
outFile: entryFile !== void 0 ? (0, import_node_path3.basename)(entryFile) : void 0
|
|
3112
3376
|
});
|
|
3113
3377
|
for (const d of result2.diagnostics) {
|
|
3114
|
-
console.error(`${(0, import_node_path3.relative)(process.cwd(), d.file)}:${d.line}:${d.column} ${d.message}`);
|
|
3378
|
+
console.error(`${(0, import_node_path3.relative)(process.cwd(), d.file)}:${d.line}:${d.column} ${d.severity === "warning" ? "warning: " : ""}${d.message}`);
|
|
3115
3379
|
}
|
|
3116
3380
|
if (result2.code === void 0) {
|
|
3117
3381
|
console.error("no bundle written");
|
|
@@ -3123,7 +3387,7 @@ async function main(from) {
|
|
|
3123
3387
|
(0, import_node_fs4.writeFileSync)(target, file.code);
|
|
3124
3388
|
console.log(`${target}: ${file.modules.length} module${file.modules.length === 1 ? "" : "s"}`);
|
|
3125
3389
|
}
|
|
3126
|
-
process.exit(result2.diagnostics.
|
|
3390
|
+
process.exit(result2.diagnostics.some((d) => d.severity !== "warning") ? 1 : 0);
|
|
3127
3391
|
}
|
|
3128
3392
|
main(entry).catch((error) => {
|
|
3129
3393
|
console.error(error instanceof Error ? error.message : error);
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
bundle,
|
|
4
4
|
resolveConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EQE22LX7.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { mkdirSync, writeFileSync } from "fs";
|
|
@@ -57,7 +57,7 @@ async function main(from) {
|
|
|
57
57
|
outFile: entryFile !== void 0 ? basename(entryFile) : void 0
|
|
58
58
|
});
|
|
59
59
|
for (const d of result.diagnostics) {
|
|
60
|
-
console.error(`${relative(process.cwd(), d.file)}:${d.line}:${d.column} ${d.message}`);
|
|
60
|
+
console.error(`${relative(process.cwd(), d.file)}:${d.line}:${d.column} ${d.severity === "warning" ? "warning: " : ""}${d.message}`);
|
|
61
61
|
}
|
|
62
62
|
if (result.code === void 0) {
|
|
63
63
|
console.error("no bundle written");
|
|
@@ -69,7 +69,7 @@ async function main(from) {
|
|
|
69
69
|
writeFileSync(target, file.code);
|
|
70
70
|
console.log(`${target}: ${file.modules.length} module${file.modules.length === 1 ? "" : "s"}`);
|
|
71
71
|
}
|
|
72
|
-
process.exit(result.diagnostics.
|
|
72
|
+
process.exit(result.diagnostics.some((d) => d.severity !== "warning") ? 1 : 0);
|
|
73
73
|
}
|
|
74
74
|
main(entry).catch((error) => {
|
|
75
75
|
console.error(error instanceof Error ? error.message : error);
|