@tilua/compiler 2.1.0 → 2.3.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.
@@ -262,12 +262,10 @@ var Lowerer = class {
262
262
  for (const node of allNodes(this.source.body)) {
263
263
  const statement = node;
264
264
  if (statement.type === "VariableDeclaration") {
265
- statement.names.forEach((target, index2) => {
266
- const value = statement.init[index2];
267
- if (target.type !== "IdentifierPattern" || !value) return;
268
- const binding = this.bindingByDeclaration.get(target);
269
- if (binding) this.declarations.set(binding.id, value);
270
- });
265
+ const target = statement.name;
266
+ if (target.type !== "IdentifierPattern" || !statement.init) continue;
267
+ const binding = this.bindingByDeclaration.get(target);
268
+ if (binding) this.declarations.set(binding.id, statement.init);
271
269
  } else if (statement.type === "FunctionDeclaration") {
272
270
  const binding = this.bindingByDeclaration.get(statement.name);
273
271
  if (binding) this.declarations.set(binding.id, statement);
@@ -541,7 +539,7 @@ var Lowerer = class {
541
539
  const declaration = statement.declaration;
542
540
  if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
543
541
  exportBinding(declaration.name, declaration.name.name);
544
- } else for (const pattern of declaration.names.flatMap(identifierPatterns)) exportBinding(pattern, pattern.name);
542
+ } else for (const pattern of identifierPatterns(declaration.name)) exportBinding(pattern, pattern.name);
545
543
  break;
546
544
  }
547
545
  case "ExportDefaultStatement":
@@ -577,7 +575,7 @@ var Lowerer = class {
577
575
  for (const statement of statements) {
578
576
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
579
577
  if (declaration.type === "VariableDeclaration") {
580
- for (const pattern of declaration.names.flatMap(identifierPatterns)) {
578
+ for (const pattern of identifierPatterns(declaration.name)) {
581
579
  if (!this.isRewritten(pattern)) locals.push(this.name(pattern.name));
582
580
  }
583
581
  } else if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && !this.isRewritten(declaration.name)) {
@@ -674,7 +672,7 @@ var Lowerer = class {
674
672
  continue;
675
673
  }
676
674
  if (statement.type !== "VariableDeclaration") continue;
677
- const patterns = statement.names.flatMap(identifierPatterns);
675
+ const patterns = identifierPatterns(statement.name);
678
676
  const end = { line: { start: statement.line.end }, column: { start: statement.column.end } };
679
677
  const needed = patterns.some((pattern) => {
680
678
  const binding = this.bindingByDeclaration.get(pattern);
@@ -715,7 +713,7 @@ var Lowerer = class {
715
713
  const body = [];
716
714
  for (const statement of statements) {
717
715
  if (statement.type === "VariableDeclaration") {
718
- for (const pattern of statement.names.flatMap(identifierPatterns)) locals.push(this.name(pattern.name));
716
+ for (const pattern of identifierPatterns(statement.name)) locals.push(this.name(pattern.name));
719
717
  body.push(...this.variableDeclaration(statement, "assign"));
720
718
  } else if (statement.type === "FunctionDeclaration") {
721
719
  locals.push(this.name(statement.name.name));
@@ -867,7 +865,7 @@ var Lowerer = class {
867
865
  )];
868
866
  }
869
867
  case "ReturnStatement":
870
- return [returns(node.argument ? [this.expression(node.argument)] : [])];
868
+ return [returns(node.argument ? [this.keptToOne(this.expression(node.argument))] : [])];
871
869
  case "BreakStatement":
872
870
  return [{ type: "BreakStatement", ...spanOf(node) }];
873
871
  case "ContinueStatement":
@@ -1116,63 +1114,37 @@ end
1116
1114
  ...spanOf(node)
1117
1115
  };
1118
1116
  }
1119
- /** `const a, { b } = x, y`. In "assign" mode — a module's top level, where
1120
- * every name is declared up front — nothing is declared, only assigned. */
1117
+ /** `const a = x`, `const { a, b } = x`. In "assign" mode — a module's top
1118
+ * level, where every name is declared up front — nothing is declared,
1119
+ * only assigned. */
1121
1120
  variableDeclaration(node, mode) {
1122
- if (mode === "assign" && !node.init.length) {
1123
- const exported = node.names.flatMap(identifierPatterns).filter((p) => this.isRewritten(p));
1121
+ const target = node.name;
1122
+ if (!node.init) {
1123
+ const names = identifierPatterns(target);
1124
+ if (mode === "declare") return [local(names.map((p) => this.name(p.name)), [])];
1125
+ const exported = names.filter((p) => this.isRewritten(p));
1124
1126
  return exported.length ? [assign(exported.map((p) => this.reference(p)), exported.map(() => nil()))] : [];
1125
1127
  }
1126
- const init = this.values(node.init);
1127
- if (node.names.every((n) => n.type === "IdentifierPattern")) {
1128
- const names2 = node.names;
1129
- return mode === "declare" ? [local(names2.map((n) => this.name(n.name)), init)] : [assign(names2.map((n) => this.reference(n)), init)];
1130
- }
1131
- const only = node.names.length === 1 && node.init.length === 1 ? node.names[0] : void 0;
1132
- if (only && only.type !== "IdentifierPattern") {
1133
- if (init[0].type === "Identifier") return this.destructure(only, init[0], mode);
1134
- const temp = this.names.fresh("ref");
1135
- return [local([temp], init), ...this.destructure(only, identifier(temp), mode)];
1136
- }
1137
- const after = [];
1138
- const names = [];
1139
- const targets = [];
1140
- for (const target of node.names) {
1141
- if (target.type === "IdentifierPattern") {
1142
- names.push(this.name(target.name));
1143
- targets.push(this.reference(target));
1144
- continue;
1145
- }
1146
- const temp = this.names.fresh("ref");
1147
- names.push(temp);
1148
- targets.push(identifier(temp));
1149
- after.push(...this.destructure(target, identifier(temp), mode));
1128
+ const value = this.expression(node.init);
1129
+ if (target.type === "IdentifierPattern") {
1130
+ return mode === "declare" ? [local([this.name(target.name)], [value])] : [assign([this.reference(target)], [value])];
1150
1131
  }
1151
- if (mode === "declare") return [local(names, init), ...after];
1152
- const temps = node.names.flatMap((t, i) => t.type === "IdentifierPattern" ? [] : [names[i]]);
1153
- return [...temps.length ? [local(temps, [])] : [], assign(targets, init), ...after];
1132
+ if (value.type === "Identifier") return this.destructure(target, value, mode);
1133
+ const temp = this.names.fresh("ref");
1134
+ return [local([temp], [value]), ...this.destructure(target, identifier(temp), mode)];
1154
1135
  }
1155
1136
  assignment(node) {
1156
- if (node.targets.every((t) => t.type !== "ObjectPattern" && t.type !== "ArrayPattern")) {
1157
- return [assign(node.targets.map((t) => this.expression(t)), this.values(node.values))];
1137
+ const target = node.target;
1138
+ const value = this.expression(node.value);
1139
+ if (target.type !== "ObjectPattern" && target.type !== "ArrayPattern") {
1140
+ return [assign([this.expression(target)], [value])];
1158
1141
  }
1159
- const values = this.values(node.values);
1160
- const only = node.targets.length === 1 && values.length === 1 ? node.targets[0] : void 0;
1161
- if (only && (only.type === "ObjectPattern" || only.type === "ArrayPattern") && values[0].type === "Identifier") {
1162
- const statements = this.destructure(only, values[0], "assign");
1142
+ if (value.type === "Identifier") {
1143
+ const statements = this.destructure(target, value, "assign");
1163
1144
  return statements.some((s) => s.type === "LocalStatement") ? [doBlock(statements)] : statements;
1164
1145
  }
1165
- const temps = node.targets.map(() => this.names.fresh("ref"));
1166
- const body = [local(temps, values)];
1167
- node.targets.forEach((target, i) => {
1168
- const value = identifier(temps[i]);
1169
- if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
1170
- body.push(...this.destructure(target, value, "assign"));
1171
- } else {
1172
- body.push(assign([this.expression(target)], [value]));
1173
- }
1174
- });
1175
- return [doBlock(body)];
1146
+ const temp = this.names.fresh("ref");
1147
+ return [doBlock([local([temp], [value]), ...this.destructure(target, identifier(temp), "assign")])];
1176
1148
  }
1177
1149
  // --------------------------------------------------------
1178
1150
  // Destructuring
@@ -1307,7 +1279,7 @@ end
1307
1279
  if (through !== void 0) {
1308
1280
  return call(member(this.superClassReference(node), through), [
1309
1281
  identifier(this.thisName()),
1310
- ...this.values(node.arguments)
1282
+ ...this.arguments(node.arguments)
1311
1283
  ]);
1312
1284
  }
1313
1285
  }
@@ -1392,35 +1364,27 @@ end
1392
1364
  return this.link(node, this.expression(object));
1393
1365
  }
1394
1366
  /** A call is one value in tilua, and Luau would hand on every value one
1395
- * in the last place of an argument list or an array returns. Types say
1396
- * how many a call returns; where a type says nothing — `any`, `unknown`
1397
- * — the call is kept to one: `f((g()))`. */
1398
- keptToOne(nodes, values) {
1399
- const node = nodes[nodes.length - 1];
1400
- const last = values[values.length - 1];
1401
- if (!node || !last || !expandsToMany(last)) return values;
1402
- const type = this.options.types?.typeOf.get(node);
1403
- if (type && type.kind !== "any" && type.kind !== "unknown") return values;
1404
- return [...values.slice(0, -1), parenthesized(last)];
1367
+ * returns in the last place of an argument list or a `return`: it is
1368
+ * kept to one there, `f((g()))`. Every value is taken only where the
1369
+ * program asks for them, as an array's last element: `[g()]`. */
1370
+ keptToOne(value) {
1371
+ return expandsToMany(value) ? parenthesized(value) : value;
1405
1372
  }
1406
1373
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1407
1374
  isLanguageGlobal(node) {
1408
1375
  const id = this.bindingIdOf(node);
1409
1376
  return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1410
1377
  }
1411
- /** A list of values — a call's arguments, a `return`'s, a declaration's,
1412
- * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1378
+ /** A call's arguments. `f(a, ...xs)` is Lua's own last-value expansion,
1413
1379
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1414
1380
  * the last value of a list expands, so the whole list is built as an
1415
- * array first and that is what expands.
1416
- *
1417
- * `arguments` is set for a call's arguments, where Luau hands on every
1418
- * value the last one returns — see `keptToOne`. */
1419
- values(list, asArguments = false) {
1381
+ * array first and that is what expands. A call written last is one
1382
+ * value — see `keptToOne`. */
1383
+ arguments(list) {
1420
1384
  const spreads = list.filter((a) => a.type === "SpreadElement");
1421
1385
  if (!spreads.length) {
1422
1386
  const lowered = list.map((a) => this.expression(a));
1423
- return asArguments ? this.keptToOne(list, lowered) : lowered;
1387
+ return lowered.length ? [...lowered.slice(0, -1), this.keptToOne(lowered[lowered.length - 1])] : lowered;
1424
1388
  }
1425
1389
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1426
1390
  const last = list[list.length - 1];
@@ -1442,13 +1406,13 @@ end
1442
1406
  return index(object, this.expression(node.index));
1443
1407
  case "CallExpression": {
1444
1408
  const lowered = this.loweredGlobalCall(node);
1445
- const args = this.values(node.arguments, true);
1409
+ const args = this.arguments(node.arguments);
1446
1410
  if (!lowered) return call(object, args);
1447
1411
  return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
1448
1412
  }
1449
1413
  case "MethodCallExpression": {
1450
1414
  const lowered = this.loweredMethodCall(node);
1451
- const args = this.values(node.arguments, true);
1415
+ const args = this.arguments(node.arguments);
1452
1416
  if (lowered) {
1453
1417
  return call(calleePath(lowered.callee), [
1454
1418
  ...lowered.prepend,
@@ -1682,29 +1646,27 @@ end
1682
1646
  * runs of plain elements and the spread arrays are joined by `concat`. */
1683
1647
  arrayExpression(node) {
1684
1648
  if (!node.elements.some((e) => e.type === "SpreadElement")) {
1685
- const elements = node.elements;
1686
- return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1649
+ return table(node.elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1687
1650
  }
1688
1651
  const parts = [];
1689
1652
  let current = [];
1690
- const flush = () => {
1653
+ const flush = (last) => {
1691
1654
  if (!current.length) return;
1692
1655
  parts.push(table(current.map((e, i) => {
1693
1656
  const value = this.expression(e);
1694
- const last = i === current.length - 1;
1695
- return { type: "TableFieldPositional", value: last && expandsToMany(value) ? parenthesized(value) : value };
1657
+ return { type: "TableFieldPositional", value: last || i < current.length - 1 ? value : this.keptToOne(value) };
1696
1658
  })));
1697
1659
  current = [];
1698
1660
  };
1699
1661
  for (const element of node.elements) {
1700
1662
  if (element.type === "SpreadElement") {
1701
- flush();
1663
+ flush(false);
1702
1664
  parts.push(this.expression(element.argument));
1703
1665
  } else {
1704
1666
  current.push(element);
1705
1667
  }
1706
1668
  }
1707
- flush();
1669
+ flush(true);
1708
1670
  return call(this.helper("concat"), parts);
1709
1671
  }
1710
1672
  };
@@ -1773,7 +1735,7 @@ function topLevelDeclaration(statements, name) {
1773
1735
  for (const statement of statements) {
1774
1736
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1775
1737
  if (declaration.type === "VariableDeclaration") {
1776
- const pattern = declaration.names.flatMap(identifierPatterns).find((p) => p.name === name);
1738
+ const pattern = identifierPatterns(declaration.name).find((p) => p.name === name);
1777
1739
  if (pattern) return { type: "Declaration", node: pattern };
1778
1740
  } else if (declaration.type === "FunctionDeclaration" && declaration.name.name === name) {
1779
1741
  return { type: "Declaration", node: declaration.name };
package/dist/cli.cjs CHANGED
@@ -274,12 +274,10 @@ var Lowerer = class {
274
274
  for (const node of allNodes(this.source.body)) {
275
275
  const statement = node;
276
276
  if (statement.type === "VariableDeclaration") {
277
- statement.names.forEach((target, index2) => {
278
- const value = statement.init[index2];
279
- if (target.type !== "IdentifierPattern" || !value) return;
280
- const binding = this.bindingByDeclaration.get(target);
281
- if (binding) this.declarations.set(binding.id, value);
282
- });
277
+ const target = statement.name;
278
+ if (target.type !== "IdentifierPattern" || !statement.init) continue;
279
+ const binding = this.bindingByDeclaration.get(target);
280
+ if (binding) this.declarations.set(binding.id, statement.init);
283
281
  } else if (statement.type === "FunctionDeclaration") {
284
282
  const binding = this.bindingByDeclaration.get(statement.name);
285
283
  if (binding) this.declarations.set(binding.id, statement);
@@ -553,7 +551,7 @@ var Lowerer = class {
553
551
  const declaration = statement.declaration;
554
552
  if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
555
553
  exportBinding(declaration.name, declaration.name.name);
556
- } else for (const pattern of declaration.names.flatMap(identifierPatterns)) exportBinding(pattern, pattern.name);
554
+ } else for (const pattern of identifierPatterns(declaration.name)) exportBinding(pattern, pattern.name);
557
555
  break;
558
556
  }
559
557
  case "ExportDefaultStatement":
@@ -589,7 +587,7 @@ var Lowerer = class {
589
587
  for (const statement of statements) {
590
588
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
591
589
  if (declaration.type === "VariableDeclaration") {
592
- for (const pattern of declaration.names.flatMap(identifierPatterns)) {
590
+ for (const pattern of identifierPatterns(declaration.name)) {
593
591
  if (!this.isRewritten(pattern)) locals.push(this.name(pattern.name));
594
592
  }
595
593
  } else if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && !this.isRewritten(declaration.name)) {
@@ -686,7 +684,7 @@ var Lowerer = class {
686
684
  continue;
687
685
  }
688
686
  if (statement.type !== "VariableDeclaration") continue;
689
- const patterns = statement.names.flatMap(identifierPatterns);
687
+ const patterns = identifierPatterns(statement.name);
690
688
  const end = { line: { start: statement.line.end }, column: { start: statement.column.end } };
691
689
  const needed = patterns.some((pattern) => {
692
690
  const binding = this.bindingByDeclaration.get(pattern);
@@ -727,7 +725,7 @@ var Lowerer = class {
727
725
  const body = [];
728
726
  for (const statement of statements) {
729
727
  if (statement.type === "VariableDeclaration") {
730
- for (const pattern of statement.names.flatMap(identifierPatterns)) locals.push(this.name(pattern.name));
728
+ for (const pattern of identifierPatterns(statement.name)) locals.push(this.name(pattern.name));
731
729
  body.push(...this.variableDeclaration(statement, "assign"));
732
730
  } else if (statement.type === "FunctionDeclaration") {
733
731
  locals.push(this.name(statement.name.name));
@@ -879,7 +877,7 @@ var Lowerer = class {
879
877
  )];
880
878
  }
881
879
  case "ReturnStatement":
882
- return [returns(node.argument ? [this.expression(node.argument)] : [])];
880
+ return [returns(node.argument ? [this.keptToOne(this.expression(node.argument))] : [])];
883
881
  case "BreakStatement":
884
882
  return [{ type: "BreakStatement", ...spanOf(node) }];
885
883
  case "ContinueStatement":
@@ -1128,63 +1126,37 @@ end
1128
1126
  ...spanOf(node)
1129
1127
  };
1130
1128
  }
1131
- /** `const a, { b } = x, y`. In "assign" mode — a module's top level, where
1132
- * every name is declared up front — nothing is declared, only assigned. */
1129
+ /** `const a = x`, `const { a, b } = x`. In "assign" mode — a module's top
1130
+ * level, where every name is declared up front — nothing is declared,
1131
+ * only assigned. */
1133
1132
  variableDeclaration(node, mode) {
1134
- if (mode === "assign" && !node.init.length) {
1135
- const exported = node.names.flatMap(identifierPatterns).filter((p) => this.isRewritten(p));
1133
+ const target = node.name;
1134
+ if (!node.init) {
1135
+ const names = identifierPatterns(target);
1136
+ if (mode === "declare") return [local(names.map((p) => this.name(p.name)), [])];
1137
+ const exported = names.filter((p) => this.isRewritten(p));
1136
1138
  return exported.length ? [assign(exported.map((p) => this.reference(p)), exported.map(() => nil()))] : [];
1137
1139
  }
1138
- const init = this.values(node.init);
1139
- if (node.names.every((n) => n.type === "IdentifierPattern")) {
1140
- const names2 = node.names;
1141
- return mode === "declare" ? [local(names2.map((n) => this.name(n.name)), init)] : [assign(names2.map((n) => this.reference(n)), init)];
1142
- }
1143
- const only = node.names.length === 1 && node.init.length === 1 ? node.names[0] : void 0;
1144
- if (only && only.type !== "IdentifierPattern") {
1145
- if (init[0].type === "Identifier") return this.destructure(only, init[0], mode);
1146
- const temp = this.names.fresh("ref");
1147
- return [local([temp], init), ...this.destructure(only, identifier(temp), mode)];
1148
- }
1149
- const after = [];
1150
- const names = [];
1151
- const targets = [];
1152
- for (const target of node.names) {
1153
- if (target.type === "IdentifierPattern") {
1154
- names.push(this.name(target.name));
1155
- targets.push(this.reference(target));
1156
- continue;
1157
- }
1158
- const temp = this.names.fresh("ref");
1159
- names.push(temp);
1160
- targets.push(identifier(temp));
1161
- after.push(...this.destructure(target, identifier(temp), mode));
1140
+ const value = this.expression(node.init);
1141
+ if (target.type === "IdentifierPattern") {
1142
+ return mode === "declare" ? [local([this.name(target.name)], [value])] : [assign([this.reference(target)], [value])];
1162
1143
  }
1163
- if (mode === "declare") return [local(names, init), ...after];
1164
- const temps = node.names.flatMap((t, i) => t.type === "IdentifierPattern" ? [] : [names[i]]);
1165
- return [...temps.length ? [local(temps, [])] : [], assign(targets, init), ...after];
1144
+ if (value.type === "Identifier") return this.destructure(target, value, mode);
1145
+ const temp = this.names.fresh("ref");
1146
+ return [local([temp], [value]), ...this.destructure(target, identifier(temp), mode)];
1166
1147
  }
1167
1148
  assignment(node) {
1168
- if (node.targets.every((t) => t.type !== "ObjectPattern" && t.type !== "ArrayPattern")) {
1169
- return [assign(node.targets.map((t) => this.expression(t)), this.values(node.values))];
1149
+ const target = node.target;
1150
+ const value = this.expression(node.value);
1151
+ if (target.type !== "ObjectPattern" && target.type !== "ArrayPattern") {
1152
+ return [assign([this.expression(target)], [value])];
1170
1153
  }
1171
- const values = this.values(node.values);
1172
- const only = node.targets.length === 1 && values.length === 1 ? node.targets[0] : void 0;
1173
- if (only && (only.type === "ObjectPattern" || only.type === "ArrayPattern") && values[0].type === "Identifier") {
1174
- const statements = this.destructure(only, values[0], "assign");
1154
+ if (value.type === "Identifier") {
1155
+ const statements = this.destructure(target, value, "assign");
1175
1156
  return statements.some((s) => s.type === "LocalStatement") ? [doBlock(statements)] : statements;
1176
1157
  }
1177
- const temps = node.targets.map(() => this.names.fresh("ref"));
1178
- const body = [local(temps, values)];
1179
- node.targets.forEach((target, i) => {
1180
- const value = identifier(temps[i]);
1181
- if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
1182
- body.push(...this.destructure(target, value, "assign"));
1183
- } else {
1184
- body.push(assign([this.expression(target)], [value]));
1185
- }
1186
- });
1187
- return [doBlock(body)];
1158
+ const temp = this.names.fresh("ref");
1159
+ return [doBlock([local([temp], [value]), ...this.destructure(target, identifier(temp), "assign")])];
1188
1160
  }
1189
1161
  // --------------------------------------------------------
1190
1162
  // Destructuring
@@ -1319,7 +1291,7 @@ end
1319
1291
  if (through !== void 0) {
1320
1292
  return call(member(this.superClassReference(node), through), [
1321
1293
  identifier(this.thisName()),
1322
- ...this.values(node.arguments)
1294
+ ...this.arguments(node.arguments)
1323
1295
  ]);
1324
1296
  }
1325
1297
  }
@@ -1404,35 +1376,27 @@ end
1404
1376
  return this.link(node, this.expression(object));
1405
1377
  }
1406
1378
  /** A call is one value in tilua, and Luau would hand on every value one
1407
- * in the last place of an argument list or an array returns. Types say
1408
- * how many a call returns; where a type says nothing — `any`, `unknown`
1409
- * — the call is kept to one: `f((g()))`. */
1410
- keptToOne(nodes, values) {
1411
- const node = nodes[nodes.length - 1];
1412
- const last = values[values.length - 1];
1413
- if (!node || !last || !expandsToMany(last)) return values;
1414
- const type = this.options.types?.typeOf.get(node);
1415
- if (type && type.kind !== "any" && type.kind !== "unknown") return values;
1416
- return [...values.slice(0, -1), parenthesized(last)];
1379
+ * returns in the last place of an argument list or a `return`: it is
1380
+ * kept to one there, `f((g()))`. Every value is taken only where the
1381
+ * program asks for them, as an array's last element: `[g()]`. */
1382
+ keptToOne(value) {
1383
+ return expandsToMany(value) ? parenthesized(value) : value;
1417
1384
  }
1418
1385
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1419
1386
  isLanguageGlobal(node) {
1420
1387
  const id = this.bindingIdOf(node);
1421
1388
  return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1422
1389
  }
1423
- /** A list of values — a call's arguments, a `return`'s, a declaration's,
1424
- * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1390
+ /** A call's arguments. `f(a, ...xs)` is Lua's own last-value expansion,
1425
1391
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1426
1392
  * the last value of a list expands, so the whole list is built as an
1427
- * array first and that is what expands.
1428
- *
1429
- * `arguments` is set for a call's arguments, where Luau hands on every
1430
- * value the last one returns — see `keptToOne`. */
1431
- values(list, asArguments = false) {
1393
+ * array first and that is what expands. A call written last is one
1394
+ * value — see `keptToOne`. */
1395
+ arguments(list) {
1432
1396
  const spreads = list.filter((a) => a.type === "SpreadElement");
1433
1397
  if (!spreads.length) {
1434
1398
  const lowered = list.map((a) => this.expression(a));
1435
- return asArguments ? this.keptToOne(list, lowered) : lowered;
1399
+ return lowered.length ? [...lowered.slice(0, -1), this.keptToOne(lowered[lowered.length - 1])] : lowered;
1436
1400
  }
1437
1401
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1438
1402
  const last = list[list.length - 1];
@@ -1454,13 +1418,13 @@ end
1454
1418
  return index(object, this.expression(node.index));
1455
1419
  case "CallExpression": {
1456
1420
  const lowered = this.loweredGlobalCall(node);
1457
- const args2 = this.values(node.arguments, true);
1421
+ const args2 = this.arguments(node.arguments);
1458
1422
  if (!lowered) return call(object, args2);
1459
1423
  return call(calleePath(lowered.callee), [...lowered.prepend, ...args2]);
1460
1424
  }
1461
1425
  case "MethodCallExpression": {
1462
1426
  const lowered = this.loweredMethodCall(node);
1463
- const args2 = this.values(node.arguments, true);
1427
+ const args2 = this.arguments(node.arguments);
1464
1428
  if (lowered) {
1465
1429
  return call(calleePath(lowered.callee), [
1466
1430
  ...lowered.prepend,
@@ -1694,29 +1658,27 @@ end
1694
1658
  * runs of plain elements and the spread arrays are joined by `concat`. */
1695
1659
  arrayExpression(node) {
1696
1660
  if (!node.elements.some((e) => e.type === "SpreadElement")) {
1697
- const elements = node.elements;
1698
- return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1661
+ return table(node.elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1699
1662
  }
1700
1663
  const parts = [];
1701
1664
  let current = [];
1702
- const flush = () => {
1665
+ const flush = (last) => {
1703
1666
  if (!current.length) return;
1704
1667
  parts.push(table(current.map((e, i) => {
1705
1668
  const value = this.expression(e);
1706
- const last = i === current.length - 1;
1707
- return { type: "TableFieldPositional", value: last && expandsToMany(value) ? parenthesized(value) : value };
1669
+ return { type: "TableFieldPositional", value: last || i < current.length - 1 ? value : this.keptToOne(value) };
1708
1670
  })));
1709
1671
  current = [];
1710
1672
  };
1711
1673
  for (const element of node.elements) {
1712
1674
  if (element.type === "SpreadElement") {
1713
- flush();
1675
+ flush(false);
1714
1676
  parts.push(this.expression(element.argument));
1715
1677
  } else {
1716
1678
  current.push(element);
1717
1679
  }
1718
1680
  }
1719
- flush();
1681
+ flush(true);
1720
1682
  return call(this.helper("concat"), parts);
1721
1683
  }
1722
1684
  };
@@ -1785,7 +1747,7 @@ function topLevelDeclaration(statements, name) {
1785
1747
  for (const statement of statements) {
1786
1748
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1787
1749
  if (declaration.type === "VariableDeclaration") {
1788
- const pattern = declaration.names.flatMap(identifierPatterns).find((p) => p.name === name);
1750
+ const pattern = identifierPatterns(declaration.name).find((p) => p.name === name);
1789
1751
  if (pattern) return { type: "Declaration", node: pattern };
1790
1752
  } else if (declaration.type === "FunctionDeclaration" && declaration.name.name === name) {
1791
1753
  return { type: "Declaration", node: declaration.name };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  bundle
4
- } from "./chunk-TBSJRVKA.js";
4
+ } from "./chunk-HE6YH7U3.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { writeFileSync } from "fs";
package/dist/index.cjs CHANGED
@@ -298,12 +298,10 @@ var Lowerer = class {
298
298
  for (const node of allNodes(this.source.body)) {
299
299
  const statement = node;
300
300
  if (statement.type === "VariableDeclaration") {
301
- statement.names.forEach((target, index2) => {
302
- const value = statement.init[index2];
303
- if (target.type !== "IdentifierPattern" || !value) return;
304
- const binding = this.bindingByDeclaration.get(target);
305
- if (binding) this.declarations.set(binding.id, value);
306
- });
301
+ const target = statement.name;
302
+ if (target.type !== "IdentifierPattern" || !statement.init) continue;
303
+ const binding = this.bindingByDeclaration.get(target);
304
+ if (binding) this.declarations.set(binding.id, statement.init);
307
305
  } else if (statement.type === "FunctionDeclaration") {
308
306
  const binding = this.bindingByDeclaration.get(statement.name);
309
307
  if (binding) this.declarations.set(binding.id, statement);
@@ -577,7 +575,7 @@ var Lowerer = class {
577
575
  const declaration = statement.declaration;
578
576
  if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
579
577
  exportBinding(declaration.name, declaration.name.name);
580
- } else for (const pattern of declaration.names.flatMap(identifierPatterns)) exportBinding(pattern, pattern.name);
578
+ } else for (const pattern of identifierPatterns(declaration.name)) exportBinding(pattern, pattern.name);
581
579
  break;
582
580
  }
583
581
  case "ExportDefaultStatement":
@@ -613,7 +611,7 @@ var Lowerer = class {
613
611
  for (const statement of statements) {
614
612
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
615
613
  if (declaration.type === "VariableDeclaration") {
616
- for (const pattern of declaration.names.flatMap(identifierPatterns)) {
614
+ for (const pattern of identifierPatterns(declaration.name)) {
617
615
  if (!this.isRewritten(pattern)) locals.push(this.name(pattern.name));
618
616
  }
619
617
  } else if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && !this.isRewritten(declaration.name)) {
@@ -710,7 +708,7 @@ var Lowerer = class {
710
708
  continue;
711
709
  }
712
710
  if (statement.type !== "VariableDeclaration") continue;
713
- const patterns = statement.names.flatMap(identifierPatterns);
711
+ const patterns = identifierPatterns(statement.name);
714
712
  const end = { line: { start: statement.line.end }, column: { start: statement.column.end } };
715
713
  const needed = patterns.some((pattern) => {
716
714
  const binding = this.bindingByDeclaration.get(pattern);
@@ -751,7 +749,7 @@ var Lowerer = class {
751
749
  const body = [];
752
750
  for (const statement of statements) {
753
751
  if (statement.type === "VariableDeclaration") {
754
- for (const pattern of statement.names.flatMap(identifierPatterns)) locals.push(this.name(pattern.name));
752
+ for (const pattern of identifierPatterns(statement.name)) locals.push(this.name(pattern.name));
755
753
  body.push(...this.variableDeclaration(statement, "assign"));
756
754
  } else if (statement.type === "FunctionDeclaration") {
757
755
  locals.push(this.name(statement.name.name));
@@ -903,7 +901,7 @@ var Lowerer = class {
903
901
  )];
904
902
  }
905
903
  case "ReturnStatement":
906
- return [returns(node.argument ? [this.expression(node.argument)] : [])];
904
+ return [returns(node.argument ? [this.keptToOne(this.expression(node.argument))] : [])];
907
905
  case "BreakStatement":
908
906
  return [{ type: "BreakStatement", ...spanOf(node) }];
909
907
  case "ContinueStatement":
@@ -1152,63 +1150,37 @@ end
1152
1150
  ...spanOf(node)
1153
1151
  };
1154
1152
  }
1155
- /** `const a, { b } = x, y`. In "assign" mode — a module's top level, where
1156
- * every name is declared up front — nothing is declared, only assigned. */
1153
+ /** `const a = x`, `const { a, b } = x`. In "assign" mode — a module's top
1154
+ * level, where every name is declared up front — nothing is declared,
1155
+ * only assigned. */
1157
1156
  variableDeclaration(node, mode) {
1158
- if (mode === "assign" && !node.init.length) {
1159
- const exported = node.names.flatMap(identifierPatterns).filter((p) => this.isRewritten(p));
1157
+ const target = node.name;
1158
+ if (!node.init) {
1159
+ const names = identifierPatterns(target);
1160
+ if (mode === "declare") return [local(names.map((p) => this.name(p.name)), [])];
1161
+ const exported = names.filter((p) => this.isRewritten(p));
1160
1162
  return exported.length ? [assign(exported.map((p) => this.reference(p)), exported.map(() => nil()))] : [];
1161
1163
  }
1162
- const init = this.values(node.init);
1163
- if (node.names.every((n) => n.type === "IdentifierPattern")) {
1164
- const names2 = node.names;
1165
- return mode === "declare" ? [local(names2.map((n) => this.name(n.name)), init)] : [assign(names2.map((n) => this.reference(n)), init)];
1166
- }
1167
- const only = node.names.length === 1 && node.init.length === 1 ? node.names[0] : void 0;
1168
- if (only && only.type !== "IdentifierPattern") {
1169
- if (init[0].type === "Identifier") return this.destructure(only, init[0], mode);
1170
- const temp = this.names.fresh("ref");
1171
- return [local([temp], init), ...this.destructure(only, identifier(temp), mode)];
1172
- }
1173
- const after = [];
1174
- const names = [];
1175
- const targets = [];
1176
- for (const target of node.names) {
1177
- if (target.type === "IdentifierPattern") {
1178
- names.push(this.name(target.name));
1179
- targets.push(this.reference(target));
1180
- continue;
1181
- }
1182
- const temp = this.names.fresh("ref");
1183
- names.push(temp);
1184
- targets.push(identifier(temp));
1185
- after.push(...this.destructure(target, identifier(temp), mode));
1164
+ const value = this.expression(node.init);
1165
+ if (target.type === "IdentifierPattern") {
1166
+ return mode === "declare" ? [local([this.name(target.name)], [value])] : [assign([this.reference(target)], [value])];
1186
1167
  }
1187
- if (mode === "declare") return [local(names, init), ...after];
1188
- const temps = node.names.flatMap((t, i) => t.type === "IdentifierPattern" ? [] : [names[i]]);
1189
- return [...temps.length ? [local(temps, [])] : [], assign(targets, init), ...after];
1168
+ if (value.type === "Identifier") return this.destructure(target, value, mode);
1169
+ const temp = this.names.fresh("ref");
1170
+ return [local([temp], [value]), ...this.destructure(target, identifier(temp), mode)];
1190
1171
  }
1191
1172
  assignment(node) {
1192
- if (node.targets.every((t) => t.type !== "ObjectPattern" && t.type !== "ArrayPattern")) {
1193
- return [assign(node.targets.map((t) => this.expression(t)), this.values(node.values))];
1173
+ const target = node.target;
1174
+ const value = this.expression(node.value);
1175
+ if (target.type !== "ObjectPattern" && target.type !== "ArrayPattern") {
1176
+ return [assign([this.expression(target)], [value])];
1194
1177
  }
1195
- const values = this.values(node.values);
1196
- const only = node.targets.length === 1 && values.length === 1 ? node.targets[0] : void 0;
1197
- if (only && (only.type === "ObjectPattern" || only.type === "ArrayPattern") && values[0].type === "Identifier") {
1198
- const statements = this.destructure(only, values[0], "assign");
1178
+ if (value.type === "Identifier") {
1179
+ const statements = this.destructure(target, value, "assign");
1199
1180
  return statements.some((s) => s.type === "LocalStatement") ? [doBlock(statements)] : statements;
1200
1181
  }
1201
- const temps = node.targets.map(() => this.names.fresh("ref"));
1202
- const body = [local(temps, values)];
1203
- node.targets.forEach((target, i) => {
1204
- const value = identifier(temps[i]);
1205
- if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
1206
- body.push(...this.destructure(target, value, "assign"));
1207
- } else {
1208
- body.push(assign([this.expression(target)], [value]));
1209
- }
1210
- });
1211
- return [doBlock(body)];
1182
+ const temp = this.names.fresh("ref");
1183
+ return [doBlock([local([temp], [value]), ...this.destructure(target, identifier(temp), "assign")])];
1212
1184
  }
1213
1185
  // --------------------------------------------------------
1214
1186
  // Destructuring
@@ -1343,7 +1315,7 @@ end
1343
1315
  if (through !== void 0) {
1344
1316
  return call(member(this.superClassReference(node), through), [
1345
1317
  identifier(this.thisName()),
1346
- ...this.values(node.arguments)
1318
+ ...this.arguments(node.arguments)
1347
1319
  ]);
1348
1320
  }
1349
1321
  }
@@ -1428,35 +1400,27 @@ end
1428
1400
  return this.link(node, this.expression(object));
1429
1401
  }
1430
1402
  /** A call is one value in tilua, and Luau would hand on every value one
1431
- * in the last place of an argument list or an array returns. Types say
1432
- * how many a call returns; where a type says nothing — `any`, `unknown`
1433
- * — the call is kept to one: `f((g()))`. */
1434
- keptToOne(nodes, values) {
1435
- const node = nodes[nodes.length - 1];
1436
- const last = values[values.length - 1];
1437
- if (!node || !last || !expandsToMany(last)) return values;
1438
- const type = this.options.types?.typeOf.get(node);
1439
- if (type && type.kind !== "any" && type.kind !== "unknown") return values;
1440
- return [...values.slice(0, -1), parenthesized(last)];
1403
+ * returns in the last place of an argument list or a `return`: it is
1404
+ * kept to one there, `f((g()))`. Every value is taken only where the
1405
+ * program asks for them, as an array's last element: `[g()]`. */
1406
+ keptToOne(value) {
1407
+ return expandsToMany(value) ? parenthesized(value) : value;
1441
1408
  }
1442
1409
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1443
1410
  isLanguageGlobal(node) {
1444
1411
  const id = this.bindingIdOf(node);
1445
1412
  return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1446
1413
  }
1447
- /** A list of values — a call's arguments, a `return`'s, a declaration's,
1448
- * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1414
+ /** A call's arguments. `f(a, ...xs)` is Lua's own last-value expansion,
1449
1415
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1450
1416
  * the last value of a list expands, so the whole list is built as an
1451
- * array first and that is what expands.
1452
- *
1453
- * `arguments` is set for a call's arguments, where Luau hands on every
1454
- * value the last one returns — see `keptToOne`. */
1455
- values(list, asArguments = false) {
1417
+ * array first and that is what expands. A call written last is one
1418
+ * value — see `keptToOne`. */
1419
+ arguments(list) {
1456
1420
  const spreads = list.filter((a) => a.type === "SpreadElement");
1457
1421
  if (!spreads.length) {
1458
1422
  const lowered = list.map((a) => this.expression(a));
1459
- return asArguments ? this.keptToOne(list, lowered) : lowered;
1423
+ return lowered.length ? [...lowered.slice(0, -1), this.keptToOne(lowered[lowered.length - 1])] : lowered;
1460
1424
  }
1461
1425
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1462
1426
  const last = list[list.length - 1];
@@ -1478,13 +1442,13 @@ end
1478
1442
  return index(object, this.expression(node.index));
1479
1443
  case "CallExpression": {
1480
1444
  const lowered = this.loweredGlobalCall(node);
1481
- const args = this.values(node.arguments, true);
1445
+ const args = this.arguments(node.arguments);
1482
1446
  if (!lowered) return call(object, args);
1483
1447
  return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
1484
1448
  }
1485
1449
  case "MethodCallExpression": {
1486
1450
  const lowered = this.loweredMethodCall(node);
1487
- const args = this.values(node.arguments, true);
1451
+ const args = this.arguments(node.arguments);
1488
1452
  if (lowered) {
1489
1453
  return call(calleePath(lowered.callee), [
1490
1454
  ...lowered.prepend,
@@ -1718,29 +1682,27 @@ end
1718
1682
  * runs of plain elements and the spread arrays are joined by `concat`. */
1719
1683
  arrayExpression(node) {
1720
1684
  if (!node.elements.some((e) => e.type === "SpreadElement")) {
1721
- const elements = node.elements;
1722
- return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1685
+ return table(node.elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1723
1686
  }
1724
1687
  const parts = [];
1725
1688
  let current = [];
1726
- const flush = () => {
1689
+ const flush = (last) => {
1727
1690
  if (!current.length) return;
1728
1691
  parts.push(table(current.map((e, i) => {
1729
1692
  const value = this.expression(e);
1730
- const last = i === current.length - 1;
1731
- return { type: "TableFieldPositional", value: last && expandsToMany(value) ? parenthesized(value) : value };
1693
+ return { type: "TableFieldPositional", value: last || i < current.length - 1 ? value : this.keptToOne(value) };
1732
1694
  })));
1733
1695
  current = [];
1734
1696
  };
1735
1697
  for (const element of node.elements) {
1736
1698
  if (element.type === "SpreadElement") {
1737
- flush();
1699
+ flush(false);
1738
1700
  parts.push(this.expression(element.argument));
1739
1701
  } else {
1740
1702
  current.push(element);
1741
1703
  }
1742
1704
  }
1743
- flush();
1705
+ flush(true);
1744
1706
  return call(this.helper("concat"), parts);
1745
1707
  }
1746
1708
  };
@@ -1809,7 +1771,7 @@ function topLevelDeclaration(statements, name) {
1809
1771
  for (const statement of statements) {
1810
1772
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1811
1773
  if (declaration.type === "VariableDeclaration") {
1812
- const pattern = declaration.names.flatMap(identifierPatterns).find((p) => p.name === name);
1774
+ const pattern = identifierPatterns(declaration.name).find((p) => p.name === name);
1813
1775
  if (pattern) return { type: "Declaration", node: pattern };
1814
1776
  } else if (declaration.type === "FunctionDeclaration" && declaration.name.name === name) {
1815
1777
  return { type: "Declaration", node: declaration.name };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  lower,
5
5
  program,
6
6
  resolveConfig
7
- } from "./chunk-TBSJRVKA.js";
7
+ } from "./chunk-HE6YH7U3.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": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Compiles tilua to Luau: lowers the @tilua/parser AST to a luau-parser AST and prints it",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -37,7 +37,7 @@
37
37
  "type": "module",
38
38
  "dependencies": {
39
39
  "luau-parser": "^1.0.3",
40
- "@tilua/parser": "^2.1.0"
40
+ "@tilua/parser": "^2.3.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^22.20.2",