@tilua/compiler 1.0.2 → 1.1.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 +27 -0
- package/dist/{chunk-QSRDDIU4.js → chunk-2IPPO4V3.js} +196 -218
- package/dist/cli.cjs +193 -215
- package/dist/cli.js +1 -1
- package/dist/index.cjs +193 -215
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +1 -1
- package/package.json +2 -2
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) {
|
|
@@ -398,60 +260,9 @@ var Lowerer = class {
|
|
|
398
260
|
}
|
|
399
261
|
loweringNames = /* @__PURE__ */ new Map();
|
|
400
262
|
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
263
|
/** What a name was declared as: the value of `const f = ...`, or the whole
|
|
453
|
-
* `function f() { ... }`. Built once, on the first
|
|
454
|
-
*
|
|
264
|
+
* `function f() { ... }`. Built once, on the first call a library asks
|
|
265
|
+
* about — most files have none. */
|
|
455
266
|
declarationOf(argument) {
|
|
456
267
|
if (argument.type !== "Identifier") return void 0;
|
|
457
268
|
if (!this.declarations) {
|
|
@@ -490,21 +301,127 @@ var Lowerer = class {
|
|
|
490
301
|
const trim = (line) => line.startsWith(indent) ? line.slice(indent.length) : line;
|
|
491
302
|
return [first, ...middle.map(trim), trim(last)].join("\n");
|
|
492
303
|
}
|
|
304
|
+
/** Where `node` was written, as a library is told it. */
|
|
305
|
+
callSite(node) {
|
|
306
|
+
return { file: this.options.file, line: node.line.start, column: node.column.start };
|
|
307
|
+
}
|
|
308
|
+
/** What the compiler knows about each argument and the runtime cannot:
|
|
309
|
+
* its type, and the code it — or what it names — was written as. Built
|
|
310
|
+
* only if a library reads it. */
|
|
311
|
+
argumentInfo(args2) {
|
|
312
|
+
return args2.map((argument) => {
|
|
313
|
+
const declaration = this.declarationOf(argument);
|
|
314
|
+
const type = this.options.types?.typeOf.get(argument);
|
|
315
|
+
return {
|
|
316
|
+
type,
|
|
317
|
+
typeText: type ? (0, import_parser2.formatType)(type) : void 0,
|
|
318
|
+
source: this.writtenSource(argument),
|
|
319
|
+
declaration: declaration ? this.writtenSource(declaration) : void 0,
|
|
320
|
+
spread: argument.type === "SpreadElement"
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/** The name a global is read by at `node`, or `undefined` when `node` is
|
|
325
|
+
* not a global — a local of the same name is the author's. */
|
|
326
|
+
globalName(node) {
|
|
327
|
+
if (node.type !== "Identifier") return void 0;
|
|
328
|
+
const id = this.scopes.bindingOf.get(node);
|
|
329
|
+
if (id === void 0) return node.name;
|
|
330
|
+
return this.scopes.bindings.get(id)?.kind === "global" ? node.name : void 0;
|
|
331
|
+
}
|
|
332
|
+
/** What a library says `receiver:method(...)` is. The last library
|
|
333
|
+
* loaded is asked first, so a project's own library can answer for a
|
|
334
|
+
* method an earlier one also claims; no answer leaves an ordinary Luau
|
|
335
|
+
* method call, which is what a value that answers to the method itself
|
|
336
|
+
* wants (`text:upper()`). */
|
|
493
337
|
loweredMethodCall(node) {
|
|
494
338
|
const lowerings = this.options.lowerings;
|
|
495
|
-
if (!lowerings?.
|
|
339
|
+
if (!lowerings?.some((l) => l.plugin.methodCall)) return void 0;
|
|
496
340
|
const receiver = this.options.types?.typeOf.get(node.object);
|
|
341
|
+
const receiverGlobal = this.globalName(node.object);
|
|
342
|
+
const info = this.lazyArguments(node.arguments);
|
|
497
343
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
498
|
-
const
|
|
344
|
+
const plugin = lowerings[i].plugin;
|
|
345
|
+
if (!plugin.methodCall) continue;
|
|
346
|
+
const answer = plugin.methodCall({
|
|
499
347
|
method: node.method.name,
|
|
500
348
|
receiver,
|
|
349
|
+
receiverGlobal,
|
|
501
350
|
argumentCount: node.arguments.length,
|
|
351
|
+
at: this.callSite(node.method),
|
|
352
|
+
get arguments() {
|
|
353
|
+
return info();
|
|
354
|
+
},
|
|
502
355
|
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
503
356
|
});
|
|
504
|
-
if (answer)
|
|
357
|
+
if (answer) {
|
|
358
|
+
return {
|
|
359
|
+
callee: answer.callee,
|
|
360
|
+
prepend: this.prepended(answer.prepend, i, node),
|
|
361
|
+
passReceiver: answer.passReceiver !== false
|
|
362
|
+
};
|
|
363
|
+
}
|
|
505
364
|
}
|
|
506
365
|
return void 0;
|
|
507
366
|
}
|
|
367
|
+
/** What a library says a call to a global — `print(x)` — is. */
|
|
368
|
+
loweredGlobalCall(node) {
|
|
369
|
+
const lowerings = this.options.lowerings;
|
|
370
|
+
if (!lowerings?.some((l) => l.plugin.globalCall)) return void 0;
|
|
371
|
+
const name = this.globalName(node.callee);
|
|
372
|
+
if (name === void 0) return void 0;
|
|
373
|
+
const info = this.lazyArguments(node.arguments);
|
|
374
|
+
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
375
|
+
const plugin = lowerings[i].plugin;
|
|
376
|
+
if (!plugin.globalCall) continue;
|
|
377
|
+
const answer = plugin.globalCall({
|
|
378
|
+
name,
|
|
379
|
+
at: this.callSite(node.callee),
|
|
380
|
+
get arguments() {
|
|
381
|
+
return info();
|
|
382
|
+
},
|
|
383
|
+
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
384
|
+
});
|
|
385
|
+
if (answer) return { callee: answer.callee, prepend: this.prepended(answer.prepend, i, node) };
|
|
386
|
+
}
|
|
387
|
+
return void 0;
|
|
388
|
+
}
|
|
389
|
+
/** What a library says a global read as a value — `local p = print` — is. */
|
|
390
|
+
loweredGlobalValue(node) {
|
|
391
|
+
const lowerings = this.options.lowerings;
|
|
392
|
+
if (!lowerings?.some((l) => l.plugin.globalValue)) return void 0;
|
|
393
|
+
const name = this.globalName(node);
|
|
394
|
+
if (name === void 0) return void 0;
|
|
395
|
+
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
396
|
+
const answer = lowerings[i].plugin.globalValue?.({
|
|
397
|
+
name,
|
|
398
|
+
at: this.callSite(node),
|
|
399
|
+
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
400
|
+
});
|
|
401
|
+
if (answer !== void 0) return this.libraryExpression(answer, i, node);
|
|
402
|
+
}
|
|
403
|
+
return void 0;
|
|
404
|
+
}
|
|
405
|
+
lazyArguments(args2) {
|
|
406
|
+
let info;
|
|
407
|
+
return () => info ??= this.argumentInfo(args2);
|
|
408
|
+
}
|
|
409
|
+
prepended(sources, index2, node) {
|
|
410
|
+
return (sources ?? []).flatMap((source) => {
|
|
411
|
+
const expression = this.libraryExpression(source, index2, node);
|
|
412
|
+
return expression ? [expression] : [];
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
/** Luau a library wrote as an expression. One that does not parse is the
|
|
416
|
+
* library's mistake, reported at the call it was written for. */
|
|
417
|
+
libraryExpression(source, index2, node) {
|
|
418
|
+
try {
|
|
419
|
+
return (0, import_luau_parser.parseExpressionFromSource)(source);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
this.report(node, `'${this.options.lowerings[index2].from}' lowered this to Luau that does not parse: ${JSON.stringify(source)} (${error.message})`);
|
|
422
|
+
return void 0;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
508
425
|
helperDefinitions() {
|
|
509
426
|
const out2 = [];
|
|
510
427
|
const assign2 = this.helpers.get("assign");
|
|
@@ -532,12 +449,9 @@ var Lowerer = class {
|
|
|
532
449
|
});
|
|
533
450
|
continue;
|
|
534
451
|
}
|
|
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
452
|
const lines = this.options.module?.lines ?? "nil";
|
|
540
|
-
|
|
453
|
+
const fail = this.options.module?.fail ?? "nil";
|
|
454
|
+
out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
|
|
541
455
|
}
|
|
542
456
|
const concat = this.helpers.get("concat");
|
|
543
457
|
if (concat) {
|
|
@@ -1395,7 +1309,7 @@ end
|
|
|
1395
1309
|
}
|
|
1396
1310
|
switch (node.type) {
|
|
1397
1311
|
case "Identifier":
|
|
1398
|
-
return this.reference(node);
|
|
1312
|
+
return this.loweredGlobalValue(node) ?? this.reference(node);
|
|
1399
1313
|
case "NilLiteral":
|
|
1400
1314
|
return nil();
|
|
1401
1315
|
case "BooleanLiteral":
|
|
@@ -1444,7 +1358,11 @@ end
|
|
|
1444
1358
|
case "MethodCallExpression": {
|
|
1445
1359
|
const chain = optionalChain(node);
|
|
1446
1360
|
if (chain) return this.optionalChainExpression(chain);
|
|
1447
|
-
|
|
1361
|
+
const object = linkObject(node);
|
|
1362
|
+
if (node.type === "CallExpression" && object.type === "Identifier") {
|
|
1363
|
+
return this.link(node, this.reference(object));
|
|
1364
|
+
}
|
|
1365
|
+
return this.link(node, this.expression(object));
|
|
1448
1366
|
}
|
|
1449
1367
|
case "ParenthesizedExpression":
|
|
1450
1368
|
return parenthesized(this.expression(node.expression));
|
|
@@ -1489,15 +1407,21 @@ end
|
|
|
1489
1407
|
return member(object, node.property.name);
|
|
1490
1408
|
case "IndexExpression":
|
|
1491
1409
|
return index(object, this.expression(node.index));
|
|
1492
|
-
case "CallExpression":
|
|
1493
|
-
|
|
1494
|
-
case "MethodCallExpression": {
|
|
1495
|
-
const consoleCall = this.consoleCall(node);
|
|
1496
|
-
if (consoleCall) return consoleCall;
|
|
1410
|
+
case "CallExpression": {
|
|
1411
|
+
const lowered = this.loweredGlobalCall(node);
|
|
1497
1412
|
const args2 = this.values(node.arguments);
|
|
1413
|
+
if (lowered) return call(calleePath(lowered.callee), [...lowered.prepend, ...args2]);
|
|
1414
|
+
return call(object, args2);
|
|
1415
|
+
}
|
|
1416
|
+
case "MethodCallExpression": {
|
|
1498
1417
|
const lowered = this.loweredMethodCall(node);
|
|
1418
|
+
const args2 = this.values(node.arguments);
|
|
1499
1419
|
if (lowered) {
|
|
1500
|
-
return call(calleePath(lowered.callee),
|
|
1420
|
+
return call(calleePath(lowered.callee), [
|
|
1421
|
+
...lowered.prepend,
|
|
1422
|
+
...lowered.passReceiver ? [object] : [],
|
|
1423
|
+
...args2
|
|
1424
|
+
]);
|
|
1501
1425
|
}
|
|
1502
1426
|
if (!isLuauName(node.method.name)) {
|
|
1503
1427
|
this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
|
|
@@ -1910,10 +1834,6 @@ function mentions(expression, name) {
|
|
|
1910
1834
|
walk(expression);
|
|
1911
1835
|
return found;
|
|
1912
1836
|
}
|
|
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
1837
|
function allNodes(root) {
|
|
1918
1838
|
const out2 = [];
|
|
1919
1839
|
const visit = (value) => {
|
|
@@ -2037,16 +1957,19 @@ async function bundle(options) {
|
|
|
2037
1957
|
const file = pending.shift();
|
|
2038
1958
|
if (modules.has(file)) continue;
|
|
2039
1959
|
const source = sources.get(file);
|
|
1960
|
+
const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
|
|
2040
1961
|
const lowered = lower(source.program, source.scopes, {
|
|
2041
1962
|
names,
|
|
2042
1963
|
types: analysis.types.get(file),
|
|
2043
1964
|
lowerings,
|
|
2044
1965
|
target: options.target ?? config2?.target,
|
|
2045
1966
|
source: source.text,
|
|
1967
|
+
file: shown,
|
|
2046
1968
|
module: {
|
|
2047
1969
|
name: source.name,
|
|
2048
1970
|
require: requireExpression,
|
|
2049
1971
|
lines: `${G}.lines`,
|
|
1972
|
+
fail: `${G}.fail`,
|
|
2050
1973
|
resolve: (specifier) => {
|
|
2051
1974
|
const target = (0, import_parser3.resolveModulePath)(file, specifier, config2);
|
|
2052
1975
|
if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
|
|
@@ -2056,7 +1979,6 @@ async function bundle(options) {
|
|
|
2056
1979
|
}
|
|
2057
1980
|
});
|
|
2058
1981
|
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
1982
|
for (const statement of allStatements(lowered.statements)) {
|
|
2061
1983
|
const line = lowered.origins.get(statement);
|
|
2062
1984
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
@@ -2106,11 +2028,21 @@ async function bundle(options) {
|
|
|
2106
2028
|
fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
|
|
2107
2029
|
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
|
|
2108
2030
|
}
|
|
2109
|
-
const
|
|
2110
|
-
|
|
2031
|
+
const ok = names.fresh("ok");
|
|
2032
|
+
const result = names.fresh("result");
|
|
2033
|
+
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
2034
|
+
local ${ok}, ${result} = xpcall(function()
|
|
2035
|
+
return ${G}.require(${JSON.stringify(sources.get(entry2).name)})
|
|
2036
|
+
end, ${G}.fail)
|
|
2037
|
+
if not ${ok} then
|
|
2038
|
+
error(${result}, 0)
|
|
2039
|
+
end
|
|
2040
|
+
${modules.get(entry2).info.exports ? `return ${result}` : ""}
|
|
2041
|
+
`).body.statements;
|
|
2042
|
+
program.body.statements.push(...entryStatements);
|
|
2111
2043
|
const map = buildLineMap(program, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program)), origins);
|
|
2112
2044
|
if (map.size) {
|
|
2113
|
-
program.body.statements.splice(program.body.statements.length -
|
|
2045
|
+
program.body.statements.splice(program.body.statements.length - entryStatements.length, 0, assign(
|
|
2114
2046
|
[member(identifier(G), "lines")],
|
|
2115
2047
|
[(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
|
|
2116
2048
|
));
|
|
@@ -2122,6 +2054,52 @@ function runtime(G) {
|
|
|
2122
2054
|
local ${G}
|
|
2123
2055
|
${G} = {
|
|
2124
2056
|
modules = {},
|
|
2057
|
+
-- Filled in just before the entry runs: bundle line -> { file, line }.
|
|
2058
|
+
lines = nil,
|
|
2059
|
+
-- What an error nothing caught says, in the project's files: the bundle's
|
|
2060
|
+
-- own positions in the message are replaced by the places they came from,
|
|
2061
|
+
-- and a traceback of those places follows. A message that is not a string
|
|
2062
|
+
-- is left as it is, and a position that is not this bundle's (a library
|
|
2063
|
+
-- that raised its error with one already mapped) is left alone.
|
|
2064
|
+
fail = function(message)
|
|
2065
|
+
if type(message) ~= "string" then
|
|
2066
|
+
return message
|
|
2067
|
+
end
|
|
2068
|
+
local lines = ${G}.lines
|
|
2069
|
+
if lines == nil then
|
|
2070
|
+
return debug.traceback(message, 2)
|
|
2071
|
+
end
|
|
2072
|
+
local chunk
|
|
2073
|
+
if debug.info ~= nil then
|
|
2074
|
+
chunk = debug.info(1, "s")
|
|
2075
|
+
else
|
|
2076
|
+
chunk = debug.getinfo(1, "S").short_src
|
|
2077
|
+
end
|
|
2078
|
+
local prefix = chunk:gsub("%p", "%%%0")
|
|
2079
|
+
local function place(line)
|
|
2080
|
+
local origin = lines[tonumber(line)]
|
|
2081
|
+
if origin == nil then
|
|
2082
|
+
return nil
|
|
2083
|
+
end
|
|
2084
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2085
|
+
end
|
|
2086
|
+
local mapped = message:gsub(prefix .. ":(%d+)", function(line)
|
|
2087
|
+
return place(line)
|
|
2088
|
+
end)
|
|
2089
|
+
local frames = {}
|
|
2090
|
+
for frame in debug.traceback("", 2):gmatch("[^\\n]+") do
|
|
2091
|
+
local line = frame:match(prefix .. ":(%d+)")
|
|
2092
|
+
local at = line ~= nil and place(line) or nil
|
|
2093
|
+
if at ~= nil then
|
|
2094
|
+
local name = frame:match("function ([%w_.:]+)")
|
|
2095
|
+
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2096
|
+
end
|
|
2097
|
+
end
|
|
2098
|
+
if #frames == 0 then
|
|
2099
|
+
return mapped
|
|
2100
|
+
end
|
|
2101
|
+
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2102
|
+
end,
|
|
2125
2103
|
records = {},
|
|
2126
2104
|
require = function(name)
|
|
2127
2105
|
local record = ${G}.records[name]
|