@tilua/compiler 6.1.0 → 7.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 +69 -22
- package/dist/{chunk-574KDV5W.js → chunk-NM3EVX2J.js} +566 -82
- package/dist/cli.cjs +569 -91
- package/dist/cli.js +4 -10
- package/dist/index.cjs +566 -83
- package/dist/index.d.cts +165 -115
- package/dist/index.d.ts +165 -115
- package/dist/index.js +1 -2
- package/package.json +2 -2
- package/runtime/json.luau +255 -0
|
@@ -27,7 +27,7 @@ var index = (object2, key) => ({ type: "IndexExpression", object: object2, index
|
|
|
27
27
|
var call = (callee, args) => ({ type: "CallExpression", callee, arguments: args, ...span() });
|
|
28
28
|
var methodCall = (object2, method, args) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args, ...span() });
|
|
29
29
|
var functionExpression = (func) => ({ type: "FunctionExpression", func, ...span() });
|
|
30
|
-
var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left, right, ...span() });
|
|
30
|
+
var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left: operator === "^" && left.type === "UnaryExpression" ? parenthesized(left) : left, right, ...span() });
|
|
31
31
|
var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
|
|
32
32
|
var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
|
|
33
33
|
var table = (fields) => ({ type: "TableExpression", fields, ...span() });
|
|
@@ -86,19 +86,19 @@ var Names = class _Names {
|
|
|
86
86
|
* a name shared across modules (the module table) collides with none. */
|
|
87
87
|
static from(...programs) {
|
|
88
88
|
const taken = /* @__PURE__ */ new Set(["select", "pairs", "table", "tostring", "setmetatable", "error"]);
|
|
89
|
-
const
|
|
89
|
+
const walk2 = (value) => {
|
|
90
90
|
if (!value || typeof value !== "object") return;
|
|
91
91
|
if (Array.isArray(value)) {
|
|
92
|
-
for (const item of value)
|
|
92
|
+
for (const item of value) walk2(item);
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
const node = value;
|
|
96
96
|
if (typeof node.name === "string" && node.name) taken.add(node.name);
|
|
97
97
|
for (const [key, child] of Object.entries(value)) {
|
|
98
|
-
if (key !== "line" && key !== "column")
|
|
98
|
+
if (key !== "line" && key !== "column") walk2(child);
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
|
-
for (const program2 of programs)
|
|
101
|
+
for (const program2 of programs) walk2(program2);
|
|
102
102
|
return new _Names(taken);
|
|
103
103
|
}
|
|
104
104
|
/** A copy that goes on to take names independently — one per module, each
|
|
@@ -106,6 +106,10 @@ var Names = class _Names {
|
|
|
106
106
|
fork() {
|
|
107
107
|
return new _Names(new Set(this.taken));
|
|
108
108
|
}
|
|
109
|
+
/** `name`, chosen elsewhere, is not to be chosen again here. */
|
|
110
|
+
take(name) {
|
|
111
|
+
this.taken.add(name);
|
|
112
|
+
}
|
|
109
113
|
/** `base`, or `base2`, `base3`, ... — whichever is free first. */
|
|
110
114
|
fresh(base) {
|
|
111
115
|
let name = base;
|
|
@@ -123,6 +127,7 @@ var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\
|
|
|
123
127
|
var array = read("array.luau");
|
|
124
128
|
var string2 = read("string.luau");
|
|
125
129
|
var object = read("object.luau");
|
|
130
|
+
var json = read("json.luau");
|
|
126
131
|
var arrayMethods = implemented(array);
|
|
127
132
|
var stringMethods = implemented(string2);
|
|
128
133
|
var objectMethods = implemented(object);
|
|
@@ -155,7 +160,7 @@ function objectKeys(type) {
|
|
|
155
160
|
return { known, open: String(open) };
|
|
156
161
|
}
|
|
157
162
|
var plugin = {
|
|
158
|
-
runtime: { array, string: string2, object },
|
|
163
|
+
runtime: { array, string: string2, object, json },
|
|
159
164
|
methodCall({ method, receiver, use }) {
|
|
160
165
|
if (isString(receiver)) {
|
|
161
166
|
return stringMethods.has(method) ? { callee: `${use("string")}.${method}` } : void 0;
|
|
@@ -244,6 +249,141 @@ var plugin2 = {
|
|
|
244
249
|
var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
|
|
245
250
|
|
|
246
251
|
// src/lower.ts
|
|
252
|
+
function syntaxOf(lowerings) {
|
|
253
|
+
return new Set((lowerings ?? []).flatMap((l) => l.plugin.syntax ?? []));
|
|
254
|
+
}
|
|
255
|
+
var SharedRuntime = class {
|
|
256
|
+
constructor(names) {
|
|
257
|
+
this.names = names;
|
|
258
|
+
}
|
|
259
|
+
names;
|
|
260
|
+
byKey = /* @__PURE__ */ new Map();
|
|
261
|
+
/** Each group of names and the statements that declare them as locals,
|
|
262
|
+
* in the order they were first defined: each after what it uses. */
|
|
263
|
+
definitions = [];
|
|
264
|
+
defined = /* @__PURE__ */ new Set();
|
|
265
|
+
/** The name the helper `key` goes by, in every module. */
|
|
266
|
+
name(key, base) {
|
|
267
|
+
let name = this.byKey.get(key);
|
|
268
|
+
if (!name) {
|
|
269
|
+
name = this.names.fresh(base);
|
|
270
|
+
this.byKey.set(key, name);
|
|
271
|
+
}
|
|
272
|
+
return name;
|
|
273
|
+
}
|
|
274
|
+
/** `statements` declare `names`. Only the first module to say so is heard:
|
|
275
|
+
* the others' are the same. */
|
|
276
|
+
define(names, statements) {
|
|
277
|
+
if (names.every((name) => this.defined.has(name))) return;
|
|
278
|
+
for (const name of names) this.defined.add(name);
|
|
279
|
+
this.definitions.push({ names, statements });
|
|
280
|
+
}
|
|
281
|
+
/** Tree shaking: leave out of each runtime table the members nothing
|
|
282
|
+
* reads. `code` is everything else the output holds — the modules —
|
|
283
|
+
* and a member is read when it, or a member read already, names it:
|
|
284
|
+
* `tilua_array.filter(xs, f)` keeps `filter`, and whatever `filter`
|
|
285
|
+
* itself calls. A table used as anything but `table.member` — handed
|
|
286
|
+
* on, walked, indexed by what only the running program knows — keeps
|
|
287
|
+
* every member. */
|
|
288
|
+
shake(code) {
|
|
289
|
+
const names = new Set(this.definitions.flatMap((group) => group.names));
|
|
290
|
+
const read3 = /* @__PURE__ */ new Map();
|
|
291
|
+
const use = (name, key) => {
|
|
292
|
+
const now = read3.get(name);
|
|
293
|
+
if (now === "all") return;
|
|
294
|
+
if (key === void 0) {
|
|
295
|
+
read3.set(name, "all");
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
read3.set(name, (now ?? /* @__PURE__ */ new Set()).add(key));
|
|
299
|
+
};
|
|
300
|
+
const isRead = (name, key) => {
|
|
301
|
+
const now = read3.get(name);
|
|
302
|
+
return now === "all" || (now?.has(key) ?? false);
|
|
303
|
+
};
|
|
304
|
+
const scan = (value) => {
|
|
305
|
+
if (!value || typeof value !== "object") return;
|
|
306
|
+
if (Array.isArray(value)) return void value.forEach(scan);
|
|
307
|
+
const node = value;
|
|
308
|
+
const object2 = node.object;
|
|
309
|
+
if (object2?.type === "Identifier" && names.has(object2.name)) {
|
|
310
|
+
if (node.type === "MemberExpression") return use(object2.name, node.property.name);
|
|
311
|
+
if (node.type === "MethodCallExpression") {
|
|
312
|
+
use(object2.name, node.method.name);
|
|
313
|
+
return scan(node.arguments);
|
|
314
|
+
}
|
|
315
|
+
if (node.type === "IndexExpression") {
|
|
316
|
+
const index2 = node.index;
|
|
317
|
+
if (index2.type === "StringLiteral") return use(object2.name, String(index2.value));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (node.type === "Identifier" && names.has(node.name)) return use(node.name);
|
|
321
|
+
if (node.type === "LocalFunctionStatement") return scan(node.func);
|
|
322
|
+
for (const [key, child] of Object.entries(node)) {
|
|
323
|
+
if (key !== "line" && key !== "column") scan(child);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const defines = (statement, own) => {
|
|
327
|
+
if (statement.type === "FunctionDeclarationStatement") {
|
|
328
|
+
const target = statement.target;
|
|
329
|
+
if (own.includes(target.base.name) && target.path.length === 1 && !target.method) {
|
|
330
|
+
return { name: target.base.name, key: target.path[0].name, value: statement.func };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (statement.type === "AssignmentStatement" && statement.targets.length === 1 && statement.values.length === 1) {
|
|
334
|
+
const target = statement.targets[0];
|
|
335
|
+
if (target.object?.type === "Identifier" && own.includes(target.object.name)) {
|
|
336
|
+
if (target.type === "MemberExpression") return { name: target.object.name, key: target.property.name, value: statement.values[0] };
|
|
337
|
+
if (target.type === "IndexExpression" && target.index?.type === "StringLiteral") {
|
|
338
|
+
return { name: target.object.name, key: String(target.index.value), value: statement.values[0] };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return void 0;
|
|
343
|
+
};
|
|
344
|
+
scan(code);
|
|
345
|
+
const scanned = /* @__PURE__ */ new Set();
|
|
346
|
+
let more = true;
|
|
347
|
+
while (more) {
|
|
348
|
+
more = false;
|
|
349
|
+
for (const group of this.definitions) {
|
|
350
|
+
for (const statement of group.statements) {
|
|
351
|
+
if (scanned.has(statement)) continue;
|
|
352
|
+
const definition = defines(statement, group.names);
|
|
353
|
+
if (definition && !isRead(definition.name, definition.key)) continue;
|
|
354
|
+
scanned.add(statement);
|
|
355
|
+
more = true;
|
|
356
|
+
scan(definition ? definition.value : statement);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const group of this.definitions) {
|
|
361
|
+
group.statements = group.statements.filter((statement) => {
|
|
362
|
+
const definition = defines(statement, group.names);
|
|
363
|
+
return !definition || isRead(definition.name, definition.key);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/** Luau setting up the helpers `used` for a chunk whose code names them:
|
|
368
|
+
* `declare` goes before that code, which reads each as an upvalue, and
|
|
369
|
+
* `define` after it — before any of it runs, and after what a helper
|
|
370
|
+
* reads as it is set up (the line map). Each definition runs in a
|
|
371
|
+
* function of its own, so what it keeps to itself stays there. */
|
|
372
|
+
chunk(used) {
|
|
373
|
+
const groups = this.definitions.filter((group) => group.names.some((name) => used.has(name)));
|
|
374
|
+
const all = groups.flatMap((group) => group.names);
|
|
375
|
+
const declare = [];
|
|
376
|
+
for (let i = 0; i < all.length; i += 100) declare.push(local(all.slice(i, i + 100), []));
|
|
377
|
+
const define = groups.map((group) => assign(
|
|
378
|
+
group.names.map((name) => identifier(name)),
|
|
379
|
+
[call(parenthesized(functionExpression(functionBody([], [
|
|
380
|
+
...group.statements,
|
|
381
|
+
returns(group.names.map((name) => identifier(name)))
|
|
382
|
+
]))), [])]
|
|
383
|
+
));
|
|
384
|
+
return { declare, define };
|
|
385
|
+
}
|
|
386
|
+
};
|
|
247
387
|
function lower(program2, scopes, options = {}) {
|
|
248
388
|
const result2 = new Lowerer(program2, scopes, options).run();
|
|
249
389
|
dropUnreachable(result2.statements);
|
|
@@ -274,7 +414,7 @@ var Lowerer = class {
|
|
|
274
414
|
this.source = source;
|
|
275
415
|
this.scopes = scopes;
|
|
276
416
|
this.options = options;
|
|
277
|
-
this.
|
|
417
|
+
this.syntax = syntaxOf(options.lowerings);
|
|
278
418
|
this.names = options.names ? options.names.fork() : Names.from(source);
|
|
279
419
|
this.exportsName = this.names.fresh("exports");
|
|
280
420
|
for (const binding of scopes.bindings.values()) {
|
|
@@ -299,6 +439,11 @@ var Lowerer = class {
|
|
|
299
439
|
names;
|
|
300
440
|
diagnostics = [];
|
|
301
441
|
helpers = /* @__PURE__ */ new Map();
|
|
442
|
+
/** The shared helpers this file names. */
|
|
443
|
+
sharedUsed = /* @__PURE__ */ new Set();
|
|
444
|
+
/** While a shared helper is written: it is defined outside every module,
|
|
445
|
+
* where none of a module's own names shadows a global. */
|
|
446
|
+
rawGlobals = false;
|
|
302
447
|
/** The binding each declaration node creates. */
|
|
303
448
|
bindingByDeclaration = /* @__PURE__ */ new Map();
|
|
304
449
|
/** tilua names that are Luau keywords, and what they are called instead. */
|
|
@@ -314,8 +459,8 @@ var Lowerer = class {
|
|
|
314
459
|
tryFrames = [];
|
|
315
460
|
/** The loops being lowered in the current function, innermost last. */
|
|
316
461
|
loops = [];
|
|
317
|
-
/**
|
|
318
|
-
|
|
462
|
+
/** What the output's Lua can write beyond Lua 5.1 (`syntaxOf`). */
|
|
463
|
+
syntax;
|
|
319
464
|
/** The source line each emitted statement came from. */
|
|
320
465
|
origins = /* @__PURE__ */ new WeakMap();
|
|
321
466
|
/** The name each class written as a value is known by. */
|
|
@@ -323,15 +468,19 @@ var Lowerer = class {
|
|
|
323
468
|
run() {
|
|
324
469
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
325
470
|
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
471
|
+
this.rawGlobals = this.options.shared !== void 0;
|
|
326
472
|
const helpers = this.helperDefinitions();
|
|
473
|
+
const classes = this.classRuntimeStatements();
|
|
474
|
+
this.rawGlobals = false;
|
|
327
475
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
328
476
|
return {
|
|
329
|
-
statements: [...captured, ...helpers, ...scriptArgs, ...
|
|
477
|
+
statements: [...captured, ...helpers, ...scriptArgs, ...classes, ...body],
|
|
330
478
|
exportsName: this.exportsName,
|
|
331
479
|
module: this.info,
|
|
332
480
|
diagnostics: this.diagnostics,
|
|
333
481
|
origins: this.origins,
|
|
334
|
-
usesScriptArgs: this.usesScriptArgs
|
|
482
|
+
usesScriptArgs: this.usesScriptArgs,
|
|
483
|
+
shared: [...this.sharedUsed]
|
|
335
484
|
};
|
|
336
485
|
}
|
|
337
486
|
/** Whether the file reads `scriptArgs`, the language's own global. */
|
|
@@ -364,6 +513,7 @@ var Lowerer = class {
|
|
|
364
513
|
/** A global the generated code calls — `pairs`, `table`, ... — under a name
|
|
365
514
|
* the source cannot have shadowed. */
|
|
366
515
|
builtin(global) {
|
|
516
|
+
if (this.rawGlobals) return identifier(global);
|
|
367
517
|
let local2 = this.builtins.get(global);
|
|
368
518
|
if (!local2) {
|
|
369
519
|
const shadowed = [...this.scopes.bindings.values()].some((b) => b.name === global && b.kind !== "global");
|
|
@@ -375,16 +525,31 @@ var Lowerer = class {
|
|
|
375
525
|
}
|
|
376
526
|
/** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
|
|
377
527
|
move() {
|
|
378
|
-
return this.
|
|
528
|
+
return this.helper("move");
|
|
379
529
|
}
|
|
380
530
|
helper(kind) {
|
|
381
531
|
let name = this.helpers.get(kind);
|
|
382
532
|
if (!name) {
|
|
383
|
-
name = this.
|
|
533
|
+
name = this.sharedName(`helper:${kind}`, `tilua_${kind === "array" ? "asArray" : kind}`);
|
|
384
534
|
this.helpers.set(kind, name);
|
|
385
535
|
}
|
|
386
536
|
return identifier(name);
|
|
387
537
|
}
|
|
538
|
+
/** A fresh name for a helper — in a bundle, the one every module calls
|
|
539
|
+
* that helper by. */
|
|
540
|
+
sharedName(key, base) {
|
|
541
|
+
const shared = this.options.shared;
|
|
542
|
+
if (!shared) return this.names.fresh(base);
|
|
543
|
+
const name = shared.name(key, base);
|
|
544
|
+
this.names.take(name);
|
|
545
|
+
this.sharedUsed.add(name);
|
|
546
|
+
return name;
|
|
547
|
+
}
|
|
548
|
+
/** `statements` declare `names`: in this file, or once for the bundle. */
|
|
549
|
+
define(names, statements, out) {
|
|
550
|
+
if (this.options.shared) this.options.shared.define(names, statements);
|
|
551
|
+
else out.push(...statements);
|
|
552
|
+
}
|
|
388
553
|
// `typeof v` tells an array from an object, which Luau cannot: both are
|
|
389
554
|
// tables. So every array the program makes — `[a, b]`, a rest parameter,
|
|
390
555
|
// what `:map` answers — is written down as one, in a table that holds its
|
|
@@ -417,7 +582,8 @@ var Lowerer = class {
|
|
|
417
582
|
const key = `${index2}:${runtime2}`;
|
|
418
583
|
let name = this.loweringNames.get(key);
|
|
419
584
|
if (!name) {
|
|
420
|
-
|
|
585
|
+
const lines = this.lowerings[index2].plugin.runtime?.[runtime2]?.includes("__LINES__") ? this.options.module?.lines : "";
|
|
586
|
+
name = this.sharedName(`lowering:${key}:${lines}`, `tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
|
|
421
587
|
this.loweringNames.set(key, name);
|
|
422
588
|
this.loweringUsed.push({ index: index2, runtime: runtime2, name });
|
|
423
589
|
}
|
|
@@ -617,19 +783,25 @@ var Lowerer = class {
|
|
|
617
783
|
const runtimes = this.runtimeDefinitions();
|
|
618
784
|
if (this.helpers.has("concat")) this.helper("array");
|
|
619
785
|
if (this.helpers.has("concat")) this.move();
|
|
786
|
+
const unpack = this.helpers.get("unpack");
|
|
787
|
+
if (unpack) {
|
|
788
|
+
this.define([unpack], parseLuau(
|
|
789
|
+
`local ${unpack} = ${this.builtin("table").name}.unpack or ${this.builtin("unpack").name}`
|
|
790
|
+
).body.statements, out);
|
|
791
|
+
}
|
|
620
792
|
const move = this.helpers.get("move");
|
|
621
793
|
if (move) {
|
|
622
|
-
|
|
623
|
-
local
|
|
794
|
+
this.define([move], parseLuau(`
|
|
795
|
+
local ${move} = ${this.builtin("table").name}.move or function(from, first, last, at, to)
|
|
624
796
|
for i = first, last do
|
|
625
797
|
to[at + i - first] = from[i]
|
|
626
798
|
end
|
|
627
799
|
return to
|
|
628
|
-
end`).body.statements);
|
|
800
|
+
end`).body.statements, out);
|
|
629
801
|
}
|
|
630
802
|
const each = this.helpers.get("each");
|
|
631
803
|
if (each) {
|
|
632
|
-
|
|
804
|
+
this.define([each], parseLuau(`
|
|
633
805
|
local function ${each}(source, ...)
|
|
634
806
|
if ${this.builtin("type").name}(source) == "function" then
|
|
635
807
|
return source, ...
|
|
@@ -639,7 +811,7 @@ local function ${each}(source, ...)
|
|
|
639
811
|
return meta.__iter(source)
|
|
640
812
|
end
|
|
641
813
|
return ${this.builtin("next").name}, source
|
|
642
|
-
end`).body.statements);
|
|
814
|
+
end`).body.statements, out);
|
|
643
815
|
}
|
|
644
816
|
const marker = this.helpers.get("array");
|
|
645
817
|
const typeOf = this.helpers.get("typeof");
|
|
@@ -647,19 +819,19 @@ end`).body.statements);
|
|
|
647
819
|
const arrays = this.arrays();
|
|
648
820
|
const own = this.helpers.get("arrays");
|
|
649
821
|
if (own) {
|
|
650
|
-
|
|
822
|
+
this.define([own], parseLuau(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements, out);
|
|
651
823
|
}
|
|
652
824
|
if (marker) {
|
|
653
|
-
|
|
825
|
+
this.define([marker], parseLuau(`
|
|
654
826
|
local function ${marker}(t)
|
|
655
827
|
${arrays}[t] = true
|
|
656
828
|
return t
|
|
657
|
-
end`).body.statements);
|
|
829
|
+
end`).body.statements, out);
|
|
658
830
|
}
|
|
659
831
|
if (typeOf) {
|
|
660
|
-
|
|
832
|
+
this.define([typeOf], parseLuau(`
|
|
661
833
|
local function ${typeOf}(value, known)
|
|
662
|
-
local name = ${this.builtin(
|
|
834
|
+
local name = (${this.builtin("typeof").name} or ${this.builtin("type").name})(value)
|
|
663
835
|
if name ~= "table" then
|
|
664
836
|
return name
|
|
665
837
|
end
|
|
@@ -676,22 +848,22 @@ local function ${typeOf}(value, known)
|
|
|
676
848
|
return "array"
|
|
677
849
|
end
|
|
678
850
|
return "object"
|
|
679
|
-
end`).body.statements);
|
|
851
|
+
end`).body.statements, out);
|
|
680
852
|
}
|
|
681
853
|
}
|
|
682
854
|
const add = this.helpers.get("add");
|
|
683
855
|
if (add) {
|
|
684
|
-
|
|
856
|
+
this.define([add], parseLuau(`
|
|
685
857
|
local function ${add}(a, b)
|
|
686
858
|
if ${this.builtin("type").name}(a) == "string" or ${this.builtin("type").name}(b) == "string" then
|
|
687
859
|
return ${this.builtin("tostring").name}(a) .. ${this.builtin("tostring").name}(b)
|
|
688
860
|
end
|
|
689
861
|
return a + b
|
|
690
|
-
end`).body.statements);
|
|
862
|
+
end`).body.statements, out);
|
|
691
863
|
}
|
|
692
864
|
const instanceOf = this.helpers.get("instanceof");
|
|
693
865
|
if (instanceOf) {
|
|
694
|
-
|
|
866
|
+
this.define([instanceOf], parseLuau(`
|
|
695
867
|
local function ${instanceOf}(value, class)
|
|
696
868
|
if ${this.builtin("type").name}(value) ~= "table" then
|
|
697
869
|
return false
|
|
@@ -704,11 +876,11 @@ local function ${instanceOf}(value, class)
|
|
|
704
876
|
current = current.ParentClass
|
|
705
877
|
end
|
|
706
878
|
return false
|
|
707
|
-
end`).body.statements);
|
|
879
|
+
end`).body.statements, out);
|
|
708
880
|
}
|
|
709
881
|
const accessors = this.helpers.get("accessors");
|
|
710
882
|
if (accessors) {
|
|
711
|
-
|
|
883
|
+
this.define([accessors], parseLuau(`
|
|
712
884
|
local function ${accessors}(target, getters, setters)
|
|
713
885
|
return ${this.builtin("setmetatable").name}(target, {
|
|
714
886
|
-- Read by a spread, which copies what the getters give.
|
|
@@ -732,11 +904,11 @@ local function ${accessors}(target, getters, setters)
|
|
|
732
904
|
${this.builtin("rawset").name}(self, key, value)
|
|
733
905
|
end,
|
|
734
906
|
})
|
|
735
|
-
end`).body.statements);
|
|
907
|
+
end`).body.statements, out);
|
|
736
908
|
}
|
|
737
909
|
const assign2 = this.helpers.get("assign");
|
|
738
910
|
if (assign2) {
|
|
739
|
-
|
|
911
|
+
this.define([assign2], parseLuau(`
|
|
740
912
|
local function ${assign2}(target, ...)
|
|
741
913
|
for i = 1, ${this.builtin("select").name}("#", ...) do
|
|
742
914
|
local source = (${this.builtin("select").name}(i, ...))
|
|
@@ -754,12 +926,12 @@ local function ${assign2}(target, ...)
|
|
|
754
926
|
end
|
|
755
927
|
end
|
|
756
928
|
return target
|
|
757
|
-
end`).body.statements);
|
|
929
|
+
end`).body.statements, out);
|
|
758
930
|
}
|
|
759
|
-
|
|
931
|
+
for (const { names, statements } of runtimes) this.define(names, statements, out);
|
|
760
932
|
const concat = this.helpers.get("concat");
|
|
761
933
|
if (concat) {
|
|
762
|
-
|
|
934
|
+
this.define([concat], [localFunction(concat, functionBody([], [
|
|
763
935
|
local(["result"], [this.array(table([]))]),
|
|
764
936
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
765
937
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
@@ -772,7 +944,7 @@ end`).body.statements);
|
|
|
772
944
|
]))
|
|
773
945
|
]),
|
|
774
946
|
returns([identifier("result")])
|
|
775
|
-
], true)));
|
|
947
|
+
], true))], out);
|
|
776
948
|
}
|
|
777
949
|
return out;
|
|
778
950
|
}
|
|
@@ -792,7 +964,12 @@ end`).body.statements);
|
|
|
792
964
|
}
|
|
793
965
|
const lines = this.options.module?.lines ?? "nil";
|
|
794
966
|
const fail = this.options.module?.fail ?? "nil";
|
|
795
|
-
|
|
967
|
+
const group = {
|
|
968
|
+
names: [name],
|
|
969
|
+
statements: parseLuau(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAYS__/g, () => this.arrays()).replace(/__ARRAY__/g, () => this.helper("array").name).replace(/__JSON__/g, () => this.loweringRuntime(0, "json"))).body.statements
|
|
970
|
+
};
|
|
971
|
+
if (index2 === 0 && runtime2 === "json") out.unshift(group);
|
|
972
|
+
else out.push(group);
|
|
796
973
|
}
|
|
797
974
|
return out;
|
|
798
975
|
}
|
|
@@ -807,7 +984,8 @@ end`).body.statements);
|
|
|
807
984
|
* util = require("util") -- the imports
|
|
808
985
|
* ... -- the rest, in source order */
|
|
809
986
|
module(context) {
|
|
810
|
-
const
|
|
987
|
+
const shaken = this.options.shaken;
|
|
988
|
+
const statements = shaken ? this.source.body.statements.filter((s) => !shaken.removed.has(s)) : this.source.body.statements;
|
|
811
989
|
const exportsTable = () => identifier(this.exportsName);
|
|
812
990
|
const { names, links, stars } = this.info;
|
|
813
991
|
const locals = [];
|
|
@@ -846,13 +1024,24 @@ end`).body.statements);
|
|
|
846
1024
|
};
|
|
847
1025
|
for (const statement of statements) {
|
|
848
1026
|
if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
|
|
1027
|
+
if (!statement.defaultImport && !statement.namespaceImport && !statement.specifiers.length) {
|
|
1028
|
+
const key2 = resolve3(statement.source);
|
|
1029
|
+
if (key2 !== void 0) load(key2);
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
849
1032
|
const specifiers = [
|
|
850
1033
|
...statement.defaultImport ? [{ local: statement.defaultImport, imported: "default" }] : [],
|
|
851
1034
|
...statement.namespaceImport ? [{ local: statement.namespaceImport, imported: void 0 }] : [],
|
|
852
1035
|
...statement.specifiers.map((s) => ({ local: s.local, imported: s.imported.name }))
|
|
853
1036
|
];
|
|
854
|
-
const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references
|
|
855
|
-
if (!used.length)
|
|
1037
|
+
const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references ?? []).some((reference) => !shaken?.dead.has(reference)));
|
|
1038
|
+
if (!used.length) {
|
|
1039
|
+
if (shaken?.runs(statement.source.value)) {
|
|
1040
|
+
const key2 = resolve3(statement.source);
|
|
1041
|
+
if (key2 !== void 0) load(key2);
|
|
1042
|
+
}
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
856
1045
|
const key = resolve3(statement.source);
|
|
857
1046
|
if (key === void 0) continue;
|
|
858
1047
|
const local2 = moduleLocal(key);
|
|
@@ -894,6 +1083,7 @@ end`).body.statements);
|
|
|
894
1083
|
break;
|
|
895
1084
|
}
|
|
896
1085
|
case "ExportAllStatement": {
|
|
1086
|
+
if (shaken && !shaken.needed(statement.source.value)) break;
|
|
897
1087
|
const key = resolve3(statement.source);
|
|
898
1088
|
if (key === void 0) break;
|
|
899
1089
|
load(key);
|
|
@@ -1168,7 +1358,7 @@ end`).body.statements);
|
|
|
1168
1358
|
case "VariableDeclaration":
|
|
1169
1359
|
return this.variableDeclaration(node, "declare");
|
|
1170
1360
|
case "FunctionDeclaration":
|
|
1171
|
-
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.
|
|
1361
|
+
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.syntax.has("attributes") ? node.attributes : void 0 }];
|
|
1172
1362
|
case "FunctionDeclarationStatement":
|
|
1173
1363
|
return [this.functionDeclarationStatement(node)];
|
|
1174
1364
|
case "ClassDeclaration":
|
|
@@ -1180,7 +1370,7 @@ end`).body.statements);
|
|
|
1180
1370
|
const join = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
|
|
1181
1371
|
if (join === "dynamic") return this.dynamicAddition(node);
|
|
1182
1372
|
const operator = join === "concat" ? "..=" : node.operator;
|
|
1183
|
-
if (this.
|
|
1373
|
+
if (!this.syntax.has("compoundAssignment")) return this.compoundAssignment51(node, operator);
|
|
1184
1374
|
return [{
|
|
1185
1375
|
type: "CompoundAssignmentStatement",
|
|
1186
1376
|
operator,
|
|
@@ -1218,10 +1408,10 @@ end`).body.statements);
|
|
|
1218
1408
|
case "RepeatStatement": {
|
|
1219
1409
|
const body = this.block(node.body);
|
|
1220
1410
|
const condition = this.expression(node.condition);
|
|
1221
|
-
if (this.
|
|
1411
|
+
if (!this.syntax.has("continue") && hasOwnJump(body.statements, "ContinueStatement")) {
|
|
1222
1412
|
const hidden = declaredNames(body.statements).filter((name) => mentions(condition, name));
|
|
1223
1413
|
if (hidden.length) {
|
|
1224
|
-
this.report(node, `'continue' in a 'repeat' whose 'until' reads ${hidden.map((n) => `'${n}'`).join(", ")} cannot be compiled
|
|
1414
|
+
this.report(node, `'continue' in a 'repeat' whose 'until' reads ${hidden.map((n) => `'${n}'`).join(", ")} cannot be compiled to Lua, which has no 'continue' to lower it to. Move what the condition reads out of the loop body, or use a 'while' loop \u2014 or load a library whose Lua has 'continue' ("luau").`);
|
|
1225
1415
|
}
|
|
1226
1416
|
}
|
|
1227
1417
|
return [{ type: "RepeatStatement", body: this.loopBody51(body), condition, ...spanOf(node) }];
|
|
@@ -1262,8 +1452,8 @@ end`).body.statements);
|
|
|
1262
1452
|
let source = this.expression(node.iterator);
|
|
1263
1453
|
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
1264
1454
|
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
1265
|
-
let iterators = form.walks === "iteration" ? [call(this.
|
|
1266
|
-
if (this.
|
|
1455
|
+
let iterators = form.walks === "iteration" ? [call(this.helper("unpack"), [source, number(1), number(3)])] : [source];
|
|
1456
|
+
if (!this.syntax.has("iteration") && form.walks === "values") {
|
|
1267
1457
|
const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
|
|
1268
1458
|
iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
|
|
1269
1459
|
}
|
|
@@ -1419,8 +1609,8 @@ end`).body.statements);
|
|
|
1419
1609
|
classHelpers;
|
|
1420
1610
|
classRuntime() {
|
|
1421
1611
|
return this.classHelpers ??= {
|
|
1422
|
-
build: this.
|
|
1423
|
-
accessors: this.
|
|
1612
|
+
build: this.sharedName("class:build", "tilua_class"),
|
|
1613
|
+
accessors: this.sharedName("class:accessors", "tilua_accessors")
|
|
1424
1614
|
};
|
|
1425
1615
|
}
|
|
1426
1616
|
/** The helpers, as Luau — emitted only in a file that declares a class.
|
|
@@ -1438,7 +1628,8 @@ end`).body.statements);
|
|
|
1438
1628
|
if (!this.classHelpers) return [];
|
|
1439
1629
|
const { build, accessors } = this.classHelpers;
|
|
1440
1630
|
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
1441
|
-
|
|
1631
|
+
const out = [];
|
|
1632
|
+
this.define([build, accessors], parseLuau(`
|
|
1442
1633
|
local function ${build}(base, name)
|
|
1443
1634
|
local class = { __getters = {}, __setters = {} }
|
|
1444
1635
|
class.__index = class
|
|
@@ -1476,7 +1667,8 @@ local function ${accessors}(class)
|
|
|
1476
1667
|
rawset(this, key, value)
|
|
1477
1668
|
end
|
|
1478
1669
|
end
|
|
1479
|
-
`).body.statements;
|
|
1670
|
+
`).body.statements, out);
|
|
1671
|
+
return out;
|
|
1480
1672
|
}
|
|
1481
1673
|
/** The class being lowered, so `super` knows what to name. */
|
|
1482
1674
|
classContext;
|
|
@@ -1620,7 +1812,7 @@ end
|
|
|
1620
1812
|
isMethod,
|
|
1621
1813
|
func,
|
|
1622
1814
|
// `@native` and the like are Luau's.
|
|
1623
|
-
attributes: this.
|
|
1815
|
+
attributes: this.syntax.has("attributes") ? node.attributes : void 0,
|
|
1624
1816
|
...spanOf(node)
|
|
1625
1817
|
};
|
|
1626
1818
|
}
|
|
@@ -1860,7 +2052,7 @@ end
|
|
|
1860
2052
|
if (join === "concat") return binary("..", left, right);
|
|
1861
2053
|
if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1862
2054
|
}
|
|
1863
|
-
if (this.
|
|
2055
|
+
if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
|
|
1864
2056
|
return binary(node.operator, left, right);
|
|
1865
2057
|
}
|
|
1866
2058
|
case "UnaryExpression":
|
|
@@ -1894,7 +2086,7 @@ end
|
|
|
1894
2086
|
case "AsConstExpression":
|
|
1895
2087
|
return this.expression(node.expression);
|
|
1896
2088
|
case "IfElseExpression":
|
|
1897
|
-
if (this.
|
|
2089
|
+
if (!this.syntax.has("ifExpression")) return this.ifElse51(node);
|
|
1898
2090
|
return {
|
|
1899
2091
|
type: "IfElseExpression",
|
|
1900
2092
|
clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
|
|
@@ -1941,7 +2133,7 @@ end
|
|
|
1941
2133
|
const last2 = list.length - 1;
|
|
1942
2134
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1943
2135
|
}
|
|
1944
|
-
const unpack = (value) => call(this.
|
|
2136
|
+
const unpack = (value) => call(this.helper("unpack"), [value]);
|
|
1945
2137
|
const last = list[list.length - 1];
|
|
1946
2138
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1947
2139
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -2055,7 +2247,7 @@ end
|
|
|
2055
2247
|
const segments = this.segments(chain);
|
|
2056
2248
|
const base = this.expression(chain.base);
|
|
2057
2249
|
const last = chain.links[chain.links.length - 1];
|
|
2058
|
-
if (
|
|
2250
|
+
if (this.syntax.has("ifExpression") && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
2059
2251
|
return {
|
|
2060
2252
|
type: "IfElseExpression",
|
|
2061
2253
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -2082,7 +2274,7 @@ end
|
|
|
2082
2274
|
nullish(node) {
|
|
2083
2275
|
const left = this.keptToOne(this.expression(node.left));
|
|
2084
2276
|
const right = this.keptToOne(this.expression(node.right));
|
|
2085
|
-
if (
|
|
2277
|
+
if (this.syntax.has("ifExpression") && left.type === "Identifier") {
|
|
2086
2278
|
return {
|
|
2087
2279
|
type: "IfElseExpression",
|
|
2088
2280
|
clauses: [{ condition: binary("~=", left, nil()), body: left }],
|
|
@@ -2169,14 +2361,19 @@ end
|
|
|
2169
2361
|
let target;
|
|
2170
2362
|
if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
|
|
2171
2363
|
const object2 = this.expression(node.target.object);
|
|
2172
|
-
const
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
target = member(identifier(objectName), node.target.property.name);
|
|
2364
|
+
const key = node.target.type === "IndexExpression" ? node.target.index : void 0;
|
|
2365
|
+
if (object2.type === "Identifier" && (key === void 0 || key.type === "StringLiteral" || key.type === "NumberLiteral")) {
|
|
2366
|
+
target = key === void 0 ? member(object2, node.target.property.name) : index(object2, this.expression(key));
|
|
2176
2367
|
} else {
|
|
2177
|
-
const
|
|
2178
|
-
before.push(local([
|
|
2179
|
-
target
|
|
2368
|
+
const objectName = this.names.fresh("target");
|
|
2369
|
+
before.push(local([objectName], [object2]));
|
|
2370
|
+
if (node.target.type === "MemberExpression") {
|
|
2371
|
+
target = member(identifier(objectName), node.target.property.name);
|
|
2372
|
+
} else {
|
|
2373
|
+
const keyName = this.names.fresh("key");
|
|
2374
|
+
before.push(local([keyName], [this.expression(node.target.index)]));
|
|
2375
|
+
target = index(identifier(objectName), identifier(keyName));
|
|
2376
|
+
}
|
|
2180
2377
|
}
|
|
2181
2378
|
} else {
|
|
2182
2379
|
target = this.expression(node.target);
|
|
@@ -2197,7 +2394,7 @@ end
|
|
|
2197
2394
|
* done, and breaks for real there. A body with no `continue` is left
|
|
2198
2395
|
* exactly as it was. */
|
|
2199
2396
|
loopBody51(body) {
|
|
2200
|
-
if (
|
|
2397
|
+
if (this.syntax.has("continue") || !hasOwnJump(body.statements, "ContinueStatement")) return body;
|
|
2201
2398
|
const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
|
|
2202
2399
|
const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
|
|
2203
2400
|
if (!flag) return block([once]);
|
|
@@ -2249,7 +2446,7 @@ end
|
|
|
2249
2446
|
* underscores and `0b1010` becomes the decimal it means. A hex literal
|
|
2250
2447
|
* keeps its base — 5.1 reads those. */
|
|
2251
2448
|
numberRaw(node) {
|
|
2252
|
-
if (
|
|
2449
|
+
if (this.syntax.has("numberLiterals")) return node.raw;
|
|
2253
2450
|
if (/^0[bB]/.test(node.raw)) return String(node.value);
|
|
2254
2451
|
return node.raw.replace(/_/g, "");
|
|
2255
2452
|
}
|
|
@@ -2502,10 +2699,10 @@ function declaredNames(statements) {
|
|
|
2502
2699
|
}
|
|
2503
2700
|
function mentions(expression, name) {
|
|
2504
2701
|
let found = false;
|
|
2505
|
-
const
|
|
2702
|
+
const walk2 = (value) => {
|
|
2506
2703
|
if (found || !value || typeof value !== "object") return;
|
|
2507
2704
|
if (Array.isArray(value)) {
|
|
2508
|
-
for (const item of value)
|
|
2705
|
+
for (const item of value) walk2(item);
|
|
2509
2706
|
return;
|
|
2510
2707
|
}
|
|
2511
2708
|
const node = value;
|
|
@@ -2514,10 +2711,10 @@ function mentions(expression, name) {
|
|
|
2514
2711
|
return;
|
|
2515
2712
|
}
|
|
2516
2713
|
for (const [key, child] of Object.entries(value)) {
|
|
2517
|
-
if (key !== "line" && key !== "column")
|
|
2714
|
+
if (key !== "line" && key !== "column") walk2(child);
|
|
2518
2715
|
}
|
|
2519
2716
|
};
|
|
2520
|
-
|
|
2717
|
+
walk2(expression);
|
|
2521
2718
|
return found;
|
|
2522
2719
|
}
|
|
2523
2720
|
function allNodes(root) {
|
|
@@ -2600,6 +2797,269 @@ import {
|
|
|
2600
2797
|
} from "@tilua/parser";
|
|
2601
2798
|
import { parse as parseLuau2, parseExpressionFromSource as parseExpressionFromSource2, print } from "luau-parser";
|
|
2602
2799
|
|
|
2800
|
+
// src/treeshake.ts
|
|
2801
|
+
function walk(node, visit, parent) {
|
|
2802
|
+
if (!node || typeof node !== "object") return;
|
|
2803
|
+
if (Array.isArray(node)) {
|
|
2804
|
+
for (const item of node) walk(item, visit, parent);
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
const n = node;
|
|
2808
|
+
if (typeof n.type === "string" && visit(n, parent) === false) return;
|
|
2809
|
+
for (const [key, child] of Object.entries(n)) {
|
|
2810
|
+
if (key === "line" || key === "column") continue;
|
|
2811
|
+
walk(child, visit, typeof n.type === "string" ? n : parent);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
function pure(expression) {
|
|
2815
|
+
let ok = true;
|
|
2816
|
+
walk(expression, (node) => {
|
|
2817
|
+
if (!ok) return false;
|
|
2818
|
+
switch (node.type) {
|
|
2819
|
+
case "CallExpression":
|
|
2820
|
+
case "MethodCallExpression":
|
|
2821
|
+
case "ImportExpression":
|
|
2822
|
+
case "SuperExpression":
|
|
2823
|
+
ok = false;
|
|
2824
|
+
return false;
|
|
2825
|
+
case "FunctionExpression":
|
|
2826
|
+
case "FunctionBody":
|
|
2827
|
+
case "ClassMethod":
|
|
2828
|
+
case "ClassAccessor":
|
|
2829
|
+
case "ClassConstructor":
|
|
2830
|
+
return false;
|
|
2831
|
+
case "ClassField":
|
|
2832
|
+
if (!node.isStatic) return false;
|
|
2833
|
+
}
|
|
2834
|
+
});
|
|
2835
|
+
return ok;
|
|
2836
|
+
}
|
|
2837
|
+
function declarationOf(statement) {
|
|
2838
|
+
return statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
2839
|
+
}
|
|
2840
|
+
function removable(statement) {
|
|
2841
|
+
const declaration = declarationOf(statement);
|
|
2842
|
+
switch (declaration.type) {
|
|
2843
|
+
case "FunctionDeclaration":
|
|
2844
|
+
return true;
|
|
2845
|
+
case "ClassDeclaration":
|
|
2846
|
+
return pure(declaration);
|
|
2847
|
+
case "VariableDeclaration":
|
|
2848
|
+
return declaration.init === void 0 || pure(declaration.init);
|
|
2849
|
+
case "ExportDefaultStatement":
|
|
2850
|
+
return pure(declaration.declaration);
|
|
2851
|
+
default:
|
|
2852
|
+
return false;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
function structural(statement) {
|
|
2856
|
+
switch (statement.type) {
|
|
2857
|
+
case "TypeAliasStatement":
|
|
2858
|
+
case "ExportTypeAliasStatement":
|
|
2859
|
+
case "DeclareStatement":
|
|
2860
|
+
case "DeclareClassStatement":
|
|
2861
|
+
case "DeclareMetatableStatement":
|
|
2862
|
+
case "ImportStatement":
|
|
2863
|
+
case "ExportNamedStatement":
|
|
2864
|
+
case "ExportAllStatement":
|
|
2865
|
+
return true;
|
|
2866
|
+
default:
|
|
2867
|
+
return false;
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
function exportedNames(statement) {
|
|
2871
|
+
switch (statement.type) {
|
|
2872
|
+
case "ExportStatement": {
|
|
2873
|
+
const declaration = statement.declaration;
|
|
2874
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return [declaration.name.name];
|
|
2875
|
+
const names = [];
|
|
2876
|
+
walk(declaration.name, (node) => {
|
|
2877
|
+
if (node.type === "IdentifierPattern") names.push(node.name);
|
|
2878
|
+
});
|
|
2879
|
+
return names;
|
|
2880
|
+
}
|
|
2881
|
+
case "ExportDefaultStatement":
|
|
2882
|
+
return ["default"];
|
|
2883
|
+
default:
|
|
2884
|
+
return [];
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
function factsOf(module) {
|
|
2888
|
+
const tops = module.program.body.statements;
|
|
2889
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
2890
|
+
for (const binding of module.scopes.bindings.values()) {
|
|
2891
|
+
if (binding.declarationNode) byNode.set(binding.declarationNode, binding.id);
|
|
2892
|
+
}
|
|
2893
|
+
const declaredBy = /* @__PURE__ */ new Map();
|
|
2894
|
+
const imported = /* @__PURE__ */ new Map();
|
|
2895
|
+
for (const statement of tops) {
|
|
2896
|
+
const declaration = declarationOf(statement);
|
|
2897
|
+
const declare = (node) => {
|
|
2898
|
+
const id = node && byNode.get(node);
|
|
2899
|
+
if (id !== void 0) declaredBy.set(id, statement);
|
|
2900
|
+
};
|
|
2901
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") declare(declaration.name);
|
|
2902
|
+
else if (declaration.type === "VariableDeclaration") {
|
|
2903
|
+
walk(declaration.name, (node) => {
|
|
2904
|
+
if (node.type === "IdentifierPattern") declare(node);
|
|
2905
|
+
});
|
|
2906
|
+
} else if (declaration.type === "ImportStatement" && !declaration.isTypeOnly) {
|
|
2907
|
+
const target = module.resolve(declaration.source.value);
|
|
2908
|
+
if (target === void 0) continue;
|
|
2909
|
+
const add = (node, name) => {
|
|
2910
|
+
const id = node && byNode.get(node);
|
|
2911
|
+
if (id !== void 0) imported.set(id, { target, name });
|
|
2912
|
+
};
|
|
2913
|
+
if (declaration.defaultImport) add(declaration.defaultImport, "default");
|
|
2914
|
+
if (declaration.namespaceImport) add(declaration.namespaceImport, "*");
|
|
2915
|
+
for (const s of declaration.specifiers) add(s.local, s.imported.name);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
const parents = /* @__PURE__ */ new WeakMap();
|
|
2919
|
+
walk(module.program.body, (node, parent) => {
|
|
2920
|
+
if (parent) parents.set(node, parent);
|
|
2921
|
+
});
|
|
2922
|
+
const sideEffects = tops.some((s) => !structural(s) && !removable(s));
|
|
2923
|
+
return { module, tops, declaredBy, imported, parents, sideEffects };
|
|
2924
|
+
}
|
|
2925
|
+
function treeShake(modules, entry) {
|
|
2926
|
+
const facts = new Map(modules.map((m) => [m.file, factsOf(m)]));
|
|
2927
|
+
const used = /* @__PURE__ */ new Map();
|
|
2928
|
+
const live = /* @__PURE__ */ new Map();
|
|
2929
|
+
const required = /* @__PURE__ */ new Set();
|
|
2930
|
+
let changed = true;
|
|
2931
|
+
const require2 = (file) => {
|
|
2932
|
+
if (required.has(file) || !facts.has(file)) return;
|
|
2933
|
+
required.add(file);
|
|
2934
|
+
changed = true;
|
|
2935
|
+
};
|
|
2936
|
+
const read3 = (file, name) => {
|
|
2937
|
+
if (!facts.has(file)) return;
|
|
2938
|
+
require2(file);
|
|
2939
|
+
const now = used.get(file);
|
|
2940
|
+
if (now === "all") return;
|
|
2941
|
+
if (name === "*") {
|
|
2942
|
+
used.set(file, "all");
|
|
2943
|
+
changed = true;
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
const names = now ?? /* @__PURE__ */ new Set();
|
|
2947
|
+
if (!names.has(name)) {
|
|
2948
|
+
names.add(name);
|
|
2949
|
+
used.set(file, names);
|
|
2950
|
+
changed = true;
|
|
2951
|
+
}
|
|
2952
|
+
};
|
|
2953
|
+
const isRead = (file, name) => {
|
|
2954
|
+
const now = used.get(file);
|
|
2955
|
+
return now === "all" || (now?.has(name) ?? false);
|
|
2956
|
+
};
|
|
2957
|
+
require2(entry);
|
|
2958
|
+
used.set(entry, "all");
|
|
2959
|
+
while (changed) {
|
|
2960
|
+
changed = false;
|
|
2961
|
+
for (const file of [...required]) {
|
|
2962
|
+
const f = facts.get(file);
|
|
2963
|
+
const kept = live.get(file) ?? /* @__PURE__ */ new Set();
|
|
2964
|
+
live.set(file, kept);
|
|
2965
|
+
const queue = [];
|
|
2966
|
+
const keep = (statement) => {
|
|
2967
|
+
if (!statement || kept.has(statement)) return;
|
|
2968
|
+
kept.add(statement);
|
|
2969
|
+
queue.push(statement);
|
|
2970
|
+
changed = true;
|
|
2971
|
+
};
|
|
2972
|
+
for (const statement of f.tops) {
|
|
2973
|
+
if (!structural(statement) && !removable(statement)) keep(statement);
|
|
2974
|
+
if (exportedNames(statement).some((name) => isRead(file, name))) keep(statement);
|
|
2975
|
+
const declaration = declarationOf(statement);
|
|
2976
|
+
if (declaration.type === "ImportStatement" && !declaration.isTypeOnly && !declaration.defaultImport && !declaration.namespaceImport && !declaration.specifiers.length) {
|
|
2977
|
+
const target = f.module.resolve(declaration.source.value);
|
|
2978
|
+
if (target !== void 0) require2(target);
|
|
2979
|
+
}
|
|
2980
|
+
if (declaration.type === "ExportNamedStatement" && !declaration.typeOnly) {
|
|
2981
|
+
const target = declaration.source ? f.module.resolve(declaration.source.value) : void 0;
|
|
2982
|
+
for (const s of declaration.specifiers) {
|
|
2983
|
+
if (!isRead(file, s.exported.name)) continue;
|
|
2984
|
+
if (declaration.source) {
|
|
2985
|
+
if (target !== void 0) read3(target, s.local.name);
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
const id = f.module.scopes.bindingOf.get(s.local);
|
|
2989
|
+
const own = [...f.declaredBy].find(([bid, st]) => (id !== void 0 ? bid === id : false) || exportedLocal(st, s.local.name));
|
|
2990
|
+
if (own) keep(own[1]);
|
|
2991
|
+
const from = id !== void 0 ? f.imported.get(id) : void 0;
|
|
2992
|
+
if (from) read3(from.target, from.name);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
if (declaration.type === "ExportAllStatement") {
|
|
2996
|
+
const target = f.module.resolve(declaration.source.value);
|
|
2997
|
+
if (target === void 0) continue;
|
|
2998
|
+
const mine = used.get(file);
|
|
2999
|
+
if (mine === "all") read3(target, "*");
|
|
3000
|
+
else for (const name of mine ?? []) if (!ownExport(f, name)) read3(target, name);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
for (const statement of [...kept]) queue.push(statement);
|
|
3004
|
+
while (queue.length) {
|
|
3005
|
+
const statement = queue.pop();
|
|
3006
|
+
walk(statement, (node) => {
|
|
3007
|
+
if (node.type === "ImportExpression") {
|
|
3008
|
+
const source = node.source;
|
|
3009
|
+
const target = source?.type === "StringLiteral" ? f.module.resolve(source.value) : void 0;
|
|
3010
|
+
if (target !== void 0) read3(target, "*");
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
if (node.type !== "Identifier") return;
|
|
3014
|
+
const id = f.module.scopes.bindingOf.get(node);
|
|
3015
|
+
if (id === void 0) return;
|
|
3016
|
+
const declaring = f.declaredBy.get(id);
|
|
3017
|
+
if (declaring && declaring !== statement) keep(declaring);
|
|
3018
|
+
const from = f.imported.get(id);
|
|
3019
|
+
if (!from) return;
|
|
3020
|
+
if (from.name !== "*") return read3(from.target, from.name);
|
|
3021
|
+
const parent = f.parents.get(node);
|
|
3022
|
+
if (parent?.type === "MemberExpression" && parent.object === node) read3(from.target, parent.property.name);
|
|
3023
|
+
else read3(from.target, "*");
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
for (const statement of f.tops) {
|
|
3027
|
+
if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
|
|
3028
|
+
const target = f.module.resolve(statement.source.value);
|
|
3029
|
+
if (target !== void 0 && facts.get(target)?.sideEffects) require2(target);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
const shaken = /* @__PURE__ */ new Map();
|
|
3034
|
+
for (const [file, f] of facts) {
|
|
3035
|
+
const kept = live.get(file) ?? /* @__PURE__ */ new Set();
|
|
3036
|
+
const removed = new Set(f.tops.filter((s) => !kept.has(s) && removable(s)));
|
|
3037
|
+
const dead = /* @__PURE__ */ new Set();
|
|
3038
|
+
for (const statement of removed) walk(statement, (node) => {
|
|
3039
|
+
if (node.type === "Identifier") dead.add(node);
|
|
3040
|
+
});
|
|
3041
|
+
shaken.set(file, { removed, dead });
|
|
3042
|
+
}
|
|
3043
|
+
return {
|
|
3044
|
+
modules: shaken,
|
|
3045
|
+
sideEffects: new Set([...facts].filter(([, f]) => f.sideEffects).map(([file]) => file)),
|
|
3046
|
+
required
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
function exportedLocal(statement, name) {
|
|
3050
|
+
const declaration = declarationOf(statement);
|
|
3051
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.name.name === name;
|
|
3052
|
+
if (declaration.type !== "VariableDeclaration") return false;
|
|
3053
|
+
let found = false;
|
|
3054
|
+
walk(declaration.name, (node) => {
|
|
3055
|
+
if (node.type === "IdentifierPattern" && node.name === name) found = true;
|
|
3056
|
+
});
|
|
3057
|
+
return found;
|
|
3058
|
+
}
|
|
3059
|
+
function ownExport(f, name) {
|
|
3060
|
+
return f.tops.some((statement) => exportedNames(statement).includes(name) || statement.type === "ExportNamedStatement" && statement.specifiers.some((s) => s.exported.name === name));
|
|
3061
|
+
}
|
|
3062
|
+
|
|
2603
3063
|
// src/linemap.ts
|
|
2604
3064
|
import { luauString as luauString3 } from "@tilua/parser";
|
|
2605
3065
|
function statementsOf(node) {
|
|
@@ -2663,8 +3123,8 @@ async function bundle(options) {
|
|
|
2663
3123
|
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2664
3124
|
const dynamic = importExpressions(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
|
|
2665
3125
|
for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
|
|
2666
|
-
const
|
|
2667
|
-
if (
|
|
3126
|
+
const target = resolveModulePath(file, specifier, config);
|
|
3127
|
+
if (target && !target.endsWith(".d.tilua")) queue.push(target);
|
|
2668
3128
|
}
|
|
2669
3129
|
}
|
|
2670
3130
|
const analysis = analyzeModules([...sources.values()], config);
|
|
@@ -2678,9 +3138,16 @@ async function bundle(options) {
|
|
|
2678
3138
|
const requireExpression = member(identifier(G), "require");
|
|
2679
3139
|
const importExpression = member(identifier(G), "import");
|
|
2680
3140
|
const mode = options.output ?? config?.output ?? "single";
|
|
2681
|
-
const
|
|
3141
|
+
const byPath = syntaxOf(lowerings).has("requireByPath");
|
|
2682
3142
|
const modules = /* @__PURE__ */ new Map();
|
|
2683
3143
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
3144
|
+
const shared = new SharedRuntime(names);
|
|
3145
|
+
const shaking = options.treeShaking ?? config?.treeShaking ?? false;
|
|
3146
|
+
const resolveIn = (file) => (specifier) => {
|
|
3147
|
+
const found = resolveModulePath(file, specifier, config);
|
|
3148
|
+
return found !== void 0 && sources.has(found) ? found : void 0;
|
|
3149
|
+
};
|
|
3150
|
+
const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry) : void 0;
|
|
2684
3151
|
const pending = [entry];
|
|
2685
3152
|
while (pending.length) {
|
|
2686
3153
|
const file = pending.shift();
|
|
@@ -2693,10 +3160,21 @@ async function bundle(options) {
|
|
|
2693
3160
|
names,
|
|
2694
3161
|
types: analysis.types.get(file),
|
|
2695
3162
|
lowerings,
|
|
2696
|
-
target,
|
|
2697
3163
|
noLib: config?.noLib,
|
|
2698
3164
|
source: source.text,
|
|
2699
3165
|
file: shown,
|
|
3166
|
+
shared,
|
|
3167
|
+
shaken: shaken && {
|
|
3168
|
+
...shaken.modules.get(file),
|
|
3169
|
+
runs: (specifier) => {
|
|
3170
|
+
const target = resolveIn(file)(specifier);
|
|
3171
|
+
return target !== void 0 && shaken.sideEffects.has(target);
|
|
3172
|
+
},
|
|
3173
|
+
needed: (specifier) => {
|
|
3174
|
+
const target = resolveIn(file)(specifier);
|
|
3175
|
+
return target !== void 0 && shaken.required.has(target);
|
|
3176
|
+
}
|
|
3177
|
+
},
|
|
2700
3178
|
module: {
|
|
2701
3179
|
name: source.name,
|
|
2702
3180
|
require: requireExpression,
|
|
@@ -2727,6 +3205,7 @@ async function bundle(options) {
|
|
|
2727
3205
|
exportsName: lowered2.exportsName,
|
|
2728
3206
|
info: lowered2.module,
|
|
2729
3207
|
usesScriptArgs: lowered2.usesScriptArgs,
|
|
3208
|
+
shared: lowered2.shared,
|
|
2730
3209
|
imports,
|
|
2731
3210
|
dynamicImports
|
|
2732
3211
|
});
|
|
@@ -2752,16 +3231,17 @@ async function bundle(options) {
|
|
|
2752
3231
|
if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
|
|
2753
3232
|
return { files: [], modules: moduleNames, diagnostics };
|
|
2754
3233
|
}
|
|
3234
|
+
if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
|
|
2755
3235
|
const several = mode !== "single";
|
|
2756
3236
|
const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
|
|
2757
3237
|
const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
|
|
2758
3238
|
const elsewhere = /* @__PURE__ */ new Map();
|
|
2759
3239
|
for (const file of layout.slice(1)) {
|
|
2760
|
-
for (const module of file.modules) elsewhere.set(module.name, loadPath(layout[0].path, file.path,
|
|
3240
|
+
for (const module of file.modules) elsewhere.set(module.name, loadPath(layout[0].path, file.path, byPath));
|
|
2761
3241
|
}
|
|
2762
3242
|
const program2 = parseLuau2(runtime(G, {
|
|
2763
3243
|
several,
|
|
2764
|
-
|
|
3244
|
+
byPath,
|
|
2765
3245
|
files: elsewhere,
|
|
2766
3246
|
imports: lowered.some((m) => m.dynamicImports.size > 0)
|
|
2767
3247
|
}));
|
|
@@ -2776,6 +3256,8 @@ async function bundle(options) {
|
|
|
2776
3256
|
for (const module of layout[0].modules) {
|
|
2777
3257
|
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module.name), value: moduleTable(module) });
|
|
2778
3258
|
}
|
|
3259
|
+
const helpers = shared.chunk(new Set(layout[0].modules.flatMap((m) => m.shared)));
|
|
3260
|
+
program2.body.statements.unshift(...helpers.declare);
|
|
2779
3261
|
const ok = names.fresh("ok");
|
|
2780
3262
|
const result2 = names.fresh("result");
|
|
2781
3263
|
const entryStatements = parseLuau2(`
|
|
@@ -2797,9 +3279,11 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
|
2797
3279
|
if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
|
|
2798
3280
|
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
|
|
2799
3281
|
}
|
|
3282
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
|
|
2800
3283
|
const files = [{ path: layout[0].path, code: print(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
|
|
2801
3284
|
for (const file of layout.slice(1)) {
|
|
2802
|
-
const
|
|
3285
|
+
const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
|
|
3286
|
+
const body = [...fileHelpers.declare];
|
|
2803
3287
|
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...parseLuau2(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
2804
3288
|
for (const module of file.modules) {
|
|
2805
3289
|
body.push(assign(
|
|
@@ -2809,7 +3293,7 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
|
2809
3293
|
}
|
|
2810
3294
|
const chunk = program([returns([functionExpression(functionBody([G], body))])]);
|
|
2811
3295
|
const lines = buildLineMap(chunk, parseLuau2(print(chunk)), origins);
|
|
2812
|
-
body.push(...mapRegistration(G, file, lineMapSource(lines)));
|
|
3296
|
+
body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
|
|
2813
3297
|
files.push({ path: file.path, code: print(chunk) + "\n", modules: file.modules.map((m) => m.name) });
|
|
2814
3298
|
}
|
|
2815
3299
|
return { code: files[0].code, files, modules: moduleNames, diagnostics };
|
|
@@ -2871,9 +3355,9 @@ function layoutOf(mode, entry, modules, outFile) {
|
|
|
2871
3355
|
}
|
|
2872
3356
|
return files;
|
|
2873
3357
|
}
|
|
2874
|
-
function loadPath(entryPath, file,
|
|
3358
|
+
function loadPath(entryPath, file, byPath) {
|
|
2875
3359
|
const path = posix.relative(posix.dirname(entryPath), file);
|
|
2876
|
-
if (
|
|
3360
|
+
if (!byPath) return path;
|
|
2877
3361
|
const bare = path.slice(0, path.length - posix.extname(path).length);
|
|
2878
3362
|
return bare.startsWith("../") ? bare : `./${bare}`;
|
|
2879
3363
|
}
|
|
@@ -2886,8 +3370,8 @@ function mapRegistration(G, file, map) {
|
|
|
2886
3370
|
...file.modules.map((module) => `${G}.maps[${luauString4(module.name)}] = ${G}.sources[#${G}.sources][2]`)
|
|
2887
3371
|
].join("\n")).body.statements;
|
|
2888
3372
|
}
|
|
2889
|
-
function loader(G,
|
|
2890
|
-
if (
|
|
3373
|
+
function loader(G, byPath) {
|
|
3374
|
+
if (!byPath) {
|
|
2891
3375
|
return `
|
|
2892
3376
|
-- From beside this file, wherever that is: its own path says where.
|
|
2893
3377
|
local folder = ""
|
|
@@ -3095,7 +3579,7 @@ ${several ? `
|
|
|
3095
3579
|
if ${G}.loaded[file] then
|
|
3096
3580
|
return
|
|
3097
3581
|
end
|
|
3098
|
-
${G}.loaded[file] = true${loader(G, options.
|
|
3582
|
+
${G}.loaded[file] = true${loader(G, options.byPath)}
|
|
3099
3583
|
end,` : ""}${options.imports ? `
|
|
3100
3584
|
-- \`import("./x")\`: the module's exports, asked for where the code runs
|
|
3101
3585
|
-- rather than before its module does. \`require\` does the rest \u2014 and
|