@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/README.md +24 -3
- package/dist/{chunk-X4DM2BX2.js → chunk-VZ7XC4BG.js} +162 -59
- package/dist/cli.cjs +185 -83
- package/dist/cli.js +1 -1
- package/dist/index.cjs +192 -90
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/runtime/array.luau +5 -5
- package/runtime/object.luau +43 -4
- package/runtime/string.luau +2 -2
package/README.md
CHANGED
|
@@ -54,9 +54,10 @@ JavaScript module the build loads.
|
|
|
54
54
|
| tilua | Luau |
|
|
55
55
|
|---|---|
|
|
56
56
|
| `const { a, b } = value` | `local a, b = value.a, value.b` |
|
|
57
|
-
| `const [x, ...rest] = list` | `local x = list[1]; local rest = table.move(list, 2, #list, 1, {})` |
|
|
57
|
+
| `const [x, ...rest] = list` | `local x = list[1]; local rest = table.move(list, 2, #list, 1, {})` — `{}` written down as an array |
|
|
58
58
|
| `const { a = 1 } = t` | `local a = t.a; if a == nil then a = 1 end` |
|
|
59
|
-
| `[1, 2]` / `{ a: 1, b }` | `{ 1, 2 }` / `{ a = 1, b = b }` |
|
|
59
|
+
| `[1, 2]` / `{ a: 1, b }` | `tilua_asArray({ 1, 2 })` / `{ a = 1, b = b }` — see `typeof` below |
|
|
60
|
+
| `typeof v` | `tilua_typeof(v)`, or `tilua_typeof(v, "array")` where `v`'s type already says which table it is |
|
|
60
61
|
| `{ ...base, a: 1 }` / `[...xs, 1]` | a small helper copying the parts in order |
|
|
61
62
|
| `` `${a} any` `` | `("%s any"):format(tostring(a))` |
|
|
62
63
|
| `a?.b` | `if a == nil then nil else a.b` |
|
|
@@ -77,6 +78,26 @@ JavaScript module the build loads.
|
|
|
77
78
|
| `x as T`, `x satisfies T`, types, `declare` | removed |
|
|
78
79
|
| `names:filter(f)` | whatever the type library that declared `filter` says — see below |
|
|
79
80
|
|
|
81
|
+
## `typeof`, arrays and objects
|
|
82
|
+
|
|
83
|
+
Luau cannot tell an array from an object: both are tables, and an empty one
|
|
84
|
+
has nothing to go by. So every array the program makes is written down as
|
|
85
|
+
one where it is made — `[a, b]`, a rest parameter, `scriptArgs`, what
|
|
86
|
+
`:map`, `:filter`, `:keys` and the rest answer — in a table that holds its
|
|
87
|
+
keys weakly, one for the whole bundle. `tilua_asArray(t)` does that and hands
|
|
88
|
+
`t` back.
|
|
89
|
+
|
|
90
|
+
`typeof v` is Luau's `typeof` (Lua 5.1's `type`) for anything but a table.
|
|
91
|
+
For a table it answers, in order:
|
|
92
|
+
|
|
93
|
+
1. what `v`'s type says, where every table it can be is an array, or every
|
|
94
|
+
one an object — passed by the compiler, so nothing is looked up;
|
|
95
|
+
2. `"array"` if it was written down as one;
|
|
96
|
+
3. `"object"` if it has a metatable — an instance of a class;
|
|
97
|
+
4. `"array"` if it has a first element, and `"object"` otherwise: a table the
|
|
98
|
+
program did not make (one `JSONDecode` answered) is read by its shape, and
|
|
99
|
+
an empty one of those is an `"object"`.
|
|
100
|
+
|
|
80
101
|
## Classes
|
|
81
102
|
|
|
82
103
|
`class` is sugar over the Lua idiom, and it lowers to that idiom and nothing
|
|
@@ -185,7 +206,7 @@ argument (`arguments`: type, the code it was written as, and what a name was
|
|
|
185
206
|
declared as). An answer can `prepend` Luau expressions ahead of the written
|
|
186
207
|
arguments. `globalCall` and `globalValue` answer for a global, such as
|
|
187
208
|
`print(x)` or a bare `print`. A runtime can read `__LINES__` to reach the
|
|
188
|
-
bundle's line map
|
|
209
|
+
bundle's line map, and wraps a new array it answers with in `__ARRAY__(...)`. See the `@tilua/parser` README for the details.
|
|
189
210
|
`console:log` is not built into the compiler: `@tilua-types/lua` implements it
|
|
190
211
|
with these hooks.
|
|
191
212
|
|
|
@@ -14,6 +14,7 @@ import { formatType } from "@tilua/parser";
|
|
|
14
14
|
import { parse as parseLuau, parseExpressionFromSource } from "luau-parser";
|
|
15
15
|
|
|
16
16
|
// src/luau.ts
|
|
17
|
+
import { LUAU_KEYWORDS, isLuauName } from "@tilua/parser";
|
|
17
18
|
var NO_SPAN = { line: { start: 0, end: 0 }, column: { start: 0, end: 0 } };
|
|
18
19
|
var span = () => ({ line: { ...NO_SPAN.line }, column: { ...NO_SPAN.column } });
|
|
19
20
|
var identifier = (name) => ({ type: "Identifier", name, ...span() });
|
|
@@ -74,32 +75,6 @@ var functionBody = (params, statements, hasVarargs = false) => ({
|
|
|
74
75
|
});
|
|
75
76
|
var localFunction = (name, func) => ({ type: "LocalFunctionStatement", name: identifier(name), func, ...span() });
|
|
76
77
|
var program = (statements) => ({ type: "Program", body: block(statements), ...span() });
|
|
77
|
-
var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
|
|
78
|
-
"and",
|
|
79
|
-
"break",
|
|
80
|
-
"do",
|
|
81
|
-
"else",
|
|
82
|
-
"elseif",
|
|
83
|
-
"end",
|
|
84
|
-
"false",
|
|
85
|
-
"for",
|
|
86
|
-
"function",
|
|
87
|
-
"if",
|
|
88
|
-
"in",
|
|
89
|
-
"local",
|
|
90
|
-
"nil",
|
|
91
|
-
"not",
|
|
92
|
-
"or",
|
|
93
|
-
"repeat",
|
|
94
|
-
"return",
|
|
95
|
-
"then",
|
|
96
|
-
"true",
|
|
97
|
-
"until",
|
|
98
|
-
"while"
|
|
99
|
-
]);
|
|
100
|
-
function isLuauName(name) {
|
|
101
|
-
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
|
|
102
|
-
}
|
|
103
78
|
|
|
104
79
|
// src/names.ts
|
|
105
80
|
var Names = class _Names {
|
|
@@ -142,6 +117,7 @@ var Names = class _Names {
|
|
|
142
117
|
|
|
143
118
|
// src/language.ts
|
|
144
119
|
import { readFileSync } from "fs";
|
|
120
|
+
import { luauString } from "@tilua/parser";
|
|
145
121
|
var read = (name) => readFileSync(new URL(`../runtime/${name}`, import.meta.url), "utf8");
|
|
146
122
|
var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\w+)/g)].map((m) => m[1]));
|
|
147
123
|
var array = read("array.luau");
|
|
@@ -175,7 +151,7 @@ function objectKeys(type) {
|
|
|
175
151
|
orders.push(keys);
|
|
176
152
|
}
|
|
177
153
|
const order = JSON.stringify(orders[0] ?? []);
|
|
178
|
-
const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) =>
|
|
154
|
+
const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) => luauString(key)).join(", ")} }` : "nil";
|
|
179
155
|
return { known, open: String(open) };
|
|
180
156
|
}
|
|
181
157
|
var plugin = {
|
|
@@ -234,11 +210,11 @@ var Lowerer = class {
|
|
|
234
210
|
origins = /* @__PURE__ */ new WeakMap();
|
|
235
211
|
run() {
|
|
236
212
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
213
|
+
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
237
214
|
const helpers = this.helperDefinitions();
|
|
238
215
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
239
|
-
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
|
|
240
216
|
return {
|
|
241
|
-
statements: [...
|
|
217
|
+
statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
|
|
242
218
|
exportsName: this.exportsName,
|
|
243
219
|
module: this.info,
|
|
244
220
|
diagnostics: this.diagnostics,
|
|
@@ -288,11 +264,36 @@ var Lowerer = class {
|
|
|
288
264
|
helper(kind) {
|
|
289
265
|
let name = this.helpers.get(kind);
|
|
290
266
|
if (!name) {
|
|
291
|
-
name = this.names.fresh(`tilua_${kind}`);
|
|
267
|
+
name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
|
|
292
268
|
this.helpers.set(kind, name);
|
|
293
269
|
}
|
|
294
270
|
return identifier(name);
|
|
295
271
|
}
|
|
272
|
+
// `typeof v` tells an array from an object, which Luau cannot: both are
|
|
273
|
+
// tables. So every array the program makes — `[a, b]`, a rest parameter,
|
|
274
|
+
// what `:map` answers — is written down as one, in a table that holds its
|
|
275
|
+
// keys weakly, and `typeof` looks there. A table nothing wrote down (one a
|
|
276
|
+
// Lua library made) is told by its type where that says, and by its shape
|
|
277
|
+
// where it does not.
|
|
278
|
+
/** `value`, a new array, written down as one. */
|
|
279
|
+
array(value) {
|
|
280
|
+
const marked = call(this.helper("array"), [value]);
|
|
281
|
+
this.marked.add(marked);
|
|
282
|
+
return marked;
|
|
283
|
+
}
|
|
284
|
+
/** The calls `array` made: each answers the one table it was given. */
|
|
285
|
+
marked = /* @__PURE__ */ new WeakSet();
|
|
286
|
+
/** Luau reaching the record of arrays: the bundle's, or this file's own. */
|
|
287
|
+
arrays() {
|
|
288
|
+
return this.options.module?.arrays ?? this.helper("arrays").name;
|
|
289
|
+
}
|
|
290
|
+
typeofExpression(node) {
|
|
291
|
+
const known = this.options.types?.typeofTables.get(node);
|
|
292
|
+
let argument = node.argument;
|
|
293
|
+
while (argument.type === "ParenthesizedExpression") argument = argument.expression;
|
|
294
|
+
const value = this.expression(argument);
|
|
295
|
+
return call(this.helper("typeof"), known ? [value, string(known)] : [this.keptToOne(value)]);
|
|
296
|
+
}
|
|
296
297
|
/** The local a library's runtime table gets in this file, emitting it the
|
|
297
298
|
* first time something asks. Keyed by library and by the key the library
|
|
298
299
|
* itself chose, so two libraries' tables never collide. */
|
|
@@ -487,6 +488,46 @@ var Lowerer = class {
|
|
|
487
488
|
}
|
|
488
489
|
helperDefinitions() {
|
|
489
490
|
const out = [];
|
|
491
|
+
const runtimes = this.runtimeDefinitions();
|
|
492
|
+
if (this.helpers.has("concat")) this.helper("array");
|
|
493
|
+
const marker = this.helpers.get("array");
|
|
494
|
+
const typeOf = this.helpers.get("typeof");
|
|
495
|
+
if (marker || typeOf) {
|
|
496
|
+
const arrays = this.arrays();
|
|
497
|
+
const own = this.helpers.get("arrays");
|
|
498
|
+
if (own) {
|
|
499
|
+
out.push(...parseLuau(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
|
|
500
|
+
}
|
|
501
|
+
if (marker) {
|
|
502
|
+
out.push(...parseLuau(`
|
|
503
|
+
local function ${marker}(t)
|
|
504
|
+
${arrays}[t] = true
|
|
505
|
+
return t
|
|
506
|
+
end`).body.statements);
|
|
507
|
+
}
|
|
508
|
+
if (typeOf) {
|
|
509
|
+
out.push(...parseLuau(`
|
|
510
|
+
local function ${typeOf}(value, known)
|
|
511
|
+
local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
|
|
512
|
+
if name ~= "table" then
|
|
513
|
+
return name
|
|
514
|
+
end
|
|
515
|
+
if known ~= nil then
|
|
516
|
+
return known
|
|
517
|
+
end
|
|
518
|
+
if ${arrays}[value] then
|
|
519
|
+
return "array"
|
|
520
|
+
end
|
|
521
|
+
if ${this.builtin("getmetatable").name}(value) ~= nil then
|
|
522
|
+
return "object"
|
|
523
|
+
end
|
|
524
|
+
if #value > 0 then
|
|
525
|
+
return "array"
|
|
526
|
+
end
|
|
527
|
+
return "object"
|
|
528
|
+
end`).body.statements);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
490
531
|
const assign2 = this.helpers.get("assign");
|
|
491
532
|
if (assign2) {
|
|
492
533
|
out.push(localFunction(assign2, functionBody(["target"], [
|
|
@@ -501,25 +542,11 @@ var Lowerer = class {
|
|
|
501
542
|
returns([identifier("target")])
|
|
502
543
|
], true)));
|
|
503
544
|
}
|
|
504
|
-
|
|
505
|
-
const { plugin: plugin2, from } = this.lowerings[index2];
|
|
506
|
-
const source = plugin2.runtime?.[runtime2];
|
|
507
|
-
if (source === void 0) {
|
|
508
|
-
this.diagnostics.push({
|
|
509
|
-
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
510
|
-
line: 1,
|
|
511
|
-
column: 1
|
|
512
|
-
});
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
const lines = this.options.module?.lines ?? "nil";
|
|
516
|
-
const fail = this.options.module?.fail ?? "nil";
|
|
517
|
-
out.push(...parseLuau(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
|
|
518
|
-
}
|
|
545
|
+
out.push(...runtimes);
|
|
519
546
|
const concat = this.helpers.get("concat");
|
|
520
547
|
if (concat) {
|
|
521
548
|
out.push(localFunction(concat, functionBody([], [
|
|
522
|
-
local(["result"], [table([])]),
|
|
549
|
+
local(["result"], [this.array(table([]))]),
|
|
523
550
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
524
551
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
525
552
|
callStatement(call(member(this.builtin("table"), "move"), [
|
|
@@ -535,6 +562,26 @@ var Lowerer = class {
|
|
|
535
562
|
}
|
|
536
563
|
return out;
|
|
537
564
|
}
|
|
565
|
+
/** The runtime tables the file's libraries were asked for. */
|
|
566
|
+
runtimeDefinitions() {
|
|
567
|
+
const out = [];
|
|
568
|
+
for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
|
|
569
|
+
const { plugin: plugin2, from } = this.lowerings[index2];
|
|
570
|
+
const source = plugin2.runtime?.[runtime2];
|
|
571
|
+
if (source === void 0) {
|
|
572
|
+
this.diagnostics.push({
|
|
573
|
+
message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
|
|
574
|
+
line: 1,
|
|
575
|
+
column: 1
|
|
576
|
+
});
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const lines = this.options.module?.lines ?? "nil";
|
|
580
|
+
const fail = this.options.module?.fail ?? "nil";
|
|
581
|
+
out.push(...parseLuau(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAY__/g, () => this.helper("array").name)).body.statements);
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
538
585
|
// --------------------------------------------------------
|
|
539
586
|
// Modules
|
|
540
587
|
// --------------------------------------------------------
|
|
@@ -1287,7 +1334,7 @@ end
|
|
|
1287
1334
|
number(pattern.elements.length + 1),
|
|
1288
1335
|
unary("#", source),
|
|
1289
1336
|
number(1),
|
|
1290
|
-
table([])
|
|
1337
|
+
this.array(table([]))
|
|
1291
1338
|
]);
|
|
1292
1339
|
const rest = this.restTarget(pattern.rest, elements, mode, after);
|
|
1293
1340
|
after.push(...rest.then);
|
|
@@ -1341,7 +1388,7 @@ end
|
|
|
1341
1388
|
const names = [];
|
|
1342
1389
|
for (const p of params) {
|
|
1343
1390
|
if (p.rest) {
|
|
1344
|
-
prelude.push(local([this.name(p.name)], [table([{ type: "TableFieldPositional", value: vararg() }])]));
|
|
1391
|
+
prelude.push(local([this.name(p.name)], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))]));
|
|
1345
1392
|
continue;
|
|
1346
1393
|
}
|
|
1347
1394
|
const name = p.pattern ? this.names.fresh("arg") : this.name(p.name);
|
|
@@ -1399,6 +1446,7 @@ end
|
|
|
1399
1446
|
return binary(node.operator, left, right);
|
|
1400
1447
|
}
|
|
1401
1448
|
case "UnaryExpression":
|
|
1449
|
+
if (node.operator === "typeof") return this.typeofExpression(node);
|
|
1402
1450
|
return unary(node.operator, this.expression(node.argument));
|
|
1403
1451
|
case "SuperExpression":
|
|
1404
1452
|
return this.superClassReference(node);
|
|
@@ -1439,6 +1487,9 @@ end
|
|
|
1439
1487
|
callValues(node) {
|
|
1440
1488
|
const chain = optionalChain(node);
|
|
1441
1489
|
if (chain) return this.optionalChainExpression(chain);
|
|
1490
|
+
if (node.type === "CallExpression" && (node.callee.type === "SuperExpression" || node.callee.type === "MemberExpression" && node.callee.object.type === "SuperExpression")) {
|
|
1491
|
+
return this.expression(node);
|
|
1492
|
+
}
|
|
1442
1493
|
const object2 = linkObject(node);
|
|
1443
1494
|
if (node.type === "CallExpression" && object2.type === "Identifier") {
|
|
1444
1495
|
return this.link(node, this.reference(object2));
|
|
@@ -1450,7 +1501,7 @@ end
|
|
|
1450
1501
|
* kept to one there, `f((g()))`. Every value is taken only where the
|
|
1451
1502
|
* program asks for them, as an array's last element: `[g()]`. */
|
|
1452
1503
|
keptToOne(value) {
|
|
1453
|
-
return expandsToMany(value) ? parenthesized(value) : value;
|
|
1504
|
+
return expandsToMany(value) && !this.marked.has(value) ? parenthesized(value) : value;
|
|
1454
1505
|
}
|
|
1455
1506
|
/** Is this `scriptArgs` the language's own, not a name the file declared? */
|
|
1456
1507
|
isLanguageGlobal(node) {
|
|
@@ -1731,7 +1782,7 @@ end
|
|
|
1731
1782
|
arrayExpression(node) {
|
|
1732
1783
|
const elements = packs(node.elements).items;
|
|
1733
1784
|
if (!elements.some((e) => e.type === "SpreadElement")) {
|
|
1734
|
-
return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
|
|
1785
|
+
return this.array(table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) }))));
|
|
1735
1786
|
}
|
|
1736
1787
|
const parts = [];
|
|
1737
1788
|
let current = [];
|
|
@@ -2021,11 +2072,13 @@ import {
|
|
|
2021
2072
|
applyDirectives,
|
|
2022
2073
|
UNUSED_EXPECT_ERROR,
|
|
2023
2074
|
ParseError,
|
|
2024
|
-
LexError
|
|
2075
|
+
LexError,
|
|
2076
|
+
luauString as luauString3
|
|
2025
2077
|
} from "@tilua/parser";
|
|
2026
2078
|
import { parse as parseLuau2, parseExpressionFromSource as parseExpressionFromSource2, print } from "luau-parser";
|
|
2027
2079
|
|
|
2028
2080
|
// src/linemap.ts
|
|
2081
|
+
import { luauString as luauString2 } from "@tilua/parser";
|
|
2029
2082
|
function statementsOf(node) {
|
|
2030
2083
|
const out = [];
|
|
2031
2084
|
const visit = (value) => {
|
|
@@ -2058,7 +2111,7 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
2058
2111
|
}
|
|
2059
2112
|
function lineMapSource(map) {
|
|
2060
2113
|
const entries = [...map.entries()].sort((a, b) => a[0] - b[0]);
|
|
2061
|
-
const quoted = (s) =>
|
|
2114
|
+
const quoted = (s) => luauString2(s.replace(/\\/g, "/"));
|
|
2062
2115
|
return `{${entries.map(([line, o]) => `[${line}]={${quoted(o.file)},${o.line}}`).join(",")}}`;
|
|
2063
2116
|
}
|
|
2064
2117
|
|
|
@@ -2119,6 +2172,7 @@ async function bundle(options) {
|
|
|
2119
2172
|
require: requireExpression,
|
|
2120
2173
|
lines: `${G}.lines`,
|
|
2121
2174
|
fail: `${G}.fail`,
|
|
2175
|
+
arrays: `${G}.arrays`,
|
|
2122
2176
|
resolve: (specifier) => {
|
|
2123
2177
|
const target = resolveModulePath(file, specifier, config);
|
|
2124
2178
|
if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
|
|
@@ -2164,6 +2218,7 @@ async function bundle(options) {
|
|
|
2164
2218
|
const assignment = program2.body.statements[1];
|
|
2165
2219
|
if ([...modules.values()].some((m) => m.usesScriptArgs)) {
|
|
2166
2220
|
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
2221
|
+
program2.body.statements.push(...parseLuau2(`${G}.arrays[scriptArgs] = true`).body.statements);
|
|
2167
2222
|
}
|
|
2168
2223
|
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;
|
|
2169
2224
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
@@ -2185,7 +2240,7 @@ async function bundle(options) {
|
|
|
2185
2240
|
const result = names.fresh("result");
|
|
2186
2241
|
const entryStatements = parseLuau2(`
|
|
2187
2242
|
local ${ok}, ${result} = xpcall(function()
|
|
2188
|
-
return ${G}.require(${
|
|
2243
|
+
return ${G}.require(${luauString3(sources.get(entry).name)})
|
|
2189
2244
|
end, ${G}.fail)
|
|
2190
2245
|
if not ${ok} then
|
|
2191
2246
|
error(${result}, 0)
|
|
@@ -2207,6 +2262,9 @@ function runtime(G) {
|
|
|
2207
2262
|
local ${G}
|
|
2208
2263
|
${G} = {
|
|
2209
2264
|
modules = {},
|
|
2265
|
+
-- Every array the program made, so \`typeof\` can tell one from an object.
|
|
2266
|
+
-- The keys are held weakly: being written down keeps no array alive.
|
|
2267
|
+
arrays = setmetatable({}, { __mode = "k" }),
|
|
2210
2268
|
-- Filled in just before the entry runs: bundle line -> { file, line }.
|
|
2211
2269
|
lines = nil,
|
|
2212
2270
|
-- What an error nothing caught says, in the project's files: the bundle's
|
|
@@ -2214,19 +2272,64 @@ ${G} = {
|
|
|
2214
2272
|
-- and a traceback of those places follows. A message that is not a string
|
|
2215
2273
|
-- is left as it is, and a position that is not this bundle's (a library
|
|
2216
2274
|
-- that raised its error with one already mapped) is left alone.
|
|
2275
|
+
--
|
|
2276
|
+
-- It is an \`xpcall\` handler, and a handler that fails loses the error
|
|
2277
|
+
-- altogether: Luau reports only "error in error handling". So the work is
|
|
2278
|
+
-- done under \`pcall\`, leaning on nothing it has not checked is there (an
|
|
2279
|
+
-- executor may lack \`debug.info\`, or refuse it), and an error it cannot
|
|
2280
|
+
-- map is still answered as Luau raised it, with why.
|
|
2217
2281
|
fail = function(message)
|
|
2282
|
+
-- Taken first, before anything that can fail. Level 3 is the frame
|
|
2283
|
+
-- that raised: past \`pcall\` and this handler.
|
|
2284
|
+
local trace
|
|
2285
|
+
if debug ~= nil and debug.traceback ~= nil then
|
|
2286
|
+
local ok, text = pcall(debug.traceback, "", 3)
|
|
2287
|
+
if ok and type(text) == "string" then
|
|
2288
|
+
trace = text
|
|
2289
|
+
end
|
|
2290
|
+
end
|
|
2291
|
+
local ok, described = pcall(${G}.describe, message, trace)
|
|
2292
|
+
if ok then
|
|
2293
|
+
return described
|
|
2294
|
+
end
|
|
2295
|
+
if type(message) ~= "string" then
|
|
2296
|
+
return message
|
|
2297
|
+
end
|
|
2298
|
+
local why = type(described) == "string" and described or "unknown"
|
|
2299
|
+
return message .. "\\n(tilua could not map this error to the project's files: " .. why .. ")" .. (trace or "")
|
|
2300
|
+
end,
|
|
2301
|
+
-- \`fail\`'s work. \`trace\` is a traceback from where the error was
|
|
2302
|
+
-- raised, or nil when none could be taken.
|
|
2303
|
+
describe = function(message, trace)
|
|
2218
2304
|
if type(message) ~= "string" then
|
|
2219
2305
|
return message
|
|
2220
2306
|
end
|
|
2221
2307
|
local lines = ${G}.lines
|
|
2222
2308
|
if lines == nil then
|
|
2223
|
-
return
|
|
2309
|
+
return message .. (trace or "")
|
|
2224
2310
|
end
|
|
2311
|
+
-- The bundle's name, as a position in a message spells it: where this
|
|
2312
|
+
-- function is, asked of the debug library by the function rather
|
|
2313
|
+
-- than by a level, which \`pcall\` would shift. Failing that, a message
|
|
2314
|
+
-- starts with the position it was raised at.
|
|
2225
2315
|
local chunk
|
|
2226
|
-
if debug.info ~= nil then
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2316
|
+
if debug ~= nil and debug.info ~= nil then
|
|
2317
|
+
local found, source = pcall(debug.info, ${G}.describe, "s")
|
|
2318
|
+
if found and type(source) == "string" then
|
|
2319
|
+
chunk = source
|
|
2320
|
+
end
|
|
2321
|
+
end
|
|
2322
|
+
if chunk == nil and debug ~= nil and debug.getinfo ~= nil then
|
|
2323
|
+
local found, info = pcall(debug.getinfo, ${G}.describe, "S")
|
|
2324
|
+
if found and type(info) == "table" and type(info.short_src) == "string" then
|
|
2325
|
+
chunk = info.short_src
|
|
2326
|
+
end
|
|
2327
|
+
end
|
|
2328
|
+
if chunk == nil then
|
|
2329
|
+
chunk = message:match("^(.-):%d+:")
|
|
2330
|
+
end
|
|
2331
|
+
if chunk == nil then
|
|
2332
|
+
return message .. (trace or "")
|
|
2230
2333
|
end
|
|
2231
2334
|
local prefix = chunk:gsub("%p", "%%%0")
|
|
2232
2335
|
local function place(line)
|
|
@@ -2240,7 +2343,7 @@ ${G} = {
|
|
|
2240
2343
|
return place(line)
|
|
2241
2344
|
end)
|
|
2242
2345
|
local frames = {}
|
|
2243
|
-
for frame in
|
|
2346
|
+
for frame in (trace or ""):gmatch("[^\\n]+") do
|
|
2244
2347
|
local line = frame:match(prefix .. ":(%d+)")
|
|
2245
2348
|
local at = line ~= nil and place(line) or nil
|
|
2246
2349
|
if at ~= nil then
|