@tilua/compiler 1.0.1 → 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/index.cjs
CHANGED
|
@@ -137,144 +137,6 @@ function isLuauName(name) {
|
|
|
137
137
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
// src/console.ts
|
|
141
|
-
var CONSOLE_RUNTIME = `
|
|
142
|
-
local __NAME__ = (function()
|
|
143
|
-
local console = {}
|
|
144
|
-
local MAX_DEPTH = 4
|
|
145
|
-
local ESCAPES = { ["\\n"] = "\\\\n", ["\\r"] = "\\\\r", ["\\t"] = "\\\\t", ["\\""] = "\\\\\\"", ["\\\\"] = "\\\\\\\\" }
|
|
146
|
-
|
|
147
|
-
local function quote(text)
|
|
148
|
-
return '"' .. text:gsub('[%c"\\\\]', function(c) return ESCAPES[c] or ("\\\\%d"):format(c:byte()) end) .. '"'
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
-- A table with 1..n and nothing else prints as a list, as it was written.
|
|
152
|
-
local function isArray(value)
|
|
153
|
-
local count = 0
|
|
154
|
-
for key in pairs(value) do
|
|
155
|
-
if type(key) ~= "number" then return false end
|
|
156
|
-
count = count + 1
|
|
157
|
-
end
|
|
158
|
-
return count == #value
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
local function isIdentifier(key)
|
|
162
|
-
return type(key) == "string" and key:match("^[A-Za-z_][A-Za-z0-9_]*$") ~= nil
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
local render
|
|
166
|
-
|
|
167
|
-
local function renderTable(value, meta, seen, depth, indent)
|
|
168
|
-
if seen[value] then return "<cycle>" end
|
|
169
|
-
if depth > MAX_DEPTH then return "..." end
|
|
170
|
-
seen[value] = true
|
|
171
|
-
local inner = indent .. " "
|
|
172
|
-
local parts = {}
|
|
173
|
-
if isArray(value) then
|
|
174
|
-
for i = 1, #value do
|
|
175
|
-
parts[#parts + 1] = inner .. render(value[i], nil, seen, depth + 1, inner)
|
|
176
|
-
end
|
|
177
|
-
else
|
|
178
|
-
local keys = {}
|
|
179
|
-
for key in pairs(value) do keys[#keys + 1] = key end
|
|
180
|
-
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
|
|
181
|
-
for _, key in ipairs(keys) do
|
|
182
|
-
local name = isIdentifier(key) and key or ("[" .. render(key, nil, seen, depth + 1, inner) .. "]")
|
|
183
|
-
parts[#parts + 1] = inner .. name .. " = " .. render(value[key], nil, seen, depth + 1, inner)
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
seen[value] = nil
|
|
187
|
-
if #parts == 0 then return "{}" end
|
|
188
|
-
local open = isArray(value) and "[" or "{"
|
|
189
|
-
local close = isArray(value) and "]" or "}"
|
|
190
|
-
return open .. "\\n" .. table.concat(parts, ",\\n") .. "\\n" .. indent .. close
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
-- \`meta\` is what the compiler knew about this value and the runtime cannot:
|
|
194
|
-
-- a function's type, and the code it was written as.
|
|
195
|
-
render = function(value, meta, seen, depth, indent)
|
|
196
|
-
local kind = type(value)
|
|
197
|
-
if kind == "string" then return quote(value) end
|
|
198
|
-
if kind == "number" or kind == "boolean" or kind == "nil" then return tostring(value) end
|
|
199
|
-
if kind == "function" then
|
|
200
|
-
if meta == nil then return "function" end
|
|
201
|
-
if meta[2] == nil or meta[2] == "" then return meta[1] end
|
|
202
|
-
return meta[1] .. " " .. meta[2]
|
|
203
|
-
end
|
|
204
|
-
if kind == "table" then
|
|
205
|
-
local shown = rawget(value, "__tostring") == nil and getmetatable(value)
|
|
206
|
-
-- A value with its own \`__tostring\` (a Roblox Vector3, a class
|
|
207
|
-
-- instance that says how it reads) is trusted to describe itself.
|
|
208
|
-
if shown ~= nil and rawget(shown, "__tostring") ~= nil then return tostring(value) end
|
|
209
|
-
return renderTable(value, meta, seen, depth, indent)
|
|
210
|
-
end
|
|
211
|
-
return tostring(value)
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
local function describe(meta, index, value)
|
|
215
|
-
return render(value, meta ~= nil and meta[index] or nil, {}, 1, "")
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
local function join(meta, count, ...)
|
|
219
|
-
local parts = {}
|
|
220
|
-
for i = 1, count do
|
|
221
|
-
parts[#parts + 1] = describe(meta, i, (select(i, ...)))
|
|
222
|
-
end
|
|
223
|
-
return table.concat(parts, " ")
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
-- A line of the bundle, as the place in the project that wrote it.
|
|
227
|
-
local lines = __LINES__
|
|
228
|
-
local function place(line)
|
|
229
|
-
local origin = lines ~= nil and lines[tonumber(line)]
|
|
230
|
-
if origin == nil then return nil end
|
|
231
|
-
return origin[1] .. ":" .. tostring(origin[2])
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
-- Luau names the bundle in every frame; the project's files are what the
|
|
235
|
-
-- reader knows. Frames the map has nothing for are dropped: they are this
|
|
236
|
-
-- runtime's own, and the bundle's.
|
|
237
|
-
local function trace(level)
|
|
238
|
-
local raw = debug.traceback(nil, level)
|
|
239
|
-
local out = {}
|
|
240
|
-
for frame in raw:gmatch("[^\\n]+") do
|
|
241
|
-
local line = frame:match(":(%d+)")
|
|
242
|
-
local at = line ~= nil and place(line) or nil
|
|
243
|
-
if at ~= nil then
|
|
244
|
-
local name = frame:match("function ([%w_.:]+)")
|
|
245
|
-
out[#out + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
return table.concat(out, "\\n")
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
-- Never on the caller's thread: reporting a problem must not become one.
|
|
252
|
-
local detach = (task ~= nil and task.spawn) or function(f) return f() end
|
|
253
|
-
|
|
254
|
-
local function raise(prefix, meta, count, ...)
|
|
255
|
-
local message = prefix .. join(meta, count, ...)
|
|
256
|
-
local where = trace(3)
|
|
257
|
-
detach(function()
|
|
258
|
-
error(where ~= "" and (message .. "\\n" .. where) or message, 0)
|
|
259
|
-
end)
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
function console.log(meta, ...)
|
|
263
|
-
print(join(meta, select("#", ...), ...))
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
function console.warn(meta, ...)
|
|
267
|
-
raise("[warn] ", meta, select("#", ...), ...)
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
function console.error(meta, ...)
|
|
271
|
-
raise("", meta, select("#", ...), ...)
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
return console
|
|
275
|
-
end)()
|
|
276
|
-
`;
|
|
277
|
-
|
|
278
140
|
// src/names.ts
|
|
279
141
|
var Names = class _Names {
|
|
280
142
|
constructor(taken) {
|
|
@@ -422,60 +284,9 @@ var Lowerer = class {
|
|
|
422
284
|
}
|
|
423
285
|
loweringNames = /* @__PURE__ */ new Map();
|
|
424
286
|
loweringUsed = [];
|
|
425
|
-
/** What a library says `receiver:method(...)` is. The last library
|
|
426
|
-
* loaded is asked first, so a project's own library can answer for a
|
|
427
|
-
* method an earlier one also claims; no answer leaves an ordinary Luau
|
|
428
|
-
* method call, which is what a value that answers to the method itself
|
|
429
|
-
* wants (`text:upper()`). */
|
|
430
|
-
/** `console:log(a, f)` and its two siblings.
|
|
431
|
-
*
|
|
432
|
-
* Handled here rather than through a library's lowering because what they
|
|
433
|
-
* need is not a different callee but *more than was written*: a function
|
|
434
|
-
* prints as its type and its code, and neither survives to runtime. The
|
|
435
|
-
* compiler is the only thing that has both, so it passes them along:
|
|
436
|
-
*
|
|
437
|
-
* console:log(a, double)
|
|
438
|
-
* -- becomes
|
|
439
|
-
* tilua_console.log({ [2] = { "(x: number) => number", "(x: number) => x * 2" } }, a, double)
|
|
440
|
-
*
|
|
441
|
-
* The meta table is `nil` when nothing was worth saying, which is most
|
|
442
|
-
* calls. `console` itself is never passed: it holds nothing. */
|
|
443
|
-
consoleCall(node) {
|
|
444
|
-
if (node.object.type !== "Identifier" || node.object.name !== "console") return void 0;
|
|
445
|
-
const method = node.method.name;
|
|
446
|
-
if (method !== "log" && method !== "warn" && method !== "error") return void 0;
|
|
447
|
-
const binding = this.scopes.bindingOf.get(node.object);
|
|
448
|
-
if (binding !== void 0 && this.scopes.bindings.get(binding)?.kind !== "global") return void 0;
|
|
449
|
-
const meta = this.consoleMeta(node.arguments);
|
|
450
|
-
return call(
|
|
451
|
-
member(this.helper("console"), method),
|
|
452
|
-
[meta ?? nil(), ...this.values(node.arguments)]
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
/** What the compiler knows about the arguments and the runtime cannot:
|
|
456
|
-
* for each function argument, the type it was inferred as and the code it
|
|
457
|
-
* was written as. Keyed by position, so an argument that needs nothing
|
|
458
|
-
* costs nothing. */
|
|
459
|
-
consoleMeta(args) {
|
|
460
|
-
const fields = [];
|
|
461
|
-
args.forEach((argument, index2) => {
|
|
462
|
-
const type = this.options.types?.typeOf.get(argument);
|
|
463
|
-
if (!type || !isFunctionType(type)) return;
|
|
464
|
-
const written = this.writtenSource(this.declarationOf(argument) ?? argument);
|
|
465
|
-
fields.push({
|
|
466
|
-
type: "TableFieldComputed",
|
|
467
|
-
key: number(index2 + 1),
|
|
468
|
-
value: table([
|
|
469
|
-
{ type: "TableFieldPositional", value: string((0, import_parser2.formatType)(type)) },
|
|
470
|
-
{ type: "TableFieldPositional", value: string(written) }
|
|
471
|
-
])
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
return fields.length ? table(fields) : void 0;
|
|
475
|
-
}
|
|
476
287
|
/** What a name was declared as: the value of `const f = ...`, or the whole
|
|
477
|
-
* `function f() { ... }`. Built once, on the first
|
|
478
|
-
*
|
|
288
|
+
* `function f() { ... }`. Built once, on the first call a library asks
|
|
289
|
+
* about — most files have none. */
|
|
479
290
|
declarationOf(argument) {
|
|
480
291
|
if (argument.type !== "Identifier") return void 0;
|
|
481
292
|
if (!this.declarations) {
|
|
@@ -514,21 +325,127 @@ var Lowerer = class {
|
|
|
514
325
|
const trim = (line) => line.startsWith(indent) ? line.slice(indent.length) : line;
|
|
515
326
|
return [first, ...middle.map(trim), trim(last)].join("\n");
|
|
516
327
|
}
|
|
328
|
+
/** Where `node` was written, as a library is told it. */
|
|
329
|
+
callSite(node) {
|
|
330
|
+
return { file: this.options.file, line: node.line.start, column: node.column.start };
|
|
331
|
+
}
|
|
332
|
+
/** What the compiler knows about each argument and the runtime cannot:
|
|
333
|
+
* its type, and the code it — or what it names — was written as. Built
|
|
334
|
+
* only if a library reads it. */
|
|
335
|
+
argumentInfo(args) {
|
|
336
|
+
return args.map((argument) => {
|
|
337
|
+
const declaration = this.declarationOf(argument);
|
|
338
|
+
const type = this.options.types?.typeOf.get(argument);
|
|
339
|
+
return {
|
|
340
|
+
type,
|
|
341
|
+
typeText: type ? (0, import_parser2.formatType)(type) : void 0,
|
|
342
|
+
source: this.writtenSource(argument),
|
|
343
|
+
declaration: declaration ? this.writtenSource(declaration) : void 0,
|
|
344
|
+
spread: argument.type === "SpreadElement"
|
|
345
|
+
};
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/** The name a global is read by at `node`, or `undefined` when `node` is
|
|
349
|
+
* not a global — a local of the same name is the author's. */
|
|
350
|
+
globalName(node) {
|
|
351
|
+
if (node.type !== "Identifier") return void 0;
|
|
352
|
+
const id = this.scopes.bindingOf.get(node);
|
|
353
|
+
if (id === void 0) return node.name;
|
|
354
|
+
return this.scopes.bindings.get(id)?.kind === "global" ? node.name : void 0;
|
|
355
|
+
}
|
|
356
|
+
/** What a library says `receiver:method(...)` is. The last library
|
|
357
|
+
* loaded is asked first, so a project's own library can answer for a
|
|
358
|
+
* method an earlier one also claims; no answer leaves an ordinary Luau
|
|
359
|
+
* method call, which is what a value that answers to the method itself
|
|
360
|
+
* wants (`text:upper()`). */
|
|
517
361
|
loweredMethodCall(node) {
|
|
518
362
|
const lowerings = this.options.lowerings;
|
|
519
|
-
if (!lowerings?.
|
|
363
|
+
if (!lowerings?.some((l) => l.plugin.methodCall)) return void 0;
|
|
520
364
|
const receiver = this.options.types?.typeOf.get(node.object);
|
|
365
|
+
const receiverGlobal = this.globalName(node.object);
|
|
366
|
+
const info = this.lazyArguments(node.arguments);
|
|
521
367
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
522
|
-
const
|
|
368
|
+
const plugin = lowerings[i].plugin;
|
|
369
|
+
if (!plugin.methodCall) continue;
|
|
370
|
+
const answer = plugin.methodCall({
|
|
523
371
|
method: node.method.name,
|
|
524
372
|
receiver,
|
|
373
|
+
receiverGlobal,
|
|
525
374
|
argumentCount: node.arguments.length,
|
|
375
|
+
at: this.callSite(node.method),
|
|
376
|
+
get arguments() {
|
|
377
|
+
return info();
|
|
378
|
+
},
|
|
526
379
|
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
527
380
|
});
|
|
528
|
-
if (answer)
|
|
381
|
+
if (answer) {
|
|
382
|
+
return {
|
|
383
|
+
callee: answer.callee,
|
|
384
|
+
prepend: this.prepended(answer.prepend, i, node),
|
|
385
|
+
passReceiver: answer.passReceiver !== false
|
|
386
|
+
};
|
|
387
|
+
}
|
|
529
388
|
}
|
|
530
389
|
return void 0;
|
|
531
390
|
}
|
|
391
|
+
/** What a library says a call to a global — `print(x)` — is. */
|
|
392
|
+
loweredGlobalCall(node) {
|
|
393
|
+
const lowerings = this.options.lowerings;
|
|
394
|
+
if (!lowerings?.some((l) => l.plugin.globalCall)) return void 0;
|
|
395
|
+
const name = this.globalName(node.callee);
|
|
396
|
+
if (name === void 0) return void 0;
|
|
397
|
+
const info = this.lazyArguments(node.arguments);
|
|
398
|
+
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
399
|
+
const plugin = lowerings[i].plugin;
|
|
400
|
+
if (!plugin.globalCall) continue;
|
|
401
|
+
const answer = plugin.globalCall({
|
|
402
|
+
name,
|
|
403
|
+
at: this.callSite(node.callee),
|
|
404
|
+
get arguments() {
|
|
405
|
+
return info();
|
|
406
|
+
},
|
|
407
|
+
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
408
|
+
});
|
|
409
|
+
if (answer) return { callee: answer.callee, prepend: this.prepended(answer.prepend, i, node) };
|
|
410
|
+
}
|
|
411
|
+
return void 0;
|
|
412
|
+
}
|
|
413
|
+
/** What a library says a global read as a value — `local p = print` — is. */
|
|
414
|
+
loweredGlobalValue(node) {
|
|
415
|
+
const lowerings = this.options.lowerings;
|
|
416
|
+
if (!lowerings?.some((l) => l.plugin.globalValue)) return void 0;
|
|
417
|
+
const name = this.globalName(node);
|
|
418
|
+
if (name === void 0) return void 0;
|
|
419
|
+
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
420
|
+
const answer = lowerings[i].plugin.globalValue?.({
|
|
421
|
+
name,
|
|
422
|
+
at: this.callSite(node),
|
|
423
|
+
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
424
|
+
});
|
|
425
|
+
if (answer !== void 0) return this.libraryExpression(answer, i, node);
|
|
426
|
+
}
|
|
427
|
+
return void 0;
|
|
428
|
+
}
|
|
429
|
+
lazyArguments(args) {
|
|
430
|
+
let info;
|
|
431
|
+
return () => info ??= this.argumentInfo(args);
|
|
432
|
+
}
|
|
433
|
+
prepended(sources, index2, node) {
|
|
434
|
+
return (sources ?? []).flatMap((source) => {
|
|
435
|
+
const expression = this.libraryExpression(source, index2, node);
|
|
436
|
+
return expression ? [expression] : [];
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/** Luau a library wrote as an expression. One that does not parse is the
|
|
440
|
+
* library's mistake, reported at the call it was written for. */
|
|
441
|
+
libraryExpression(source, index2, node) {
|
|
442
|
+
try {
|
|
443
|
+
return (0, import_luau_parser.parseExpressionFromSource)(source);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
this.report(node, `'${this.options.lowerings[index2].from}' lowered this to Luau that does not parse: ${JSON.stringify(source)} (${error.message})`);
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
532
449
|
helperDefinitions() {
|
|
533
450
|
const out = [];
|
|
534
451
|
const assign2 = this.helpers.get("assign");
|
|
@@ -556,12 +473,9 @@ var Lowerer = class {
|
|
|
556
473
|
});
|
|
557
474
|
continue;
|
|
558
475
|
}
|
|
559
|
-
out.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name)).body.statements);
|
|
560
|
-
}
|
|
561
|
-
const console = this.helpers.get("console");
|
|
562
|
-
if (console) {
|
|
563
476
|
const lines = this.options.module?.lines ?? "nil";
|
|
564
|
-
|
|
477
|
+
const fail = this.options.module?.fail ?? "nil";
|
|
478
|
+
out.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
|
|
565
479
|
}
|
|
566
480
|
const concat = this.helpers.get("concat");
|
|
567
481
|
if (concat) {
|
|
@@ -1419,7 +1333,7 @@ end
|
|
|
1419
1333
|
}
|
|
1420
1334
|
switch (node.type) {
|
|
1421
1335
|
case "Identifier":
|
|
1422
|
-
return this.reference(node);
|
|
1336
|
+
return this.loweredGlobalValue(node) ?? this.reference(node);
|
|
1423
1337
|
case "NilLiteral":
|
|
1424
1338
|
return nil();
|
|
1425
1339
|
case "BooleanLiteral":
|
|
@@ -1468,7 +1382,11 @@ end
|
|
|
1468
1382
|
case "MethodCallExpression": {
|
|
1469
1383
|
const chain = optionalChain(node);
|
|
1470
1384
|
if (chain) return this.optionalChainExpression(chain);
|
|
1471
|
-
|
|
1385
|
+
const object = linkObject(node);
|
|
1386
|
+
if (node.type === "CallExpression" && object.type === "Identifier") {
|
|
1387
|
+
return this.link(node, this.reference(object));
|
|
1388
|
+
}
|
|
1389
|
+
return this.link(node, this.expression(object));
|
|
1472
1390
|
}
|
|
1473
1391
|
case "ParenthesizedExpression":
|
|
1474
1392
|
return parenthesized(this.expression(node.expression));
|
|
@@ -1513,15 +1431,21 @@ end
|
|
|
1513
1431
|
return member(object, node.property.name);
|
|
1514
1432
|
case "IndexExpression":
|
|
1515
1433
|
return index(object, this.expression(node.index));
|
|
1516
|
-
case "CallExpression":
|
|
1517
|
-
|
|
1518
|
-
case "MethodCallExpression": {
|
|
1519
|
-
const consoleCall = this.consoleCall(node);
|
|
1520
|
-
if (consoleCall) return consoleCall;
|
|
1434
|
+
case "CallExpression": {
|
|
1435
|
+
const lowered = this.loweredGlobalCall(node);
|
|
1521
1436
|
const args = this.values(node.arguments);
|
|
1437
|
+
if (lowered) return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
|
|
1438
|
+
return call(object, args);
|
|
1439
|
+
}
|
|
1440
|
+
case "MethodCallExpression": {
|
|
1522
1441
|
const lowered = this.loweredMethodCall(node);
|
|
1442
|
+
const args = this.values(node.arguments);
|
|
1523
1443
|
if (lowered) {
|
|
1524
|
-
return call(calleePath(lowered.callee),
|
|
1444
|
+
return call(calleePath(lowered.callee), [
|
|
1445
|
+
...lowered.prepend,
|
|
1446
|
+
...lowered.passReceiver ? [object] : [],
|
|
1447
|
+
...args
|
|
1448
|
+
]);
|
|
1525
1449
|
}
|
|
1526
1450
|
if (!isLuauName(node.method.name)) {
|
|
1527
1451
|
this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
|
|
@@ -1934,10 +1858,6 @@ function mentions(expression, name) {
|
|
|
1934
1858
|
walk(expression);
|
|
1935
1859
|
return found;
|
|
1936
1860
|
}
|
|
1937
|
-
function isFunctionType(type) {
|
|
1938
|
-
if (type.kind === "function") return true;
|
|
1939
|
-
return type.kind === "intersection" && type.types.some((t) => t.kind === "function");
|
|
1940
|
-
}
|
|
1941
1861
|
function allNodes(root) {
|
|
1942
1862
|
const out = [];
|
|
1943
1863
|
const visit = (value) => {
|
|
@@ -2061,16 +1981,19 @@ async function bundle(options) {
|
|
|
2061
1981
|
const file = pending.shift();
|
|
2062
1982
|
if (modules.has(file)) continue;
|
|
2063
1983
|
const source = sources.get(file);
|
|
1984
|
+
const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
|
|
2064
1985
|
const lowered = lower(source.program, source.scopes, {
|
|
2065
1986
|
names,
|
|
2066
1987
|
types: analysis.types.get(file),
|
|
2067
1988
|
lowerings,
|
|
2068
1989
|
target: options.target ?? config?.target,
|
|
2069
1990
|
source: source.text,
|
|
1991
|
+
file: shown,
|
|
2070
1992
|
module: {
|
|
2071
1993
|
name: source.name,
|
|
2072
1994
|
require: requireExpression,
|
|
2073
1995
|
lines: `${G}.lines`,
|
|
1996
|
+
fail: `${G}.fail`,
|
|
2074
1997
|
resolve: (specifier) => {
|
|
2075
1998
|
const target = (0, import_parser3.resolveModulePath)(file, specifier, config);
|
|
2076
1999
|
if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
|
|
@@ -2080,7 +2003,6 @@ async function bundle(options) {
|
|
|
2080
2003
|
}
|
|
2081
2004
|
});
|
|
2082
2005
|
for (const d of lowered.diagnostics) diagnostics.push({ file, ...d, category: "module" });
|
|
2083
|
-
const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
|
|
2084
2006
|
for (const statement of allStatements(lowered.statements)) {
|
|
2085
2007
|
const line = lowered.origins.get(statement);
|
|
2086
2008
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
@@ -2130,11 +2052,21 @@ async function bundle(options) {
|
|
|
2130
2052
|
fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
|
|
2131
2053
|
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
|
|
2132
2054
|
}
|
|
2133
|
-
const
|
|
2134
|
-
|
|
2055
|
+
const ok = names.fresh("ok");
|
|
2056
|
+
const result = names.fresh("result");
|
|
2057
|
+
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
2058
|
+
local ${ok}, ${result} = xpcall(function()
|
|
2059
|
+
return ${G}.require(${JSON.stringify(sources.get(entry).name)})
|
|
2060
|
+
end, ${G}.fail)
|
|
2061
|
+
if not ${ok} then
|
|
2062
|
+
error(${result}, 0)
|
|
2063
|
+
end
|
|
2064
|
+
${modules.get(entry).info.exports ? `return ${result}` : ""}
|
|
2065
|
+
`).body.statements;
|
|
2066
|
+
program2.body.statements.push(...entryStatements);
|
|
2135
2067
|
const map = buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins);
|
|
2136
2068
|
if (map.size) {
|
|
2137
|
-
program2.body.statements.splice(program2.body.statements.length -
|
|
2069
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, assign(
|
|
2138
2070
|
[member(identifier(G), "lines")],
|
|
2139
2071
|
[(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
|
|
2140
2072
|
));
|
|
@@ -2146,6 +2078,52 @@ function runtime(G) {
|
|
|
2146
2078
|
local ${G}
|
|
2147
2079
|
${G} = {
|
|
2148
2080
|
modules = {},
|
|
2081
|
+
-- Filled in just before the entry runs: bundle line -> { file, line }.
|
|
2082
|
+
lines = nil,
|
|
2083
|
+
-- What an error nothing caught says, in the project's files: the bundle's
|
|
2084
|
+
-- own positions in the message are replaced by the places they came from,
|
|
2085
|
+
-- and a traceback of those places follows. A message that is not a string
|
|
2086
|
+
-- is left as it is, and a position that is not this bundle's (a library
|
|
2087
|
+
-- that raised its error with one already mapped) is left alone.
|
|
2088
|
+
fail = function(message)
|
|
2089
|
+
if type(message) ~= "string" then
|
|
2090
|
+
return message
|
|
2091
|
+
end
|
|
2092
|
+
local lines = ${G}.lines
|
|
2093
|
+
if lines == nil then
|
|
2094
|
+
return debug.traceback(message, 2)
|
|
2095
|
+
end
|
|
2096
|
+
local chunk
|
|
2097
|
+
if debug.info ~= nil then
|
|
2098
|
+
chunk = debug.info(1, "s")
|
|
2099
|
+
else
|
|
2100
|
+
chunk = debug.getinfo(1, "S").short_src
|
|
2101
|
+
end
|
|
2102
|
+
local prefix = chunk:gsub("%p", "%%%0")
|
|
2103
|
+
local function place(line)
|
|
2104
|
+
local origin = lines[tonumber(line)]
|
|
2105
|
+
if origin == nil then
|
|
2106
|
+
return nil
|
|
2107
|
+
end
|
|
2108
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2109
|
+
end
|
|
2110
|
+
local mapped = message:gsub(prefix .. ":(%d+)", function(line)
|
|
2111
|
+
return place(line)
|
|
2112
|
+
end)
|
|
2113
|
+
local frames = {}
|
|
2114
|
+
for frame in debug.traceback("", 2):gmatch("[^\\n]+") do
|
|
2115
|
+
local line = frame:match(prefix .. ":(%d+)")
|
|
2116
|
+
local at = line ~= nil and place(line) or nil
|
|
2117
|
+
if at ~= nil then
|
|
2118
|
+
local name = frame:match("function ([%w_.:]+)")
|
|
2119
|
+
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2120
|
+
end
|
|
2121
|
+
end
|
|
2122
|
+
if #frames == 0 then
|
|
2123
|
+
return mapped
|
|
2124
|
+
end
|
|
2125
|
+
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2126
|
+
end,
|
|
2149
2127
|
records = {},
|
|
2150
2128
|
require = function(name)
|
|
2151
2129
|
local record = ${G}.records[name]
|
package/dist/index.d.cts
CHANGED
|
@@ -88,9 +88,13 @@ interface ModuleContext {
|
|
|
88
88
|
/** The bundle's `require`: an expression that, called with a module's
|
|
89
89
|
* name, returns its exports table. */
|
|
90
90
|
readonly require: L.Expression;
|
|
91
|
-
/** Luau reaching the bundle's line map — what
|
|
92
|
-
* name the file a frame was written in. */
|
|
91
|
+
/** Luau reaching the bundle's line map — what a library's runtime reads,
|
|
92
|
+
* as `__LINES__`, to name the file a frame was written in. */
|
|
93
93
|
readonly lines?: string;
|
|
94
|
+
/** Luau reaching the bundle's error reporter — what a library's runtime
|
|
95
|
+
* reads, as `__FAIL__`, to report an error from a callback the way an
|
|
96
|
+
* error that reached the entry is reported. */
|
|
97
|
+
readonly fail?: string;
|
|
94
98
|
}
|
|
95
99
|
interface LowerOptions {
|
|
96
100
|
/** Lower the file as a module of a bundle. Without it, `import` and
|
|
@@ -107,9 +111,12 @@ interface LowerOptions {
|
|
|
107
111
|
* `loadLowerings`). The compiler lowers the language; a call written
|
|
108
112
|
* against a library's types is the library's to explain. */
|
|
109
113
|
readonly lowerings?: readonly LoadedLowering[];
|
|
110
|
-
/** The file's own text, so
|
|
111
|
-
*
|
|
114
|
+
/** The file's own text, so a library can be told the code an argument was
|
|
115
|
+
* written as (`console:log` shows a function that way). */
|
|
112
116
|
readonly source?: string;
|
|
117
|
+
/** The file as a library's runtime names it — `src/shop/buy.tilua` —
|
|
118
|
+
* in the call sites it is handed. */
|
|
119
|
+
readonly file?: string;
|
|
113
120
|
/** Which Lua the output has to run on. Default `"luau"`. `"lua51"` also
|
|
114
121
|
* lowers what Luau adds to Lua and 5.1 has no syntax for — `a += b`,
|
|
115
122
|
* `continue`, `if c then a else b` as an expression, `//`. */
|
package/dist/index.d.ts
CHANGED
|
@@ -88,9 +88,13 @@ interface ModuleContext {
|
|
|
88
88
|
/** The bundle's `require`: an expression that, called with a module's
|
|
89
89
|
* name, returns its exports table. */
|
|
90
90
|
readonly require: L.Expression;
|
|
91
|
-
/** Luau reaching the bundle's line map — what
|
|
92
|
-
* name the file a frame was written in. */
|
|
91
|
+
/** Luau reaching the bundle's line map — what a library's runtime reads,
|
|
92
|
+
* as `__LINES__`, to name the file a frame was written in. */
|
|
93
93
|
readonly lines?: string;
|
|
94
|
+
/** Luau reaching the bundle's error reporter — what a library's runtime
|
|
95
|
+
* reads, as `__FAIL__`, to report an error from a callback the way an
|
|
96
|
+
* error that reached the entry is reported. */
|
|
97
|
+
readonly fail?: string;
|
|
94
98
|
}
|
|
95
99
|
interface LowerOptions {
|
|
96
100
|
/** Lower the file as a module of a bundle. Without it, `import` and
|
|
@@ -107,9 +111,12 @@ interface LowerOptions {
|
|
|
107
111
|
* `loadLowerings`). The compiler lowers the language; a call written
|
|
108
112
|
* against a library's types is the library's to explain. */
|
|
109
113
|
readonly lowerings?: readonly LoadedLowering[];
|
|
110
|
-
/** The file's own text, so
|
|
111
|
-
*
|
|
114
|
+
/** The file's own text, so a library can be told the code an argument was
|
|
115
|
+
* written as (`console:log` shows a function that way). */
|
|
112
116
|
readonly source?: string;
|
|
117
|
+
/** The file as a library's runtime names it — `src/shop/buy.tilua` —
|
|
118
|
+
* in the call sites it is handed. */
|
|
119
|
+
readonly file?: string;
|
|
113
120
|
/** Which Lua the output has to run on. Default `"luau"`. `"lua51"` also
|
|
114
121
|
* lowers what Luau adds to Lua and 5.1 has no syntax for — `a += b`,
|
|
115
122
|
* `continue`, `if c then a else b` as an expression, `//`. */
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tilua/compiler",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"type": "module",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"luau-parser": "^1.0.3",
|
|
40
|
-
"@tilua/parser": "^1.0
|
|
40
|
+
"@tilua/parser": "^1.1.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^22.20.2",
|