@tilua/compiler 1.0.2 → 2.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
@@ -113,144 +113,6 @@ function isLuauName(name) {
113
113
  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
114
114
  }
115
115
 
116
- // src/console.ts
117
- var CONSOLE_RUNTIME = `
118
- local __NAME__ = (function()
119
- local console = {}
120
- local MAX_DEPTH = 4
121
- local ESCAPES = { ["\\n"] = "\\\\n", ["\\r"] = "\\\\r", ["\\t"] = "\\\\t", ["\\""] = "\\\\\\"", ["\\\\"] = "\\\\\\\\" }
122
-
123
- local function quote(text)
124
- return '"' .. text:gsub('[%c"\\\\]', function(c) return ESCAPES[c] or ("\\\\%d"):format(c:byte()) end) .. '"'
125
- end
126
-
127
- -- A table with 1..n and nothing else prints as a list, as it was written.
128
- local function isArray(value)
129
- local count = 0
130
- for key in pairs(value) do
131
- if type(key) ~= "number" then return false end
132
- count = count + 1
133
- end
134
- return count == #value
135
- end
136
-
137
- local function isIdentifier(key)
138
- return type(key) == "string" and key:match("^[A-Za-z_][A-Za-z0-9_]*$") ~= nil
139
- end
140
-
141
- local render
142
-
143
- local function renderTable(value, meta, seen, depth, indent)
144
- if seen[value] then return "<cycle>" end
145
- if depth > MAX_DEPTH then return "..." end
146
- seen[value] = true
147
- local inner = indent .. " "
148
- local parts = {}
149
- if isArray(value) then
150
- for i = 1, #value do
151
- parts[#parts + 1] = inner .. render(value[i], nil, seen, depth + 1, inner)
152
- end
153
- else
154
- local keys = {}
155
- for key in pairs(value) do keys[#keys + 1] = key end
156
- table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
157
- for _, key in ipairs(keys) do
158
- local name = isIdentifier(key) and key or ("[" .. render(key, nil, seen, depth + 1, inner) .. "]")
159
- parts[#parts + 1] = inner .. name .. " = " .. render(value[key], nil, seen, depth + 1, inner)
160
- end
161
- end
162
- seen[value] = nil
163
- if #parts == 0 then return "{}" end
164
- local open = isArray(value) and "[" or "{"
165
- local close = isArray(value) and "]" or "}"
166
- return open .. "\\n" .. table.concat(parts, ",\\n") .. "\\n" .. indent .. close
167
- end
168
-
169
- -- \`meta\` is what the compiler knew about this value and the runtime cannot:
170
- -- a function's type, and the code it was written as.
171
- render = function(value, meta, seen, depth, indent)
172
- local kind = type(value)
173
- if kind == "string" then return quote(value) end
174
- if kind == "number" or kind == "boolean" or kind == "nil" then return tostring(value) end
175
- if kind == "function" then
176
- if meta == nil then return "function" end
177
- if meta[2] == nil or meta[2] == "" then return meta[1] end
178
- return meta[1] .. " " .. meta[2]
179
- end
180
- if kind == "table" then
181
- local shown = rawget(value, "__tostring") == nil and getmetatable(value)
182
- -- A value with its own \`__tostring\` (a Roblox Vector3, a class
183
- -- instance that says how it reads) is trusted to describe itself.
184
- if shown ~= nil and rawget(shown, "__tostring") ~= nil then return tostring(value) end
185
- return renderTable(value, meta, seen, depth, indent)
186
- end
187
- return tostring(value)
188
- end
189
-
190
- local function describe(meta, index, value)
191
- return render(value, meta ~= nil and meta[index] or nil, {}, 1, "")
192
- end
193
-
194
- local function join(meta, count, ...)
195
- local parts = {}
196
- for i = 1, count do
197
- parts[#parts + 1] = describe(meta, i, (select(i, ...)))
198
- end
199
- return table.concat(parts, " ")
200
- end
201
-
202
- -- A line of the bundle, as the place in the project that wrote it.
203
- local lines = __LINES__
204
- local function place(line)
205
- local origin = lines ~= nil and lines[tonumber(line)]
206
- if origin == nil then return nil end
207
- return origin[1] .. ":" .. tostring(origin[2])
208
- end
209
-
210
- -- Luau names the bundle in every frame; the project's files are what the
211
- -- reader knows. Frames the map has nothing for are dropped: they are this
212
- -- runtime's own, and the bundle's.
213
- local function trace(level)
214
- local raw = debug.traceback(nil, level)
215
- local out = {}
216
- for frame in raw:gmatch("[^\\n]+") do
217
- local line = frame:match(":(%d+)")
218
- local at = line ~= nil and place(line) or nil
219
- if at ~= nil then
220
- local name = frame:match("function ([%w_.:]+)")
221
- out[#out + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
222
- end
223
- end
224
- return table.concat(out, "\\n")
225
- end
226
-
227
- -- Never on the caller's thread: reporting a problem must not become one.
228
- local detach = (task ~= nil and task.spawn) or function(f) return f() end
229
-
230
- local function raise(prefix, meta, count, ...)
231
- local message = prefix .. join(meta, count, ...)
232
- local where = trace(3)
233
- detach(function()
234
- error(where ~= "" and (message .. "\\n" .. where) or message, 0)
235
- end)
236
- end
237
-
238
- function console.log(meta, ...)
239
- print(join(meta, select("#", ...), ...))
240
- end
241
-
242
- function console.warn(meta, ...)
243
- raise("[warn] ", meta, select("#", ...), ...)
244
- end
245
-
246
- function console.error(meta, ...)
247
- raise("", meta, select("#", ...), ...)
248
- end
249
-
250
- return console
251
- end)()
252
- `;
253
-
254
116
  // src/names.ts
255
117
  var Names = class _Names {
256
118
  constructor(taken) {
@@ -330,14 +192,18 @@ var Lowerer = class {
330
192
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
331
193
  const helpers = this.helperDefinitions();
332
194
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
195
+ const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
333
196
  return {
334
- statements: [...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
197
+ statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
335
198
  exportsName: this.exportsName,
336
199
  module: this.info,
337
200
  diagnostics: this.diagnostics,
338
- origins: this.origins
201
+ origins: this.origins,
202
+ usesScriptArgs: this.usesScriptArgs
339
203
  };
340
204
  }
205
+ /** Whether the file reads `scriptArgs`, the language's own global. */
206
+ usesScriptArgs = false;
341
207
  report(node, message) {
342
208
  this.diagnostics.push({ message, line: node.line.start, column: node.column.start });
343
209
  }
@@ -398,60 +264,9 @@ var Lowerer = class {
398
264
  }
399
265
  loweringNames = /* @__PURE__ */ new Map();
400
266
  loweringUsed = [];
401
- /** What a library says `receiver:method(...)` is. The last library
402
- * loaded is asked first, so a project's own library can answer for a
403
- * method an earlier one also claims; no answer leaves an ordinary Luau
404
- * method call, which is what a value that answers to the method itself
405
- * wants (`text:upper()`). */
406
- /** `console:log(a, f)` and its two siblings.
407
- *
408
- * Handled here rather than through a library's lowering because what they
409
- * need is not a different callee but *more than was written*: a function
410
- * prints as its type and its code, and neither survives to runtime. The
411
- * compiler is the only thing that has both, so it passes them along:
412
- *
413
- * console:log(a, double)
414
- * -- becomes
415
- * tilua_console.log({ [2] = { "(x: number) => number", "(x: number) => x * 2" } }, a, double)
416
- *
417
- * The meta table is `nil` when nothing was worth saying, which is most
418
- * calls. `console` itself is never passed: it holds nothing. */
419
- consoleCall(node) {
420
- if (node.object.type !== "Identifier" || node.object.name !== "console") return void 0;
421
- const method = node.method.name;
422
- if (method !== "log" && method !== "warn" && method !== "error") return void 0;
423
- const binding = this.scopes.bindingOf.get(node.object);
424
- if (binding !== void 0 && this.scopes.bindings.get(binding)?.kind !== "global") return void 0;
425
- const meta = this.consoleMeta(node.arguments);
426
- return call(
427
- member(this.helper("console"), method),
428
- [meta ?? nil(), ...this.values(node.arguments)]
429
- );
430
- }
431
- /** What the compiler knows about the arguments and the runtime cannot:
432
- * for each function argument, the type it was inferred as and the code it
433
- * was written as. Keyed by position, so an argument that needs nothing
434
- * costs nothing. */
435
- consoleMeta(args2) {
436
- const fields = [];
437
- args2.forEach((argument, index2) => {
438
- const type = this.options.types?.typeOf.get(argument);
439
- if (!type || !isFunctionType(type)) return;
440
- const written = this.writtenSource(this.declarationOf(argument) ?? argument);
441
- fields.push({
442
- type: "TableFieldComputed",
443
- key: number(index2 + 1),
444
- value: table([
445
- { type: "TableFieldPositional", value: string((0, import_parser2.formatType)(type)) },
446
- { type: "TableFieldPositional", value: string(written) }
447
- ])
448
- });
449
- });
450
- return fields.length ? table(fields) : void 0;
451
- }
452
267
  /** What a name was declared as: the value of `const f = ...`, or the whole
453
- * `function f() { ... }`. Built once, on the first `console` call that
454
- * needs it — most files have none. */
268
+ * `function f() { ... }`. Built once, on the first call a library asks
269
+ * about — most files have none. */
455
270
  declarationOf(argument) {
456
271
  if (argument.type !== "Identifier") return void 0;
457
272
  if (!this.declarations) {
@@ -490,21 +305,131 @@ var Lowerer = class {
490
305
  const trim = (line) => line.startsWith(indent) ? line.slice(indent.length) : line;
491
306
  return [first, ...middle.map(trim), trim(last)].join("\n");
492
307
  }
308
+ /** Where `node` was written, as a library is told it. */
309
+ callSite(node) {
310
+ return { file: this.options.file, line: node.line.start, column: node.column.start };
311
+ }
312
+ /** What the compiler knows about each argument and the runtime cannot:
313
+ * its type, and the code it — or what it names — was written as. Built
314
+ * only if a library reads it. */
315
+ argumentInfo(args2) {
316
+ return args2.map((argument) => {
317
+ const declaration = this.declarationOf(argument);
318
+ const type = this.options.types?.typeOf.get(argument);
319
+ return {
320
+ type,
321
+ typeText: type ? (0, import_parser2.formatType)(type) : void 0,
322
+ source: this.writtenSource(argument),
323
+ declaration: declaration ? this.writtenSource(declaration) : void 0,
324
+ spread: argument.type === "SpreadElement"
325
+ };
326
+ });
327
+ }
328
+ /** The name a global is read by at `node`, or `undefined` when `node` is
329
+ * not a global — a local of the same name is the author's. */
330
+ globalName(node) {
331
+ if (node.type === "MemberExpression" && !node.optional) {
332
+ const base = this.globalName(node.object);
333
+ return base === void 0 ? void 0 : `${base}.${node.property.name}`;
334
+ }
335
+ if (node.type !== "Identifier") return void 0;
336
+ const id = this.scopes.bindingOf.get(node);
337
+ if (id === void 0) return node.name;
338
+ return this.scopes.bindings.get(id)?.kind === "global" ? node.name : void 0;
339
+ }
340
+ /** What a library says `receiver:method(...)` is. The last library
341
+ * loaded is asked first, so a project's own library can answer for a
342
+ * method an earlier one also claims; no answer leaves an ordinary Luau
343
+ * method call, which is what a value that answers to the method itself
344
+ * wants (`text:upper()`). */
493
345
  loweredMethodCall(node) {
494
346
  const lowerings = this.options.lowerings;
495
- if (!lowerings?.length) return void 0;
347
+ if (!lowerings?.some((l) => l.plugin.methodCall)) return void 0;
496
348
  const receiver = this.options.types?.typeOf.get(node.object);
349
+ const receiverGlobal = this.globalName(node.object);
350
+ const info = this.lazyArguments(node.arguments);
497
351
  for (let i = lowerings.length - 1; i >= 0; i--) {
498
- const answer = lowerings[i].plugin.methodCall?.({
352
+ const plugin = lowerings[i].plugin;
353
+ if (!plugin.methodCall) continue;
354
+ const answer = plugin.methodCall({
499
355
  method: node.method.name,
500
356
  receiver,
357
+ receiverGlobal,
501
358
  argumentCount: node.arguments.length,
359
+ at: this.callSite(node.method),
360
+ get arguments() {
361
+ return info();
362
+ },
363
+ use: (runtime2) => this.loweringRuntime(i, runtime2)
364
+ });
365
+ if (answer) {
366
+ return {
367
+ callee: answer.callee,
368
+ prepend: this.prepended(answer.prepend, i, node),
369
+ passReceiver: answer.passReceiver !== false
370
+ };
371
+ }
372
+ }
373
+ return void 0;
374
+ }
375
+ /** What a library says a call to a global — `print(x)` — is. */
376
+ loweredGlobalCall(node) {
377
+ const lowerings = this.options.lowerings;
378
+ if (!lowerings?.some((l) => l.plugin.globalCall)) return void 0;
379
+ const name = this.globalName(node.callee);
380
+ if (name === void 0) return void 0;
381
+ const info = this.lazyArguments(node.arguments);
382
+ for (let i = lowerings.length - 1; i >= 0; i--) {
383
+ const plugin = lowerings[i].plugin;
384
+ if (!plugin.globalCall) continue;
385
+ const answer = plugin.globalCall({
386
+ name,
387
+ at: this.callSite(node.callee),
388
+ get arguments() {
389
+ return info();
390
+ },
391
+ use: (runtime2) => this.loweringRuntime(i, runtime2)
392
+ });
393
+ if (answer) return { callee: answer.callee, prepend: this.prepended(answer.prepend, i, node) };
394
+ }
395
+ return void 0;
396
+ }
397
+ /** What a library says a global read as a value — `local p = print` — is. */
398
+ loweredGlobalValue(node) {
399
+ const lowerings = this.options.lowerings;
400
+ if (!lowerings?.some((l) => l.plugin.globalValue)) return void 0;
401
+ const name = this.globalName(node);
402
+ if (name === void 0) return void 0;
403
+ for (let i = lowerings.length - 1; i >= 0; i--) {
404
+ const answer = lowerings[i].plugin.globalValue?.({
405
+ name,
406
+ at: this.callSite(node),
502
407
  use: (runtime2) => this.loweringRuntime(i, runtime2)
503
408
  });
504
- if (answer) return { callee: answer.callee, passReceiver: answer.passReceiver !== false };
409
+ if (answer !== void 0) return this.libraryExpression(answer, i, node);
505
410
  }
506
411
  return void 0;
507
412
  }
413
+ lazyArguments(args2) {
414
+ let info;
415
+ return () => info ??= this.argumentInfo(args2);
416
+ }
417
+ prepended(sources, index2, node) {
418
+ return (sources ?? []).flatMap((source) => {
419
+ const expression = this.libraryExpression(source, index2, node);
420
+ return expression ? [expression] : [];
421
+ });
422
+ }
423
+ /** Luau a library wrote as an expression. One that does not parse is the
424
+ * library's mistake, reported at the call it was written for. */
425
+ libraryExpression(source, index2, node) {
426
+ try {
427
+ return (0, import_luau_parser.parseExpressionFromSource)(source);
428
+ } catch (error) {
429
+ this.report(node, `'${this.options.lowerings[index2].from}' lowered this to Luau that does not parse: ${JSON.stringify(source)} (${error.message})`);
430
+ return void 0;
431
+ }
432
+ }
508
433
  helperDefinitions() {
509
434
  const out2 = [];
510
435
  const assign2 = this.helpers.get("assign");
@@ -532,12 +457,9 @@ var Lowerer = class {
532
457
  });
533
458
  continue;
534
459
  }
535
- out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name)).body.statements);
536
- }
537
- const console2 = this.helpers.get("console");
538
- if (console2) {
539
460
  const lines = this.options.module?.lines ?? "nil";
540
- out2.push(...(0, import_luau_parser.parse)(CONSOLE_RUNTIME.replace(/__NAME__/g, console2).replace(/__LINES__/g, lines)).body.statements);
461
+ const fail = this.options.module?.fail ?? "nil";
462
+ out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
541
463
  }
542
464
  const concat = this.helpers.get("concat");
543
465
  if (concat) {
@@ -706,7 +628,7 @@ var Lowerer = class {
706
628
  break;
707
629
  }
708
630
  case "ReturnStatement":
709
- if (declaration.arguments.length) this.report(declaration, "A module cannot return a value; export it instead");
631
+ if (declaration.argument) this.report(declaration, "A module cannot return a value; export it instead");
710
632
  body.push(returns([]));
711
633
  break;
712
634
  case "ImportStatement":
@@ -885,7 +807,7 @@ var Lowerer = class {
885
807
  case "CallStatement": {
886
808
  const chain = optionalChain(node.expression);
887
809
  if (chain) return [this.optionalCallStatement(chain)];
888
- const expression = this.expression(node.expression);
810
+ const expression = this.callValues(node.expression);
889
811
  if (expression.type !== "CallExpression" && expression.type !== "MethodCallExpression") {
890
812
  this.report(node, "A statement must be a call");
891
813
  return [];
@@ -938,23 +860,26 @@ var Lowerer = class {
938
860
  }];
939
861
  case "GenericForStatement": {
940
862
  const prelude = [];
941
- const variables = node.variables.map((target) => {
942
- if (target.type === "IdentifierPattern") return this.name(target.name);
943
- const temp = this.names.fresh("item");
944
- prelude.push(...this.destructure(target, identifier(temp), "declare"));
945
- return temp;
946
- });
947
- if (variables.length === 1 && this.options.types?.iteratesValues.has(node)) {
948
- variables.unshift(this.names.fresh("_"));
863
+ let item;
864
+ if (node.variable.type === "IdentifierPattern") {
865
+ item = this.name(node.variable.name);
866
+ } else {
867
+ item = this.names.fresh("item");
868
+ prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
949
869
  }
870
+ const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
871
+ let source = this.expression(node.iterator);
872
+ if (form.viaIter) source = methodCall(source, "__iter", []);
873
+ const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
874
+ const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
950
875
  return [genericFor(
951
876
  variables,
952
- node.iterators.map((e) => this.expression(e)),
877
+ iterators,
953
878
  this.loopBody51(this.block(node.body, prelude)).statements
954
879
  )];
955
880
  }
956
881
  case "ReturnStatement":
957
- return [returns(this.values(node.arguments))];
882
+ return [returns(node.argument ? [this.expression(node.argument)] : [])];
958
883
  case "BreakStatement":
959
884
  return [{ type: "BreakStatement", ...spanOf(node) }];
960
885
  case "ContinueStatement":
@@ -998,7 +923,7 @@ var Lowerer = class {
998
923
  // end
999
924
  // function Dog.speak(this) ... end
1000
925
  //
1001
- // `new Dog(x)` is `Dog.new(x)`, `dog:speak()` passes the instance
926
+ // `Dog.new(x)` builds one, `dog:speak()` passes the instance
1002
927
  // as `this`, and `super.speak()` is `Animal.speak(this)` — the
1003
928
  // base's own function, run on this instance.
1004
929
  // ============================================================
@@ -1024,6 +949,7 @@ var Lowerer = class {
1024
949
  classRuntimeStatements() {
1025
950
  if (!this.classHelpers) return [];
1026
951
  const { build, accessors } = this.classHelpers;
952
+ const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
1027
953
  return (0, import_luau_parser.parse)(`
1028
954
  local function ${build}(base)
1029
955
  local class = { __getters = {}, __setters = {} }
@@ -1034,6 +960,9 @@ local function ${build}(base)
1034
960
  setmetatable(class, { __index = base })
1035
961
  setmetatable(class.__getters, { __index = base.__getters })
1036
962
  setmetatable(class.__setters, { __index = base.__setters })
963
+ for _, key in ipairs(${metamethods}) do
964
+ class[key] = base[key]
965
+ end
1037
966
  end
1038
967
  return class
1039
968
  end
@@ -1096,6 +1025,7 @@ end
1096
1025
  }
1097
1026
  break;
1098
1027
  case "ClassMethod":
1028
+ if (member2.isAbstract) break;
1099
1029
  out2.push(this.functionStatement(
1100
1030
  member(self, member2.name.name),
1101
1031
  member2,
@@ -1117,7 +1047,7 @@ end
1117
1047
  }
1118
1048
  }
1119
1049
  out2.push(this.classInit(node, self));
1120
- out2.push(this.classNew(node, self));
1050
+ if (!node.isAbstract) out2.push(this.classNew(node, self));
1121
1051
  out2.push(callStatement(call(identifier(accessors), [self])));
1122
1052
  } finally {
1123
1053
  this.classContext = previous;
@@ -1395,7 +1325,11 @@ end
1395
1325
  }
1396
1326
  switch (node.type) {
1397
1327
  case "Identifier":
1398
- return this.reference(node);
1328
+ if (node.name === "scriptArgs" && this.isLanguageGlobal(node)) {
1329
+ this.usesScriptArgs = true;
1330
+ return identifier("scriptArgs");
1331
+ }
1332
+ return this.loweredGlobalValue(node) ?? this.reference(node);
1399
1333
  case "NilLiteral":
1400
1334
  return nil();
1401
1335
  case "BooleanLiteral":
@@ -1404,8 +1338,6 @@ end
1404
1338
  return number(node.value, this.numberRaw(node));
1405
1339
  case "StringLiteral":
1406
1340
  return string(node.value);
1407
- case "VarargExpression":
1408
- return vararg();
1409
1341
  // Only a recovering parse makes one, and a syntax error stops the build.
1410
1342
  case "ErrorExpression":
1411
1343
  this.report(node, "Syntax error");
@@ -1426,11 +1358,6 @@ end
1426
1358
  }
1427
1359
  case "UnaryExpression":
1428
1360
  return unary(node.operator, this.expression(node.argument));
1429
- case "NewExpression":
1430
- return call(
1431
- member(this.expression(node.callee), "new"),
1432
- this.values(node.arguments)
1433
- );
1434
1361
  case "SuperExpression":
1435
1362
  return this.superClassReference(node);
1436
1363
  case "SpreadElement":
@@ -1438,12 +1365,15 @@ end
1438
1365
  return nil();
1439
1366
  case "ClassExpression":
1440
1367
  return this.classExpression(node);
1441
- case "MemberExpression":
1442
- case "IndexExpression":
1443
1368
  case "CallExpression":
1444
- case "MethodCallExpression": {
1369
+ case "MethodCallExpression":
1370
+ return this.callValues(node);
1371
+ case "MemberExpression":
1372
+ case "IndexExpression": {
1445
1373
  const chain = optionalChain(node);
1446
1374
  if (chain) return this.optionalChainExpression(chain);
1375
+ const global = node.type === "MemberExpression" ? this.loweredGlobalValue(node) : void 0;
1376
+ if (global) return global;
1447
1377
  return this.link(node, this.expression(linkObject(node)));
1448
1378
  }
1449
1379
  case "ParenthesizedExpression":
@@ -1463,14 +1393,47 @@ end
1463
1393
  };
1464
1394
  }
1465
1395
  }
1396
+ /** A call. */
1397
+ callValues(node) {
1398
+ const chain = optionalChain(node);
1399
+ if (chain) return this.optionalChainExpression(chain);
1400
+ const object = linkObject(node);
1401
+ if (node.type === "CallExpression" && object.type === "Identifier") {
1402
+ return this.link(node, this.reference(object));
1403
+ }
1404
+ return this.link(node, this.expression(object));
1405
+ }
1406
+ /** A call is one value in tilua, and Luau would hand on every value one
1407
+ * in the last place of an argument list or an array returns. Types say
1408
+ * how many a call returns; where a type says nothing — `any`, `unknown`
1409
+ * — the call is kept to one: `f((g()))`. */
1410
+ keptToOne(nodes, values) {
1411
+ const node = nodes[nodes.length - 1];
1412
+ const last = values[values.length - 1];
1413
+ if (!node || !last || !expandsToMany(last)) return values;
1414
+ const type = this.options.types?.typeOf.get(node);
1415
+ if (type && type.kind !== "any" && type.kind !== "unknown") return values;
1416
+ return [...values.slice(0, -1), parenthesized(last)];
1417
+ }
1418
+ /** Is this `scriptArgs` the language's own, not a name the file declared? */
1419
+ isLanguageGlobal(node) {
1420
+ const id = this.bindingIdOf(node);
1421
+ return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1422
+ }
1466
1423
  /** A list of values — a call's arguments, a `return`'s, a declaration's,
1467
1424
  * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1468
1425
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1469
1426
  * the last value of a list expands, so the whole list is built as an
1470
- * array first and that is what expands. */
1471
- values(list) {
1427
+ * array first and that is what expands.
1428
+ *
1429
+ * `arguments` is set for a call's arguments, where Luau hands on every
1430
+ * value the last one returns — see `keptToOne`. */
1431
+ values(list, asArguments = false) {
1472
1432
  const spreads = list.filter((a) => a.type === "SpreadElement");
1473
- if (!spreads.length) return list.map((a) => this.expression(a));
1433
+ if (!spreads.length) {
1434
+ const lowered = list.map((a) => this.expression(a));
1435
+ return asArguments ? this.keptToOne(list, lowered) : lowered;
1436
+ }
1474
1437
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1475
1438
  const last = list[list.length - 1];
1476
1439
  if (spreads.length === 1 && last.type === "SpreadElement") {
@@ -1489,15 +1452,21 @@ end
1489
1452
  return member(object, node.property.name);
1490
1453
  case "IndexExpression":
1491
1454
  return index(object, this.expression(node.index));
1492
- case "CallExpression":
1493
- return call(object, this.values(node.arguments));
1455
+ case "CallExpression": {
1456
+ const lowered = this.loweredGlobalCall(node);
1457
+ const args2 = this.values(node.arguments, true);
1458
+ if (!lowered) return call(object, args2);
1459
+ return call(calleePath(lowered.callee), [...lowered.prepend, ...args2]);
1460
+ }
1494
1461
  case "MethodCallExpression": {
1495
- const consoleCall = this.consoleCall(node);
1496
- if (consoleCall) return consoleCall;
1497
- const args2 = this.values(node.arguments);
1498
1462
  const lowered = this.loweredMethodCall(node);
1463
+ const args2 = this.values(node.arguments, true);
1499
1464
  if (lowered) {
1500
- return call(calleePath(lowered.callee), lowered.passReceiver ? [object, ...args2] : args2);
1465
+ return call(calleePath(lowered.callee), [
1466
+ ...lowered.prepend,
1467
+ ...lowered.passReceiver ? [object] : [],
1468
+ ...args2
1469
+ ]);
1501
1470
  }
1502
1471
  if (!isLuauName(node.method.name)) {
1503
1472
  this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
@@ -1725,7 +1694,8 @@ end
1725
1694
  * runs of plain elements and the spread arrays are joined by `concat`. */
1726
1695
  arrayExpression(node) {
1727
1696
  if (!node.elements.some((e) => e.type === "SpreadElement")) {
1728
- return table(node.elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1697
+ const elements = node.elements;
1698
+ return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1729
1699
  }
1730
1700
  const parts = [];
1731
1701
  let current = [];
@@ -1910,10 +1880,6 @@ function mentions(expression, name) {
1910
1880
  walk(expression);
1911
1881
  return found;
1912
1882
  }
1913
- function isFunctionType(type) {
1914
- if (type.kind === "function") return true;
1915
- return type.kind === "intersection" && type.types.some((t) => t.kind === "function");
1916
- }
1917
1883
  function allNodes(root) {
1918
1884
  const out2 = [];
1919
1885
  const visit = (value) => {
@@ -1928,6 +1894,24 @@ function allNodes(root) {
1928
1894
  visit(root);
1929
1895
  return out2;
1930
1896
  }
1897
+ var CLASS_METAMETHODS = [
1898
+ "__add",
1899
+ "__sub",
1900
+ "__mul",
1901
+ "__div",
1902
+ "__idiv",
1903
+ "__mod",
1904
+ "__pow",
1905
+ "__unm",
1906
+ "__concat",
1907
+ "__len",
1908
+ "__eq",
1909
+ "__lt",
1910
+ "__le",
1911
+ "__call",
1912
+ "__tostring",
1913
+ "__iter"
1914
+ ];
1931
1915
 
1932
1916
  // src/linemap.ts
1933
1917
  function statementsOf(node) {
@@ -2037,16 +2021,19 @@ async function bundle(options) {
2037
2021
  const file = pending.shift();
2038
2022
  if (modules.has(file)) continue;
2039
2023
  const source = sources.get(file);
2024
+ const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
2040
2025
  const lowered = lower(source.program, source.scopes, {
2041
2026
  names,
2042
2027
  types: analysis.types.get(file),
2043
2028
  lowerings,
2044
2029
  target: options.target ?? config2?.target,
2045
2030
  source: source.text,
2031
+ file: shown,
2046
2032
  module: {
2047
2033
  name: source.name,
2048
2034
  require: requireExpression,
2049
2035
  lines: `${G}.lines`,
2036
+ fail: `${G}.fail`,
2050
2037
  resolve: (specifier) => {
2051
2038
  const target = (0, import_parser3.resolveModulePath)(file, specifier, config2);
2052
2039
  if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
@@ -2056,7 +2043,6 @@ async function bundle(options) {
2056
2043
  }
2057
2044
  });
2058
2045
  for (const d of lowered.diagnostics) diagnostics.push({ file, ...d, category: "module" });
2059
- const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
2060
2046
  for (const statement of allStatements(lowered.statements)) {
2061
2047
  const line = lowered.origins.get(statement);
2062
2048
  if (line !== void 0) origins.set(statement, { file: shown, line });
@@ -2065,7 +2051,8 @@ async function bundle(options) {
2065
2051
  name: source.name,
2066
2052
  statements: lowered.statements,
2067
2053
  exportsName: lowered.exportsName,
2068
- info: lowered.module
2054
+ info: lowered.module,
2055
+ usesScriptArgs: lowered.usesScriptArgs
2069
2056
  });
2070
2057
  }
2071
2058
  for (const [file, fileDirectives] of directives) {
@@ -2090,6 +2077,9 @@ async function bundle(options) {
2090
2077
  }
2091
2078
  const program = (0, import_luau_parser2.parse)(runtime(G));
2092
2079
  const assignment = program.body.statements[1];
2080
+ if ([...modules.values()].some((m) => m.usesScriptArgs)) {
2081
+ program.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
2082
+ }
2093
2083
  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;
2094
2084
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
2095
2085
  for (const module2 of modules.values()) {
@@ -2106,11 +2096,21 @@ async function bundle(options) {
2106
2096
  fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
2107
2097
  modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
2108
2098
  }
2109
- const start = call(requireExpression, [string(sources.get(entry2).name)]);
2110
- program.body.statements.push(modules.get(entry2).info.exports ? returns([start]) : callStatement(start));
2099
+ const ok = names.fresh("ok");
2100
+ const result = names.fresh("result");
2101
+ const entryStatements = (0, import_luau_parser2.parse)(`
2102
+ local ${ok}, ${result} = xpcall(function()
2103
+ return ${G}.require(${JSON.stringify(sources.get(entry2).name)})
2104
+ end, ${G}.fail)
2105
+ if not ${ok} then
2106
+ error(${result}, 0)
2107
+ end
2108
+ ${modules.get(entry2).info.exports ? `return ${result}` : ""}
2109
+ `).body.statements;
2110
+ program.body.statements.push(...entryStatements);
2111
2111
  const map = buildLineMap(program, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program)), origins);
2112
2112
  if (map.size) {
2113
- program.body.statements.splice(program.body.statements.length - 1, 0, assign(
2113
+ program.body.statements.splice(program.body.statements.length - entryStatements.length, 0, assign(
2114
2114
  [member(identifier(G), "lines")],
2115
2115
  [(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
2116
2116
  ));
@@ -2122,6 +2122,52 @@ function runtime(G) {
2122
2122
  local ${G}
2123
2123
  ${G} = {
2124
2124
  modules = {},
2125
+ -- Filled in just before the entry runs: bundle line -> { file, line }.
2126
+ lines = nil,
2127
+ -- What an error nothing caught says, in the project's files: the bundle's
2128
+ -- own positions in the message are replaced by the places they came from,
2129
+ -- and a traceback of those places follows. A message that is not a string
2130
+ -- is left as it is, and a position that is not this bundle's (a library
2131
+ -- that raised its error with one already mapped) is left alone.
2132
+ fail = function(message)
2133
+ if type(message) ~= "string" then
2134
+ return message
2135
+ end
2136
+ local lines = ${G}.lines
2137
+ if lines == nil then
2138
+ return debug.traceback(message, 2)
2139
+ end
2140
+ local chunk
2141
+ if debug.info ~= nil then
2142
+ chunk = debug.info(1, "s")
2143
+ else
2144
+ chunk = debug.getinfo(1, "S").short_src
2145
+ end
2146
+ local prefix = chunk:gsub("%p", "%%%0")
2147
+ local function place(line)
2148
+ local origin = lines[tonumber(line)]
2149
+ if origin == nil then
2150
+ return nil
2151
+ end
2152
+ return origin[1] .. ":" .. tostring(origin[2])
2153
+ end
2154
+ local mapped = message:gsub(prefix .. ":(%d+)", function(line)
2155
+ return place(line)
2156
+ end)
2157
+ local frames = {}
2158
+ for frame in debug.traceback("", 2):gmatch("[^\\n]+") do
2159
+ local line = frame:match(prefix .. ":(%d+)")
2160
+ local at = line ~= nil and place(line) or nil
2161
+ if at ~= nil then
2162
+ local name = frame:match("function ([%w_.:]+)")
2163
+ frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
2164
+ end
2165
+ end
2166
+ if #frames == 0 then
2167
+ return mapped
2168
+ end
2169
+ return mapped .. "\\n" .. table.concat(frames, "\\n")
2170
+ end,
2125
2171
  records = {},
2126
2172
  require = function(name)
2127
2173
  local record = ${G}.records[name]