@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/README.md
CHANGED
|
@@ -75,6 +75,14 @@ JavaScript module the build loads.
|
|
|
75
75
|
| `for (const item in xs)` | `for _, item in xs do … end` — an array or a table walked for its values |
|
|
76
76
|
| `for (const item in step)` | `for item in step do … end` — an iterator function |
|
|
77
77
|
| `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]` |
|
|
78
|
+
| `t ??= v` | `if t == nil then t = v end` — what addresses `t` read once, into locals |
|
|
79
|
+
| `{ m() { … } }` | `{ m = function(this) … end }` — called with `:` |
|
|
80
|
+
| `break outer` from a loop inside `outer` | a flag `outer` declares, raised, and a `break`; after each loop on the way out, a check that breaks again — or, at `outer`, does the `break` or `continue` |
|
|
81
|
+
| `throw value` | `error(value, 0)` — level 0, so a `catch` is handed exactly what was thrown |
|
|
82
|
+
| `try { a } catch (e) { b }` | `local ok, result = pcall(function() a end) if not ok then local e = result b end` |
|
|
83
|
+
| `try { a } catch (e) { b } finally { c }` | `a` and then `b` each in a `pcall`, `c` after them, and what neither caught thrown again |
|
|
84
|
+
| a `return`, `break` or `continue` that leaves a `try` | its function returns a code — and the value — and an `if` after the `pcall` does the jump there |
|
|
85
|
+
| code after a `return`, `break` or `continue` | left out: Luau allows nothing after one in its block |
|
|
78
86
|
| `pcall(f)`, and other globals Luau answers several values from | the type library's own function, answering one — see below |
|
|
79
87
|
| `function f(a, ...rest)` | `function f(a, ...) local rest = {...}` |
|
|
80
88
|
| `scriptArgs` | the chunk's own `...`, as `local scriptArgs = { ... }` |
|
|
@@ -83,6 +91,8 @@ JavaScript module the build loads.
|
|
|
83
91
|
| `import("./shop")` | `G.import("src/shop")` — the bundle's `require`, wrapped |
|
|
84
92
|
| `a ?? b` | `if a ~= nil then a else b` for a name; otherwise a function that reads `a` once |
|
|
85
93
|
| `n++` / `n--` | `n += 1` / `n -= 1` |
|
|
94
|
+
| `s + t`, `s += t` with a string on a side | `s .. t`, `s ..= t` |
|
|
95
|
+
| `a + b` where only the values can say — `any`, `string \| number` | `tilua_add(a, b)`: joins if either is a string, else adds |
|
|
86
96
|
| `"key" in t` | `(t["key"] ~= nil)` |
|
|
87
97
|
| `v instanceof Dog` | `tilua_instanceof(v, Dog)` — walks `v.ClassObject`, then each `.ParentClass` |
|
|
88
98
|
| `{ a: 1, get b() { … }, set b(v) { … } }` | `tilua_accessors({ a = 1 }, { b = … }, { b = … })` — a metatable runs the accessors |
|
|
@@ -180,7 +190,9 @@ another, and one the class writes itself replaces the copy.
|
|
|
180
190
|
Two links come with every class, and they cost an instance nothing because
|
|
181
191
|
they live on the class table: `ClassObject`, which an instance reads through
|
|
182
192
|
its metatable to reach its own class, and `ParentClass`, which a class reads
|
|
183
|
-
to reach the one it extends.
|
|
193
|
+
to reach the one it extends. The class table holds its `name` too
|
|
194
|
+
(`tilua_class(base, "Dog")`), so `rex.ClassObject.name` says what built an
|
|
195
|
+
instance.
|
|
184
196
|
|
|
185
197
|
A class written as a value (`const Counter = class { … }`) is the same code,
|
|
186
198
|
inside a function that runs where the class is written and hands the table
|
|
@@ -245,7 +245,29 @@ var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
|
|
|
245
245
|
|
|
246
246
|
// src/lower.ts
|
|
247
247
|
function lower(program2, scopes, options = {}) {
|
|
248
|
-
|
|
248
|
+
const result2 = new Lowerer(program2, scopes, options).run();
|
|
249
|
+
dropUnreachable(result2.statements);
|
|
250
|
+
return result2;
|
|
251
|
+
}
|
|
252
|
+
function dropUnreachable(statements) {
|
|
253
|
+
const exit = statements.findIndex((s) => s.type === "ReturnStatement" || s.type === "BreakStatement" || s.type === "ContinueStatement");
|
|
254
|
+
if (exit >= 0) statements.length = exit + 1;
|
|
255
|
+
for (const statement of statements) visitBlocks(statement);
|
|
256
|
+
}
|
|
257
|
+
function visitBlocks(node) {
|
|
258
|
+
if (!node || typeof node !== "object") return;
|
|
259
|
+
if (Array.isArray(node)) {
|
|
260
|
+
for (const item of node) visitBlocks(item);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const record = node;
|
|
264
|
+
if (record.type === "Block" && Array.isArray(record.statements)) {
|
|
265
|
+
dropUnreachable(record.statements);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const key in record) {
|
|
269
|
+
if (key !== "line" && key !== "column") visitBlocks(record[key]);
|
|
270
|
+
}
|
|
249
271
|
}
|
|
250
272
|
var Lowerer = class {
|
|
251
273
|
constructor(source, scopes, options) {
|
|
@@ -258,6 +280,18 @@ var Lowerer = class {
|
|
|
258
280
|
for (const binding of scopes.bindings.values()) {
|
|
259
281
|
if (binding.declarationNode) this.bindingByDeclaration.set(binding.declarationNode, binding);
|
|
260
282
|
}
|
|
283
|
+
const visit = (value) => {
|
|
284
|
+
if (!value || typeof value !== "object") return;
|
|
285
|
+
if (Array.isArray(value)) return void value.forEach(visit);
|
|
286
|
+
const node = value;
|
|
287
|
+
if (node.type === "VariableDeclaration" && node.name?.type === "IdentifierPattern" && node.name.name) {
|
|
288
|
+
let init = node.init;
|
|
289
|
+
while (init?.type === "ParenthesizedExpression") init = init.expression;
|
|
290
|
+
if (init?.type === "ClassExpression") this.classExpressionNames.set(init, node.name.name);
|
|
291
|
+
}
|
|
292
|
+
for (const [key, child] of Object.entries(value)) if (key !== "line" && key !== "column") visit(child);
|
|
293
|
+
};
|
|
294
|
+
visit(source.body);
|
|
261
295
|
}
|
|
262
296
|
source;
|
|
263
297
|
scopes;
|
|
@@ -275,10 +309,17 @@ var Lowerer = class {
|
|
|
275
309
|
exportsName;
|
|
276
310
|
/** Globals generated code calls, renamed where the source shadows them. */
|
|
277
311
|
builtins = /* @__PURE__ */ new Map();
|
|
312
|
+
/** The `try` blocks being lowered in the current function, innermost
|
|
313
|
+
* last; see `tryStatement`. A function written inside one starts afresh. */
|
|
314
|
+
tryFrames = [];
|
|
315
|
+
/** The loops being lowered in the current function, innermost last. */
|
|
316
|
+
loops = [];
|
|
278
317
|
/** Whether the output has to be plain Lua 5.1 rather than Luau. */
|
|
279
318
|
lua51;
|
|
280
319
|
/** The source line each emitted statement came from. */
|
|
281
320
|
origins = /* @__PURE__ */ new WeakMap();
|
|
321
|
+
/** The name each class written as a value is known by. */
|
|
322
|
+
classExpressionNames = /* @__PURE__ */ new WeakMap();
|
|
282
323
|
run() {
|
|
283
324
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
284
325
|
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
@@ -638,6 +679,16 @@ local function ${typeOf}(value, known)
|
|
|
638
679
|
end`).body.statements);
|
|
639
680
|
}
|
|
640
681
|
}
|
|
682
|
+
const add = this.helpers.get("add");
|
|
683
|
+
if (add) {
|
|
684
|
+
out.push(...parseLuau(`
|
|
685
|
+
local function ${add}(a, b)
|
|
686
|
+
if ${this.builtin("type").name}(a) == "string" or ${this.builtin("type").name}(b) == "string" then
|
|
687
|
+
return ${this.builtin("tostring").name}(a) .. ${this.builtin("tostring").name}(b)
|
|
688
|
+
end
|
|
689
|
+
return a + b
|
|
690
|
+
end`).body.statements);
|
|
691
|
+
}
|
|
641
692
|
const instanceOf = this.helpers.get("instanceof");
|
|
642
693
|
if (instanceOf) {
|
|
643
694
|
out.push(...parseLuau(`
|
|
@@ -660,6 +711,8 @@ end`).body.statements);
|
|
|
660
711
|
out.push(...parseLuau(`
|
|
661
712
|
local function ${accessors}(target, getters, setters)
|
|
662
713
|
return ${this.builtin("setmetatable").name}(target, {
|
|
714
|
+
-- Read by a spread, which copies what the getters give.
|
|
715
|
+
__tiluaGetters = getters,
|
|
663
716
|
__index = function(self, key)
|
|
664
717
|
local get = getters[key]
|
|
665
718
|
if get ~= nil then
|
|
@@ -683,17 +736,25 @@ end`).body.statements);
|
|
|
683
736
|
}
|
|
684
737
|
const assign2 = this.helpers.get("assign");
|
|
685
738
|
if (assign2) {
|
|
686
|
-
out.push(
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
739
|
+
out.push(...parseLuau(`
|
|
740
|
+
local function ${assign2}(target, ...)
|
|
741
|
+
for i = 1, ${this.builtin("select").name}("#", ...) do
|
|
742
|
+
local source = (${this.builtin("select").name}(i, ...))
|
|
743
|
+
if source ~= nil then
|
|
744
|
+
for key, value in ${this.builtin("pairs").name}(source) do
|
|
745
|
+
target[key] = value
|
|
746
|
+
end
|
|
747
|
+
local meta = ${this.builtin("type").name}(source) == "table" and ${this.builtin("getmetatable").name}(source) or nil
|
|
748
|
+
local getters = ${this.builtin("type").name}(meta) == "table" and ${this.builtin("rawget").name}(meta, "__tiluaGetters") or nil
|
|
749
|
+
if getters ~= nil then
|
|
750
|
+
for key, get in ${this.builtin("pairs").name}(getters) do
|
|
751
|
+
target[key] = get(source)
|
|
752
|
+
end
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
end
|
|
756
|
+
return target
|
|
757
|
+
end`).body.statements);
|
|
697
758
|
}
|
|
698
759
|
out.push(...runtimes);
|
|
699
760
|
const concat = this.helpers.get("concat");
|
|
@@ -1052,7 +1113,49 @@ end`).body.statements);
|
|
|
1052
1113
|
if (!this.origins.has(statement)) this.origins.set(statement, node.line.start);
|
|
1053
1114
|
return statement;
|
|
1054
1115
|
}
|
|
1116
|
+
/** A statement; a loop with what its labels need around it.
|
|
1117
|
+
*
|
|
1118
|
+
* Luau has no labels, and no `goto` to stand in for them. `break outer`
|
|
1119
|
+
* in a loop inside `outer` sets a flag `outer` declares and breaks the
|
|
1120
|
+
* loop it is in; right after that loop, the flag is checked, and the
|
|
1121
|
+
* check breaks again — or, once it is `outer` that would be left, does
|
|
1122
|
+
* what was asked of `outer` itself. `continue outer` is the same, with
|
|
1123
|
+
* `continue` done there and the flag lowered for the next time round. */
|
|
1055
1124
|
statementOf(node) {
|
|
1125
|
+
if (!LOOPS.has(node.type)) return this.statementKind(node);
|
|
1126
|
+
const loop = {
|
|
1127
|
+
label: node.label?.name,
|
|
1128
|
+
tryDepth: this.tryFrames.length,
|
|
1129
|
+
leaving: /* @__PURE__ */ new Set()
|
|
1130
|
+
};
|
|
1131
|
+
this.loops.push(loop);
|
|
1132
|
+
let lowered;
|
|
1133
|
+
try {
|
|
1134
|
+
lowered = this.statementKind(node);
|
|
1135
|
+
} finally {
|
|
1136
|
+
this.loops.pop();
|
|
1137
|
+
}
|
|
1138
|
+
const flags = [loop.breakFlag, loop.continueFlag].filter((f) => f !== void 0);
|
|
1139
|
+
const out = [...flags.length ? [local(flags, flags.map(() => boolean(false)))] : [], ...lowered];
|
|
1140
|
+
const enclosing = this.loops[this.loops.length - 1];
|
|
1141
|
+
for (const target of loop.leaving) {
|
|
1142
|
+
if (target === enclosing) {
|
|
1143
|
+
if (target.breakFlag) out.push(ifThen(identifier(target.breakFlag), this.jump("break", void 0)));
|
|
1144
|
+
if (target.continueFlag) {
|
|
1145
|
+
out.push(ifThen(identifier(target.continueFlag), [
|
|
1146
|
+
assign([identifier(target.continueFlag)], [boolean(false)]),
|
|
1147
|
+
...this.jump("continue", void 0)
|
|
1148
|
+
]));
|
|
1149
|
+
}
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
enclosing?.leaving.add(target);
|
|
1153
|
+
const raised = [target.breakFlag, target.continueFlag].filter((f) => f !== void 0).map((f) => identifier(f)).reduce((a, b) => binary("or", a, b));
|
|
1154
|
+
out.push(ifThen(raised, this.jump("break", void 0)));
|
|
1155
|
+
}
|
|
1156
|
+
return out;
|
|
1157
|
+
}
|
|
1158
|
+
statementKind(node) {
|
|
1056
1159
|
switch (node.type) {
|
|
1057
1160
|
// Types, and what exists only for them.
|
|
1058
1161
|
case "TypeAliasStatement":
|
|
@@ -1072,15 +1175,20 @@ end`).body.statements);
|
|
|
1072
1175
|
return this.classDeclaration(node, "declare");
|
|
1073
1176
|
case "AssignmentStatement":
|
|
1074
1177
|
return this.assignment(node);
|
|
1075
|
-
case "CompoundAssignmentStatement":
|
|
1076
|
-
if (
|
|
1178
|
+
case "CompoundAssignmentStatement": {
|
|
1179
|
+
if (node.operator === "??=") return this.nullishAssignment(node);
|
|
1180
|
+
const join = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
|
|
1181
|
+
if (join === "dynamic") return this.dynamicAddition(node);
|
|
1182
|
+
const operator = join === "concat" ? "..=" : node.operator;
|
|
1183
|
+
if (this.lua51) return this.compoundAssignment51(node, operator);
|
|
1077
1184
|
return [{
|
|
1078
1185
|
type: "CompoundAssignmentStatement",
|
|
1079
|
-
operator
|
|
1186
|
+
operator,
|
|
1080
1187
|
target: this.expression(node.target),
|
|
1081
1188
|
value: this.expression(node.value),
|
|
1082
1189
|
...spanOf(node)
|
|
1083
1190
|
}];
|
|
1191
|
+
}
|
|
1084
1192
|
case "CallStatement": {
|
|
1085
1193
|
const chain = optionalChain(node.expression);
|
|
1086
1194
|
if (chain) return [this.optionalCallStatement(chain)];
|
|
@@ -1166,11 +1274,20 @@ end`).body.statements);
|
|
|
1166
1274
|
)];
|
|
1167
1275
|
}
|
|
1168
1276
|
case "ReturnStatement":
|
|
1169
|
-
return
|
|
1277
|
+
return this.jump("return", node.argument && this.keptToOne(this.expression(node.argument)), node);
|
|
1170
1278
|
case "BreakStatement":
|
|
1171
|
-
return
|
|
1279
|
+
return this.jump("break", void 0, node, node.label?.name);
|
|
1172
1280
|
case "ContinueStatement":
|
|
1173
|
-
return
|
|
1281
|
+
return this.jump("continue", void 0, node, node.label?.name);
|
|
1282
|
+
// Level 0: what is thrown is what a `catch` is handed, with no
|
|
1283
|
+
// position put in front of a string.
|
|
1284
|
+
case "ThrowStatement":
|
|
1285
|
+
return [callStatement(call(
|
|
1286
|
+
this.builtin("error"),
|
|
1287
|
+
[this.keptToOne(this.expression(node.argument)), number(0)]
|
|
1288
|
+
))];
|
|
1289
|
+
case "TryStatement":
|
|
1290
|
+
return this.tryStatement(node);
|
|
1174
1291
|
case "ImportStatement":
|
|
1175
1292
|
if (node.isTypeOnly) return [];
|
|
1176
1293
|
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");
|
|
@@ -1183,6 +1300,90 @@ end`).body.statements);
|
|
|
1183
1300
|
return [];
|
|
1184
1301
|
}
|
|
1185
1302
|
}
|
|
1303
|
+
/** A `return`, `break` or `continue`. Inside a `try` that it leaves, it
|
|
1304
|
+
* is the `try`'s function returning a code saying which — and the value
|
|
1305
|
+
* — for the code after the `pcall` to do it there. */
|
|
1306
|
+
jump(kind, value, node, label) {
|
|
1307
|
+
const at = node ? spanOf(node) : {};
|
|
1308
|
+
const frame = this.tryFrames[this.tryFrames.length - 1];
|
|
1309
|
+
const target = kind === "return" ? void 0 : label === void 0 ? this.loops[this.loops.length - 1] : this.loops.findLast((loop) => loop.label === label);
|
|
1310
|
+
if (frame && (kind === "return" || target && target.tryDepth < this.tryFrames.length)) {
|
|
1311
|
+
const key = `${kind}:${label ?? ""}`;
|
|
1312
|
+
let exit = frame.exits.get(key);
|
|
1313
|
+
if (!exit) {
|
|
1314
|
+
exit = { kind, label, code: kind === "return" ? 1 : frame.exits.size + 2 };
|
|
1315
|
+
frame.exits.set(key, exit);
|
|
1316
|
+
}
|
|
1317
|
+
if (value) frame.returnsValue = true;
|
|
1318
|
+
return [returns([number(exit.code), ...value ? [value] : []])];
|
|
1319
|
+
}
|
|
1320
|
+
if (kind === "return") return [{ ...returns(value ? [value] : []), ...at }];
|
|
1321
|
+
const plain = () => kind === "break" ? { ...breakStatement(), ...at } : { ...breakStatement(), type: "ContinueStatement", ...at };
|
|
1322
|
+
const innermost = this.loops[this.loops.length - 1];
|
|
1323
|
+
if (!target || target === innermost) return [plain()];
|
|
1324
|
+
const flag = kind === "break" ? target.breakFlag ??= this.names.fresh(`${target.label}Broke`) : target.continueFlag ??= this.names.fresh(`${target.label}Continued`);
|
|
1325
|
+
innermost.leaving.add(target);
|
|
1326
|
+
return [assign([identifier(flag)], [boolean(true)]), { ...breakStatement(), ...at }];
|
|
1327
|
+
}
|
|
1328
|
+
/** `try { a } catch (e) { b } finally { c }`:
|
|
1329
|
+
*
|
|
1330
|
+
* local ok, result, value = pcall(function() a end)
|
|
1331
|
+
* if not ok then
|
|
1332
|
+
* ok, result, value = pcall(function(e) b end, result)
|
|
1333
|
+
* end
|
|
1334
|
+
* do c end
|
|
1335
|
+
* if not ok then error(result, 0) end
|
|
1336
|
+
* if result == 1 then return value elseif ... end
|
|
1337
|
+
*
|
|
1338
|
+
* Without a `finally`, the `catch` runs in place of the second `pcall`:
|
|
1339
|
+
* nothing has to run after it. A `return`, `break` or `continue` that
|
|
1340
|
+
* leaves the `try` cannot be written inside its function; it returns a
|
|
1341
|
+
* code instead (see `jump`), and the last `if` does what it says. */
|
|
1342
|
+
tryStatement(node) {
|
|
1343
|
+
const ok = this.names.fresh("ok");
|
|
1344
|
+
const result2 = this.names.fresh("result");
|
|
1345
|
+
const value = this.names.fresh("value");
|
|
1346
|
+
const frame = { exits: /* @__PURE__ */ new Map(), returnsValue: false };
|
|
1347
|
+
const guarded = (block2, params, args) => {
|
|
1348
|
+
this.tryFrames.push(frame);
|
|
1349
|
+
let statements;
|
|
1350
|
+
try {
|
|
1351
|
+
statements = this.block(block2).statements;
|
|
1352
|
+
} finally {
|
|
1353
|
+
this.tryFrames.pop();
|
|
1354
|
+
}
|
|
1355
|
+
const passesVarargs = statements.some(usesVararg);
|
|
1356
|
+
const func = functionExpression(functionBody(params, statements, passesVarargs));
|
|
1357
|
+
return call(this.builtin("pcall"), [func, ...args, ...passesVarargs ? [vararg()] : []]);
|
|
1358
|
+
};
|
|
1359
|
+
const attempt = guarded(node.block, [], []);
|
|
1360
|
+
const param = node.handler?.param && this.name(node.handler.param.name);
|
|
1361
|
+
const inPlace = node.handler && !node.finalizer ? this.block(node.handler.body, param ? [local([param], [identifier(result2)])] : []) : void 0;
|
|
1362
|
+
const caught = node.handler && node.finalizer ? guarded(node.handler.body, [param ?? "_"], [identifier(result2)]) : void 0;
|
|
1363
|
+
const finalizer = node.finalizer && this.block(node.finalizer);
|
|
1364
|
+
const returnsValue = frame.returnsValue;
|
|
1365
|
+
const usesResult = returnsValue || frame.exits.size > 0 || !!param || !!node.finalizer;
|
|
1366
|
+
const names = [ok, ...usesResult ? [result2] : [], ...returnsValue ? [value] : []];
|
|
1367
|
+
const failed = () => unary("not", identifier(ok));
|
|
1368
|
+
const out = [this.from(local(names, [attempt]), node)];
|
|
1369
|
+
const clauses = [];
|
|
1370
|
+
if (inPlace) clauses.push({ type: "IfClause", condition: failed(), body: inPlace, ...spanOf(node.handler) });
|
|
1371
|
+
if (caught) out.push(ifThen(failed(), [assign(names.map(identifier), [caught])]));
|
|
1372
|
+
if (finalizer) {
|
|
1373
|
+
out.push(doBlock(finalizer.statements));
|
|
1374
|
+
out.push(ifThen(failed(), [callStatement(call(this.builtin("error"), [identifier(result2), number(0)]))]));
|
|
1375
|
+
}
|
|
1376
|
+
for (const exit of [...frame.exits.values()].sort((a, b) => a.code - b.code)) {
|
|
1377
|
+
clauses.push({
|
|
1378
|
+
type: "IfClause",
|
|
1379
|
+
condition: binary("==", identifier(result2), number(exit.code)),
|
|
1380
|
+
body: block(this.jump(exit.kind, exit.kind === "return" && returnsValue ? identifier(value) : void 0, void 0, exit.label)),
|
|
1381
|
+
...spanOf(node)
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
if (clauses.length) out.push({ type: "IfStatement", clauses, ...spanOf(node) });
|
|
1385
|
+
return out;
|
|
1386
|
+
}
|
|
1186
1387
|
functionDeclarationStatement(node) {
|
|
1187
1388
|
const statement = this.functionStatement(this.reference(node.target.base), node, this.functionBody(node.func), node.isMethod, node.target.path.map((p) => p.name));
|
|
1188
1389
|
if (!node.target.method) return statement;
|
|
@@ -1238,11 +1439,12 @@ end`).body.statements);
|
|
|
1238
1439
|
const { build, accessors } = this.classHelpers;
|
|
1239
1440
|
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
1240
1441
|
return parseLuau(`
|
|
1241
|
-
local function ${build}(base)
|
|
1442
|
+
local function ${build}(base, name)
|
|
1242
1443
|
local class = { __getters = {}, __setters = {} }
|
|
1243
1444
|
class.__index = class
|
|
1244
1445
|
class.ClassObject = class
|
|
1245
1446
|
class.ParentClass = base
|
|
1447
|
+
class.name = name
|
|
1246
1448
|
if base ~= nil then
|
|
1247
1449
|
setmetatable(class, { __index = base })
|
|
1248
1450
|
setmetatable(class.__getters, { __index = base.__getters })
|
|
@@ -1296,7 +1498,11 @@ end
|
|
|
1296
1498
|
* table is assigned to `self`, which a module has already declared. */
|
|
1297
1499
|
classStatements(node, self, declare) {
|
|
1298
1500
|
const { build, accessors } = this.classRuntime();
|
|
1299
|
-
const
|
|
1501
|
+
const called = node.name?.name ?? (node.type === "ClassExpression" ? this.classExpressionNames.get(node) : void 0) ?? "";
|
|
1502
|
+
const create = call(identifier(build), [
|
|
1503
|
+
node.superclass ? this.reference(node.superclass) : nil(),
|
|
1504
|
+
string(called)
|
|
1505
|
+
]);
|
|
1300
1506
|
const out = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
|
|
1301
1507
|
const previous = this.classContext;
|
|
1302
1508
|
this.classContext = node;
|
|
@@ -1567,6 +1773,18 @@ end
|
|
|
1567
1773
|
// Functions
|
|
1568
1774
|
// --------------------------------------------------------
|
|
1569
1775
|
functionBody(func) {
|
|
1776
|
+
const frames = this.tryFrames;
|
|
1777
|
+
const loops = this.loops;
|
|
1778
|
+
this.tryFrames = [];
|
|
1779
|
+
this.loops = [];
|
|
1780
|
+
try {
|
|
1781
|
+
return this.functionBodyIn(func);
|
|
1782
|
+
} finally {
|
|
1783
|
+
this.tryFrames = frames;
|
|
1784
|
+
this.loops = loops;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
functionBodyIn(func) {
|
|
1570
1788
|
const params = func.isMethod ? func.params.slice(1) : func.params;
|
|
1571
1789
|
const prelude = [];
|
|
1572
1790
|
const names = [];
|
|
@@ -1617,8 +1835,10 @@ end
|
|
|
1617
1835
|
return nil();
|
|
1618
1836
|
case "InterpolatedStringExpression":
|
|
1619
1837
|
return this.interpolatedString(node);
|
|
1838
|
+
// An object's method, `{ m() { ... } }`, keeps its `this` as the
|
|
1839
|
+
// first parameter written out: a function value has no `:`.
|
|
1620
1840
|
case "FunctionExpression":
|
|
1621
|
-
return functionExpression(this.
|
|
1841
|
+
return functionExpression(this.classFunctionBody(node.func));
|
|
1622
1842
|
case "TableExpression":
|
|
1623
1843
|
return this.tableExpression(node);
|
|
1624
1844
|
case "ArrayExpression":
|
|
@@ -1633,6 +1853,11 @@ end
|
|
|
1633
1853
|
if (node.operator === "instanceof") {
|
|
1634
1854
|
return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1635
1855
|
}
|
|
1856
|
+
if (node.operator === "+") {
|
|
1857
|
+
const join = this.options.types?.joins.get(node);
|
|
1858
|
+
if (join === "concat") return binary("..", left, right);
|
|
1859
|
+
if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1860
|
+
}
|
|
1636
1861
|
if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
|
|
1637
1862
|
return binary(node.operator, left, right);
|
|
1638
1863
|
}
|
|
@@ -1892,14 +2117,52 @@ end
|
|
|
1892
2117
|
if (!args.length) return string(text);
|
|
1893
2118
|
return methodCall(parenthesized(string(format)), "format", args);
|
|
1894
2119
|
}
|
|
2120
|
+
/** `t ??= v`: `if t == nil then t = v end`, which no Luau has an
|
|
2121
|
+
* operator for. `v` is read only where `t` is nil. As in `a += b` for
|
|
2122
|
+
* Lua 5.1, what addresses the target is read once: an object or key
|
|
2123
|
+
* that is not a plain name goes into a local first. */
|
|
2124
|
+
nullishAssignment(node) {
|
|
2125
|
+
const { before, target } = this.targetOnce(node.target);
|
|
2126
|
+
const statement = ifThen(binary("==", target, nil()), [assign([target], [this.expression(node.value)])]);
|
|
2127
|
+
return before.length ? [doBlock([...before, statement])] : [statement];
|
|
2128
|
+
}
|
|
2129
|
+
/** `t += v` where only the values can say whether it joins or adds:
|
|
2130
|
+
* `t = tilua_add(t, v)`, with what addresses `t` read once. */
|
|
2131
|
+
dynamicAddition(node) {
|
|
2132
|
+
const { before, target } = this.targetOnce(node.target);
|
|
2133
|
+
const statement = assign([target], [call(this.helper("add"), [target, this.keptToOne(this.expression(node.value))])]);
|
|
2134
|
+
return before.length ? [doBlock([...before, statement])] : [statement];
|
|
2135
|
+
}
|
|
2136
|
+
/** An assignment's target, as an expression that may be read and written
|
|
2137
|
+
* freely: an object or key that is not a plain name goes into a local
|
|
2138
|
+
* first, so that `t[next()]` advances once. */
|
|
2139
|
+
targetOnce(node) {
|
|
2140
|
+
const before = [];
|
|
2141
|
+
const once = (expression, name) => {
|
|
2142
|
+
if (expression.type === "Identifier" || expression.type === "StringLiteral" || expression.type === "NumberLiteral") return expression;
|
|
2143
|
+
const local2 = this.names.fresh(name);
|
|
2144
|
+
before.push(local([local2], [expression]));
|
|
2145
|
+
return identifier(local2);
|
|
2146
|
+
};
|
|
2147
|
+
let target;
|
|
2148
|
+
if (node.type === "MemberExpression") {
|
|
2149
|
+
target = member(once(this.expression(node.object), "target"), node.property.name);
|
|
2150
|
+
} else if (node.type === "IndexExpression") {
|
|
2151
|
+
const object2 = once(this.expression(node.object), "target");
|
|
2152
|
+
target = index(object2, once(this.expression(node.index), "key"));
|
|
2153
|
+
} else {
|
|
2154
|
+
target = this.expression(node);
|
|
2155
|
+
}
|
|
2156
|
+
return { before, target };
|
|
2157
|
+
}
|
|
1895
2158
|
/** `a += b` for Lua 5.1, which has no compound assignment: `a = a + b`.
|
|
1896
2159
|
*
|
|
1897
2160
|
* The target has to be read and written, and writing it out twice would
|
|
1898
2161
|
* evaluate whatever addresses it twice — `t[next()] += 1` would advance
|
|
1899
2162
|
* twice and add to the wrong slot. So anything but a plain name has its
|
|
1900
2163
|
* object and key taken into locals first, and both halves use those. */
|
|
1901
|
-
compoundAssignment51(node) {
|
|
1902
|
-
const operator =
|
|
2164
|
+
compoundAssignment51(node, written = node.operator) {
|
|
2165
|
+
const operator = written.slice(0, -1);
|
|
1903
2166
|
const before = [];
|
|
1904
2167
|
let target;
|
|
1905
2168
|
if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
|
|
@@ -2098,6 +2361,7 @@ function optionalChain(node) {
|
|
|
2098
2361
|
if (first < 0) return void 0;
|
|
2099
2362
|
return { base: linkObject(spine[first]), links: spine.slice(first) };
|
|
2100
2363
|
}
|
|
2364
|
+
var LOOPS = /* @__PURE__ */ new Set(["WhileStatement", "RepeatStatement", "NumericForStatement", "GenericForStatement"]);
|
|
2101
2365
|
function usesVararg(node) {
|
|
2102
2366
|
if (Array.isArray(node)) return node.some(usesVararg);
|
|
2103
2367
|
if (!node || typeof node !== "object") return false;
|
|
@@ -2985,7 +3249,7 @@ function analyzeModules(modules, config) {
|
|
|
2985
3249
|
});
|
|
2986
3250
|
for (const d of types.diagnostics) {
|
|
2987
3251
|
const at = d.node;
|
|
2988
|
-
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
|
|
3252
|
+
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type", severity: d.severity });
|
|
2989
3253
|
}
|
|
2990
3254
|
const result2 = { types, diagnostics: said };
|
|
2991
3255
|
if (partials === before) {
|