@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/README.md
CHANGED
|
@@ -168,6 +168,33 @@ needed it; the receiver becomes the call's first argument unless
|
|
|
168
168
|
`passReceiver: false`. The last library loaded is asked first, and `undefined`
|
|
169
169
|
leaves an ordinary Luau method call.
|
|
170
170
|
|
|
171
|
+
Hooks also get the call site (`at`) and what the compiler knew about each
|
|
172
|
+
argument (`arguments`: type, the code it was written as, and what a name was
|
|
173
|
+
declared as). An answer can `prepend` Luau expressions ahead of the written
|
|
174
|
+
arguments. `globalCall` and `globalValue` answer for a global, such as
|
|
175
|
+
`print(x)` or a bare `print`. A runtime can read `__LINES__` to reach the
|
|
176
|
+
bundle's line map. See the `@tilua/parser` README for the details.
|
|
177
|
+
`console:log` is not built into the compiler: `@tilua-types/lua` implements it
|
|
178
|
+
with these hooks.
|
|
179
|
+
|
|
180
|
+
## Errors at runtime
|
|
181
|
+
|
|
182
|
+
A bundle is one Luau file, so a position Luau reports names a line of that
|
|
183
|
+
file. The entry therefore runs under `xpcall`. If an error reaches the top,
|
|
184
|
+
its message is rewritten from bundle lines to the places in the project that
|
|
185
|
+
wrote them, a traceback of those places is added, and the error is raised
|
|
186
|
+
again:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
src/util.tilua:3: attempt to index nil with 'value'
|
|
190
|
+
at src/util.tilua:3 (boom)
|
|
191
|
+
at src/main.tilua:3 (load)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
A message that is not a string is raised unchanged. A position that is already
|
|
195
|
+
in the project's files, such as one from `error` in `@tilua-types/lua`, is
|
|
196
|
+
left as it is.
|
|
197
|
+
|
|
171
198
|
## Modules
|
|
172
199
|
|
|
173
200
|
Roblox's `require` takes an Instance, so the bundle has its own. Each module
|
|
@@ -11,7 +11,7 @@ function resolveConfig(input, entry, cwd = process.cwd()) {
|
|
|
11
11
|
|
|
12
12
|
// src/lower.ts
|
|
13
13
|
import { formatType } from "@tilua/parser";
|
|
14
|
-
import { parse as parseLuau } from "luau-parser";
|
|
14
|
+
import { parse as parseLuau, parseExpressionFromSource } from "luau-parser";
|
|
15
15
|
|
|
16
16
|
// src/luau.ts
|
|
17
17
|
var NO_SPAN = { line: { start: 0, end: 0 }, column: { start: 0, end: 0 } };
|
|
@@ -101,144 +101,6 @@ function isLuauName(name) {
|
|
|
101
101
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
// src/console.ts
|
|
105
|
-
var CONSOLE_RUNTIME = `
|
|
106
|
-
local __NAME__ = (function()
|
|
107
|
-
local console = {}
|
|
108
|
-
local MAX_DEPTH = 4
|
|
109
|
-
local ESCAPES = { ["\\n"] = "\\\\n", ["\\r"] = "\\\\r", ["\\t"] = "\\\\t", ["\\""] = "\\\\\\"", ["\\\\"] = "\\\\\\\\" }
|
|
110
|
-
|
|
111
|
-
local function quote(text)
|
|
112
|
-
return '"' .. text:gsub('[%c"\\\\]', function(c) return ESCAPES[c] or ("\\\\%d"):format(c:byte()) end) .. '"'
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
-- A table with 1..n and nothing else prints as a list, as it was written.
|
|
116
|
-
local function isArray(value)
|
|
117
|
-
local count = 0
|
|
118
|
-
for key in pairs(value) do
|
|
119
|
-
if type(key) ~= "number" then return false end
|
|
120
|
-
count = count + 1
|
|
121
|
-
end
|
|
122
|
-
return count == #value
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
local function isIdentifier(key)
|
|
126
|
-
return type(key) == "string" and key:match("^[A-Za-z_][A-Za-z0-9_]*$") ~= nil
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
local render
|
|
130
|
-
|
|
131
|
-
local function renderTable(value, meta, seen, depth, indent)
|
|
132
|
-
if seen[value] then return "<cycle>" end
|
|
133
|
-
if depth > MAX_DEPTH then return "..." end
|
|
134
|
-
seen[value] = true
|
|
135
|
-
local inner = indent .. " "
|
|
136
|
-
local parts = {}
|
|
137
|
-
if isArray(value) then
|
|
138
|
-
for i = 1, #value do
|
|
139
|
-
parts[#parts + 1] = inner .. render(value[i], nil, seen, depth + 1, inner)
|
|
140
|
-
end
|
|
141
|
-
else
|
|
142
|
-
local keys = {}
|
|
143
|
-
for key in pairs(value) do keys[#keys + 1] = key end
|
|
144
|
-
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
|
|
145
|
-
for _, key in ipairs(keys) do
|
|
146
|
-
local name = isIdentifier(key) and key or ("[" .. render(key, nil, seen, depth + 1, inner) .. "]")
|
|
147
|
-
parts[#parts + 1] = inner .. name .. " = " .. render(value[key], nil, seen, depth + 1, inner)
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
seen[value] = nil
|
|
151
|
-
if #parts == 0 then return "{}" end
|
|
152
|
-
local open = isArray(value) and "[" or "{"
|
|
153
|
-
local close = isArray(value) and "]" or "}"
|
|
154
|
-
return open .. "\\n" .. table.concat(parts, ",\\n") .. "\\n" .. indent .. close
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
-- \`meta\` is what the compiler knew about this value and the runtime cannot:
|
|
158
|
-
-- a function's type, and the code it was written as.
|
|
159
|
-
render = function(value, meta, seen, depth, indent)
|
|
160
|
-
local kind = type(value)
|
|
161
|
-
if kind == "string" then return quote(value) end
|
|
162
|
-
if kind == "number" or kind == "boolean" or kind == "nil" then return tostring(value) end
|
|
163
|
-
if kind == "function" then
|
|
164
|
-
if meta == nil then return "function" end
|
|
165
|
-
if meta[2] == nil or meta[2] == "" then return meta[1] end
|
|
166
|
-
return meta[1] .. " " .. meta[2]
|
|
167
|
-
end
|
|
168
|
-
if kind == "table" then
|
|
169
|
-
local shown = rawget(value, "__tostring") == nil and getmetatable(value)
|
|
170
|
-
-- A value with its own \`__tostring\` (a Roblox Vector3, a class
|
|
171
|
-
-- instance that says how it reads) is trusted to describe itself.
|
|
172
|
-
if shown ~= nil and rawget(shown, "__tostring") ~= nil then return tostring(value) end
|
|
173
|
-
return renderTable(value, meta, seen, depth, indent)
|
|
174
|
-
end
|
|
175
|
-
return tostring(value)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
local function describe(meta, index, value)
|
|
179
|
-
return render(value, meta ~= nil and meta[index] or nil, {}, 1, "")
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
local function join(meta, count, ...)
|
|
183
|
-
local parts = {}
|
|
184
|
-
for i = 1, count do
|
|
185
|
-
parts[#parts + 1] = describe(meta, i, (select(i, ...)))
|
|
186
|
-
end
|
|
187
|
-
return table.concat(parts, " ")
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
-- A line of the bundle, as the place in the project that wrote it.
|
|
191
|
-
local lines = __LINES__
|
|
192
|
-
local function place(line)
|
|
193
|
-
local origin = lines ~= nil and lines[tonumber(line)]
|
|
194
|
-
if origin == nil then return nil end
|
|
195
|
-
return origin[1] .. ":" .. tostring(origin[2])
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
-- Luau names the bundle in every frame; the project's files are what the
|
|
199
|
-
-- reader knows. Frames the map has nothing for are dropped: they are this
|
|
200
|
-
-- runtime's own, and the bundle's.
|
|
201
|
-
local function trace(level)
|
|
202
|
-
local raw = debug.traceback(nil, level)
|
|
203
|
-
local out = {}
|
|
204
|
-
for frame in raw:gmatch("[^\\n]+") do
|
|
205
|
-
local line = frame:match(":(%d+)")
|
|
206
|
-
local at = line ~= nil and place(line) or nil
|
|
207
|
-
if at ~= nil then
|
|
208
|
-
local name = frame:match("function ([%w_.:]+)")
|
|
209
|
-
out[#out + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
return table.concat(out, "\\n")
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
-- Never on the caller's thread: reporting a problem must not become one.
|
|
216
|
-
local detach = (task ~= nil and task.spawn) or function(f) return f() end
|
|
217
|
-
|
|
218
|
-
local function raise(prefix, meta, count, ...)
|
|
219
|
-
local message = prefix .. join(meta, count, ...)
|
|
220
|
-
local where = trace(3)
|
|
221
|
-
detach(function()
|
|
222
|
-
error(where ~= "" and (message .. "\\n" .. where) or message, 0)
|
|
223
|
-
end)
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
function console.log(meta, ...)
|
|
227
|
-
print(join(meta, select("#", ...), ...))
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
function console.warn(meta, ...)
|
|
231
|
-
raise("[warn] ", meta, select("#", ...), ...)
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
function console.error(meta, ...)
|
|
235
|
-
raise("", meta, select("#", ...), ...)
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
return console
|
|
239
|
-
end)()
|
|
240
|
-
`;
|
|
241
|
-
|
|
242
104
|
// src/names.ts
|
|
243
105
|
var Names = class _Names {
|
|
244
106
|
constructor(taken) {
|
|
@@ -386,60 +248,9 @@ var Lowerer = class {
|
|
|
386
248
|
}
|
|
387
249
|
loweringNames = /* @__PURE__ */ new Map();
|
|
388
250
|
loweringUsed = [];
|
|
389
|
-
/** What a library says `receiver:method(...)` is. The last library
|
|
390
|
-
* loaded is asked first, so a project's own library can answer for a
|
|
391
|
-
* method an earlier one also claims; no answer leaves an ordinary Luau
|
|
392
|
-
* method call, which is what a value that answers to the method itself
|
|
393
|
-
* wants (`text:upper()`). */
|
|
394
|
-
/** `console:log(a, f)` and its two siblings.
|
|
395
|
-
*
|
|
396
|
-
* Handled here rather than through a library's lowering because what they
|
|
397
|
-
* need is not a different callee but *more than was written*: a function
|
|
398
|
-
* prints as its type and its code, and neither survives to runtime. The
|
|
399
|
-
* compiler is the only thing that has both, so it passes them along:
|
|
400
|
-
*
|
|
401
|
-
* console:log(a, double)
|
|
402
|
-
* -- becomes
|
|
403
|
-
* tilua_console.log({ [2] = { "(x: number) => number", "(x: number) => x * 2" } }, a, double)
|
|
404
|
-
*
|
|
405
|
-
* The meta table is `nil` when nothing was worth saying, which is most
|
|
406
|
-
* calls. `console` itself is never passed: it holds nothing. */
|
|
407
|
-
consoleCall(node) {
|
|
408
|
-
if (node.object.type !== "Identifier" || node.object.name !== "console") return void 0;
|
|
409
|
-
const method = node.method.name;
|
|
410
|
-
if (method !== "log" && method !== "warn" && method !== "error") return void 0;
|
|
411
|
-
const binding = this.scopes.bindingOf.get(node.object);
|
|
412
|
-
if (binding !== void 0 && this.scopes.bindings.get(binding)?.kind !== "global") return void 0;
|
|
413
|
-
const meta = this.consoleMeta(node.arguments);
|
|
414
|
-
return call(
|
|
415
|
-
member(this.helper("console"), method),
|
|
416
|
-
[meta ?? nil(), ...this.values(node.arguments)]
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
/** What the compiler knows about the arguments and the runtime cannot:
|
|
420
|
-
* for each function argument, the type it was inferred as and the code it
|
|
421
|
-
* was written as. Keyed by position, so an argument that needs nothing
|
|
422
|
-
* costs nothing. */
|
|
423
|
-
consoleMeta(args) {
|
|
424
|
-
const fields = [];
|
|
425
|
-
args.forEach((argument, index2) => {
|
|
426
|
-
const type = this.options.types?.typeOf.get(argument);
|
|
427
|
-
if (!type || !isFunctionType(type)) return;
|
|
428
|
-
const written = this.writtenSource(this.declarationOf(argument) ?? argument);
|
|
429
|
-
fields.push({
|
|
430
|
-
type: "TableFieldComputed",
|
|
431
|
-
key: number(index2 + 1),
|
|
432
|
-
value: table([
|
|
433
|
-
{ type: "TableFieldPositional", value: string(formatType(type)) },
|
|
434
|
-
{ type: "TableFieldPositional", value: string(written) }
|
|
435
|
-
])
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
return fields.length ? table(fields) : void 0;
|
|
439
|
-
}
|
|
440
251
|
/** What a name was declared as: the value of `const f = ...`, or the whole
|
|
441
|
-
* `function f() { ... }`. Built once, on the first
|
|
442
|
-
*
|
|
252
|
+
* `function f() { ... }`. Built once, on the first call a library asks
|
|
253
|
+
* about — most files have none. */
|
|
443
254
|
declarationOf(argument) {
|
|
444
255
|
if (argument.type !== "Identifier") return void 0;
|
|
445
256
|
if (!this.declarations) {
|
|
@@ -478,21 +289,127 @@ var Lowerer = class {
|
|
|
478
289
|
const trim = (line) => line.startsWith(indent) ? line.slice(indent.length) : line;
|
|
479
290
|
return [first, ...middle.map(trim), trim(last)].join("\n");
|
|
480
291
|
}
|
|
292
|
+
/** Where `node` was written, as a library is told it. */
|
|
293
|
+
callSite(node) {
|
|
294
|
+
return { file: this.options.file, line: node.line.start, column: node.column.start };
|
|
295
|
+
}
|
|
296
|
+
/** What the compiler knows about each argument and the runtime cannot:
|
|
297
|
+
* its type, and the code it — or what it names — was written as. Built
|
|
298
|
+
* only if a library reads it. */
|
|
299
|
+
argumentInfo(args) {
|
|
300
|
+
return args.map((argument) => {
|
|
301
|
+
const declaration = this.declarationOf(argument);
|
|
302
|
+
const type = this.options.types?.typeOf.get(argument);
|
|
303
|
+
return {
|
|
304
|
+
type,
|
|
305
|
+
typeText: type ? formatType(type) : void 0,
|
|
306
|
+
source: this.writtenSource(argument),
|
|
307
|
+
declaration: declaration ? this.writtenSource(declaration) : void 0,
|
|
308
|
+
spread: argument.type === "SpreadElement"
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/** The name a global is read by at `node`, or `undefined` when `node` is
|
|
313
|
+
* not a global — a local of the same name is the author's. */
|
|
314
|
+
globalName(node) {
|
|
315
|
+
if (node.type !== "Identifier") return void 0;
|
|
316
|
+
const id = this.scopes.bindingOf.get(node);
|
|
317
|
+
if (id === void 0) return node.name;
|
|
318
|
+
return this.scopes.bindings.get(id)?.kind === "global" ? node.name : void 0;
|
|
319
|
+
}
|
|
320
|
+
/** What a library says `receiver:method(...)` is. The last library
|
|
321
|
+
* loaded is asked first, so a project's own library can answer for a
|
|
322
|
+
* method an earlier one also claims; no answer leaves an ordinary Luau
|
|
323
|
+
* method call, which is what a value that answers to the method itself
|
|
324
|
+
* wants (`text:upper()`). */
|
|
481
325
|
loweredMethodCall(node) {
|
|
482
326
|
const lowerings = this.options.lowerings;
|
|
483
|
-
if (!lowerings?.
|
|
327
|
+
if (!lowerings?.some((l) => l.plugin.methodCall)) return void 0;
|
|
484
328
|
const receiver = this.options.types?.typeOf.get(node.object);
|
|
329
|
+
const receiverGlobal = this.globalName(node.object);
|
|
330
|
+
const info = this.lazyArguments(node.arguments);
|
|
485
331
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
486
|
-
const
|
|
332
|
+
const plugin = lowerings[i].plugin;
|
|
333
|
+
if (!plugin.methodCall) continue;
|
|
334
|
+
const answer = plugin.methodCall({
|
|
487
335
|
method: node.method.name,
|
|
488
336
|
receiver,
|
|
337
|
+
receiverGlobal,
|
|
489
338
|
argumentCount: node.arguments.length,
|
|
339
|
+
at: this.callSite(node.method),
|
|
340
|
+
get arguments() {
|
|
341
|
+
return info();
|
|
342
|
+
},
|
|
490
343
|
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
491
344
|
});
|
|
492
|
-
if (answer)
|
|
345
|
+
if (answer) {
|
|
346
|
+
return {
|
|
347
|
+
callee: answer.callee,
|
|
348
|
+
prepend: this.prepended(answer.prepend, i, node),
|
|
349
|
+
passReceiver: answer.passReceiver !== false
|
|
350
|
+
};
|
|
351
|
+
}
|
|
493
352
|
}
|
|
494
353
|
return void 0;
|
|
495
354
|
}
|
|
355
|
+
/** What a library says a call to a global — `print(x)` — is. */
|
|
356
|
+
loweredGlobalCall(node) {
|
|
357
|
+
const lowerings = this.options.lowerings;
|
|
358
|
+
if (!lowerings?.some((l) => l.plugin.globalCall)) return void 0;
|
|
359
|
+
const name = this.globalName(node.callee);
|
|
360
|
+
if (name === void 0) return void 0;
|
|
361
|
+
const info = this.lazyArguments(node.arguments);
|
|
362
|
+
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
363
|
+
const plugin = lowerings[i].plugin;
|
|
364
|
+
if (!plugin.globalCall) continue;
|
|
365
|
+
const answer = plugin.globalCall({
|
|
366
|
+
name,
|
|
367
|
+
at: this.callSite(node.callee),
|
|
368
|
+
get arguments() {
|
|
369
|
+
return info();
|
|
370
|
+
},
|
|
371
|
+
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
372
|
+
});
|
|
373
|
+
if (answer) return { callee: answer.callee, prepend: this.prepended(answer.prepend, i, node) };
|
|
374
|
+
}
|
|
375
|
+
return void 0;
|
|
376
|
+
}
|
|
377
|
+
/** What a library says a global read as a value — `local p = print` — is. */
|
|
378
|
+
loweredGlobalValue(node) {
|
|
379
|
+
const lowerings = this.options.lowerings;
|
|
380
|
+
if (!lowerings?.some((l) => l.plugin.globalValue)) return void 0;
|
|
381
|
+
const name = this.globalName(node);
|
|
382
|
+
if (name === void 0) return void 0;
|
|
383
|
+
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
384
|
+
const answer = lowerings[i].plugin.globalValue?.({
|
|
385
|
+
name,
|
|
386
|
+
at: this.callSite(node),
|
|
387
|
+
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
388
|
+
});
|
|
389
|
+
if (answer !== void 0) return this.libraryExpression(answer, i, node);
|
|
390
|
+
}
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
lazyArguments(args) {
|
|
394
|
+
let info;
|
|
395
|
+
return () => info ??= this.argumentInfo(args);
|
|
396
|
+
}
|
|
397
|
+
prepended(sources, index2, node) {
|
|
398
|
+
return (sources ?? []).flatMap((source) => {
|
|
399
|
+
const expression = this.libraryExpression(source, index2, node);
|
|
400
|
+
return expression ? [expression] : [];
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
/** Luau a library wrote as an expression. One that does not parse is the
|
|
404
|
+
* library's mistake, reported at the call it was written for. */
|
|
405
|
+
libraryExpression(source, index2, node) {
|
|
406
|
+
try {
|
|
407
|
+
return parseExpressionFromSource(source);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
this.report(node, `'${this.options.lowerings[index2].from}' lowered this to Luau that does not parse: ${JSON.stringify(source)} (${error.message})`);
|
|
410
|
+
return void 0;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
496
413
|
helperDefinitions() {
|
|
497
414
|
const out = [];
|
|
498
415
|
const assign2 = this.helpers.get("assign");
|
|
@@ -520,12 +437,9 @@ var Lowerer = class {
|
|
|
520
437
|
});
|
|
521
438
|
continue;
|
|
522
439
|
}
|
|
523
|
-
out.push(...parseLuau(source.replace(/__NAME__/g, name)).body.statements);
|
|
524
|
-
}
|
|
525
|
-
const console = this.helpers.get("console");
|
|
526
|
-
if (console) {
|
|
527
440
|
const lines = this.options.module?.lines ?? "nil";
|
|
528
|
-
|
|
441
|
+
const fail = this.options.module?.fail ?? "nil";
|
|
442
|
+
out.push(...parseLuau(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
|
|
529
443
|
}
|
|
530
444
|
const concat = this.helpers.get("concat");
|
|
531
445
|
if (concat) {
|
|
@@ -1383,7 +1297,7 @@ end
|
|
|
1383
1297
|
}
|
|
1384
1298
|
switch (node.type) {
|
|
1385
1299
|
case "Identifier":
|
|
1386
|
-
return this.reference(node);
|
|
1300
|
+
return this.loweredGlobalValue(node) ?? this.reference(node);
|
|
1387
1301
|
case "NilLiteral":
|
|
1388
1302
|
return nil();
|
|
1389
1303
|
case "BooleanLiteral":
|
|
@@ -1432,7 +1346,11 @@ end
|
|
|
1432
1346
|
case "MethodCallExpression": {
|
|
1433
1347
|
const chain = optionalChain(node);
|
|
1434
1348
|
if (chain) return this.optionalChainExpression(chain);
|
|
1435
|
-
|
|
1349
|
+
const object = linkObject(node);
|
|
1350
|
+
if (node.type === "CallExpression" && object.type === "Identifier") {
|
|
1351
|
+
return this.link(node, this.reference(object));
|
|
1352
|
+
}
|
|
1353
|
+
return this.link(node, this.expression(object));
|
|
1436
1354
|
}
|
|
1437
1355
|
case "ParenthesizedExpression":
|
|
1438
1356
|
return parenthesized(this.expression(node.expression));
|
|
@@ -1477,15 +1395,21 @@ end
|
|
|
1477
1395
|
return member(object, node.property.name);
|
|
1478
1396
|
case "IndexExpression":
|
|
1479
1397
|
return index(object, this.expression(node.index));
|
|
1480
|
-
case "CallExpression":
|
|
1481
|
-
|
|
1482
|
-
case "MethodCallExpression": {
|
|
1483
|
-
const consoleCall = this.consoleCall(node);
|
|
1484
|
-
if (consoleCall) return consoleCall;
|
|
1398
|
+
case "CallExpression": {
|
|
1399
|
+
const lowered = this.loweredGlobalCall(node);
|
|
1485
1400
|
const args = this.values(node.arguments);
|
|
1401
|
+
if (lowered) return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
|
|
1402
|
+
return call(object, args);
|
|
1403
|
+
}
|
|
1404
|
+
case "MethodCallExpression": {
|
|
1486
1405
|
const lowered = this.loweredMethodCall(node);
|
|
1406
|
+
const args = this.values(node.arguments);
|
|
1487
1407
|
if (lowered) {
|
|
1488
|
-
return call(calleePath(lowered.callee),
|
|
1408
|
+
return call(calleePath(lowered.callee), [
|
|
1409
|
+
...lowered.prepend,
|
|
1410
|
+
...lowered.passReceiver ? [object] : [],
|
|
1411
|
+
...args
|
|
1412
|
+
]);
|
|
1489
1413
|
}
|
|
1490
1414
|
if (!isLuauName(node.method.name)) {
|
|
1491
1415
|
this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
|
|
@@ -1898,10 +1822,6 @@ function mentions(expression, name) {
|
|
|
1898
1822
|
walk(expression);
|
|
1899
1823
|
return found;
|
|
1900
1824
|
}
|
|
1901
|
-
function isFunctionType(type) {
|
|
1902
|
-
if (type.kind === "function") return true;
|
|
1903
|
-
return type.kind === "intersection" && type.types.some((t) => t.kind === "function");
|
|
1904
|
-
}
|
|
1905
1825
|
function allNodes(root) {
|
|
1906
1826
|
const out = [];
|
|
1907
1827
|
const visit = (value) => {
|
|
@@ -1960,7 +1880,7 @@ import {
|
|
|
1960
1880
|
ParseError,
|
|
1961
1881
|
LexError
|
|
1962
1882
|
} from "@tilua/parser";
|
|
1963
|
-
import { parse as parseLuau2, parseExpressionFromSource, print } from "luau-parser";
|
|
1883
|
+
import { parse as parseLuau2, parseExpressionFromSource as parseExpressionFromSource2, print } from "luau-parser";
|
|
1964
1884
|
|
|
1965
1885
|
// src/linemap.ts
|
|
1966
1886
|
function statementsOf(node) {
|
|
@@ -2043,16 +1963,19 @@ async function bundle(options) {
|
|
|
2043
1963
|
const file = pending.shift();
|
|
2044
1964
|
if (modules.has(file)) continue;
|
|
2045
1965
|
const source = sources.get(file);
|
|
1966
|
+
const shown = relative(root, file).replace(/\\/g, "/");
|
|
2046
1967
|
const lowered = lower(source.program, source.scopes, {
|
|
2047
1968
|
names,
|
|
2048
1969
|
types: analysis.types.get(file),
|
|
2049
1970
|
lowerings,
|
|
2050
1971
|
target: options.target ?? config?.target,
|
|
2051
1972
|
source: source.text,
|
|
1973
|
+
file: shown,
|
|
2052
1974
|
module: {
|
|
2053
1975
|
name: source.name,
|
|
2054
1976
|
require: requireExpression,
|
|
2055
1977
|
lines: `${G}.lines`,
|
|
1978
|
+
fail: `${G}.fail`,
|
|
2056
1979
|
resolve: (specifier) => {
|
|
2057
1980
|
const target = resolveModulePath(file, specifier, config);
|
|
2058
1981
|
if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
|
|
@@ -2062,7 +1985,6 @@ async function bundle(options) {
|
|
|
2062
1985
|
}
|
|
2063
1986
|
});
|
|
2064
1987
|
for (const d of lowered.diagnostics) diagnostics.push({ file, ...d, category: "module" });
|
|
2065
|
-
const shown = relative(root, file).replace(/\\/g, "/");
|
|
2066
1988
|
for (const statement of allStatements(lowered.statements)) {
|
|
2067
1989
|
const line = lowered.origins.get(statement);
|
|
2068
1990
|
if (line !== void 0) origins.set(statement, { file: shown, line });
|
|
@@ -2112,13 +2034,23 @@ async function bundle(options) {
|
|
|
2112
2034
|
fields.push(field("load", functionExpression(functionBody([module.exportsName], module.statements, true))));
|
|
2113
2035
|
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module.name), value: table(fields) });
|
|
2114
2036
|
}
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2037
|
+
const ok = names.fresh("ok");
|
|
2038
|
+
const result = names.fresh("result");
|
|
2039
|
+
const entryStatements = parseLuau2(`
|
|
2040
|
+
local ${ok}, ${result} = xpcall(function()
|
|
2041
|
+
return ${G}.require(${JSON.stringify(sources.get(entry).name)})
|
|
2042
|
+
end, ${G}.fail)
|
|
2043
|
+
if not ${ok} then
|
|
2044
|
+
error(${result}, 0)
|
|
2045
|
+
end
|
|
2046
|
+
${modules.get(entry).info.exports ? `return ${result}` : ""}
|
|
2047
|
+
`).body.statements;
|
|
2048
|
+
program2.body.statements.push(...entryStatements);
|
|
2117
2049
|
const map = buildLineMap(program2, parseLuau2(print(program2)), origins);
|
|
2118
2050
|
if (map.size) {
|
|
2119
|
-
program2.body.statements.splice(program2.body.statements.length -
|
|
2051
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, assign(
|
|
2120
2052
|
[member(identifier(G), "lines")],
|
|
2121
|
-
[
|
|
2053
|
+
[parseExpressionFromSource2(lineMapSource(map))]
|
|
2122
2054
|
));
|
|
2123
2055
|
}
|
|
2124
2056
|
return { code: print(program2) + "\n", modules: moduleNames, diagnostics };
|
|
@@ -2128,6 +2060,52 @@ function runtime(G) {
|
|
|
2128
2060
|
local ${G}
|
|
2129
2061
|
${G} = {
|
|
2130
2062
|
modules = {},
|
|
2063
|
+
-- Filled in just before the entry runs: bundle line -> { file, line }.
|
|
2064
|
+
lines = nil,
|
|
2065
|
+
-- What an error nothing caught says, in the project's files: the bundle's
|
|
2066
|
+
-- own positions in the message are replaced by the places they came from,
|
|
2067
|
+
-- and a traceback of those places follows. A message that is not a string
|
|
2068
|
+
-- is left as it is, and a position that is not this bundle's (a library
|
|
2069
|
+
-- that raised its error with one already mapped) is left alone.
|
|
2070
|
+
fail = function(message)
|
|
2071
|
+
if type(message) ~= "string" then
|
|
2072
|
+
return message
|
|
2073
|
+
end
|
|
2074
|
+
local lines = ${G}.lines
|
|
2075
|
+
if lines == nil then
|
|
2076
|
+
return debug.traceback(message, 2)
|
|
2077
|
+
end
|
|
2078
|
+
local chunk
|
|
2079
|
+
if debug.info ~= nil then
|
|
2080
|
+
chunk = debug.info(1, "s")
|
|
2081
|
+
else
|
|
2082
|
+
chunk = debug.getinfo(1, "S").short_src
|
|
2083
|
+
end
|
|
2084
|
+
local prefix = chunk:gsub("%p", "%%%0")
|
|
2085
|
+
local function place(line)
|
|
2086
|
+
local origin = lines[tonumber(line)]
|
|
2087
|
+
if origin == nil then
|
|
2088
|
+
return nil
|
|
2089
|
+
end
|
|
2090
|
+
return origin[1] .. ":" .. tostring(origin[2])
|
|
2091
|
+
end
|
|
2092
|
+
local mapped = message:gsub(prefix .. ":(%d+)", function(line)
|
|
2093
|
+
return place(line)
|
|
2094
|
+
end)
|
|
2095
|
+
local frames = {}
|
|
2096
|
+
for frame in debug.traceback("", 2):gmatch("[^\\n]+") do
|
|
2097
|
+
local line = frame:match(prefix .. ":(%d+)")
|
|
2098
|
+
local at = line ~= nil and place(line) or nil
|
|
2099
|
+
if at ~= nil then
|
|
2100
|
+
local name = frame:match("function ([%w_.:]+)")
|
|
2101
|
+
frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
|
|
2102
|
+
end
|
|
2103
|
+
end
|
|
2104
|
+
if #frames == 0 then
|
|
2105
|
+
return mapped
|
|
2106
|
+
end
|
|
2107
|
+
return mapped .. "\\n" .. table.concat(frames, "\\n")
|
|
2108
|
+
end,
|
|
2131
2109
|
records = {},
|
|
2132
2110
|
require = function(name)
|
|
2133
2111
|
local record = ${G}.records[name]
|