@tilua/compiler 3.1.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/cli.cjs CHANGED
@@ -6,13 +6,13 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
6
6
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
7
7
 
8
8
  // src/cli.ts
9
- var import_node_fs3 = require("fs");
9
+ var import_node_fs4 = require("fs");
10
10
  var import_node_path3 = require("path");
11
11
 
12
12
  // src/bundle.ts
13
- var import_node_fs2 = require("fs");
13
+ var import_node_fs3 = require("fs");
14
14
  var import_node_path2 = require("path");
15
- var import_parser6 = require("@tilua/parser");
15
+ var import_parser7 = require("@tilua/parser");
16
16
  var import_luau_parser2 = require("luau-parser");
17
17
 
18
18
  // src/config.ts
@@ -27,7 +27,7 @@ function resolveConfig(input, entry2, cwd = process.cwd()) {
27
27
  }
28
28
 
29
29
  // src/lower.ts
30
- var import_parser4 = require("@tilua/parser");
30
+ var import_parser5 = require("@tilua/parser");
31
31
  var import_luau_parser = require("luau-parser");
32
32
 
33
33
  // src/luau.ts
@@ -91,6 +91,7 @@ var functionBody = (params, statements, hasVarargs = false) => ({
91
91
  ...span()
92
92
  });
93
93
  var localFunction = (name, func) => ({ type: "LocalFunctionStatement", name: identifier(name), func, ...span() });
94
+ var program = (statements) => ({ type: "Program", body: block(statements), ...span() });
94
95
 
95
96
  // src/names.ts
96
97
  var Names = class _Names {
@@ -114,7 +115,7 @@ var Names = class _Names {
114
115
  if (key !== "line" && key !== "column") walk(child);
115
116
  }
116
117
  };
117
- for (const program of programs) walk(program);
118
+ for (const program2 of programs) walk(program2);
118
119
  return new _Names(taken);
119
120
  }
120
121
  /** A copy that goes on to take names independently — one per module, each
@@ -188,9 +189,80 @@ var plugin = {
188
189
  };
189
190
  var languageLowering = { plugin, from: "tilua" };
190
191
 
192
+ // src/standard.ts
193
+ var import_node_fs2 = require("fs");
194
+ var import_parser4 = require("@tilua/parser");
195
+ var read2 = (name) => (0, import_node_fs2.readFileSync)(new URL(`../runtime/${name}`, importMetaUrl), "utf8");
196
+ var console2 = read2("console.luau");
197
+ var result = read2("result.luau");
198
+ var place = (at) => (0, import_parser4.luauString)(at.file === void 0 ? `line ${at.line}` : `${at.file}:${at.line}`);
199
+ var tag = (at) => (0, import_parser4.luauString)(`[${at.file === void 0 ? `line ${at.line}` : `${at.file.replace(/\.tilua$/, "")}:${at.line}`}]:`);
200
+ function consoleMeta(args2) {
201
+ const fields = [];
202
+ args2.forEach((argument, index2) => {
203
+ if (argument.spread || argument.typeText === void 0 || !(0, import_parser4.isFunctionType)(argument.type)) return;
204
+ const written = argument.declaration ?? argument.source;
205
+ fields.push(`[${index2 + 1}] = { ${(0, import_parser4.luauString)(argument.typeText)}, ${(0, import_parser4.luauString)(written)} }`);
206
+ });
207
+ return fields.length ? `{ ${fields.join(", ")} }` : "nil";
208
+ }
209
+ var CONSOLE_METHODS = /* @__PURE__ */ new Set(["log", "warn", "error"]);
210
+ var REPLACED = /* @__PURE__ */ new Map([
211
+ ["pcall", "pcall"],
212
+ ["xpcall", "xpcall"],
213
+ ["ypcall", "pcall"],
214
+ ["coroutine.resume", "resume"],
215
+ ["string.find", "find"],
216
+ ["string.match", "match"],
217
+ ["string.gsub", "gsub"],
218
+ ["string.byte", "byte"],
219
+ ["math.modf", "modf"],
220
+ ["math.frexp", "frexp"],
221
+ ["next", "next"],
222
+ ["select", "select"],
223
+ ["unpack", "unpack"],
224
+ ["table.unpack", "unpack"],
225
+ ["loadstring", "loadstring"],
226
+ ["pairs", "pairs"],
227
+ ["ipairs", "ipairs"],
228
+ ["string.gmatch", "gmatch"]
229
+ ]);
230
+ var plugin2 = {
231
+ runtime: { console: console2, result },
232
+ methodCall({ method, receiverGlobal, at, arguments: args2, use }) {
233
+ if (receiverGlobal === "console" && CONSOLE_METHODS.has(method)) {
234
+ return {
235
+ callee: `${use("console")}.${method}`,
236
+ prepend: [tag(at), consoleMeta(args2)],
237
+ passReceiver: false
238
+ };
239
+ }
240
+ return void 0;
241
+ },
242
+ // `print(x)` and `warn(x)` are left exactly as written: the place a line
243
+ // came from is what `console:log` / `console:warn` are for. `error` has to
244
+ // put the place inside the message it raises, so it goes through the
245
+ // runtime.
246
+ globalCall({ name, at, use }) {
247
+ if (name === "error") return { callee: `${use("console")}.raise`, prepend: [place(at)] };
248
+ const replaced = REPLACED.get(name);
249
+ if (replaced) return { callee: `${use("result")}.${replaced}` };
250
+ return void 0;
251
+ },
252
+ // Read as a value there is no call site to write down; the runtime finds
253
+ // one on the stack.
254
+ globalValue({ name, use }) {
255
+ if (name === "error") return `${use("console")}.raiseValue`;
256
+ const replaced = REPLACED.get(name);
257
+ if (replaced) return `${use("result")}.${replaced}`;
258
+ return void 0;
259
+ }
260
+ };
261
+ var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
262
+
191
263
  // src/lower.ts
192
- function lower(program, scopes, options = {}) {
193
- return new Lowerer(program, scopes, options).run();
264
+ function lower(program2, scopes, options = {}) {
265
+ return new Lowerer(program2, scopes, options).run();
194
266
  }
195
267
  var Lowerer = class {
196
268
  constructor(source, scopes, options) {
@@ -226,11 +298,11 @@ var Lowerer = class {
226
298
  origins = /* @__PURE__ */ new WeakMap();
227
299
  run() {
228
300
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
301
+ const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
229
302
  const helpers = this.helperDefinitions();
230
303
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
231
- const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
232
304
  return {
233
- statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
305
+ statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
234
306
  exportsName: this.exportsName,
235
307
  module: this.info,
236
308
  diagnostics: this.diagnostics,
@@ -277,14 +349,43 @@ var Lowerer = class {
277
349
  }
278
350
  return identifier(local2);
279
351
  }
352
+ /** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
353
+ move() {
354
+ return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
355
+ }
280
356
  helper(kind) {
281
357
  let name = this.helpers.get(kind);
282
358
  if (!name) {
283
- name = this.names.fresh(`tilua_${kind}`);
359
+ name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
284
360
  this.helpers.set(kind, name);
285
361
  }
286
362
  return identifier(name);
287
363
  }
364
+ // `typeof v` tells an array from an object, which Luau cannot: both are
365
+ // tables. So every array the program makes — `[a, b]`, a rest parameter,
366
+ // what `:map` answers — is written down as one, in a table that holds its
367
+ // keys weakly, and `typeof` looks there. A table nothing wrote down (one a
368
+ // Lua library made) is told by its type where that says, and by its shape
369
+ // where it does not.
370
+ /** `value`, a new array, written down as one. */
371
+ array(value) {
372
+ const marked = call(this.helper("array"), [value]);
373
+ this.marked.add(marked);
374
+ return marked;
375
+ }
376
+ /** The calls `array` made: each answers the one table it was given. */
377
+ marked = /* @__PURE__ */ new WeakSet();
378
+ /** Luau reaching the record of arrays: the bundle's, or this file's own. */
379
+ arrays() {
380
+ return this.options.module?.arrays ?? this.helper("arrays").name;
381
+ }
382
+ typeofExpression(node) {
383
+ const known = this.options.types?.typeofTables.get(node);
384
+ let argument = node.argument;
385
+ while (argument.type === "ParenthesizedExpression") argument = argument.expression;
386
+ const value = this.expression(argument);
387
+ return call(this.helper("typeof"), known ? [value, string(known)] : [this.keptToOne(value)]);
388
+ }
288
389
  /** The local a library's runtime table gets in this file, emitting it the
289
390
  * first time something asks. Keyed by library and by the key the library
290
391
  * itself chose, so two libraries' tables never collide. */
@@ -352,7 +453,7 @@ var Lowerer = class {
352
453
  const type = this.options.types?.typeOf.get(argument);
353
454
  return {
354
455
  type,
355
- typeText: type ? (0, import_parser4.formatType)(type) : void 0,
456
+ typeText: type ? (0, import_parser5.formatType)(type) : void 0,
356
457
  source: this.writtenSource(argument),
357
458
  declaration: declaration ? this.writtenSource(declaration) : void 0,
358
459
  spread: argument.type === "SpreadElement"
@@ -374,7 +475,11 @@ var Lowerer = class {
374
475
  /** The language's own lowering, then each library's, in load order. A
375
476
  * runtime is named by its lowering's place here (`loweringRuntime`). */
376
477
  get lowerings() {
377
- return this.allLowerings ??= [languageLowering, ...this.options.lowerings ?? []];
478
+ return this.allLowerings ??= [
479
+ languageLowering,
480
+ ...this.options.noLib ? [] : [standardLowering],
481
+ ...this.options.lowerings ?? []
482
+ ];
378
483
  }
379
484
  allLowerings;
380
485
  /** What `receiver:method(...)` becomes, when Luau alone would not run it.
@@ -388,22 +493,28 @@ var Lowerer = class {
388
493
  * (`console:log`), the last library loaded first. No answer leaves an
389
494
  * ordinary Luau method call: `text:upper()` reaches Lua's own. */
390
495
  loweredMethodCall(node) {
496
+ return this.loweredMethod(node.method.name, this.options.types?.methodSources.get(node), node.object, node.arguments, node.method, node);
497
+ }
498
+ /** `loweredMethodCall`, for a method named `method` of what `object`
499
+ * holds — called with `:` on it, or read off it with `.` and handed its
500
+ * `self` like any other argument. `args` are the arguments after the
501
+ * receiver; a read that is not called has none. */
502
+ loweredMethod(method, source, object2, args2, name, node) {
391
503
  const lowerings = this.lowerings;
392
- const source = this.options.types?.methodSources.get(node);
393
504
  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();
394
505
  if (!asked.some((i) => lowerings[i].plugin.methodCall)) return void 0;
395
- const receiver = this.options.types?.typeOf.get(node.object);
396
- const receiverGlobal = this.globalName(node.object);
397
- const info = this.lazyArguments(node.arguments);
506
+ const receiver = this.options.types?.typeOf.get(object2);
507
+ const receiverGlobal = this.globalName(object2);
508
+ const info = this.lazyArguments(args2);
398
509
  for (const i of asked) {
399
- const plugin2 = lowerings[i].plugin;
400
- if (!plugin2.methodCall) continue;
401
- const answer = plugin2.methodCall({
402
- method: node.method.name,
510
+ const plugin3 = lowerings[i].plugin;
511
+ if (!plugin3.methodCall) continue;
512
+ const answer = plugin3.methodCall({
513
+ method,
403
514
  receiver,
404
515
  receiverGlobal,
405
- argumentCount: node.arguments.length,
406
- at: this.callSite(node.method),
516
+ argumentCount: args2.length,
517
+ at: this.callSite(name),
407
518
  get arguments() {
408
519
  return info();
409
520
  },
@@ -427,9 +538,9 @@ var Lowerer = class {
427
538
  if (name === void 0) return void 0;
428
539
  const info = this.lazyArguments(node.arguments);
429
540
  for (let i = lowerings.length - 1; i >= 0; i--) {
430
- const plugin2 = lowerings[i].plugin;
431
- if (!plugin2.globalCall) continue;
432
- const answer = plugin2.globalCall({
541
+ const plugin3 = lowerings[i].plugin;
542
+ if (!plugin3.globalCall) continue;
543
+ const answer = plugin3.globalCall({
433
544
  name,
434
545
  at: this.callSite(node.callee),
435
546
  get arguments() {
@@ -479,6 +590,114 @@ var Lowerer = class {
479
590
  }
480
591
  helperDefinitions() {
481
592
  const out2 = [];
593
+ const runtimes = this.runtimeDefinitions();
594
+ if (this.helpers.has("concat")) this.helper("array");
595
+ if (this.helpers.has("concat")) this.move();
596
+ const move = this.helpers.get("move");
597
+ if (move) {
598
+ out2.push(...(0, import_luau_parser.parse)(`
599
+ local function ${move}(from, first, last, at, to)
600
+ for i = first, last do
601
+ to[at + i - first] = from[i]
602
+ end
603
+ return to
604
+ end`).body.statements);
605
+ }
606
+ const each = this.helpers.get("each");
607
+ if (each) {
608
+ out2.push(...(0, import_luau_parser.parse)(`
609
+ local function ${each}(source, ...)
610
+ if ${this.builtin("type").name}(source) == "function" then
611
+ return source, ...
612
+ end
613
+ local meta = ${this.builtin("getmetatable").name}(source)
614
+ if meta ~= nil and meta.__iter ~= nil then
615
+ return meta.__iter(source)
616
+ end
617
+ return ${this.builtin("next").name}, source
618
+ end`).body.statements);
619
+ }
620
+ const marker = this.helpers.get("array");
621
+ const typeOf = this.helpers.get("typeof");
622
+ if (marker || typeOf) {
623
+ const arrays = this.arrays();
624
+ const own = this.helpers.get("arrays");
625
+ if (own) {
626
+ out2.push(...(0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
627
+ }
628
+ if (marker) {
629
+ out2.push(...(0, import_luau_parser.parse)(`
630
+ local function ${marker}(t)
631
+ ${arrays}[t] = true
632
+ return t
633
+ end`).body.statements);
634
+ }
635
+ if (typeOf) {
636
+ out2.push(...(0, import_luau_parser.parse)(`
637
+ local function ${typeOf}(value, known)
638
+ local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
639
+ if name ~= "table" then
640
+ return name
641
+ end
642
+ if known ~= nil then
643
+ return known
644
+ end
645
+ if ${arrays}[value] then
646
+ return "array"
647
+ end
648
+ if ${this.builtin("getmetatable").name}(value) ~= nil then
649
+ return "object"
650
+ end
651
+ if #value > 0 then
652
+ return "array"
653
+ end
654
+ return "object"
655
+ end`).body.statements);
656
+ }
657
+ }
658
+ const instanceOf = this.helpers.get("instanceof");
659
+ if (instanceOf) {
660
+ out2.push(...(0, import_luau_parser.parse)(`
661
+ local function ${instanceOf}(value, class)
662
+ if ${this.builtin("type").name}(value) ~= "table" then
663
+ return false
664
+ end
665
+ local current = value.ClassObject
666
+ while current ~= nil do
667
+ if current == class then
668
+ return true
669
+ end
670
+ current = current.ParentClass
671
+ end
672
+ return false
673
+ end`).body.statements);
674
+ }
675
+ const accessors = this.helpers.get("accessors");
676
+ if (accessors) {
677
+ out2.push(...(0, import_luau_parser.parse)(`
678
+ local function ${accessors}(target, getters, setters)
679
+ return ${this.builtin("setmetatable").name}(target, {
680
+ __index = function(self, key)
681
+ local get = getters[key]
682
+ if get ~= nil then
683
+ return get(self)
684
+ end
685
+ return nil
686
+ end,
687
+ __newindex = function(self, key, value)
688
+ local set = setters[key]
689
+ if set ~= nil then
690
+ set(self, value)
691
+ return
692
+ end
693
+ if getters[key] ~= nil then
694
+ ${this.builtin("error").name}("Cannot assign to '" .. ${this.builtin("tostring").name}(key) .. "': it has a getter and no setter", 2)
695
+ end
696
+ ${this.builtin("rawset").name}(self, key, value)
697
+ end,
698
+ })
699
+ end`).body.statements);
700
+ }
482
701
  const assign2 = this.helpers.get("assign");
483
702
  if (assign2) {
484
703
  out2.push(localFunction(assign2, functionBody(["target"], [
@@ -493,28 +712,14 @@ var Lowerer = class {
493
712
  returns([identifier("target")])
494
713
  ], true)));
495
714
  }
496
- for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
497
- const { plugin: plugin2, from } = this.lowerings[index2];
498
- const source = plugin2.runtime?.[runtime2];
499
- if (source === void 0) {
500
- this.diagnostics.push({
501
- message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
502
- line: 1,
503
- column: 1
504
- });
505
- continue;
506
- }
507
- const lines = this.options.module?.lines ?? "nil";
508
- const fail = this.options.module?.fail ?? "nil";
509
- out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
510
- }
715
+ out2.push(...runtimes);
511
716
  const concat = this.helpers.get("concat");
512
717
  if (concat) {
513
718
  out2.push(localFunction(concat, functionBody([], [
514
- local(["result"], [table([])]),
719
+ local(["result"], [this.array(table([]))]),
515
720
  numericFor("i", number(1), selectCount(this.builtin("select")), [
516
721
  local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
517
- callStatement(call(member(this.builtin("table"), "move"), [
722
+ callStatement(call(this.move(), [
518
723
  identifier("part"),
519
724
  number(1),
520
725
  unary("#", identifier("part")),
@@ -527,6 +732,26 @@ var Lowerer = class {
527
732
  }
528
733
  return out2;
529
734
  }
735
+ /** The runtime tables the file's libraries were asked for. */
736
+ runtimeDefinitions() {
737
+ const out2 = [];
738
+ for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
739
+ const { plugin: plugin3, from } = this.lowerings[index2];
740
+ const source = plugin3.runtime?.[runtime2];
741
+ if (source === void 0) {
742
+ this.diagnostics.push({
743
+ message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
744
+ line: 1,
745
+ column: 1
746
+ });
747
+ continue;
748
+ }
749
+ const lines = this.options.module?.lines ?? "nil";
750
+ const fail = this.options.module?.fail ?? "nil";
751
+ out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAY__/g, () => this.helper("array").name)).body.statements);
752
+ }
753
+ return out2;
754
+ }
530
755
  // --------------------------------------------------------
531
756
  // Modules
532
757
  // --------------------------------------------------------
@@ -545,7 +770,7 @@ var Lowerer = class {
545
770
  const requires = [];
546
771
  const moduleLocals = /* @__PURE__ */ new Map();
547
772
  const importedFrom = /* @__PURE__ */ new Map();
548
- const resolve3 = (source) => {
773
+ const resolve4 = (source) => {
549
774
  const key = context.resolve(source.value);
550
775
  if (key === void 0) this.report(source, `Cannot find module '${source.value}'`);
551
776
  return key;
@@ -584,7 +809,7 @@ var Lowerer = class {
584
809
  ];
585
810
  const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references.length ?? 0) > 0);
586
811
  if (!used.length) continue;
587
- const key = resolve3(statement.source);
812
+ const key = resolve4(statement.source);
588
813
  if (key === void 0) continue;
589
814
  const local2 = moduleLocal(key);
590
815
  for (const s of used) {
@@ -607,8 +832,9 @@ var Lowerer = class {
607
832
  names.push("default");
608
833
  break;
609
834
  case "ExportNamedStatement": {
835
+ if (statement.typeOnly) break;
610
836
  if (statement.source) {
611
- const key = resolve3(statement.source);
837
+ const key = resolve4(statement.source);
612
838
  if (key === void 0) break;
613
839
  load(key);
614
840
  for (const s of statement.specifiers) links.push({ name: s.exported.name, module: key, imported: s.local.name });
@@ -624,7 +850,7 @@ var Lowerer = class {
624
850
  break;
625
851
  }
626
852
  case "ExportAllStatement": {
627
- const key = resolve3(statement.source);
853
+ const key = resolve4(statement.source);
628
854
  if (key === void 0) break;
629
855
  load(key);
630
856
  stars.push(key);
@@ -690,6 +916,26 @@ var Lowerer = class {
690
916
  for (let i = 0; i < locals.length; i += 100) declarations.push(local(locals.slice(i, i + 100), []));
691
917
  return [...declarations, ...hoisted, ...requires, ...body];
692
918
  }
919
+ /** `import("./x")`: the bundle's `import`, called with the module's name.
920
+ * It answers the module's exports table — what `import * as` names. */
921
+ importExpression(node) {
922
+ const context = this.options.module;
923
+ if (!context) {
924
+ this.report(node, "import() needs a bundle: build the project with @tilua/compiler");
925
+ return nil();
926
+ }
927
+ const source = node.source;
928
+ if (source.type !== "StringLiteral") {
929
+ this.report(source, "import() takes a string literal: which module it loads is decided when the project is built");
930
+ return nil();
931
+ }
932
+ const key = context.resolve(source.value, true);
933
+ if (key === void 0) {
934
+ this.report(source, `Cannot find module '${source.value}'`);
935
+ return nil();
936
+ }
937
+ return call(context.import ?? context.require, [string(key)]);
938
+ }
693
939
  isRewritten(node) {
694
940
  const binding = this.bindingByDeclaration.get(node);
695
941
  return binding !== void 0 && this.rewrites.has(binding.id);
@@ -836,7 +1082,7 @@ var Lowerer = class {
836
1082
  case "VariableDeclaration":
837
1083
  return this.variableDeclaration(node, "declare");
838
1084
  case "FunctionDeclaration":
839
- return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: node.attributes }];
1085
+ return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
840
1086
  case "FunctionDeclarationStatement":
841
1087
  return [this.functionDeclarationStatement(node)];
842
1088
  case "ClassDeclaration":
@@ -867,8 +1113,13 @@ var Lowerer = class {
867
1113
  // kept — Lua has no expression statement — and it cannot run
868
1114
  // anything, since a call is a `CallStatement` instead, so dropping
869
1115
  // it changes nothing but the noise.
870
- case "ExpressionStatement":
871
- return [];
1116
+ case "ExpressionStatement": {
1117
+ let expression = node.expression;
1118
+ while (expression.type === "ParenthesizedExpression") expression = expression.expression;
1119
+ if (expression.type !== "ImportExpression") return [];
1120
+ const call2 = this.importExpression(expression);
1121
+ return call2.type === "CallExpression" ? [this.from(callStatement(call2), node)] : [];
1122
+ }
872
1123
  case "DoStatement":
873
1124
  return [{ type: "DoStatement", body: this.block(node.body), ...spanOf(node) }];
874
1125
  case "WhileStatement":
@@ -920,7 +1171,11 @@ var Lowerer = class {
920
1171
  let source = this.expression(node.iterator);
921
1172
  if (form.viaIter) source = methodCall(source, "__iter", []);
922
1173
  const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
923
- const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
1174
+ let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
1175
+ if (this.lua51 && form.walks === "values") {
1176
+ const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
1177
+ iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
1178
+ }
924
1179
  return [genericFor(
925
1180
  variables,
926
1181
  iterators,
@@ -1173,7 +1428,8 @@ end
1173
1428
  },
1174
1429
  isMethod,
1175
1430
  func,
1176
- attributes: node.attributes,
1431
+ // `@native` and the like are Luau's.
1432
+ attributes: this.lua51 ? void 0 : node.attributes,
1177
1433
  ...spanOf(node)
1178
1434
  };
1179
1435
  }
@@ -1274,12 +1530,12 @@ end
1274
1530
  if (element) bind(element.value, index(source, number(i + 1)), element.default);
1275
1531
  });
1276
1532
  if (pattern.rest) {
1277
- const elements = call(member(this.builtin("table"), "move"), [
1533
+ const elements = call(this.move(), [
1278
1534
  source,
1279
1535
  number(pattern.elements.length + 1),
1280
1536
  unary("#", source),
1281
1537
  number(1),
1282
- table([])
1538
+ this.array(table([]))
1283
1539
  ]);
1284
1540
  const rest = this.restTarget(pattern.rest, elements, mode, after);
1285
1541
  after.push(...rest.then);
@@ -1333,7 +1589,7 @@ end
1333
1589
  const names = [];
1334
1590
  for (const p of params) {
1335
1591
  if (p.rest) {
1336
- prelude.push(local([this.name(p.name)], [table([{ type: "TableFieldPositional", value: vararg() }])]));
1592
+ prelude.push(local([this.name(p.name)], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))]));
1337
1593
  continue;
1338
1594
  }
1339
1595
  const name = p.pattern ? this.names.fresh("arg") : this.name(p.name);
@@ -1385,12 +1641,20 @@ end
1385
1641
  case "ArrayExpression":
1386
1642
  return this.arrayExpression(node);
1387
1643
  case "BinaryExpression": {
1644
+ if (node.operator === "??") return this.nullish(node);
1388
1645
  const left = this.expression(node.left);
1389
1646
  const right = this.expression(node.right);
1647
+ if (node.operator === "in") {
1648
+ return parenthesized(binary("~=", index(this.keptToOne(right), this.keptToOne(left)), nil()));
1649
+ }
1650
+ if (node.operator === "instanceof") {
1651
+ return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
1652
+ }
1390
1653
  if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
1391
1654
  return binary(node.operator, left, right);
1392
1655
  }
1393
1656
  case "UnaryExpression":
1657
+ if (node.operator === "typeof") return this.typeofExpression(node);
1394
1658
  return unary(node.operator, this.expression(node.argument));
1395
1659
  case "SuperExpression":
1396
1660
  return this.superClassReference(node);
@@ -1412,6 +1676,8 @@ end
1412
1676
  }
1413
1677
  case "ParenthesizedExpression":
1414
1678
  return parenthesized(this.expression(node.expression));
1679
+ case "ImportExpression":
1680
+ return this.importExpression(node);
1415
1681
  // Types only: the value is the expression itself.
1416
1682
  case "TypeAssertionExpression":
1417
1683
  case "SatisfiesExpression":
@@ -1445,7 +1711,7 @@ end
1445
1711
  * kept to one there, `f((g()))`. Every value is taken only where the
1446
1712
  * program asks for them, as an array's last element: `[g()]`. */
1447
1713
  keptToOne(value) {
1448
- return expandsToMany(value) ? parenthesized(value) : value;
1714
+ return expandsToMany(value) && !this.marked.has(value) ? parenthesized(value) : value;
1449
1715
  }
1450
1716
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1451
1717
  isLanguageGlobal(node) {
@@ -1465,7 +1731,7 @@ end
1465
1731
  const last2 = list.length - 1;
1466
1732
  return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1467
1733
  }
1468
- const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1734
+ const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
1469
1735
  const last = list[list.length - 1];
1470
1736
  if (spreads.length === 1 && last.type === "SpreadElement") {
1471
1737
  return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
@@ -1479,11 +1745,21 @@ end
1479
1745
  /** One link of an access chain, read from `object`. */
1480
1746
  link(node, object2) {
1481
1747
  switch (node.type) {
1482
- case "MemberExpression":
1748
+ case "MemberExpression": {
1749
+ if (this.options.types?.lengths.has(node)) return unary("#", object2);
1750
+ const source = this.options.types?.methodReads.get(node);
1751
+ const lowered = source && this.loweredMethod(node.property.name, source, node.object, [], node.property, node);
1752
+ if (lowered) {
1753
+ const forwarded = call(calleePath(lowered.callee), [...lowered.prepend, vararg()]);
1754
+ return functionExpression(functionBody(lowered.passReceiver ? [] : ["_"], [returns([forwarded])], true));
1755
+ }
1483
1756
  return member(object2, node.property.name);
1757
+ }
1484
1758
  case "IndexExpression":
1485
1759
  return index(object2, this.expression(node.index));
1486
1760
  case "CallExpression": {
1761
+ const method = this.methodCalled(node);
1762
+ if (method) return method;
1487
1763
  const lowered = this.loweredGlobalCall(node);
1488
1764
  const args2 = this.arguments(node.arguments);
1489
1765
  if (!lowered) return call(object2, args2);
@@ -1506,6 +1782,24 @@ end
1506
1782
  }
1507
1783
  }
1508
1784
  }
1785
+ /** `names.filter(names, f)`: a method of the value's metatable, read with
1786
+ * `.` and handed its `self` as the first argument. Written as a call of
1787
+ * the function the method really is — `array.filter(names, f)` — where
1788
+ * whoever declared the metatable says so; `undefined` leaves an ordinary
1789
+ * call, which is right for a string's Lua methods (`text.upper(text)`
1790
+ * reaches Lua's own `string.upper`). */
1791
+ methodCalled(node) {
1792
+ const callee = node.callee;
1793
+ if (callee.type !== "MemberExpression") return void 0;
1794
+ const source = this.options.types?.methodReads.get(callee);
1795
+ if (!source) return void 0;
1796
+ const spreads = node.arguments.map((a, i) => a.type === "SpreadElement" ? i : -1).filter((i) => i >= 0);
1797
+ if (!node.arguments.length || spreads.includes(0) || spreads.some((i) => i !== node.arguments.length - 1)) return void 0;
1798
+ const lowered = this.loweredMethod(callee.property.name, source, callee.object, node.arguments.slice(1), callee.property, node);
1799
+ if (!lowered) return void 0;
1800
+ const [self, ...rest] = this.arguments(node.arguments);
1801
+ return call(calleePath(lowered.callee), [...lowered.prepend, ...lowered.passReceiver ? [self] : [], ...rest]);
1802
+ }
1509
1803
  // --------------------------------------------------------
1510
1804
  // Optional chains
1511
1805
  // --------------------------------------------------------
@@ -1551,7 +1845,7 @@ end
1551
1845
  const segments = this.segments(chain);
1552
1846
  const base = this.expression(chain.base);
1553
1847
  const last = chain.links[chain.links.length - 1];
1554
- if (base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
1848
+ if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
1555
1849
  return {
1556
1850
  type: "IfElseExpression",
1557
1851
  clauses: [{ condition: binary("==", base, nil()), body: nil() }],
@@ -1572,6 +1866,32 @@ end
1572
1866
  const func = functionExpression(functionBody([], statements, passesVarargs));
1573
1867
  return call(parenthesized(func), passesVarargs ? [vararg()] : []);
1574
1868
  }
1869
+ /** `a ?? b`: `a`, or `b` where `a` is nil — read only then. A `false`
1870
+ * stays, which is what sets it apart from `a or b`. A name is tested
1871
+ * in place; anything else is read once, into a local. */
1872
+ nullish(node) {
1873
+ const left = this.keptToOne(this.expression(node.left));
1874
+ const right = this.keptToOne(this.expression(node.right));
1875
+ if (!this.lua51 && left.type === "Identifier") {
1876
+ return {
1877
+ type: "IfElseExpression",
1878
+ clauses: [{ condition: binary("~=", left, nil()), body: left }],
1879
+ alternate: right,
1880
+ line: left.line,
1881
+ column: left.column
1882
+ };
1883
+ }
1884
+ const ref = this.names.fresh("value");
1885
+ const at = identifier(ref);
1886
+ const statements = [
1887
+ local([ref], [left]),
1888
+ ifThen(binary("~=", at, nil()), [returns([at])]),
1889
+ returns([right])
1890
+ ];
1891
+ const passesVarargs = statements.some(usesVararg);
1892
+ const func = functionExpression(functionBody([], statements, passesVarargs));
1893
+ return call(parenthesized(func), passesVarargs ? [vararg()] : []);
1894
+ }
1575
1895
  /** `` `${a} any` `` -> `("%s any"):format(tostring(a))` */
1576
1896
  interpolatedString(node) {
1577
1897
  let format = "";
@@ -1653,12 +1973,12 @@ end
1653
1973
  }));
1654
1974
  const alternate = this.expression(node.alternate);
1655
1975
  if (clauses.every((c) => alwaysTruthy(c.body))) {
1656
- let result = alternate;
1976
+ let result2 = alternate;
1657
1977
  for (let i = clauses.length - 1; i >= 0; i--) {
1658
1978
  const { condition, body } = clauses[i];
1659
- result = binary("or", binary("and", condition, body), result);
1979
+ result2 = binary("or", binary("and", condition, body), result2);
1660
1980
  }
1661
- return parenthesized(result);
1981
+ return parenthesized(result2);
1662
1982
  }
1663
1983
  const statement = {
1664
1984
  type: "IfStatement",
@@ -1715,18 +2035,27 @@ end
1715
2035
  current = [];
1716
2036
  parts.push(this.expression(f.argument));
1717
2037
  break;
2038
+ case "TableFieldAccessor":
2039
+ break;
1718
2040
  }
1719
2041
  }
1720
- if (parts.length === 0) return table(current);
1721
- if (current.length) parts.push(table(current));
1722
- return call(this.helper("assign"), [table([]), ...parts]);
2042
+ let made;
2043
+ if (parts.length === 0) made = table(current);
2044
+ else {
2045
+ if (current.length) parts.push(table(current));
2046
+ made = call(this.helper("assign"), [table([]), ...parts]);
2047
+ }
2048
+ const accessors = node.fields.filter((f) => f.type === "TableFieldAccessor");
2049
+ if (!accessors.length) return made;
2050
+ 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)))));
2051
+ return call(this.helper("accessors"), [made, table2("get"), table2("set")]);
1723
2052
  }
1724
2053
  /** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
1725
2054
  * runs of plain elements and the spread arrays are joined by `concat`. */
1726
2055
  arrayExpression(node) {
1727
2056
  const elements = packs(node.elements).items;
1728
2057
  if (!elements.some((e) => e.type === "SpreadElement")) {
1729
- return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
2058
+ return this.array(table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) }))));
1730
2059
  }
1731
2060
  const parts = [];
1732
2061
  let current = [];
@@ -1976,7 +2305,7 @@ var CLASS_METAMETHODS = [
1976
2305
  ];
1977
2306
 
1978
2307
  // src/linemap.ts
1979
- var import_parser5 = require("@tilua/parser");
2308
+ var import_parser6 = require("@tilua/parser");
1980
2309
  function statementsOf(node) {
1981
2310
  const out2 = [];
1982
2311
  const visit = (value) => {
@@ -2009,7 +2338,7 @@ function buildLineMap(emitted, printed, origins) {
2009
2338
  }
2010
2339
  function lineMapSource(map) {
2011
2340
  const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
2012
- const quoted = (s) => (0, import_parser5.luauString)(s.replace(/\\/g, "/"));
2341
+ const quoted = (s) => (0, import_parser6.luauString)(s.replace(/\\/g, "/"));
2013
2342
  return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
2014
2343
  }
2015
2344
 
@@ -2021,15 +2350,15 @@ async function loadLowerings(modules) {
2021
2350
  for (const module2 of modules) {
2022
2351
  try {
2023
2352
  const loaded = await import((0, import_node_url.pathToFileURL)(module2.file).href);
2024
- const plugin2 = loaded.default;
2025
- if (!plugin2 || typeof plugin2 !== "object") {
2353
+ const plugin3 = loaded.default;
2354
+ if (!plugin3 || typeof plugin3 !== "object") {
2026
2355
  problems.push({
2027
2356
  file: module2.file,
2028
2357
  message: `'${module2.from}' lowering module has no default export`
2029
2358
  });
2030
2359
  continue;
2031
2360
  }
2032
- lowerings.push({ plugin: plugin2, from: module2.from, library: module2.library });
2361
+ lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
2033
2362
  } catch (error) {
2034
2363
  problems.push({
2035
2364
  file: module2.file,
@@ -2056,16 +2385,17 @@ async function bundle(options) {
2056
2385
  const file = queue.shift();
2057
2386
  if (sources.has(file)) continue;
2058
2387
  const parsed = parseFile(file, diagnostics, directives);
2059
- if (!parsed) return { modules: [], diagnostics };
2060
- const { program: program2, text } = parsed;
2061
- const scopes = (0, import_parser6.analyzeScopes)(program2);
2388
+ if (!parsed) return { files: [], modules: [], diagnostics };
2389
+ const { program: program3, text } = parsed;
2390
+ const scopes = (0, import_parser7.analyzeScopes)(program3, { noLib: config2?.noLib });
2062
2391
  for (const d of scopes.diagnostics) {
2063
2392
  diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
2064
2393
  }
2065
- sources.set(file, { file, name: nameOf(file), text, program: program2, scopes });
2066
- for (const specifier of importedSpecifiers(program2)) {
2067
- const target = (0, import_parser6.resolveModulePath)(file, specifier, config2);
2068
- if (target && !target.endsWith(".d.tilua")) queue.push(target);
2394
+ sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
2395
+ const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
2396
+ for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
2397
+ const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config2);
2398
+ if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
2069
2399
  }
2070
2400
  }
2071
2401
  const analysis = analyzeModules([...sources.values()], config2);
@@ -2077,6 +2407,9 @@ async function bundle(options) {
2077
2407
  const names = Names.from(...[...sources.values()].map((s) => s.program));
2078
2408
  const G = names.fresh("G");
2079
2409
  const requireExpression = member(identifier(G), "require");
2410
+ const importExpression = member(identifier(G), "import");
2411
+ const mode = options.output ?? config2?.output ?? "single";
2412
+ const target = options.target ?? config2?.target ?? "luau";
2080
2413
  const modules = /* @__PURE__ */ new Map();
2081
2414
  const origins = /* @__PURE__ */ new WeakMap();
2082
2415
  const pending = [entry2];
@@ -2085,106 +2418,235 @@ async function bundle(options) {
2085
2418
  if (modules.has(file)) continue;
2086
2419
  const source = sources.get(file);
2087
2420
  const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
2088
- const lowered = lower(source.program, source.scopes, {
2421
+ const imports = /* @__PURE__ */ new Set();
2422
+ const dynamicImports = /* @__PURE__ */ new Set();
2423
+ const lowered2 = lower(source.program, source.scopes, {
2089
2424
  names,
2090
2425
  types: analysis.types.get(file),
2091
2426
  lowerings,
2092
- target: options.target ?? config2?.target,
2427
+ target,
2428
+ noLib: config2?.noLib,
2093
2429
  source: source.text,
2094
2430
  file: shown,
2095
2431
  module: {
2096
2432
  name: source.name,
2097
2433
  require: requireExpression,
2098
- lines: `${G}.lines`,
2434
+ import: importExpression,
2435
+ // One file has one line map. Several have one each, and a
2436
+ // module reads the one of the file it is in.
2437
+ lines: mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
2099
2438
  fail: `${G}.fail`,
2100
- resolve: (specifier) => {
2101
- const target = (0, import_parser6.resolveModulePath)(file, specifier, config2);
2102
- if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
2103
- pending.push(target);
2104
- return sources.get(target).name;
2439
+ arrays: `${G}.arrays`,
2440
+ resolve: (specifier, dynamic) => {
2441
+ const found = (0, import_parser7.resolveModulePath)(file, specifier, config2);
2442
+ if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
2443
+ pending.push(found);
2444
+ const name = sources.get(found).name;
2445
+ (dynamic ? dynamicImports : imports).add(name);
2446
+ return name;
2105
2447
  }
2106
2448
  }
2107
2449
  });
2108
- for (const d of lowered.diagnostics) diagnostics.push({ file, ...d, category: "module" });
2109
- for (const statement of allStatements(lowered.statements)) {
2110
- const line = lowered.origins.get(statement);
2450
+ for (const d of lowered2.diagnostics) diagnostics.push({ file, ...d, category: "module" });
2451
+ for (const statement of allStatements(lowered2.statements)) {
2452
+ const line = lowered2.origins.get(statement);
2111
2453
  if (line !== void 0) origins.set(statement, { file: shown, line });
2112
2454
  }
2113
2455
  modules.set(file, {
2114
2456
  name: source.name,
2115
- statements: lowered.statements,
2116
- exportsName: lowered.exportsName,
2117
- info: lowered.module,
2118
- usesScriptArgs: lowered.usesScriptArgs
2457
+ statements: lowered2.statements,
2458
+ exportsName: lowered2.exportsName,
2459
+ info: lowered2.module,
2460
+ usesScriptArgs: lowered2.usesScriptArgs,
2461
+ imports,
2462
+ dynamicImports
2119
2463
  });
2120
2464
  }
2121
2465
  for (const [file, fileDirectives] of directives) {
2122
2466
  const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
2123
- const { kept, unusedExpectErrors } = (0, import_parser6.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2467
+ const { kept, unusedExpectErrors } = (0, import_parser7.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2124
2468
  const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
2125
2469
  const remaining = diagnostics.filter((d) => !suppressed.has(d));
2126
2470
  diagnostics.length = 0;
2127
2471
  diagnostics.push(...remaining);
2128
2472
  if (options.typeCheck === false) continue;
2129
2473
  for (const d of unusedExpectErrors) {
2130
- diagnostics.push({ file, message: import_parser6.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2474
+ diagnostics.push({ file, message: import_parser7.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2131
2475
  }
2132
2476
  }
2133
2477
  const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
2134
2478
  const reported = diagnostics.filter((d) => d.category !== "type" || !moduleProblems.has(`${d.file}:${d.line}:${d.column}:${d.message}`));
2135
2479
  diagnostics.length = 0;
2136
2480
  diagnostics.push(...reported);
2137
- const moduleNames = [...modules.values()].map((m) => m.name);
2481
+ const lowered = [...modules.values()];
2482
+ const moduleNames = lowered.map((m) => m.name);
2138
2483
  if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
2139
- return { modules: moduleNames, diagnostics };
2140
- }
2141
- const program = (0, import_luau_parser2.parse)(runtime(G));
2142
- const assignment = program.body.statements[1];
2143
- if ([...modules.values()].some((m) => m.usesScriptArgs)) {
2144
- program.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
2484
+ return { files: [], modules: moduleNames, diagnostics };
2485
+ }
2486
+ const several = mode !== "single";
2487
+ const layout = layoutOf(mode, modules.get(entry2), lowered, options.outFile);
2488
+ const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
2489
+ const elsewhere = /* @__PURE__ */ new Map();
2490
+ for (const file of layout.slice(1)) {
2491
+ for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
2492
+ }
2493
+ const program2 = (0, import_luau_parser2.parse)(runtime(G, {
2494
+ several,
2495
+ target,
2496
+ files: elsewhere,
2497
+ imports: lowered.some((m) => m.dynamicImports.size > 0)
2498
+ }));
2499
+ const assignment = program2.body.statements[1];
2500
+ if (usesScriptArgs) {
2501
+ program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
2502
+ program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
2503
+ if (several) program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.scriptArgs = scriptArgs`).body.statements);
2145
2504
  }
2146
2505
  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;
2147
2506
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
2148
- for (const module2 of modules.values()) {
2149
- const { names: exported, links, stars } = module2.info;
2150
- const fields = [];
2151
- if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
2152
- if (links.length) {
2153
- fields.push(field("links", table(links.map((link) => field(link.name, table([
2154
- { type: "TableFieldPositional", value: string(link.module) },
2155
- ...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
2156
- ]))))));
2157
- }
2158
- if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
2159
- fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
2160
- modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
2507
+ for (const module2 of layout[0].modules) {
2508
+ modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
2161
2509
  }
2162
2510
  const ok = names.fresh("ok");
2163
- const result = names.fresh("result");
2511
+ const result2 = names.fresh("result");
2164
2512
  const entryStatements = (0, import_luau_parser2.parse)(`
2165
- local ${ok}, ${result} = xpcall(function()
2166
- return ${G}.require(${(0, import_parser6.luauString)(sources.get(entry2).name)})
2513
+ local ${ok}, ${result2} = xpcall(function()
2514
+ return ${G}.require(${(0, import_parser7.luauString)(sources.get(entry2).name)})
2167
2515
  end, ${G}.fail)
2168
2516
  if not ${ok} then
2169
- error(${result}, 0)
2517
+ error(${result2}, 0)
2170
2518
  end
2171
- ${modules.get(entry2).info.exports ? `return ${result}` : ""}
2519
+ ${modules.get(entry2).info.exports ? `return ${result2}` : ""}
2172
2520
  `).body.statements;
2173
- program.body.statements.push(...entryStatements);
2174
- const map = buildLineMap(program, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program)), origins);
2175
- if (map.size) {
2176
- program.body.statements.splice(program.body.statements.length - entryStatements.length, 0, assign(
2521
+ program2.body.statements.push(...entryStatements);
2522
+ const map = buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins);
2523
+ if (map.size || several) {
2524
+ const mapStatements = [assign(
2177
2525
  [member(identifier(G), "lines")],
2178
2526
  [(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
2179
- ));
2527
+ )];
2528
+ if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
2529
+ program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
2180
2530
  }
2181
- return { code: (0, import_luau_parser2.print)(program) + "\n", modules: moduleNames, diagnostics };
2531
+ const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
2532
+ for (const file of layout.slice(1)) {
2533
+ const body = [];
2534
+ if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
2535
+ for (const module2 of file.modules) {
2536
+ body.push(assign(
2537
+ [index(member(identifier(G), "modules"), string(module2.name))],
2538
+ [moduleTable(module2)]
2539
+ ));
2540
+ }
2541
+ const chunk = program([returns([functionExpression(functionBody([G], body))])]);
2542
+ const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
2543
+ body.push(...mapRegistration(G, file, lineMapSource(lines)));
2544
+ files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
2545
+ }
2546
+ return { code: files[0].code, files, modules: moduleNames, diagnostics };
2547
+ }
2548
+ function moduleTable(module2) {
2549
+ const { names: exported, links, stars } = module2.info;
2550
+ const fields = [];
2551
+ if (exported.length) fields.push(field("names", table(exported.map((n) => field(n, boolean(true))))));
2552
+ if (links.length) {
2553
+ fields.push(field("links", table(links.map((link) => field(link.name, table([
2554
+ { type: "TableFieldPositional", value: string(link.module) },
2555
+ ...link.imported === void 0 ? [] : [{ type: "TableFieldPositional", value: string(link.imported) }]
2556
+ ]))))));
2557
+ }
2558
+ if (stars.length) fields.push(field("stars", table(stars.map((star) => ({ type: "TableFieldPositional", value: string(star) })))));
2559
+ fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
2560
+ return table(fields);
2561
+ }
2562
+ function layoutOf(mode, entry2, modules, outFile) {
2563
+ const extension = outFile !== void 0 && (0, import_node_path2.extname)(outFile) || ".luau";
2564
+ const entryStem = outFile !== void 0 ? (0, import_node_path2.basename)(outFile, (0, import_node_path2.extname)(outFile)) : import_node_path2.posix.basename(entry2.name);
2565
+ const others = modules.filter((m) => m !== entry2);
2566
+ if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry2, ...others] }];
2567
+ if (mode === "modules") {
2568
+ const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
2569
+ return [entry2, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
2570
+ }
2571
+ const byName = new Map(modules.map((m) => [m.name, m]));
2572
+ const reach = (root) => {
2573
+ const seen = /* @__PURE__ */ new Set();
2574
+ const stack = [root];
2575
+ while (stack.length) {
2576
+ const name = stack.pop();
2577
+ if (seen.has(name)) continue;
2578
+ seen.add(name);
2579
+ stack.push(...byName.get(name)?.imports ?? []);
2580
+ }
2581
+ return seen;
2582
+ };
2583
+ const inEntry = reach(entry2.name);
2584
+ const roots = [...new Set(modules.flatMap((m) => [...m.dynamicImports]))].filter((root) => !inEntry.has(root));
2585
+ const reached = new Map(roots.map((root) => [root, reach(root)]));
2586
+ const groups = /* @__PURE__ */ new Map();
2587
+ for (const module2 of others) {
2588
+ if (inEntry.has(module2.name)) continue;
2589
+ const by = roots.filter((root) => reached.get(root).has(module2.name));
2590
+ const key = by.join("\n");
2591
+ if (!groups.has(key)) groups.set(key, { roots: by, modules: [] });
2592
+ groups.get(key).modules.push(module2);
2593
+ }
2594
+ const files = [{ path: `${entryStem}${extension}`, modules: modules.filter((m) => inEntry.has(m.name)) }];
2595
+ const taken = /* @__PURE__ */ new Set([entryStem]);
2596
+ for (const group of groups.values()) {
2597
+ const label = group.roots.length === 1 ? group.roots[0].replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "chunk" : "shared";
2598
+ let stem = `${entryStem}_${label}`;
2599
+ for (let n = 2; taken.has(stem); n++) stem = `${entryStem}_${label}_${n}`;
2600
+ taken.add(stem);
2601
+ files.push({ path: `${stem}${extension}`, modules: group.modules });
2602
+ }
2603
+ return files;
2604
+ }
2605
+ function loadPath(entryPath, file, target) {
2606
+ const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
2607
+ if (target === "lua51") return path;
2608
+ const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
2609
+ return bare.startsWith("../") ? bare : `./${bare}`;
2610
+ }
2611
+ function mapRegistration(G, file, map) {
2612
+ const extension = import_node_path2.posix.extname(file.path);
2613
+ const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
2614
+ const entry2 = `${G}.sources[#${G}.sources + 1]`;
2615
+ return (0, import_luau_parser2.parse)([
2616
+ `${entry2} = { { ${parts.map(import_parser7.luauString).join(", ")} }, ${map}, ${(0, import_parser7.luauString)(extension.slice(1))} }`,
2617
+ ...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
2618
+ ].join("\n")).body.statements;
2619
+ }
2620
+ function loader(G, target) {
2621
+ if (target === "lua51") {
2622
+ return `
2623
+ -- From beside this file, wherever that is: its own path says where.
2624
+ local folder = ""
2625
+ if debug ~= nil and debug.getinfo ~= nil then
2626
+ local at = debug.getinfo(${G}.load, "S").source
2627
+ if at:sub(1, 1) == "@" then
2628
+ folder = at:match("^@(.*[/\\\\])") or ""
2629
+ end
2630
+ end
2631
+ local chunk, problem = loadfile(folder .. file)
2632
+ if chunk == nil then
2633
+ error(problem, 0)
2634
+ end
2635
+ chunk()(${G})`;
2636
+ }
2637
+ return `
2638
+ require(file)(${G})`;
2182
2639
  }
2183
- function runtime(G) {
2640
+ function runtime(G, options) {
2641
+ const { several } = options;
2642
+ const files = [...options.files].map(([name, file]) => `[${(0, import_parser7.luauString)(name)}] = ${(0, import_parser7.luauString)(file)}`).join(", ");
2184
2643
  return `
2185
2644
  local ${G}
2186
2645
  ${G} = {
2187
2646
  modules = {},
2647
+ -- Every array the program made, so \`typeof\` can tell one from an object.
2648
+ -- The keys are held weakly: being written down keeps no array alive.
2649
+ arrays = setmetatable({}, { __mode = "k" }),
2188
2650
  -- Filled in just before the entry runs: bundle line -> { file, line }.
2189
2651
  lines = nil,
2190
2652
  -- What an error nothing caught says, in the project's files: the bundle's
@@ -2225,7 +2687,7 @@ ${G} = {
2225
2687
  return message
2226
2688
  end
2227
2689
  local lines = ${G}.lines
2228
- if lines == nil then
2690
+ if lines == nil ${several ? `and #${G}.sources == 0 ` : ""}then
2229
2691
  return message .. (trace or "")
2230
2692
  end
2231
2693
  -- The bundle's name, as a position in a message spells it: where this
@@ -2245,6 +2707,81 @@ ${G} = {
2245
2707
  chunk = info.short_src
2246
2708
  end
2247
2709
  end
2710
+ ${several ? `
2711
+ -- A position in this file is read by this file's map. One in another
2712
+ -- of the bundle's files is read by that file's: the one whose name
2713
+ -- the position ends with, by the most folders where several do. A
2714
+ -- message says nothing about which file it starts in, so nothing is
2715
+ -- guessed from it here.
2716
+ local prefix = nil
2717
+ if chunk ~= nil then
2718
+ prefix = chunk:gsub("%p", "%%%0")
2719
+ end
2720
+ local function place(line)
2721
+ local origin = lines ~= nil and lines[tonumber(line)] or nil
2722
+ if origin == nil then
2723
+ return nil
2724
+ end
2725
+ return origin[1] .. ":" .. tostring(origin[2])
2726
+ end
2727
+ local function elsewhere(at, line)
2728
+ -- Already a place in the project.
2729
+ if at:sub(-6) == ".tilua" then
2730
+ return nil
2731
+ end
2732
+ local parts = {}
2733
+ for part in at:gmatch("[^/\\\\%.]+") do
2734
+ parts[#parts + 1] = part
2735
+ end
2736
+ local map, most, tied = nil, 0, false
2737
+ for _, source in ipairs(${G}.sources) do
2738
+ local names = source[1]
2739
+ local last = #parts
2740
+ if parts[last] == source[3] then
2741
+ last = last - 1
2742
+ end
2743
+ local same = 0
2744
+ while same < #names and same < last and names[#names - same] == parts[last - same] do
2745
+ same = same + 1
2746
+ end
2747
+ if same > most then
2748
+ map, most, tied = source[2], same, false
2749
+ elseif same == most then
2750
+ tied = true
2751
+ end
2752
+ end
2753
+ -- Two files it could as well be: a wrong place is worse than none.
2754
+ local origin = map ~= nil and not tied and map[tonumber(line)] or nil
2755
+ if origin == nil then
2756
+ return nil
2757
+ end
2758
+ return origin[1] .. ":" .. tostring(origin[2])
2759
+ end
2760
+ local mapped = message
2761
+ if prefix ~= nil then
2762
+ mapped = mapped:gsub(prefix .. ":(%d+)", function(line)
2763
+ return place(line)
2764
+ end)
2765
+ end
2766
+ mapped = mapped:gsub("(%S+):(%d+)", elsewhere)
2767
+ local frames = {}
2768
+ for frame in (trace or ""):gmatch("[^\\n]+") do
2769
+ local at = nil
2770
+ if prefix ~= nil then
2771
+ local line = frame:match(prefix .. ":(%d+)")
2772
+ if line ~= nil then
2773
+ at = place(line)
2774
+ end
2775
+ end
2776
+ if at == nil then
2777
+ for source, line in frame:gmatch("(%S+):(%d+)") do
2778
+ at = elsewhere(source, line)
2779
+ if at ~= nil then
2780
+ break
2781
+ end
2782
+ end
2783
+ end
2784
+ if at ~= nil then` : `
2248
2785
  if chunk == nil then
2249
2786
  chunk = message:match("^(.-):%d+:")
2250
2787
  end
@@ -2266,8 +2803,9 @@ ${G} = {
2266
2803
  for frame in (trace or ""):gmatch("[^\\n]+") do
2267
2804
  local line = frame:match(prefix .. ":(%d+)")
2268
2805
  local at = line ~= nil and place(line) or nil
2269
- if at ~= nil then
2270
- local name = frame:match("function ([%w_.:]+)")
2806
+ if at ~= nil then`}
2807
+ -- Luau writes \`function name\`; Lua 5.1, \`function 'name'\`.
2808
+ local name = frame:match("function ([%w_.:]+)") or frame:match("function '([^']+)'")
2271
2809
  frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
2272
2810
  end
2273
2811
  end
@@ -2276,13 +2814,36 @@ ${G} = {
2276
2814
  end
2277
2815
  return mapped .. "\\n" .. table.concat(frames, "\\n")
2278
2816
  end,
2279
- records = {},
2817
+ records = {},${several ? `
2818
+ -- The file each module outside this one is in, as \`load\` is handed it.
2819
+ files = { ${files} },
2820
+ loaded = {},
2821
+ -- Every file's line map: by the module, and by the file's name.
2822
+ maps = {},
2823
+ sources = {},
2824
+ -- Another of the bundle's files: a function that adds its modules here.
2825
+ load = function(file)
2826
+ if ${G}.loaded[file] then
2827
+ return
2828
+ end
2829
+ ${G}.loaded[file] = true${loader(G, options.target)}
2830
+ end,` : ""}${options.imports ? `
2831
+ -- \`import("./x")\`: the module's exports, asked for where the code runs
2832
+ -- rather than before its module does. \`require\` does the rest \u2014 and
2833
+ -- loads the file the module is in, if it is not this one.
2834
+ import = function(name)
2835
+ return ${G}.require(name)
2836
+ end,` : ""}
2280
2837
  require = function(name)
2281
2838
  local record = ${G}.records[name]
2282
2839
  if record ~= nil then
2283
2840
  return record.exports
2284
2841
  end
2285
- local module = ${G}.modules[name]
2842
+ local module = ${G}.modules[name]${several ? `
2843
+ if module == nil then
2844
+ ${G}.load(${G}.files[name])
2845
+ module = ${G}.modules[name]
2846
+ end` : ""}
2286
2847
  local initialized = {}
2287
2848
  local exports = setmetatable({}, {
2288
2849
  __index = function(_, key)
@@ -2301,7 +2862,7 @@ ${G} = {
2301
2862
  return source[link[2]]
2302
2863
  end
2303
2864
  if module.stars ~= nil and key ~= "default" then
2304
- for _, star in module.stars do
2865
+ for _, star in ipairs(module.stars) do
2305
2866
  local value = ${G}.require(star)[key]
2306
2867
  if value ~= nil then
2307
2868
  return value
@@ -2326,7 +2887,7 @@ ${G} = {
2326
2887
  }
2327
2888
  function stampOf(file) {
2328
2889
  try {
2329
- const at = (0, import_node_fs2.statSync)(file);
2890
+ const at = (0, import_node_fs3.statSync)(file);
2330
2891
  return `${at.size}:${at.mtimeMs}`;
2331
2892
  } catch {
2332
2893
  return void 0;
@@ -2342,19 +2903,19 @@ function parseFile(file, diagnostics, directives) {
2342
2903
  }
2343
2904
  let text;
2344
2905
  try {
2345
- text = (0, import_node_fs2.readFileSync)(file, "utf8");
2906
+ text = (0, import_node_fs3.readFileSync)(file, "utf8");
2346
2907
  } catch {
2347
2908
  diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
2348
2909
  return void 0;
2349
2910
  }
2350
2911
  try {
2351
- const program = (0, import_parser6.parse)(text);
2352
- const found = (0, import_parser6.directivesOf)(text);
2912
+ const program2 = (0, import_parser7.parse)(text);
2913
+ const found = (0, import_parser7.directivesOf)(text);
2353
2914
  directives.set(file, found);
2354
- if (stamp !== void 0) parsedFiles.set(file, { stamp, program, text, directives: found });
2355
- return { program, text };
2915
+ if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
2916
+ return { program: program2, text };
2356
2917
  } catch (error) {
2357
- if (error instanceof import_parser6.ParseError || error instanceof import_parser6.LexError) {
2918
+ if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
2358
2919
  const { line, column } = error;
2359
2920
  diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
2360
2921
  return void 0;
@@ -2362,31 +2923,33 @@ function parseFile(file, diagnostics, directives) {
2362
2923
  throw error;
2363
2924
  }
2364
2925
  }
2365
- function importedSpecifiers(program) {
2366
- return program.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
2926
+ function importedSpecifiers(program2) {
2927
+ return program2.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
2367
2928
  }
2368
2929
  var libraryCache = /* @__PURE__ */ new Map();
2369
2930
  function parsedLibrary(file) {
2370
2931
  let stamp;
2371
2932
  try {
2372
- const at = (0, import_node_fs2.statSync)(file);
2933
+ const at = (0, import_node_fs3.statSync)(file);
2373
2934
  stamp = `${at.size}:${at.mtimeMs}`;
2374
2935
  } catch {
2375
2936
  stamp = "";
2376
2937
  }
2377
2938
  const cached = libraryCache.get(file);
2378
2939
  if (cached && cached.stamp === stamp) return cached.program;
2379
- const program = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(file, "utf8"));
2380
- libraryCache.set(file, { stamp, program });
2381
- return program;
2940
+ const program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(file, "utf8"));
2941
+ libraryCache.set(file, { stamp, program: program2 });
2942
+ return program2;
2382
2943
  }
2383
2944
  var moduleAnalyses = /* @__PURE__ */ new Map();
2384
2945
  function analyzeModules(modules, config2) {
2385
2946
  const out2 = [];
2386
2947
  const analyses = /* @__PURE__ */ new Map();
2387
- const libraries = config2 ? (0, import_parser6.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
2948
+ const libraries = config2 ? (0, import_parser7.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
2388
2949
  for (const p of libraries.problems) out2.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
2389
2950
  const libs = libraries.files.map(parsedLibrary);
2951
+ const noLib = config2?.noLib ?? false;
2952
+ const checked = libs.length > 0 || !noLib;
2390
2953
  const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
2391
2954
  const byFile = new Map(modules.map((m) => [m.file, m]));
2392
2955
  const exportsCache = /* @__PURE__ */ new Map();
@@ -2395,53 +2958,54 @@ function analyzeModules(modules, config2) {
2395
2958
  let partials = 0;
2396
2959
  const readWhile = /* @__PURE__ */ new Map();
2397
2960
  const settingFor = `${libraries.files.join("|")}#${config2?.directory ?? ""}#${JSON.stringify(config2?.paths ?? {})}`;
2398
- const analyzeModule = (file, program) => {
2961
+ const analyzeModule = (file, program2) => {
2399
2962
  const done = analyzed.get(file);
2400
2963
  if (done) return done;
2401
2964
  const remembered = moduleAnalyses.get(file);
2402
- if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program && [...remembered.read].every(([read2, stamp]) => stampOf(read2) === stamp)) {
2965
+ 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)) {
2403
2966
  analyzed.set(file, remembered.result);
2404
2967
  readWhile.set(file, remembered.read);
2405
2968
  return remembered.result;
2406
2969
  }
2407
- const scopes = (0, import_parser6.analyzeScopes)(program, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2970
+ const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: checked, noLib });
2408
2971
  const said = [];
2409
2972
  for (const d of scopes.diagnostics) {
2410
2973
  if (d.kind !== "undeclared") continue;
2411
2974
  said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
2412
2975
  }
2413
2976
  const before = partials;
2414
- const types = (0, import_parser6.analyzeTypes)(program, scopes, {
2977
+ const types = (0, import_parser7.analyzeTypes)(program2, scopes, {
2415
2978
  libs,
2416
2979
  resolveModule: resolverFor(file),
2417
- reportUnknownTypes: libs.length > 0
2980
+ noLib,
2981
+ reportUnknownTypes: checked
2418
2982
  });
2419
2983
  for (const d of types.diagnostics) {
2420
2984
  const at = d.node;
2421
2985
  said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
2422
2986
  }
2423
- const result = { types, diagnostics: said };
2987
+ const result2 = { types, diagnostics: said };
2424
2988
  if (partials === before) {
2425
- analyzed.set(file, result);
2426
- const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2989
+ analyzed.set(file, result2);
2990
+ const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2427
2991
  const own = stampOf(file);
2428
- if (own !== void 0) read2.set(file, own);
2429
- readWhile.set(file, read2);
2430
- if ([...read2.values()].every((stamp) => stamp !== void 0)) {
2431
- moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program, read: read2, result });
2992
+ if (own !== void 0) read3.set(file, own);
2993
+ readWhile.set(file, read3);
2994
+ if ([...read3.values()].every((stamp) => stamp !== void 0)) {
2995
+ moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read3, result: result2 });
2432
2996
  }
2433
2997
  }
2434
- return result;
2998
+ return result2;
2435
2999
  };
2436
3000
  const resolverFor = (file) => (specifier) => {
2437
- const target = (0, import_parser6.resolveModulePath)(file, specifier, config2);
3001
+ const target = (0, import_parser7.resolveModulePath)(file, specifier, config2);
2438
3002
  if (!target) return void 0;
2439
3003
  const noteRead = () => {
2440
- const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2441
- readWhile.set(file, read2);
3004
+ const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
3005
+ readWhile.set(file, read3);
2442
3006
  const stamp = stampOf(target);
2443
- if (stamp !== void 0) read2.set(target, stamp);
2444
- for (const [below, belowStamp] of readWhile.get(target) ?? []) read2.set(below, belowStamp);
3007
+ if (stamp !== void 0) read3.set(target, stamp);
3008
+ for (const [below, belowStamp] of readWhile.get(target) ?? []) read3.set(below, belowStamp);
2445
3009
  };
2446
3010
  if (inProgress.has(target)) {
2447
3011
  partials++;
@@ -2452,19 +3016,19 @@ function analyzeModules(modules, config2) {
2452
3016
  noteRead();
2453
3017
  return cached;
2454
3018
  }
2455
- let program = byFile.get(target)?.program;
2456
- if (!program) {
3019
+ let program2 = byFile.get(target)?.program;
3020
+ if (!program2) {
2457
3021
  try {
2458
- program = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(target, "utf8"));
3022
+ program2 = (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(target, "utf8"));
2459
3023
  } catch {
2460
3024
  return void 0;
2461
3025
  }
2462
3026
  }
2463
3027
  inProgress.add(target);
2464
3028
  try {
2465
- const scopes = (0, import_parser6.analyzeScopes)(program, { builtinGlobals: globals });
2466
- const types = byFile.has(target) ? analyzeModule(target, program).types : (0, import_parser6.analyzeTypes)(program, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2467
- const exports2 = (0, import_parser6.moduleExports)(program, scopes, types, resolverFor(target));
3029
+ const scopes = (0, import_parser7.analyzeScopes)(program2, { builtinGlobals: globals, noLib });
3030
+ const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser7.analyzeTypes)(program2, scopes, { libs, noLib, resolveModule: resolverFor(target), diagnostics: false });
3031
+ const exports2 = (0, import_parser7.moduleExports)(program2, scopes, types, resolverFor(target));
2468
3032
  exportsCache.set(target, exports2);
2469
3033
  noteRead();
2470
3034
  return exports2;
@@ -2497,13 +3061,14 @@ function allStatements(statements) {
2497
3061
  }
2498
3062
 
2499
3063
  // src/cli.ts
2500
- var usage = "usage: tilua <entry> [--out <file>] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
3064
+ var usage = "usage: tilua <entry> [--out <file>] [--output single|chunks|modules] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
2501
3065
  var args = process.argv.slice(2);
2502
3066
  var entry;
2503
3067
  var out;
2504
3068
  var config;
2505
3069
  var typeCheck = true;
2506
3070
  var buildTarget;
3071
+ var output;
2507
3072
  for (let i = 0; i < args.length; i++) {
2508
3073
  const arg = args[i];
2509
3074
  if (arg === "--out") out = args[++i];
@@ -2516,6 +3081,13 @@ for (let i = 0; i < args.length; i++) {
2516
3081
  process.exit(2);
2517
3082
  }
2518
3083
  buildTarget = value;
3084
+ } else if (arg === "--output") {
3085
+ const value = args[++i];
3086
+ if (value !== "single" && value !== "chunks" && value !== "modules") {
3087
+ console.error(`--output must be 'single', 'chunks' or 'modules', not '${value}'`);
3088
+ process.exit(2);
3089
+ }
3090
+ output = value;
2519
3091
  } else if (arg === "--help" || arg === "-h") {
2520
3092
  console.log(usage);
2521
3093
  process.exit(0);
@@ -2526,18 +3098,32 @@ if (!entry) {
2526
3098
  process.exit(2);
2527
3099
  }
2528
3100
  async function main(from) {
2529
- const result = await bundle({ entry: from, config, typeCheck, target: buildTarget });
2530
- for (const d of result.diagnostics) {
3101
+ const found = resolveConfig(config, (0, import_node_path3.resolve)(from)).config;
3102
+ const mode = output ?? found?.output ?? "single";
3103
+ const entryFile = mode === "modules" ? void 0 : out ?? from.replace(/\.tilua$/, "") + ".luau";
3104
+ const folder = entryFile !== void 0 ? (0, import_node_path3.dirname)(entryFile) : out ?? found?.directory ?? (0, import_node_path3.dirname)(from);
3105
+ const result2 = await bundle({
3106
+ entry: from,
3107
+ config,
3108
+ typeCheck,
3109
+ target: buildTarget,
3110
+ output: mode,
3111
+ outFile: entryFile !== void 0 ? (0, import_node_path3.basename)(entryFile) : void 0
3112
+ });
3113
+ for (const d of result2.diagnostics) {
2531
3114
  console.error(`${(0, import_node_path3.relative)(process.cwd(), d.file)}:${d.line}:${d.column} ${d.message}`);
2532
3115
  }
2533
- if (result.code === void 0) {
3116
+ if (result2.code === void 0) {
2534
3117
  console.error("no bundle written");
2535
3118
  process.exit(1);
2536
3119
  }
2537
- const target = out ?? from.replace(/\.tilua$/, "") + ".luau";
2538
- (0, import_node_fs3.writeFileSync)(target, result.code);
2539
- console.log(`${target}: ${result.modules.length} module${result.modules.length === 1 ? "" : "s"}`);
2540
- process.exit(result.diagnostics.length ? 1 : 0);
3120
+ for (const file of result2.files) {
3121
+ const target = (0, import_node_path3.join)(folder, file.path);
3122
+ (0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(target), { recursive: true });
3123
+ (0, import_node_fs4.writeFileSync)(target, file.code);
3124
+ console.log(`${target}: ${file.modules.length} module${file.modules.length === 1 ? "" : "s"}`);
3125
+ }
3126
+ process.exit(result2.diagnostics.length ? 1 : 0);
2541
3127
  }
2542
3128
  main(entry).catch((error) => {
2543
3129
  console.error(error instanceof Error ? error.message : error);