@tilua/compiler 4.0.0 → 5.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
@@ -33,9 +33,9 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
33
33
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
34
34
 
35
35
  // src/bundle.ts
36
- var import_node_fs2 = require("fs");
36
+ var import_node_fs3 = require("fs");
37
37
  var import_node_path2 = require("path");
38
- var import_parser6 = require("@tilua/parser");
38
+ var import_parser7 = require("@tilua/parser");
39
39
  var import_luau_parser2 = require("luau-parser");
40
40
 
41
41
  // src/config.ts
@@ -50,7 +50,7 @@ function resolveConfig(input, entry, cwd = process.cwd()) {
50
50
  }
51
51
 
52
52
  // src/lower.ts
53
- var import_parser4 = require("@tilua/parser");
53
+ var import_parser5 = require("@tilua/parser");
54
54
  var import_luau_parser = require("luau-parser");
55
55
 
56
56
  // src/luau.ts
@@ -212,6 +212,77 @@ var plugin = {
212
212
  };
213
213
  var languageLowering = { plugin, from: "tilua" };
214
214
 
215
+ // src/standard.ts
216
+ var import_node_fs2 = require("fs");
217
+ var import_parser4 = require("@tilua/parser");
218
+ var read2 = (name) => (0, import_node_fs2.readFileSync)(new URL(`../runtime/${name}`, importMetaUrl), "utf8");
219
+ var console = read2("console.luau");
220
+ var result = read2("result.luau");
221
+ var place = (at) => (0, import_parser4.luauString)(at.file === void 0 ? `line ${at.line}` : `${at.file}:${at.line}`);
222
+ var tag = (at) => (0, import_parser4.luauString)(`[${at.file === void 0 ? `line ${at.line}` : `${at.file.replace(/\.tilua$/, "")}:${at.line}`}]:`);
223
+ function consoleMeta(args) {
224
+ const fields = [];
225
+ args.forEach((argument, index2) => {
226
+ if (argument.spread || argument.typeText === void 0 || !(0, import_parser4.isFunctionType)(argument.type)) return;
227
+ const written = argument.declaration ?? argument.source;
228
+ fields.push(`[${index2 + 1}] = { ${(0, import_parser4.luauString)(argument.typeText)}, ${(0, import_parser4.luauString)(written)} }`);
229
+ });
230
+ return fields.length ? `{ ${fields.join(", ")} }` : "nil";
231
+ }
232
+ var CONSOLE_METHODS = /* @__PURE__ */ new Set(["log", "warn", "error"]);
233
+ var REPLACED = /* @__PURE__ */ new Map([
234
+ ["pcall", "pcall"],
235
+ ["xpcall", "xpcall"],
236
+ ["ypcall", "pcall"],
237
+ ["coroutine.resume", "resume"],
238
+ ["string.find", "find"],
239
+ ["string.match", "match"],
240
+ ["string.gsub", "gsub"],
241
+ ["string.byte", "byte"],
242
+ ["math.modf", "modf"],
243
+ ["math.frexp", "frexp"],
244
+ ["next", "next"],
245
+ ["select", "select"],
246
+ ["unpack", "unpack"],
247
+ ["table.unpack", "unpack"],
248
+ ["loadstring", "loadstring"],
249
+ ["pairs", "pairs"],
250
+ ["ipairs", "ipairs"],
251
+ ["string.gmatch", "gmatch"]
252
+ ]);
253
+ var plugin2 = {
254
+ runtime: { console, result },
255
+ methodCall({ method, receiverGlobal, at, arguments: args, use }) {
256
+ if (receiverGlobal === "console" && CONSOLE_METHODS.has(method)) {
257
+ return {
258
+ callee: `${use("console")}.${method}`,
259
+ prepend: [tag(at), consoleMeta(args)],
260
+ passReceiver: false
261
+ };
262
+ }
263
+ return void 0;
264
+ },
265
+ // `print(x)` and `warn(x)` are left exactly as written: the place a line
266
+ // came from is what `console:log` / `console:warn` are for. `error` has to
267
+ // put the place inside the message it raises, so it goes through the
268
+ // runtime.
269
+ globalCall({ name, at, use }) {
270
+ if (name === "error") return { callee: `${use("console")}.raise`, prepend: [place(at)] };
271
+ const replaced = REPLACED.get(name);
272
+ if (replaced) return { callee: `${use("result")}.${replaced}` };
273
+ return void 0;
274
+ },
275
+ // Read as a value there is no call site to write down; the runtime finds
276
+ // one on the stack.
277
+ globalValue({ name, use }) {
278
+ if (name === "error") return `${use("console")}.raiseValue`;
279
+ const replaced = REPLACED.get(name);
280
+ if (replaced) return `${use("result")}.${replaced}`;
281
+ return void 0;
282
+ }
283
+ };
284
+ var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
285
+
215
286
  // src/lower.ts
216
287
  function lower(program2, scopes, options = {}) {
217
288
  return new Lowerer(program2, scopes, options).run();
@@ -301,6 +372,10 @@ var Lowerer = class {
301
372
  }
302
373
  return identifier(local2);
303
374
  }
375
+ /** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
376
+ move() {
377
+ return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
378
+ }
304
379
  helper(kind) {
305
380
  let name = this.helpers.get(kind);
306
381
  if (!name) {
@@ -401,7 +476,7 @@ var Lowerer = class {
401
476
  const type = this.options.types?.typeOf.get(argument);
402
477
  return {
403
478
  type,
404
- typeText: type ? (0, import_parser4.formatType)(type) : void 0,
479
+ typeText: type ? (0, import_parser5.formatType)(type) : void 0,
405
480
  source: this.writtenSource(argument),
406
481
  declaration: declaration ? this.writtenSource(declaration) : void 0,
407
482
  spread: argument.type === "SpreadElement"
@@ -423,7 +498,11 @@ var Lowerer = class {
423
498
  /** The language's own lowering, then each library's, in load order. A
424
499
  * runtime is named by its lowering's place here (`loweringRuntime`). */
425
500
  get lowerings() {
426
- return this.allLowerings ??= [languageLowering, ...this.options.lowerings ?? []];
501
+ return this.allLowerings ??= [
502
+ languageLowering,
503
+ ...this.options.noLib ? [] : [standardLowering],
504
+ ...this.options.lowerings ?? []
505
+ ];
427
506
  }
428
507
  allLowerings;
429
508
  /** What `receiver:method(...)` becomes, when Luau alone would not run it.
@@ -437,22 +516,28 @@ var Lowerer = class {
437
516
  * (`console:log`), the last library loaded first. No answer leaves an
438
517
  * ordinary Luau method call: `text:upper()` reaches Lua's own. */
439
518
  loweredMethodCall(node) {
519
+ return this.loweredMethod(node.method.name, this.options.types?.methodSources.get(node), node.object, node.arguments, node.method, node);
520
+ }
521
+ /** `loweredMethodCall`, for a method named `method` of what `object`
522
+ * holds — called with `:` on it, or read off it with `.` and handed its
523
+ * `self` like any other argument. `args` are the arguments after the
524
+ * receiver; a read that is not called has none. */
525
+ loweredMethod(method, source, object2, args, name, node) {
440
526
  const lowerings = this.lowerings;
441
- const source = this.options.types?.methodSources.get(node);
442
527
  const asked = source?.origin === "language" ? [0] : source?.origin === "library" ? lowerings.flatMap((l, i) => l.library === source.library ? [i] : []) : source?.origin === "program" ? [] : lowerings.map((_, i) => i).slice(1).reverse();
443
528
  if (!asked.some((i) => lowerings[i].plugin.methodCall)) return void 0;
444
- const receiver = this.options.types?.typeOf.get(node.object);
445
- const receiverGlobal = this.globalName(node.object);
446
- const info = this.lazyArguments(node.arguments);
529
+ const receiver = this.options.types?.typeOf.get(object2);
530
+ const receiverGlobal = this.globalName(object2);
531
+ const info = this.lazyArguments(args);
447
532
  for (const i of asked) {
448
- const plugin2 = lowerings[i].plugin;
449
- if (!plugin2.methodCall) continue;
450
- const answer = plugin2.methodCall({
451
- method: node.method.name,
533
+ const plugin3 = lowerings[i].plugin;
534
+ if (!plugin3.methodCall) continue;
535
+ const answer = plugin3.methodCall({
536
+ method,
452
537
  receiver,
453
538
  receiverGlobal,
454
- argumentCount: node.arguments.length,
455
- at: this.callSite(node.method),
539
+ argumentCount: args.length,
540
+ at: this.callSite(name),
456
541
  get arguments() {
457
542
  return info();
458
543
  },
@@ -476,9 +561,9 @@ var Lowerer = class {
476
561
  if (name === void 0) return void 0;
477
562
  const info = this.lazyArguments(node.arguments);
478
563
  for (let i = lowerings.length - 1; i >= 0; i--) {
479
- const plugin2 = lowerings[i].plugin;
480
- if (!plugin2.globalCall) continue;
481
- const answer = plugin2.globalCall({
564
+ const plugin3 = lowerings[i].plugin;
565
+ if (!plugin3.globalCall) continue;
566
+ const answer = plugin3.globalCall({
482
567
  name,
483
568
  at: this.callSite(node.callee),
484
569
  get arguments() {
@@ -530,6 +615,31 @@ var Lowerer = class {
530
615
  const out = [];
531
616
  const runtimes = this.runtimeDefinitions();
532
617
  if (this.helpers.has("concat")) this.helper("array");
618
+ if (this.helpers.has("concat")) this.move();
619
+ const move = this.helpers.get("move");
620
+ if (move) {
621
+ out.push(...(0, import_luau_parser.parse)(`
622
+ local function ${move}(from, first, last, at, to)
623
+ for i = first, last do
624
+ to[at + i - first] = from[i]
625
+ end
626
+ return to
627
+ end`).body.statements);
628
+ }
629
+ const each = this.helpers.get("each");
630
+ if (each) {
631
+ out.push(...(0, import_luau_parser.parse)(`
632
+ local function ${each}(source, ...)
633
+ if ${this.builtin("type").name}(source) == "function" then
634
+ return source, ...
635
+ end
636
+ local meta = ${this.builtin("getmetatable").name}(source)
637
+ if meta ~= nil and meta.__iter ~= nil then
638
+ return meta.__iter(source)
639
+ end
640
+ return ${this.builtin("next").name}, source
641
+ end`).body.statements);
642
+ }
533
643
  const marker = this.helpers.get("array");
534
644
  const typeOf = this.helpers.get("typeof");
535
645
  if (marker || typeOf) {
@@ -568,6 +678,49 @@ local function ${typeOf}(value, known)
568
678
  end`).body.statements);
569
679
  }
570
680
  }
681
+ const instanceOf = this.helpers.get("instanceof");
682
+ if (instanceOf) {
683
+ out.push(...(0, import_luau_parser.parse)(`
684
+ local function ${instanceOf}(value, class)
685
+ if ${this.builtin("type").name}(value) ~= "table" then
686
+ return false
687
+ end
688
+ local current = value.ClassObject
689
+ while current ~= nil do
690
+ if current == class then
691
+ return true
692
+ end
693
+ current = current.ParentClass
694
+ end
695
+ return false
696
+ end`).body.statements);
697
+ }
698
+ const accessors = this.helpers.get("accessors");
699
+ if (accessors) {
700
+ out.push(...(0, import_luau_parser.parse)(`
701
+ local function ${accessors}(target, getters, setters)
702
+ return ${this.builtin("setmetatable").name}(target, {
703
+ __index = function(self, key)
704
+ local get = getters[key]
705
+ if get ~= nil then
706
+ return get(self)
707
+ end
708
+ return nil
709
+ end,
710
+ __newindex = function(self, key, value)
711
+ local set = setters[key]
712
+ if set ~= nil then
713
+ set(self, value)
714
+ return
715
+ end
716
+ if getters[key] ~= nil then
717
+ ${this.builtin("error").name}("Cannot assign to '" .. ${this.builtin("tostring").name}(key) .. "': it has a getter and no setter", 2)
718
+ end
719
+ ${this.builtin("rawset").name}(self, key, value)
720
+ end,
721
+ })
722
+ end`).body.statements);
723
+ }
571
724
  const assign2 = this.helpers.get("assign");
572
725
  if (assign2) {
573
726
  out.push(localFunction(assign2, functionBody(["target"], [
@@ -589,7 +742,7 @@ end`).body.statements);
589
742
  local(["result"], [this.array(table([]))]),
590
743
  numericFor("i", number(1), selectCount(this.builtin("select")), [
591
744
  local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
592
- callStatement(call(member(this.builtin("table"), "move"), [
745
+ callStatement(call(this.move(), [
593
746
  identifier("part"),
594
747
  number(1),
595
748
  unary("#", identifier("part")),
@@ -606,8 +759,8 @@ end`).body.statements);
606
759
  runtimeDefinitions() {
607
760
  const out = [];
608
761
  for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
609
- const { plugin: plugin2, from } = this.lowerings[index2];
610
- const source = plugin2.runtime?.[runtime2];
762
+ const { plugin: plugin3, from } = this.lowerings[index2];
763
+ const source = plugin3.runtime?.[runtime2];
611
764
  if (source === void 0) {
612
765
  this.diagnostics.push({
613
766
  message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
@@ -702,6 +855,7 @@ end`).body.statements);
702
855
  names.push("default");
703
856
  break;
704
857
  case "ExportNamedStatement": {
858
+ if (statement.typeOnly) break;
705
859
  if (statement.source) {
706
860
  const key = resolve4(statement.source);
707
861
  if (key === void 0) break;
@@ -785,6 +939,26 @@ end`).body.statements);
785
939
  for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
786
940
  return [...declarations, ...hoisted, ...requires, ...body];
787
941
  }
942
+ /** `import("./x")`: the bundle's `import`, called with the module's name.
943
+ * It answers the module's exports table — what `import * as` names. */
944
+ importExpression(node) {
945
+ const context = this.options.module;
946
+ if (!context) {
947
+ this.report(node, "import() needs a bundle: build the project with @tilua/compiler");
948
+ return nil();
949
+ }
950
+ const source = node.source;
951
+ if (source.type !== "StringLiteral") {
952
+ this.report(source, "import() takes a string literal: which module it loads is decided when the project is built");
953
+ return nil();
954
+ }
955
+ const key = context.resolve(source.value, true);
956
+ if (key === void 0) {
957
+ this.report(source, `Cannot find module '${source.value}'`);
958
+ return nil();
959
+ }
960
+ return call(context.import ?? context.require, [string(key)]);
961
+ }
788
962
  isRewritten(node) {
789
963
  const binding = this.bindingByDeclaration.get(node);
790
964
  return binding !== void 0 && this.rewrites.has(binding.id);
@@ -931,7 +1105,7 @@ end`).body.statements);
931
1105
  case "VariableDeclaration":
932
1106
  return this.variableDeclaration(node, "declare");
933
1107
  case "FunctionDeclaration":
934
- return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: node.attributes }];
1108
+ return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
935
1109
  case "FunctionDeclarationStatement":
936
1110
  return [this.functionDeclarationStatement(node)];
937
1111
  case "ClassDeclaration":
@@ -962,8 +1136,13 @@ end`).body.statements);
962
1136
  // kept — Lua has no expression statement — and it cannot run
963
1137
  // anything, since a call is a `CallStatement` instead, so dropping
964
1138
  // it changes nothing but the noise.
965
- case "ExpressionStatement":
966
- return [];
1139
+ case "ExpressionStatement": {
1140
+ let expression = node.expression;
1141
+ while (expression.type === "ParenthesizedExpression") expression = expression.expression;
1142
+ if (expression.type !== "ImportExpression") return [];
1143
+ const call2 = this.importExpression(expression);
1144
+ return call2.type === "CallExpression" ? [this.from(callStatement(call2), node)] : [];
1145
+ }
967
1146
  case "DoStatement":
968
1147
  return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
969
1148
  case "WhileStatement":
@@ -1015,7 +1194,11 @@ end`).body.statements);
1015
1194
  let source = this.expression(node.iterator);
1016
1195
  if (form.viaIter) source = methodCall(source, "__iter", []);
1017
1196
  const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
1018
- const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
1197
+ let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
1198
+ if (this.lua51 && form.walks === "values") {
1199
+ const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
1200
+ iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
1201
+ }
1019
1202
  return [genericFor(
1020
1203
  variables,
1021
1204
  iterators,
@@ -1268,7 +1451,8 @@ end
1268
1451
  },
1269
1452
  isMethod,
1270
1453
  func,
1271
- attributes: node.attributes,
1454
+ // `@native` and the like are Luau's.
1455
+ attributes: this.lua51 ? void 0 : node.attributes,
1272
1456
  ...spanOf(node)
1273
1457
  };
1274
1458
  }
@@ -1369,7 +1553,7 @@ end
1369
1553
  if (element) bind(element.value, index(source, number(i + 1)), element.default);
1370
1554
  });
1371
1555
  if (pattern.rest) {
1372
- const elements = call(member(this.builtin("table"), "move"), [
1556
+ const elements = call(this.move(), [
1373
1557
  source,
1374
1558
  number(pattern.elements.length + 1),
1375
1559
  unary("#", source),
@@ -1480,8 +1664,15 @@ end
1480
1664
  case "ArrayExpression":
1481
1665
  return this.arrayExpression(node);
1482
1666
  case "BinaryExpression": {
1667
+ if (node.operator === "??") return this.nullish(node);
1483
1668
  const left = this.expression(node.left);
1484
1669
  const right = this.expression(node.right);
1670
+ if (node.operator === "in") {
1671
+ return parenthesized(binary("~=", index(this.keptToOne(right), this.keptToOne(left)), nil()));
1672
+ }
1673
+ if (node.operator === "instanceof") {
1674
+ return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
1675
+ }
1485
1676
  if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
1486
1677
  return binary(node.operator, left, right);
1487
1678
  }
@@ -1508,6 +1699,8 @@ end
1508
1699
  }
1509
1700
  case "ParenthesizedExpression":
1510
1701
  return parenthesized(this.expression(node.expression));
1702
+ case "ImportExpression":
1703
+ return this.importExpression(node);
1511
1704
  // Types only: the value is the expression itself.
1512
1705
  case "TypeAssertionExpression":
1513
1706
  case "SatisfiesExpression":
@@ -1561,7 +1754,7 @@ end
1561
1754
  const last2 = list.length - 1;
1562
1755
  return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1563
1756
  }
1564
- const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1757
+ const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
1565
1758
  const last = list[list.length - 1];
1566
1759
  if (spreads.length === 1 && last.type === "SpreadElement") {
1567
1760
  return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
@@ -1575,11 +1768,21 @@ end
1575
1768
  /** One link of an access chain, read from `object`. */
1576
1769
  link(node, object2) {
1577
1770
  switch (node.type) {
1578
- case "MemberExpression":
1771
+ case "MemberExpression": {
1772
+ if (this.options.types?.lengths.has(node)) return unary("#", object2);
1773
+ const source = this.options.types?.methodReads.get(node);
1774
+ const lowered = source && this.loweredMethod(node.property.name, source, node.object, [], node.property, node);
1775
+ if (lowered) {
1776
+ const forwarded = call(calleePath(lowered.callee), [...lowered.prepend, vararg()]);
1777
+ return functionExpression(functionBody(lowered.passReceiver ? [] : ["_"], [returns([forwarded])], true));
1778
+ }
1579
1779
  return member(object2, node.property.name);
1780
+ }
1580
1781
  case "IndexExpression":
1581
1782
  return index(object2, this.expression(node.index));
1582
1783
  case "CallExpression": {
1784
+ const method = this.methodCalled(node);
1785
+ if (method) return method;
1583
1786
  const lowered = this.loweredGlobalCall(node);
1584
1787
  const args = this.arguments(node.arguments);
1585
1788
  if (!lowered) return call(object2, args);
@@ -1602,6 +1805,24 @@ end
1602
1805
  }
1603
1806
  }
1604
1807
  }
1808
+ /** `names.filter(names, f)`: a method of the value's metatable, read with
1809
+ * `.` and handed its `self` as the first argument. Written as a call of
1810
+ * the function the method really is — `array.filter(names, f)` — where
1811
+ * whoever declared the metatable says so; `undefined` leaves an ordinary
1812
+ * call, which is right for a string's Lua methods (`text.upper(text)`
1813
+ * reaches Lua's own `string.upper`). */
1814
+ methodCalled(node) {
1815
+ const callee = node.callee;
1816
+ if (callee.type !== "MemberExpression") return void 0;
1817
+ const source = this.options.types?.methodReads.get(callee);
1818
+ if (!source) return void 0;
1819
+ const spreads = node.arguments.map((a, i) => a.type === "SpreadElement" ? i : -1).filter((i) => i >= 0);
1820
+ if (!node.arguments.length || spreads.includes(0) || spreads.some((i) => i !== node.arguments.length - 1)) return void 0;
1821
+ const lowered = this.loweredMethod(callee.property.name, source, callee.object, node.arguments.slice(1), callee.property, node);
1822
+ if (!lowered) return void 0;
1823
+ const [self, ...rest] = this.arguments(node.arguments);
1824
+ return call(calleePath(lowered.callee), [...lowered.prepend, ...lowered.passReceiver ? [self] : [], ...rest]);
1825
+ }
1605
1826
  // --------------------------------------------------------
1606
1827
  // Optional chains
1607
1828
  // --------------------------------------------------------
@@ -1647,7 +1868,7 @@ end
1647
1868
  const segments = this.segments(chain);
1648
1869
  const base = this.expression(chain.base);
1649
1870
  const last = chain.links[chain.links.length - 1];
1650
- if (base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
1871
+ if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
1651
1872
  return {
1652
1873
  type: "IfElseExpression",
1653
1874
  clauses: [{ condition: binary("==", base, nil()), body: nil() }],
@@ -1668,6 +1889,32 @@ end
1668
1889
  const func = functionExpression(functionBody([], statements, passesVarargs));
1669
1890
  return call(parenthesized(func), passesVarargs ? [vararg()] : []);
1670
1891
  }
1892
+ /** `a ?? b`: `a`, or `b` where `a` is nil — read only then. A `false`
1893
+ * stays, which is what sets it apart from `a or b`. A name is tested
1894
+ * in place; anything else is read once, into a local. */
1895
+ nullish(node) {
1896
+ const left = this.keptToOne(this.expression(node.left));
1897
+ const right = this.keptToOne(this.expression(node.right));
1898
+ if (!this.lua51 && left.type === "Identifier") {
1899
+ return {
1900
+ type: "IfElseExpression",
1901
+ clauses: [{ condition: binary("~=", left, nil()), body: left }],
1902
+ alternate: right,
1903
+ line: left.line,
1904
+ column: left.column
1905
+ };
1906
+ }
1907
+ const ref = this.names.fresh("value");
1908
+ const at = identifier(ref);
1909
+ const statements = [
1910
+ local([ref], [left]),
1911
+ ifThen(binary("~=", at, nil()), [returns([at])]),
1912
+ returns([right])
1913
+ ];
1914
+ const passesVarargs = statements.some(usesVararg);
1915
+ const func = functionExpression(functionBody([], statements, passesVarargs));
1916
+ return call(parenthesized(func), passesVarargs ? [vararg()] : []);
1917
+ }
1671
1918
  /** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
1672
1919
  interpolatedString(node) {
1673
1920
  let format = "";
@@ -1749,12 +1996,12 @@ end
1749
1996
  }));
1750
1997
  const alternate = this.expression(node.alternate);
1751
1998
  if (clauses.every((c) => alwaysTruthy(c.body))) {
1752
- let result = alternate;
1999
+ let result2 = alternate;
1753
2000
  for (let i = clauses.length - 1; i >= 0; i--) {
1754
2001
  const { condition, body } = clauses[i];
1755
- result = binary("or", binary("and", condition, body), result);
2002
+ result2 = binary("or", binary("and", condition, body), result2);
1756
2003
  }
1757
- return parenthesized(result);
2004
+ return parenthesized(result2);
1758
2005
  }
1759
2006
  const statement = {
1760
2007
  type: "IfStatement",
@@ -1811,11 +2058,20 @@ end
1811
2058
  current = [];
1812
2059
  parts.push(this.expression(f.argument));
1813
2060
  break;
2061
+ case "TableFieldAccessor":
2062
+ break;
1814
2063
  }
1815
2064
  }
1816
- if (parts.length === 0) return table(current);
1817
- if (current.length) parts.push(table(current));
1818
- return call(this.helper("assign"), [table([]), ...parts]);
2065
+ let made;
2066
+ if (parts.length === 0) made = table(current);
2067
+ else {
2068
+ if (current.length) parts.push(table(current));
2069
+ made = call(this.helper("assign"), [table([]), ...parts]);
2070
+ }
2071
+ const accessors = node.fields.filter((f) => f.type === "TableFieldAccessor");
2072
+ if (!accessors.length) return made;
2073
+ const table2 = (kind) => table(accessors.filter((f) => f.kind === kind).map((f) => field(f.key.type === "Identifier" ? f.key.name : f.key.value, functionExpression(this.classFunctionBody(f.func)))));
2074
+ return call(this.helper("accessors"), [made, table2("get"), table2("set")]);
1819
2075
  }
1820
2076
  /** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
1821
2077
  * runs of plain elements and the spread arrays are joined by `concat`. */
@@ -2072,7 +2328,7 @@ var CLASS_METAMETHODS = [
2072
2328
  ];
2073
2329
 
2074
2330
  // src/linemap.ts
2075
- var import_parser5 = require("@tilua/parser");
2331
+ var import_parser6 = require("@tilua/parser");
2076
2332
  function statementsOf(node) {
2077
2333
  const out = [];
2078
2334
  const visit = (value) => {
@@ -2105,7 +2361,7 @@ function buildLineMap(emitted, printed, origins) {
2105
2361
  }
2106
2362
  function lineMapSource(map) {
2107
2363
  const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
2108
- const quoted = (s) => (0, import_parser5.luauString)(s.replace(/\\/g, "/"));
2364
+ const quoted = (s) => (0, import_parser6.luauString)(s.replace(/\\/g, "/"));
2109
2365
  return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
2110
2366
  }
2111
2367
 
@@ -2117,15 +2373,15 @@ async function loadLowerings(modules) {
2117
2373
  for (const module2 of modules) {
2118
2374
  try {
2119
2375
  const loaded = await import((0, import_node_url.pathToFileURL)(module2.file).href);
2120
- const plugin2 = loaded.default;
2121
- if (!plugin2 || typeof plugin2 !== "object") {
2376
+ const plugin3 = loaded.default;
2377
+ if (!plugin3 || typeof plugin3 !== "object") {
2122
2378
  problems.push({
2123
2379
  file: module2.file,
2124
2380
  message: `'${module2.from}' lowering module has no default export`
2125
2381
  });
2126
2382
  continue;
2127
2383
  }
2128
- lowerings.push({ plugin: plugin2, from: module2.from, library: module2.library });
2384
+ lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
2129
2385
  } catch (error) {
2130
2386
  problems.push({
2131
2387
  file: module2.file,
@@ -2152,16 +2408,17 @@ async function bundle(options) {
2152
2408
  const file = queue.shift();
2153
2409
  if (sources.has(file)) continue;
2154
2410
  const parsed = parseFile(file, diagnostics, directives);
2155
- if (!parsed) return { modules: [], diagnostics };
2411
+ if (!parsed) return { files: [], modules: [], diagnostics };
2156
2412
  const { program: program3, text } = parsed;
2157
- const scopes = (0, import_parser6.analyzeScopes)(program3);
2413
+ const scopes = (0, import_parser7.analyzeScopes)(program3, { noLib: config?.noLib });
2158
2414
  for (const d of scopes.diagnostics) {
2159
2415
  diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
2160
2416
  }
2161
2417
  sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
2162
- for (const specifier of importedSpecifiers(program3)) {
2163
- const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
2164
- if (target && !target.endsWith(".d.tilua")) queue.push(target);
2418
+ const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
2419
+ for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
2420
+ const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config);
2421
+ if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
2165
2422
  }
2166
2423
  }
2167
2424
  const analysis = analyzeModules([...sources.values()], config);
@@ -2173,6 +2430,9 @@ async function bundle(options) {
2173
2430
  const names = Names.from(...[...sources.values()].map((s) => s.program));
2174
2431
  const G = names.fresh("G");
2175
2432
  const requireExpression = member(identifier(G), "require");
2433
+ const importExpression = member(identifier(G), "import");
2434
+ const mode = options.output ?? config?.output ?? "single";
2435
+ const target = options.target ?? config?.target ?? "luau";
2176
2436
  const modules = /* @__PURE__ */ new Map();
2177
2437
  const origins = /* @__PURE__ */ new WeakMap();
2178
2438
  const pending = [entry];
@@ -2181,104 +2441,228 @@ async function bundle(options) {
2181
2441
  if (modules.has(file)) continue;
2182
2442
  const source = sources.get(file);
2183
2443
  const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
2184
- const lowered = lower(source.program, source.scopes, {
2444
+ const imports = /* @__PURE__ */ new Set();
2445
+ const dynamicImports = /* @__PURE__ */ new Set();
2446
+ const lowered2 = lower(source.program, source.scopes, {
2185
2447
  names,
2186
2448
  types: analysis.types.get(file),
2187
2449
  lowerings,
2188
- target: options.target ?? config?.target,
2450
+ target,
2451
+ noLib: config?.noLib,
2189
2452
  source: source.text,
2190
2453
  file: shown,
2191
2454
  module: {
2192
2455
  name: source.name,
2193
2456
  require: requireExpression,
2194
- lines: `${G}.lines`,
2457
+ import: importExpression,
2458
+ // One file has one line map. Several have one each, and a
2459
+ // module reads the one of the file it is in.
2460
+ lines: mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
2195
2461
  fail: `${G}.fail`,
2196
2462
  arrays: `${G}.arrays`,
2197
- resolve: (specifier) => {
2198
- const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
2199
- if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
2200
- pending.push(target);
2201
- return sources.get(target).name;
2463
+ resolve: (specifier, dynamic) => {
2464
+ const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
2465
+ if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
2466
+ pending.push(found);
2467
+ const name = sources.get(found).name;
2468
+ (dynamic ? dynamicImports : imports).add(name);
2469
+ return name;
2202
2470
  }
2203
2471
  }
2204
2472
  });
2205
- for (const d of lowered.diagnostics) diagnostics.push({ file, ...d, category: "module" });
2206
- for (const statement of allStatements(lowered.statements)) {
2207
- const line = lowered.origins.get(statement);
2473
+ for (const d of lowered2.diagnostics) diagnostics.push({ file, ...d, category: "module" });
2474
+ for (const statement of allStatements(lowered2.statements)) {
2475
+ const line = lowered2.origins.get(statement);
2208
2476
  if (line !== void 0) origins.set(statement, { file: shown, line });
2209
2477
  }
2210
2478
  modules.set(file, {
2211
2479
  name: source.name,
2212
- statements: lowered.statements,
2213
- exportsName: lowered.exportsName,
2214
- info: lowered.module,
2215
- usesScriptArgs: lowered.usesScriptArgs
2480
+ statements: lowered2.statements,
2481
+ exportsName: lowered2.exportsName,
2482
+ info: lowered2.module,
2483
+ usesScriptArgs: lowered2.usesScriptArgs,
2484
+ imports,
2485
+ dynamicImports
2216
2486
  });
2217
2487
  }
2218
2488
  for (const [file, fileDirectives] of directives) {
2219
2489
  const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
2220
- const { kept, unusedExpectErrors } = (0, import_parser6.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2490
+ const { kept, unusedExpectErrors } = (0, import_parser7.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2221
2491
  const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
2222
2492
  const remaining = diagnostics.filter((d) => !suppressed.has(d));
2223
2493
  diagnostics.length = 0;
2224
2494
  diagnostics.push(...remaining);
2225
2495
  if (options.typeCheck === false) continue;
2226
2496
  for (const d of unusedExpectErrors) {
2227
- diagnostics.push({ file, message: import_parser6.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2497
+ diagnostics.push({ file, message: import_parser7.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2228
2498
  }
2229
2499
  }
2230
2500
  const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
2231
2501
  const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
2232
2502
  diagnostics.length = 0;
2233
2503
  diagnostics.push(...reported);
2234
- const moduleNames = [...modules.values()].map((m) => m.name);
2504
+ const lowered = [...modules.values()];
2505
+ const moduleNames = lowered.map((m) => m.name);
2235
2506
  if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
2236
- return { modules: moduleNames, diagnostics };
2237
- }
2238
- const program2 = (0, import_luau_parser2.parse)(runtime(G));
2507
+ return { files: [], modules: moduleNames, diagnostics };
2508
+ }
2509
+ const several = mode !== "single";
2510
+ const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
2511
+ const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
2512
+ const elsewhere = /* @__PURE__ */ new Map();
2513
+ for (const file of layout.slice(1)) {
2514
+ for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
2515
+ }
2516
+ const program2 = (0, import_luau_parser2.parse)(runtime(G, {
2517
+ several,
2518
+ target,
2519
+ files: elsewhere,
2520
+ imports: lowered.some((m) => m.dynamicImports.size > 0)
2521
+ }));
2239
2522
  const assignment = program2.body.statements[1];
2240
- if ([...modules.values()].some((m) => m.usesScriptArgs)) {
2523
+ if (usesScriptArgs) {
2241
2524
  program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
2242
2525
  program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
2526
+ if (several) program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.scriptArgs = scriptArgs`).body.statements);
2243
2527
  }
2244
2528
  const modulesTable = assignment.type === "AssignmentStatement" && assignment.values[0].type === "TableExpression" ? assignment.values[0].fields.find((f) => f.type === "TableFieldNamed" && f.name.name === "modules")?.value : void 0;
2245
2529
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
2246
- for (const module2 of modules.values()) {
2247
- const { names: exported, links, stars } = module2.info;
2248
- const fields = [];
2249
- if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
2250
- if (links.length) {
2251
- fields.push(field("links", table(links.map((link) => field(link.name, table([
2252
- { type: "TableFieldPositional", value: string(link.module) },
2253
- ...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
2254
- ]))))));
2255
- }
2256
- if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
2257
- fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
2258
- modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
2530
+ for (const module2 of layout[0].modules) {
2531
+ modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
2259
2532
  }
2260
2533
  const ok = names.fresh("ok");
2261
- const result = names.fresh("result");
2534
+ const result2 = names.fresh("result");
2262
2535
  const entryStatements = (0, import_luau_parser2.parse)(`
2263
- local ${ok}, ${result} = xpcall(function()
2264
- return ${G}.require(${(0, import_parser6.luauString)(sources.get(entry).name)})
2536
+ local ${ok}, ${result2} = xpcall(function()
2537
+ return ${G}.require(${(0, import_parser7.luauString)(sources.get(entry).name)})
2265
2538
  end, ${G}.fail)
2266
2539
  if not ${ok} then
2267
- error(${result}, 0)
2540
+ error(${result2}, 0)
2268
2541
  end
2269
- ${modules.get(entry).info.exports ? `return ${result}` : ""}
2542
+ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
2270
2543
  `).body.statements;
2271
2544
  program2.body.statements.push(...entryStatements);
2272
2545
  const map = buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins);
2273
- if (map.size) {
2274
- program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, assign(
2546
+ if (map.size || several) {
2547
+ const mapStatements = [assign(
2275
2548
  [member(identifier(G), "lines")],
2276
2549
  [(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
2277
- ));
2550
+ )];
2551
+ if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
2552
+ program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
2553
+ }
2554
+ const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
2555
+ for (const file of layout.slice(1)) {
2556
+ const body = [];
2557
+ if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
2558
+ for (const module2 of file.modules) {
2559
+ body.push(assign(
2560
+ [index(member(identifier(G), "modules"), string(module2.name))],
2561
+ [moduleTable(module2)]
2562
+ ));
2563
+ }
2564
+ const chunk = program([returns([functionExpression(functionBody([G], body))])]);
2565
+ const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
2566
+ body.push(...mapRegistration(G, file, lineMapSource(lines)));
2567
+ files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
2568
+ }
2569
+ return { code: files[0].code, files, modules: moduleNames, diagnostics };
2570
+ }
2571
+ function moduleTable(module2) {
2572
+ const { names: exported, links, stars } = module2.info;
2573
+ const fields = [];
2574
+ if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
2575
+ if (links.length) {
2576
+ fields.push(field("links", table(links.map((link) => field(link.name, table([
2577
+ { type: "TableFieldPositional", value: string(link.module) },
2578
+ ...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
2579
+ ]))))));
2580
+ }
2581
+ if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
2582
+ fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
2583
+ return table(fields);
2584
+ }
2585
+ function layoutOf(mode, entry, modules, outFile) {
2586
+ const extension = outFile !== void 0 && (0, import_node_path2.extname)(outFile) || ".luau";
2587
+ const entryStem = outFile !== void 0 ? (0, import_node_path2.basename)(outFile, (0, import_node_path2.extname)(outFile)) : import_node_path2.posix.basename(entry.name);
2588
+ const others = modules.filter((m) => m !== entry);
2589
+ if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry, ...others] }];
2590
+ if (mode === "modules") {
2591
+ const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
2592
+ return [entry, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
2593
+ }
2594
+ const byName = new Map(modules.map((m) => [m.name, m]));
2595
+ const reach = (root) => {
2596
+ const seen = /* @__PURE__ */ new Set();
2597
+ const stack = [root];
2598
+ while (stack.length) {
2599
+ const name = stack.pop();
2600
+ if (seen.has(name)) continue;
2601
+ seen.add(name);
2602
+ stack.push(...byName.get(name)?.imports ?? []);
2603
+ }
2604
+ return seen;
2605
+ };
2606
+ const inEntry = reach(entry.name);
2607
+ const roots = [...new Set(modules.flatMap((m) => [...m.dynamicImports]))].filter((root) => !inEntry.has(root));
2608
+ const reached = new Map(roots.map((root) => [root, reach(root)]));
2609
+ const groups = /* @__PURE__ */ new Map();
2610
+ for (const module2 of others) {
2611
+ if (inEntry.has(module2.name)) continue;
2612
+ const by = roots.filter((root) => reached.get(root).has(module2.name));
2613
+ const key = by.join("\n");
2614
+ if (!groups.has(key)) groups.set(key, { roots: by, modules: [] });
2615
+ groups.get(key).modules.push(module2);
2616
+ }
2617
+ const files = [{ path: `${entryStem}${extension}`, modules: modules.filter((m) => inEntry.has(m.name)) }];
2618
+ const taken = /* @__PURE__ */ new Set([entryStem]);
2619
+ for (const group of groups.values()) {
2620
+ const label = group.roots.length === 1 ? group.roots[0].replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "chunk" : "shared";
2621
+ let stem = `${entryStem}_${label}`;
2622
+ for (let n = 2; taken.has(stem); n++) stem = `${entryStem}_${label}_${n}`;
2623
+ taken.add(stem);
2624
+ files.push({ path: `${stem}${extension}`, modules: group.modules });
2625
+ }
2626
+ return files;
2627
+ }
2628
+ function loadPath(entryPath, file, target) {
2629
+ const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
2630
+ if (target === "lua51") return path;
2631
+ const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
2632
+ return bare.startsWith("../") ? bare : `./${bare}`;
2633
+ }
2634
+ function mapRegistration(G, file, map) {
2635
+ const extension = import_node_path2.posix.extname(file.path);
2636
+ const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
2637
+ const entry = `${G}.sources[#${G}.sources + 1]`;
2638
+ return (0, import_luau_parser2.parse)([
2639
+ `${entry} = { { ${parts.map(import_parser7.luauString).join(", ")} }, ${map}, ${(0, import_parser7.luauString)(extension.slice(1))} }`,
2640
+ ...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
2641
+ ].join("\n")).body.statements;
2642
+ }
2643
+ function loader(G, target) {
2644
+ if (target === "lua51") {
2645
+ return `
2646
+ -- From beside this file, wherever that is: its own path says where.
2647
+ local folder = ""
2648
+ if debug ~= nil and debug.getinfo ~= nil then
2649
+ local at = debug.getinfo(${G}.load, "S").source
2650
+ if at:sub(1, 1) == "@" then
2651
+ folder = at:match("^@(.*[/\\\\])") or ""
2652
+ end
2653
+ end
2654
+ local chunk, problem = loadfile(folder .. file)
2655
+ if chunk == nil then
2656
+ error(problem, 0)
2657
+ end
2658
+ chunk()(${G})`;
2278
2659
  }
2279
- return { code: (0, import_luau_parser2.print)(program2) + "\n", modules: moduleNames, diagnostics };
2660
+ return `
2661
+ require(file)(${G})`;
2280
2662
  }
2281
- function runtime(G) {
2663
+ function runtime(G, options) {
2664
+ const { several } = options;
2665
+ const files = [...options.files].map(([name, file]) => `[${(0, import_parser7.luauString)(name)}] = ${(0, import_parser7.luauString)(file)}`).join(", ");
2282
2666
  return `
2283
2667
  local ${G}
2284
2668
  ${G} = {
@@ -2326,7 +2710,7 @@ ${G} = {
2326
2710
  return message
2327
2711
  end
2328
2712
  local lines = ${G}.lines
2329
- if lines == nil then
2713
+ if lines == nil ${several ? `and #${G}.sources == 0 ` : ""}then
2330
2714
  return message .. (trace or "")
2331
2715
  end
2332
2716
  -- The bundle's name, as a position in a message spells it: where this
@@ -2346,6 +2730,81 @@ ${G} = {
2346
2730
  chunk = info.short_src
2347
2731
  end
2348
2732
  end
2733
+ ${several ? `
2734
+ -- A position in this file is read by this file's map. One in another
2735
+ -- of the bundle's files is read by that file's: the one whose name
2736
+ -- the position ends with, by the most folders where several do. A
2737
+ -- message says nothing about which file it starts in, so nothing is
2738
+ -- guessed from it here.
2739
+ local prefix = nil
2740
+ if chunk ~= nil then
2741
+ prefix = chunk:gsub("%p", "%%%0")
2742
+ end
2743
+ local function place(line)
2744
+ local origin = lines ~= nil and lines[tonumber(line)] or nil
2745
+ if origin == nil then
2746
+ return nil
2747
+ end
2748
+ return origin[1] .. ":" .. tostring(origin[2])
2749
+ end
2750
+ local function elsewhere(at, line)
2751
+ -- Already a place in the project.
2752
+ if at:sub(-6) == ".tilua" then
2753
+ return nil
2754
+ end
2755
+ local parts = {}
2756
+ for part in at:gmatch("[^/\\\\%.]+") do
2757
+ parts[#parts + 1] = part
2758
+ end
2759
+ local map, most, tied = nil, 0, false
2760
+ for _, source in ipairs(${G}.sources) do
2761
+ local names = source[1]
2762
+ local last = #parts
2763
+ if parts[last] == source[3] then
2764
+ last = last - 1
2765
+ end
2766
+ local same = 0
2767
+ while same < #names and same < last and names[#names - same] == parts[last - same] do
2768
+ same = same + 1
2769
+ end
2770
+ if same > most then
2771
+ map, most, tied = source[2], same, false
2772
+ elseif same == most then
2773
+ tied = true
2774
+ end
2775
+ end
2776
+ -- Two files it could as well be: a wrong place is worse than none.
2777
+ local origin = map ~= nil and not tied and map[tonumber(line)] or nil
2778
+ if origin == nil then
2779
+ return nil
2780
+ end
2781
+ return origin[1] .. ":" .. tostring(origin[2])
2782
+ end
2783
+ local mapped = message
2784
+ if prefix ~= nil then
2785
+ mapped = mapped:gsub(prefix .. ":(%d+)", function(line)
2786
+ return place(line)
2787
+ end)
2788
+ end
2789
+ mapped = mapped:gsub("(%S+):(%d+)", elsewhere)
2790
+ local frames = {}
2791
+ for frame in (trace or ""):gmatch("[^\\n]+") do
2792
+ local at = nil
2793
+ if prefix ~= nil then
2794
+ local line = frame:match(prefix .. ":(%d+)")
2795
+ if line ~= nil then
2796
+ at = place(line)
2797
+ end
2798
+ end
2799
+ if at == nil then
2800
+ for source, line in frame:gmatch("(%S+):(%d+)") do
2801
+ at = elsewhere(source, line)
2802
+ if at ~= nil then
2803
+ break
2804
+ end
2805
+ end
2806
+ end
2807
+ if at ~= nil then` : `
2349
2808
  if chunk == nil then
2350
2809
  chunk = message:match("^(.-):%d+:")
2351
2810
  end
@@ -2367,8 +2826,9 @@ ${G} = {
2367
2826
  for frame in (trace or ""):gmatch("[^\\n]+") do
2368
2827
  local line = frame:match(prefix .. ":(%d+)")
2369
2828
  local at = line ~= nil and place(line) or nil
2370
- if at ~= nil then
2371
- local name = frame:match("function ([%w_.:]+)")
2829
+ if at ~= nil then`}
2830
+ -- Luau writes \`function name\`; Lua 5.1, \`function 'name'\`.
2831
+ local name = frame:match("function ([%w_.:]+)") or frame:match("function '([^']+)'")
2372
2832
  frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
2373
2833
  end
2374
2834
  end
@@ -2377,13 +2837,36 @@ ${G} = {
2377
2837
  end
2378
2838
  return mapped .. "\\n" .. table.concat(frames, "\\n")
2379
2839
  end,
2380
- records = {},
2840
+ records = {},${several ? `
2841
+ -- The file each module outside this one is in, as \`load\` is handed it.
2842
+ files = { ${files} },
2843
+ loaded = {},
2844
+ -- Every file's line map: by the module, and by the file's name.
2845
+ maps = {},
2846
+ sources = {},
2847
+ -- Another of the bundle's files: a function that adds its modules here.
2848
+ load = function(file)
2849
+ if ${G}.loaded[file] then
2850
+ return
2851
+ end
2852
+ ${G}.loaded[file] = true${loader(G, options.target)}
2853
+ end,` : ""}${options.imports ? `
2854
+ -- \`import("./x")\`: the module's exports, asked for where the code runs
2855
+ -- rather than before its module does. \`require\` does the rest \u2014 and
2856
+ -- loads the file the module is in, if it is not this one.
2857
+ import = function(name)
2858
+ return ${G}.require(name)
2859
+ end,` : ""}
2381
2860
  require = function(name)
2382
2861
  local record = ${G}.records[name]
2383
2862
  if record ~= nil then
2384
2863
  return record.exports
2385
2864
  end
2386
- local module = ${G}.modules[name]
2865
+ local module = ${G}.modules[name]${several ? `
2866
+ if module == nil then
2867
+ ${G}.load(${G}.files[name])
2868
+ module = ${G}.modules[name]
2869
+ end` : ""}
2387
2870
  local initialized = {}
2388
2871
  local exports = setmetatable({}, {
2389
2872
  __index = function(_, key)
@@ -2402,7 +2885,7 @@ ${G} = {
2402
2885
  return source[link[2]]
2403
2886
  end
2404
2887
  if module.stars ~= nil and key ~= "default" then
2405
- for _, star in module.stars do
2888
+ for _, star in ipairs(module.stars) do
2406
2889
  local value = ${G}.require(star)[key]
2407
2890
  if value ~= nil then
2408
2891
  return value
@@ -2427,7 +2910,7 @@ ${G} = {
2427
2910
  }
2428
2911
  function stampOf(file) {
2429
2912
  try {
2430
- const at = (0, import_node_fs2.statSync)(file);
2913
+ const at = (0, import_node_fs3.statSync)(file);
2431
2914
  return `${at.size}:${at.mtimeMs}`;
2432
2915
  } catch {
2433
2916
  return void 0;
@@ -2443,19 +2926,19 @@ function parseFile(file, diagnostics, directives) {
2443
2926
  }
2444
2927
  let text;
2445
2928
  try {
2446
- text = (0, import_node_fs2.readFileSync)(file, "utf8");
2929
+ text = (0, import_node_fs3.readFileSync)(file, "utf8");
2447
2930
  } catch {
2448
2931
  diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
2449
2932
  return void 0;
2450
2933
  }
2451
2934
  try {
2452
- const program2 = (0, import_parser6.parse)(text);
2453
- const found = (0, import_parser6.directivesOf)(text);
2935
+ const program2 = (0, import_parser7.parse)(text);
2936
+ const found = (0, import_parser7.directivesOf)(text);
2454
2937
  directives.set(file, found);
2455
2938
  if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
2456
2939
  return { program: program2, text };
2457
2940
  } catch (error) {
2458
- if (error instanceof import_parser6.ParseError || error instanceof import_parser6.LexError) {
2941
+ if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
2459
2942
  const { line, column } = error;
2460
2943
  diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
2461
2944
  return void 0;
@@ -2470,14 +2953,14 @@ var libraryCache = /* @__PURE__ */ new Map();
2470
2953
  function parsedLibrary(file) {
2471
2954
  let stamp;
2472
2955
  try {
2473
- const at = (0, import_node_fs2.statSync)(file);
2956
+ const at = (0, import_node_fs3.statSync)(file);
2474
2957
  stamp = `${at.size}:${at.mtimeMs}`;
2475
2958
  } catch {
2476
2959
  stamp = "";
2477
2960
  }
2478
2961
  const cached = libraryCache.get(file);
2479
2962
  if (cached && cached.stamp === stamp) return cached.program;
2480
- const program2 = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(file, "utf8"));
2963
+ const program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(file, "utf8"));
2481
2964
  libraryCache.set(file, { stamp, program: program2 });
2482
2965
  return program2;
2483
2966
  }
@@ -2485,9 +2968,11 @@ var moduleAnalyses = /* @__PURE__ */ new Map();
2485
2968
  function analyzeModules(modules, config) {
2486
2969
  const out = [];
2487
2970
  const analyses = /* @__PURE__ */ new Map();
2488
- const libraries = config ? (0, import_parser6.resolveTypeLibraries)(config) : { files: [], lowerings: [], problems: [] };
2971
+ const libraries = config ? (0, import_parser7.resolveTypeLibraries)(config) : { files: [], lowerings: [], problems: [] };
2489
2972
  for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
2490
2973
  const libs = libraries.files.map(parsedLibrary);
2974
+ const noLib = config?.noLib ?? false;
2975
+ const checked = libs.length > 0 || !noLib;
2491
2976
  const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
2492
2977
  const byFile = new Map(modules.map((m) => [m.file, m]));
2493
2978
  const exportsCache = /* @__PURE__ */ new Map();
@@ -2500,49 +2985,50 @@ function analyzeModules(modules, config) {
2500
2985
  const done = analyzed.get(file);
2501
2986
  if (done) return done;
2502
2987
  const remembered = moduleAnalyses.get(file);
2503
- if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([read2, stamp]) => stampOf(read2) === stamp)) {
2988
+ if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([read3, stamp]) => stampOf(read3) === stamp)) {
2504
2989
  analyzed.set(file, remembered.result);
2505
2990
  readWhile.set(file, remembered.read);
2506
2991
  return remembered.result;
2507
2992
  }
2508
- const scopes = (0, import_parser6.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2993
+ const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: checked, noLib });
2509
2994
  const said = [];
2510
2995
  for (const d of scopes.diagnostics) {
2511
2996
  if (d.kind !== "undeclared") continue;
2512
2997
  said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
2513
2998
  }
2514
2999
  const before = partials;
2515
- const types = (0, import_parser6.analyzeTypes)(program2, scopes, {
3000
+ const types = (0, import_parser7.analyzeTypes)(program2, scopes, {
2516
3001
  libs,
2517
3002
  resolveModule: resolverFor(file),
2518
- reportUnknownTypes: libs.length > 0
3003
+ noLib,
3004
+ reportUnknownTypes: checked
2519
3005
  });
2520
3006
  for (const d of types.diagnostics) {
2521
3007
  const at = d.node;
2522
3008
  said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
2523
3009
  }
2524
- const result = { types, diagnostics: said };
3010
+ const result2 = { types, diagnostics: said };
2525
3011
  if (partials === before) {
2526
- analyzed.set(file, result);
2527
- const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
3012
+ analyzed.set(file, result2);
3013
+ const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2528
3014
  const own = stampOf(file);
2529
- if (own !== void 0) read2.set(file, own);
2530
- readWhile.set(file, read2);
2531
- if ([...read2.values()].every((stamp) => stamp !== void 0)) {
2532
- moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read2, result });
3015
+ if (own !== void 0) read3.set(file, own);
3016
+ readWhile.set(file, read3);
3017
+ if ([...read3.values()].every((stamp) => stamp !== void 0)) {
3018
+ moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read3, result: result2 });
2533
3019
  }
2534
3020
  }
2535
- return result;
3021
+ return result2;
2536
3022
  };
2537
3023
  const resolverFor = (file) => (specifier) => {
2538
- const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
3024
+ const target = (0, import_parser7.resolveModulePath)(file, specifier, config);
2539
3025
  if (!target) return void 0;
2540
3026
  const noteRead = () => {
2541
- const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2542
- readWhile.set(file, read2);
3027
+ const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
3028
+ readWhile.set(file, read3);
2543
3029
  const stamp = stampOf(target);
2544
- if (stamp !== void 0) read2.set(target, stamp);
2545
- for (const [below, belowStamp] of readWhile.get(target) ?? []) read2.set(below, belowStamp);
3030
+ if (stamp !== void 0) read3.set(target, stamp);
3031
+ for (const [below, belowStamp] of readWhile.get(target) ?? []) read3.set(below, belowStamp);
2546
3032
  };
2547
3033
  if (inProgress.has(target)) {
2548
3034
  partials++;
@@ -2556,16 +3042,16 @@ function analyzeModules(modules, config) {
2556
3042
  let program2 = byFile.get(target)?.program;
2557
3043
  if (!program2) {
2558
3044
  try {
2559
- program2 = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(target, "utf8"));
3045
+ program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(target, "utf8"));
2560
3046
  } catch {
2561
3047
  return void 0;
2562
3048
  }
2563
3049
  }
2564
3050
  inProgress.add(target);
2565
3051
  try {
2566
- const scopes = (0, import_parser6.analyzeScopes)(program2, { builtinGlobals: globals });
2567
- const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser6.analyzeTypes)(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2568
- const exports2 = (0, import_parser6.moduleExports)(program2, scopes, types, resolverFor(target));
3052
+ const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, noLib });
3053
+ const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser7.analyzeTypes)(program2, scopes, { libs, noLib, resolveModule: resolverFor(target), diagnostics: false });
3054
+ const exports2 = (0, import_parser7.moduleExports)(program2, scopes, types, resolverFor(target));
2569
3055
  exportsCache.set(target, exports2);
2570
3056
  noteRead();
2571
3057
  return exports2;
@@ -2598,37 +3084,39 @@ function allStatements(statements) {
2598
3084
  }
2599
3085
 
2600
3086
  // src/compile.ts
2601
- var import_node_fs3 = require("fs");
3087
+ var import_node_fs4 = require("fs");
2602
3088
  var import_node_path3 = require("path");
2603
- var import_parser7 = require("@tilua/parser");
3089
+ var import_parser8 = require("@tilua/parser");
2604
3090
  var import_luau_parser3 = require("luau-parser");
2605
3091
  async function compile(source, config) {
2606
3092
  let program2;
2607
3093
  try {
2608
- program2 = (0, import_parser7.parse)(source);
3094
+ program2 = (0, import_parser8.parse)(source);
2609
3095
  } catch (error) {
2610
- if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
3096
+ if (error instanceof import_parser8.ParseError || error instanceof import_parser8.LexError) {
2611
3097
  const { line, column } = error;
2612
3098
  return { diagnostics: [{ message: error.message, line, column }] };
2613
3099
  }
2614
3100
  throw error;
2615
3101
  }
2616
- const scopes = (0, import_parser7.analyzeScopes)(program2);
3102
+ const { config: resolved, problems } = resolveConfig(config, (0, import_node_path3.resolve)("main.tilua"));
3103
+ const noLib = resolved?.noLib ?? false;
3104
+ const scopes = (0, import_parser8.analyzeScopes)(program2, { noLib });
2617
3105
  const diagnostics = scopes.diagnostics.map((d) => ({
2618
3106
  message: d.message,
2619
3107
  line: d.node.line.start,
2620
3108
  column: d.node.column.start
2621
3109
  }));
2622
- const { config: resolved, problems } = resolveConfig(config, (0, import_node_path3.resolve)("main.tilua"));
2623
- const libraries = resolved ? (0, import_parser7.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
3110
+ const libraries = resolved ? (0, import_parser8.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
2624
3111
  const { lowerings, problems: loweringProblems } = await loadLowerings(libraries.lowerings);
2625
3112
  for (const p of [...problems, ...libraries.problems]) {
2626
3113
  diagnostics.push({ message: p.message, line: p.line ?? 1, column: p.column ?? 1 });
2627
3114
  }
2628
3115
  for (const p of loweringProblems) diagnostics.push({ message: p.message, line: 1, column: 1 });
2629
- const libs = libraries.files.map((file) => (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(file, "utf8")));
3116
+ const libs = libraries.files.map((file) => (0, import_parser8.parse)((0, import_node_fs4.readFileSync)(file, "utf8")));
2630
3117
  const lowered = lower(program2, scopes, {
2631
- types: (0, import_parser7.analyzeTypes)(program2, scopes, { libs, diagnostics: false }),
3118
+ types: (0, import_parser8.analyzeTypes)(program2, scopes, { libs, noLib, diagnostics: false }),
3119
+ noLib,
2632
3120
  lowerings,
2633
3121
  target: resolved?.target,
2634
3122
  source