@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/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
- return new Lowerer(program2, scopes, options).run();
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(localFunction(assign2, functionBody(["target"], [
727
- numericFor("i", number(1), selectCount(this.builtin("select")), [
728
- local(["source"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
729
- ifThen(binary("~=", identifier("source"), nil()), [
730
- genericFor(["key", "value"], [call(this.builtin("pairs"), [identifier("source")])], [
731
- assign([index(identifier("target"), identifier("key"))], [identifier("value")])
732
- ])
733
- ])
734
- ]),
735
- returns([identifier("target")])
736
- ], true)));
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 (this.lua51) return this.compoundAssignment51(node);
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: node.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 [returns(node.argument ? [this.keptToOne(this.expression(node.argument))] : [])];
1317
+ return this.jump("return", node.argument && this.keptToOne(this.expression(node.argument)), node);
1210
1318
  case "BreakStatement":
1211
- return [{ type: "BreakStatement", ...spanOf(node) }];
1319
+ return this.jump("break", void 0, node, node.label?.name);
1212
1320
  case "ContinueStatement":
1213
- return [{ type: "ContinueStatement", ...spanOf(node) }];
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 create = call(identifier(build), [node.superclass ? this.reference(node.superclass) : nil()]);
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.functionBody(node.func));
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 = node.operator.slice(0, -1);
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
@@ -4,7 +4,7 @@ import {
4
4
  lower,
5
5
  program,
6
6
  resolveConfig
7
- } from "./chunk-3J2QMA4W.js";
7
+ } from "./chunk-EQE22LX7.js";
8
8
 
9
9
  // src/compile.ts
10
10
  import { readFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tilua/compiler",
3
- "version": "5.0.0",
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": "^5.0.0"
41
+ "@tilua/parser": "^6.0.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^22.20.2",
@@ -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