@tilua/compiler 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -35,7 +35,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
35
35
  // src/bundle.ts
36
36
  var import_node_fs2 = require("fs");
37
37
  var import_node_path2 = require("path");
38
- var import_parser3 = require("@tilua/parser");
38
+ var import_parser6 = require("@tilua/parser");
39
39
  var import_luau_parser2 = require("luau-parser");
40
40
 
41
41
  // src/config.ts
@@ -50,10 +50,11 @@ function resolveConfig(input, entry, cwd = process.cwd()) {
50
50
  }
51
51
 
52
52
  // src/lower.ts
53
- var import_parser2 = require("@tilua/parser");
53
+ var import_parser4 = require("@tilua/parser");
54
54
  var import_luau_parser = require("luau-parser");
55
55
 
56
56
  // src/luau.ts
57
+ var import_parser2 = require("@tilua/parser");
57
58
  var NO_SPAN = { line: { start: 0, end: 0 }, column: { start: 0, end: 0 } };
58
59
  var span = () => ({ line: { ...NO_SPAN.line }, column: { ...NO_SPAN.column } });
59
60
  var identifier = (name) => ({ type: "Identifier", name, ...span() });
@@ -61,7 +62,7 @@ var nil = () => ({ type: "NilLiteral", ...span() });
61
62
  var boolean = (value) => ({ type: "BooleanLiteral", value, ...span() });
62
63
  var number = (value, raw = String(value)) => ({ type: "NumberLiteral", value, raw, ...span() });
63
64
  var string = (value) => ({ type: "StringLiteral", value, raw: "", ...span() });
64
- var member = (object2, property) => isLuauName(property) ? { type: "MemberExpression", object: object2, property: identifier(property), ...span() } : index(object2, string(property));
65
+ var member = (object2, property) => (0, import_parser2.isLuauName)(property) ? { type: "MemberExpression", object: object2, property: identifier(property), ...span() } : index(object2, string(property));
65
66
  var index = (object2, key) => ({ type: "IndexExpression", object: object2, index: key, ...span() });
66
67
  var call = (callee, args) => ({ type: "CallExpression", callee, arguments: args, ...span() });
67
68
  var methodCall = (object2, method, args) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args, ...span() });
@@ -70,7 +71,7 @@ var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, l
70
71
  var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
71
72
  var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
72
73
  var table = (fields) => ({ type: "TableExpression", fields, ...span() });
73
- var field = (key, value) => isLuauName(key) ? { type: "TableFieldNamed", name: identifier(key), value } : { type: "TableFieldComputed", key: string(key), value };
74
+ var field = (key, value) => (0, import_parser2.isLuauName)(key) ? { type: "TableFieldNamed", name: identifier(key), value } : { type: "TableFieldComputed", key: string(key), value };
74
75
  var block = (statements) => ({ type: "Block", statements, ...span() });
75
76
  var local = (names, init) => ({
76
77
  type: "LocalStatement",
@@ -114,32 +115,6 @@ var functionBody = (params, statements, hasVarargs = false) => ({
114
115
  });
115
116
  var localFunction = (name, func) => ({ type: "LocalFunctionStatement", name: identifier(name), func, ...span() });
116
117
  var program = (statements) => ({ type: "Program", body: block(statements), ...span() });
117
- var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
118
- "and",
119
- "break",
120
- "do",
121
- "else",
122
- "elseif",
123
- "end",
124
- "false",
125
- "for",
126
- "function",
127
- "if",
128
- "in",
129
- "local",
130
- "nil",
131
- "not",
132
- "or",
133
- "repeat",
134
- "return",
135
- "then",
136
- "true",
137
- "until",
138
- "while"
139
- ]);
140
- function isLuauName(name) {
141
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
142
- }
143
118
 
144
119
  // src/names.ts
145
120
  var Names = class _Names {
@@ -182,6 +157,7 @@ var Names = class _Names {
182
157
 
183
158
  // src/language.ts
184
159
  var import_node_fs = require("fs");
160
+ var import_parser3 = require("@tilua/parser");
185
161
  var read = (name) => (0, import_node_fs.readFileSync)(new URL(`../runtime/${name}`, importMetaUrl), "utf8");
186
162
  var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\w+)/g)].map((m) => m[1]));
187
163
  var array = read("array.luau");
@@ -215,7 +191,7 @@ function objectKeys(type) {
215
191
  orders.push(keys);
216
192
  }
217
193
  const order = JSON.stringify(orders[0] ?? []);
218
- const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) => JSON.stringify(key)).join(", ")} }` : "nil";
194
+ const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) => (0, import_parser3.luauString)(key)).join(", ")} }` : "nil";
219
195
  return { known, open: String(open) };
220
196
  }
221
197
  var plugin = {
@@ -274,11 +250,11 @@ var Lowerer = class {
274
250
  origins = /* @__PURE__ */ new WeakMap();
275
251
  run() {
276
252
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
253
+ const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
277
254
  const helpers = this.helperDefinitions();
278
255
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
279
- const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
280
256
  return {
281
- statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
257
+ statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
282
258
  exportsName: this.exportsName,
283
259
  module: this.info,
284
260
  diagnostics: this.diagnostics,
@@ -296,7 +272,7 @@ var Lowerer = class {
296
272
  // --------------------------------------------------------
297
273
  /** A tilua name as Luau can write it: `local` is a keyword there. */
298
274
  name(name) {
299
- if (!LUAU_KEYWORDS.has(name)) return name;
275
+ if (!import_parser2.LUAU_KEYWORDS.has(name)) return name;
300
276
  let renamed = this.renamed.get(name);
301
277
  if (!renamed) {
302
278
  renamed = this.names.fresh(`${name}_`);
@@ -328,11 +304,36 @@ var Lowerer = class {
328
304
  helper(kind) {
329
305
  let name = this.helpers.get(kind);
330
306
  if (!name) {
331
- name = this.names.fresh(`tilua_${kind}`);
307
+ name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
332
308
  this.helpers.set(kind, name);
333
309
  }
334
310
  return identifier(name);
335
311
  }
312
+ // `typeof v` tells an array from an object, which Luau cannot: both are
313
+ // tables. So every array the program makes — `[a, b]`, a rest parameter,
314
+ // what `:map` answers — is written down as one, in a table that holds its
315
+ // keys weakly, and `typeof` looks there. A table nothing wrote down (one a
316
+ // Lua library made) is told by its type where that says, and by its shape
317
+ // where it does not.
318
+ /** `value`, a new array, written down as one. */
319
+ array(value) {
320
+ const marked = call(this.helper("array"), [value]);
321
+ this.marked.add(marked);
322
+ return marked;
323
+ }
324
+ /** The calls `array` made: each answers the one table it was given. */
325
+ marked = /* @__PURE__ */ new WeakSet();
326
+ /** Luau reaching the record of arrays: the bundle's, or this file's own. */
327
+ arrays() {
328
+ return this.options.module?.arrays ?? this.helper("arrays").name;
329
+ }
330
+ typeofExpression(node) {
331
+ const known = this.options.types?.typeofTables.get(node);
332
+ let argument = node.argument;
333
+ while (argument.type === "ParenthesizedExpression") argument = argument.expression;
334
+ const value = this.expression(argument);
335
+ return call(this.helper("typeof"), known ? [value, string(known)] : [this.keptToOne(value)]);
336
+ }
336
337
  /** The local a library's runtime table gets in this file, emitting it the
337
338
  * first time something asks. Keyed by library and by the key the library
338
339
  * itself chose, so two libraries' tables never collide. */
@@ -400,7 +401,7 @@ var Lowerer = class {
400
401
  const type = this.options.types?.typeOf.get(argument);
401
402
  return {
402
403
  type,
403
- typeText: type ? (0, import_parser2.formatType)(type) : void 0,
404
+ typeText: type ? (0, import_parser4.formatType)(type) : void 0,
404
405
  source: this.writtenSource(argument),
405
406
  declaration: declaration ? this.writtenSource(declaration) : void 0,
406
407
  spread: argument.type === "SpreadElement"
@@ -527,6 +528,46 @@ var Lowerer = class {
527
528
  }
528
529
  helperDefinitions() {
529
530
  const out = [];
531
+ const runtimes = this.runtimeDefinitions();
532
+ if (this.helpers.has("concat")) this.helper("array");
533
+ const marker = this.helpers.get("array");
534
+ const typeOf = this.helpers.get("typeof");
535
+ if (marker || typeOf) {
536
+ const arrays = this.arrays();
537
+ const own = this.helpers.get("arrays");
538
+ if (own) {
539
+ out.push(...(0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
540
+ }
541
+ if (marker) {
542
+ out.push(...(0, import_luau_parser.parse)(`
543
+ local function ${marker}(t)
544
+ ${arrays}[t] = true
545
+ return t
546
+ end`).body.statements);
547
+ }
548
+ if (typeOf) {
549
+ out.push(...(0, import_luau_parser.parse)(`
550
+ local function ${typeOf}(value, known)
551
+ local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
552
+ if name ~= "table" then
553
+ return name
554
+ end
555
+ if known ~= nil then
556
+ return known
557
+ end
558
+ if ${arrays}[value] then
559
+ return "array"
560
+ end
561
+ if ${this.builtin("getmetatable").name}(value) ~= nil then
562
+ return "object"
563
+ end
564
+ if #value > 0 then
565
+ return "array"
566
+ end
567
+ return "object"
568
+ end`).body.statements);
569
+ }
570
+ }
530
571
  const assign2 = this.helpers.get("assign");
531
572
  if (assign2) {
532
573
  out.push(localFunction(assign2, functionBody(["target"], [
@@ -541,25 +582,11 @@ var Lowerer = class {
541
582
  returns([identifier("target")])
542
583
  ], true)));
543
584
  }
544
- for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
545
- const { plugin: plugin2, from } = this.lowerings[index2];
546
- const source = plugin2.runtime?.[runtime2];
547
- if (source === void 0) {
548
- this.diagnostics.push({
549
- message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
550
- line: 1,
551
- column: 1
552
- });
553
- continue;
554
- }
555
- const lines = this.options.module?.lines ?? "nil";
556
- const fail = this.options.module?.fail ?? "nil";
557
- out.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
558
- }
585
+ out.push(...runtimes);
559
586
  const concat = this.helpers.get("concat");
560
587
  if (concat) {
561
588
  out.push(localFunction(concat, functionBody([], [
562
- local(["result"], [table([])]),
589
+ local(["result"], [this.array(table([]))]),
563
590
  numericFor("i", number(1), selectCount(this.builtin("select")), [
564
591
  local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
565
592
  callStatement(call(member(this.builtin("table"), "move"), [
@@ -575,6 +602,26 @@ var Lowerer = class {
575
602
  }
576
603
  return out;
577
604
  }
605
+ /** The runtime tables the file's libraries were asked for. */
606
+ runtimeDefinitions() {
607
+ const out = [];
608
+ for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
609
+ const { plugin: plugin2, from } = this.lowerings[index2];
610
+ const source = plugin2.runtime?.[runtime2];
611
+ if (source === void 0) {
612
+ this.diagnostics.push({
613
+ message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
614
+ line: 1,
615
+ column: 1
616
+ });
617
+ continue;
618
+ }
619
+ const lines = this.options.module?.lines ?? "nil";
620
+ const fail = this.options.module?.fail ?? "nil";
621
+ out.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);
622
+ }
623
+ return out;
624
+ }
578
625
  // --------------------------------------------------------
579
626
  // Modules
580
627
  // --------------------------------------------------------
@@ -1327,7 +1374,7 @@ end
1327
1374
  number(pattern.elements.length + 1),
1328
1375
  unary("#", source),
1329
1376
  number(1),
1330
- table([])
1377
+ this.array(table([]))
1331
1378
  ]);
1332
1379
  const rest = this.restTarget(pattern.rest, elements, mode, after);
1333
1380
  after.push(...rest.then);
@@ -1381,7 +1428,7 @@ end
1381
1428
  const names = [];
1382
1429
  for (const p of params) {
1383
1430
  if (p.rest) {
1384
- prelude.push(local([this.name(p.name)], [table([{ type: "TableFieldPositional", value: vararg() }])]));
1431
+ prelude.push(local([this.name(p.name)], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))]));
1385
1432
  continue;
1386
1433
  }
1387
1434
  const name = p.pattern ? this.names.fresh("arg") : this.name(p.name);
@@ -1439,6 +1486,7 @@ end
1439
1486
  return binary(node.operator, left, right);
1440
1487
  }
1441
1488
  case "UnaryExpression":
1489
+ if (node.operator === "typeof") return this.typeofExpression(node);
1442
1490
  return unary(node.operator, this.expression(node.argument));
1443
1491
  case "SuperExpression":
1444
1492
  return this.superClassReference(node);
@@ -1479,6 +1527,9 @@ end
1479
1527
  callValues(node) {
1480
1528
  const chain = optionalChain(node);
1481
1529
  if (chain) return this.optionalChainExpression(chain);
1530
+ if (node.type === "CallExpression" && (node.callee.type === "SuperExpression" || node.callee.type === "MemberExpression" && node.callee.object.type === "SuperExpression")) {
1531
+ return this.expression(node);
1532
+ }
1482
1533
  const object2 = linkObject(node);
1483
1534
  if (node.type === "CallExpression" && object2.type === "Identifier") {
1484
1535
  return this.link(node, this.reference(object2));
@@ -1490,7 +1541,7 @@ end
1490
1541
  * kept to one there, `f((g()))`. Every value is taken only where the
1491
1542
  * program asks for them, as an array's last element: `[g()]`. */
1492
1543
  keptToOne(value) {
1493
- return expandsToMany(value) ? parenthesized(value) : value;
1544
+ return expandsToMany(value) && !this.marked.has(value) ? parenthesized(value) : value;
1494
1545
  }
1495
1546
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1496
1547
  isLanguageGlobal(node) {
@@ -1544,7 +1595,7 @@ end
1544
1595
  ...args
1545
1596
  ]);
1546
1597
  }
1547
- if (!isLuauName(node.method.name)) {
1598
+ if (!import_parser2.isLuauName(node.method.name)) {
1548
1599
  this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
1549
1600
  }
1550
1601
  return methodCall(object2, node.method.name, args);
@@ -1771,7 +1822,7 @@ end
1771
1822
  arrayExpression(node) {
1772
1823
  const elements = packs(node.elements).items;
1773
1824
  if (!elements.some((e) => e.type === "SpreadElement")) {
1774
- return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1825
+ return this.array(table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) }))));
1775
1826
  }
1776
1827
  const parts = [];
1777
1828
  let current = [];
@@ -1906,7 +1957,7 @@ function topLevelDeclaration(statements, name) {
1906
1957
  function moduleName(key) {
1907
1958
  const last = key.split("/").filter(Boolean).pop() ?? "module";
1908
1959
  const cleaned = last.replace(/[^A-Za-z0-9_]/g, "_");
1909
- return /^[A-Za-z_]/.test(cleaned) && !LUAU_KEYWORDS.has(cleaned) ? cleaned : `module_${cleaned}`;
1960
+ return /^[A-Za-z_]/.test(cleaned) && !import_parser2.LUAU_KEYWORDS.has(cleaned) ? cleaned : `module_${cleaned}`;
1910
1961
  }
1911
1962
  function alwaysTruthy(value) {
1912
1963
  switch (value.type) {
@@ -2021,6 +2072,7 @@ var CLASS_METAMETHODS = [
2021
2072
  ];
2022
2073
 
2023
2074
  // src/linemap.ts
2075
+ var import_parser5 = require("@tilua/parser");
2024
2076
  function statementsOf(node) {
2025
2077
  const out = [];
2026
2078
  const visit = (value) => {
@@ -2053,7 +2105,7 @@ function buildLineMap(emitted, printed, origins) {
2053
2105
  }
2054
2106
  function lineMapSource(map) {
2055
2107
  const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
2056
- const quoted = (s) => JSON.stringify(s.replace(/\\/g, "/"));
2108
+ const quoted = (s) => (0, import_parser5.luauString)(s.replace(/\\/g, "/"));
2057
2109
  return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
2058
2110
  }
2059
2111
 
@@ -2102,13 +2154,13 @@ async function bundle(options) {
2102
2154
  const parsed = parseFile(file, diagnostics, directives);
2103
2155
  if (!parsed) return { modules: [], diagnostics };
2104
2156
  const { program: program3, text } = parsed;
2105
- const scopes = (0, import_parser3.analyzeScopes)(program3);
2157
+ const scopes = (0, import_parser6.analyzeScopes)(program3);
2106
2158
  for (const d of scopes.diagnostics) {
2107
2159
  diagnostics.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "scope" });
2108
2160
  }
2109
2161
  sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
2110
2162
  for (const specifier of importedSpecifiers(program3)) {
2111
- const target = (0, import_parser3.resolveModulePath)(file, specifier, config);
2163
+ const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
2112
2164
  if (target && !target.endsWith(".d.tilua")) queue.push(target);
2113
2165
  }
2114
2166
  }
@@ -2141,8 +2193,9 @@ async function bundle(options) {
2141
2193
  require: requireExpression,
2142
2194
  lines: `${G}.lines`,
2143
2195
  fail: `${G}.fail`,
2196
+ arrays: `${G}.arrays`,
2144
2197
  resolve: (specifier) => {
2145
- const target = (0, import_parser3.resolveModulePath)(file, specifier, config);
2198
+ const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
2146
2199
  if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
2147
2200
  pending.push(target);
2148
2201
  return sources.get(target).name;
@@ -2164,14 +2217,14 @@ async function bundle(options) {
2164
2217
  }
2165
2218
  for (const [file, fileDirectives] of directives) {
2166
2219
  const semantic = diagnostics.filter((d) => d.file === file && (d.category === "scope" || d.category === "type"));
2167
- const { kept, unusedExpectErrors } = (0, import_parser3.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2220
+ const { kept, unusedExpectErrors } = (0, import_parser6.applyDirectives)(fileDirectives, semantic, (d) => d.line);
2168
2221
  const suppressed = new Set(semantic.filter((d) => !kept.includes(d)));
2169
2222
  const remaining = diagnostics.filter((d) => !suppressed.has(d));
2170
2223
  diagnostics.length = 0;
2171
2224
  diagnostics.push(...remaining);
2172
2225
  if (options.typeCheck === false) continue;
2173
2226
  for (const d of unusedExpectErrors) {
2174
- diagnostics.push({ file, message: import_parser3.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2227
+ diagnostics.push({ file, message: import_parser6.UNUSED_EXPECT_ERROR, line: d.line, column: d.column, category: "type" });
2175
2228
  }
2176
2229
  }
2177
2230
  const moduleProblems = new Set(diagnostics.filter((d) => d.category === "module").map((d) => `${d.file}:${d.line}:${d.column}:${d.message}`));
@@ -2186,6 +2239,7 @@ async function bundle(options) {
2186
2239
  const assignment = program2.body.statements[1];
2187
2240
  if ([...modules.values()].some((m) => m.usesScriptArgs)) {
2188
2241
  program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
2242
+ program2.body.statements.push(...(0, import_luau_parser2.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
2189
2243
  }
2190
2244
  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;
2191
2245
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
@@ -2207,7 +2261,7 @@ async function bundle(options) {
2207
2261
  const result = names.fresh("result");
2208
2262
  const entryStatements = (0, import_luau_parser2.parse)(`
2209
2263
  local ${ok}, ${result} = xpcall(function()
2210
- return ${G}.require(${JSON.stringify(sources.get(entry).name)})
2264
+ return ${G}.require(${(0, import_parser6.luauString)(sources.get(entry).name)})
2211
2265
  end, ${G}.fail)
2212
2266
  if not ${ok} then
2213
2267
  error(${result}, 0)
@@ -2229,6 +2283,9 @@ function runtime(G) {
2229
2283
  local ${G}
2230
2284
  ${G} = {
2231
2285
  modules = {},
2286
+ -- Every array the program made, so \`typeof\` can tell one from an object.
2287
+ -- The keys are held weakly: being written down keeps no array alive.
2288
+ arrays = setmetatable({}, { __mode = "k" }),
2232
2289
  -- Filled in just before the entry runs: bundle line -> { file, line }.
2233
2290
  lines = nil,
2234
2291
  -- What an error nothing caught says, in the project's files: the bundle's
@@ -2236,19 +2293,64 @@ ${G} = {
2236
2293
  -- and a traceback of those places follows. A message that is not a string
2237
2294
  -- is left as it is, and a position that is not this bundle's (a library
2238
2295
  -- that raised its error with one already mapped) is left alone.
2296
+ --
2297
+ -- It is an \`xpcall\` handler, and a handler that fails loses the error
2298
+ -- altogether: Luau reports only "error in error handling". So the work is
2299
+ -- done under \`pcall\`, leaning on nothing it has not checked is there (an
2300
+ -- executor may lack \`debug.info\`, or refuse it), and an error it cannot
2301
+ -- map is still answered as Luau raised it, with why.
2239
2302
  fail = function(message)
2303
+ -- Taken first, before anything that can fail. Level 3 is the frame
2304
+ -- that raised: past \`pcall\` and this handler.
2305
+ local trace
2306
+ if debug ~= nil and debug.traceback ~= nil then
2307
+ local ok, text = pcall(debug.traceback, "", 3)
2308
+ if ok and type(text) == "string" then
2309
+ trace = text
2310
+ end
2311
+ end
2312
+ local ok, described = pcall(${G}.describe, message, trace)
2313
+ if ok then
2314
+ return described
2315
+ end
2316
+ if type(message) ~= "string" then
2317
+ return message
2318
+ end
2319
+ local why = type(described) == "string" and described or "unknown"
2320
+ return message .. "\\n(tilua could not map this error to the project's files: " .. why .. ")" .. (trace or "")
2321
+ end,
2322
+ -- \`fail\`'s work. \`trace\` is a traceback from where the error was
2323
+ -- raised, or nil when none could be taken.
2324
+ describe = function(message, trace)
2240
2325
  if type(message) ~= "string" then
2241
2326
  return message
2242
2327
  end
2243
2328
  local lines = ${G}.lines
2244
2329
  if lines == nil then
2245
- return debug.traceback(message, 2)
2330
+ return message .. (trace or "")
2246
2331
  end
2332
+ -- The bundle's name, as a position in a message spells it: where this
2333
+ -- function is, asked of the debug library by the function rather
2334
+ -- than by a level, which \`pcall\` would shift. Failing that, a message
2335
+ -- starts with the position it was raised at.
2247
2336
  local chunk
2248
- if debug.info ~= nil then
2249
- chunk = debug.info(1, "s")
2250
- else
2251
- chunk = debug.getinfo(1, "S").short_src
2337
+ if debug ~= nil and debug.info ~= nil then
2338
+ local found, source = pcall(debug.info, ${G}.describe, "s")
2339
+ if found and type(source) == "string" then
2340
+ chunk = source
2341
+ end
2342
+ end
2343
+ if chunk == nil and debug ~= nil and debug.getinfo ~= nil then
2344
+ local found, info = pcall(debug.getinfo, ${G}.describe, "S")
2345
+ if found and type(info) == "table" and type(info.short_src) == "string" then
2346
+ chunk = info.short_src
2347
+ end
2348
+ end
2349
+ if chunk == nil then
2350
+ chunk = message:match("^(.-):%d+:")
2351
+ end
2352
+ if chunk == nil then
2353
+ return message .. (trace or "")
2252
2354
  end
2253
2355
  local prefix = chunk:gsub("%p", "%%%0")
2254
2356
  local function place(line)
@@ -2262,7 +2364,7 @@ ${G} = {
2262
2364
  return place(line)
2263
2365
  end)
2264
2366
  local frames = {}
2265
- for frame in debug.traceback("", 2):gmatch("[^\\n]+") do
2367
+ for frame in (trace or ""):gmatch("[^\\n]+") do
2266
2368
  local line = frame:match(prefix .. ":(%d+)")
2267
2369
  local at = line ~= nil and place(line) or nil
2268
2370
  if at ~= nil then
@@ -2347,13 +2449,13 @@ function parseFile(file, diagnostics, directives) {
2347
2449
  return void 0;
2348
2450
  }
2349
2451
  try {
2350
- const program2 = (0, import_parser3.parse)(text);
2351
- const found = (0, import_parser3.directivesOf)(text);
2452
+ const program2 = (0, import_parser6.parse)(text);
2453
+ const found = (0, import_parser6.directivesOf)(text);
2352
2454
  directives.set(file, found);
2353
2455
  if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
2354
2456
  return { program: program2, text };
2355
2457
  } catch (error) {
2356
- if (error instanceof import_parser3.ParseError || error instanceof import_parser3.LexError) {
2458
+ if (error instanceof import_parser6.ParseError || error instanceof import_parser6.LexError) {
2357
2459
  const { line, column } = error;
2358
2460
  diagnostics.push({ file, message: error.message, line, column, category: "syntax" });
2359
2461
  return void 0;
@@ -2375,7 +2477,7 @@ function parsedLibrary(file) {
2375
2477
  }
2376
2478
  const cached = libraryCache.get(file);
2377
2479
  if (cached && cached.stamp === stamp) return cached.program;
2378
- const program2 = (0, import_parser3.parse)((0, import_node_fs2.readFileSync)(file, "utf8"));
2480
+ const program2 = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(file, "utf8"));
2379
2481
  libraryCache.set(file, { stamp, program: program2 });
2380
2482
  return program2;
2381
2483
  }
@@ -2383,7 +2485,7 @@ var moduleAnalyses = /* @__PURE__ */ new Map();
2383
2485
  function analyzeModules(modules, config) {
2384
2486
  const out = [];
2385
2487
  const analyses = /* @__PURE__ */ new Map();
2386
- const libraries = config ? (0, import_parser3.resolveTypeLibraries)(config) : { files: [], lowerings: [], problems: [] };
2488
+ const libraries = config ? (0, import_parser6.resolveTypeLibraries)(config) : { files: [], lowerings: [], problems: [] };
2387
2489
  for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
2388
2490
  const libs = libraries.files.map(parsedLibrary);
2389
2491
  const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
@@ -2403,14 +2505,14 @@ function analyzeModules(modules, config) {
2403
2505
  readWhile.set(file, remembered.read);
2404
2506
  return remembered.result;
2405
2507
  }
2406
- const scopes = (0, import_parser3.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2508
+ const scopes = (0, import_parser6.analyzeScopes)(program2, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2407
2509
  const said = [];
2408
2510
  for (const d of scopes.diagnostics) {
2409
2511
  if (d.kind !== "undeclared") continue;
2410
2512
  said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
2411
2513
  }
2412
2514
  const before = partials;
2413
- const types = (0, import_parser3.analyzeTypes)(program2, scopes, {
2515
+ const types = (0, import_parser6.analyzeTypes)(program2, scopes, {
2414
2516
  libs,
2415
2517
  resolveModule: resolverFor(file),
2416
2518
  reportUnknownTypes: libs.length > 0
@@ -2433,7 +2535,7 @@ function analyzeModules(modules, config) {
2433
2535
  return result;
2434
2536
  };
2435
2537
  const resolverFor = (file) => (specifier) => {
2436
- const target = (0, import_parser3.resolveModulePath)(file, specifier, config);
2538
+ const target = (0, import_parser6.resolveModulePath)(file, specifier, config);
2437
2539
  if (!target) return void 0;
2438
2540
  const noteRead = () => {
2439
2541
  const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
@@ -2454,16 +2556,16 @@ function analyzeModules(modules, config) {
2454
2556
  let program2 = byFile.get(target)?.program;
2455
2557
  if (!program2) {
2456
2558
  try {
2457
- program2 = (0, import_parser3.parse)((0, import_node_fs2.readFileSync)(target, "utf8"));
2559
+ program2 = (0, import_parser6.parse)((0, import_node_fs2.readFileSync)(target, "utf8"));
2458
2560
  } catch {
2459
2561
  return void 0;
2460
2562
  }
2461
2563
  }
2462
2564
  inProgress.add(target);
2463
2565
  try {
2464
- const scopes = (0, import_parser3.analyzeScopes)(program2, { builtinGlobals: globals });
2465
- const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser3.analyzeTypes)(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2466
- const exports2 = (0, import_parser3.moduleExports)(program2, scopes, types, resolverFor(target));
2566
+ const scopes = (0, import_parser6.analyzeScopes)(program2, { builtinGlobals: globals });
2567
+ const types = byFile.has(target) ? analyzeModule(target, program2).types : (0, import_parser6.analyzeTypes)(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2568
+ const exports2 = (0, import_parser6.moduleExports)(program2, scopes, types, resolverFor(target));
2467
2569
  exportsCache.set(target, exports2);
2468
2570
  noteRead();
2469
2571
  return exports2;
@@ -2498,35 +2600,35 @@ function allStatements(statements) {
2498
2600
  // src/compile.ts
2499
2601
  var import_node_fs3 = require("fs");
2500
2602
  var import_node_path3 = require("path");
2501
- var import_parser4 = require("@tilua/parser");
2603
+ var import_parser7 = require("@tilua/parser");
2502
2604
  var import_luau_parser3 = require("luau-parser");
2503
2605
  async function compile(source, config) {
2504
2606
  let program2;
2505
2607
  try {
2506
- program2 = (0, import_parser4.parse)(source);
2608
+ program2 = (0, import_parser7.parse)(source);
2507
2609
  } catch (error) {
2508
- if (error instanceof import_parser4.ParseError || error instanceof import_parser4.LexError) {
2610
+ if (error instanceof import_parser7.ParseError || error instanceof import_parser7.LexError) {
2509
2611
  const { line, column } = error;
2510
2612
  return { diagnostics: [{ message: error.message, line, column }] };
2511
2613
  }
2512
2614
  throw error;
2513
2615
  }
2514
- const scopes = (0, import_parser4.analyzeScopes)(program2);
2616
+ const scopes = (0, import_parser7.analyzeScopes)(program2);
2515
2617
  const diagnostics = scopes.diagnostics.map((d) => ({
2516
2618
  message: d.message,
2517
2619
  line: d.node.line.start,
2518
2620
  column: d.node.column.start
2519
2621
  }));
2520
2622
  const { config: resolved, problems } = resolveConfig(config, (0, import_node_path3.resolve)("main.tilua"));
2521
- const libraries = resolved ? (0, import_parser4.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
2623
+ const libraries = resolved ? (0, import_parser7.resolveTypeLibraries)(resolved) : { files: [], lowerings: [], problems: [] };
2522
2624
  const { lowerings, problems: loweringProblems } = await loadLowerings(libraries.lowerings);
2523
2625
  for (const p of [...problems, ...libraries.problems]) {
2524
2626
  diagnostics.push({ message: p.message, line: p.line ?? 1, column: p.column ?? 1 });
2525
2627
  }
2526
2628
  for (const p of loweringProblems) diagnostics.push({ message: p.message, line: 1, column: 1 });
2527
- const libs = libraries.files.map((file) => (0, import_parser4.parse)((0, import_node_fs3.readFileSync)(file, "utf8")));
2629
+ const libs = libraries.files.map((file) => (0, import_parser7.parse)((0, import_node_fs3.readFileSync)(file, "utf8")));
2528
2630
  const lowered = lower(program2, scopes, {
2529
- types: (0, import_parser4.analyzeTypes)(program2, scopes, { libs, diagnostics: false }),
2631
+ types: (0, import_parser7.analyzeTypes)(program2, scopes, { libs, diagnostics: false }),
2530
2632
  lowerings,
2531
2633
  target: resolved?.target,
2532
2634
  source
package/dist/index.d.cts CHANGED
@@ -100,6 +100,9 @@ interface ModuleContext {
100
100
  * reads, as `__FAIL__`, to report an error from a callback the way an
101
101
  * error that reached the entry is reported. */
102
102
  readonly fail?: string;
103
+ /** Luau reaching the bundle's record of which tables are arrays, so that
104
+ * every module marks and reads the same one (`typeof`). */
105
+ readonly arrays?: string;
103
106
  }
104
107
  interface LowerOptions {
105
108
  /** Lower the file as a module of a bundle. Without it, `import` and
package/dist/index.d.ts CHANGED
@@ -100,6 +100,9 @@ interface ModuleContext {
100
100
  * reads, as `__FAIL__`, to report an error from a callback the way an
101
101
  * error that reached the entry is reported. */
102
102
  readonly fail?: string;
103
+ /** Luau reaching the bundle's record of which tables are arrays, so that
104
+ * every module marks and reads the same one (`typeof`). */
105
+ readonly arrays?: string;
103
106
  }
104
107
  interface LowerOptions {
105
108
  /** Lower the file as a module of a bundle. Without it, `import` and
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  lower,
5
5
  program,
6
6
  resolveConfig
7
- } from "./chunk-X4DM2BX2.js";
7
+ } from "./chunk-VZ7XC4BG.js";
8
8
 
9
9
  // src/compile.ts
10
10
  import { readFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tilua/compiler",
3
- "version": "3.0.1",
3
+ "version": "4.0.0",
4
4
  "description": "Compiles tilua to Luau: lowers the @tilua/parser AST to a luau-parser AST and prints it",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -38,7 +38,7 @@
38
38
  "type": "module",
39
39
  "dependencies": {
40
40
  "luau-parser": "^1.0.3",
41
- "@tilua/parser": "^3.0.1"
41
+ "@tilua/parser": "^4.0.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^22.20.2",