@tilua/compiler 6.0.1 → 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-EQE22LX7.js → chunk-NM3EVX2J.js} +569 -83
- package/dist/cli.cjs +572 -92
- package/dist/cli.js +4 -10
- package/dist/index.cjs +569 -84
- 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
package/dist/index.cjs
CHANGED
|
@@ -67,7 +67,7 @@ var index = (object2, key) => ({ type: "IndexExpression", object: object2, index
|
|
|
67
67
|
var call = (callee, args) => ({ type: "CallExpression", callee, arguments: args, ...span() });
|
|
68
68
|
var methodCall = (object2, method, args) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args, ...span() });
|
|
69
69
|
var functionExpression = (func) => ({ type: "FunctionExpression", func, ...span() });
|
|
70
|
-
var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left, right, ...span() });
|
|
70
|
+
var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left: operator === "^" && left.type === "UnaryExpression" ? parenthesized(left) : left, right, ...span() });
|
|
71
71
|
var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
|
|
72
72
|
var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
|
|
73
73
|
var table = (fields) => ({ type: "TableExpression", fields, ...span() });
|
|
@@ -126,19 +126,19 @@ var Names = class _Names {
|
|
|
126
126
|
* a name shared across modules (the module table) collides with none. */
|
|
127
127
|
static from(...programs) {
|
|
128
128
|
const taken = /* @__PURE__ */ new Set(["select", "pairs", "table", "tostring", "setmetatable", "error"]);
|
|
129
|
-
const
|
|
129
|
+
const walk2 = (value) => {
|
|
130
130
|
if (!value || typeof value !== "object") return;
|
|
131
131
|
if (Array.isArray(value)) {
|
|
132
|
-
for (const item of value)
|
|
132
|
+
for (const item of value) walk2(item);
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
135
|
const node = value;
|
|
136
136
|
if (typeof node.name === "string" && node.name) taken.add(node.name);
|
|
137
137
|
for (const [key, child] of Object.entries(value)) {
|
|
138
|
-
if (key !== "line" && key !== "column")
|
|
138
|
+
if (key !== "line" && key !== "column") walk2(child);
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
|
-
for (const program2 of programs)
|
|
141
|
+
for (const program2 of programs) walk2(program2);
|
|
142
142
|
return new _Names(taken);
|
|
143
143
|
}
|
|
144
144
|
/** A copy that goes on to take names independently — one per module, each
|
|
@@ -146,6 +146,10 @@ var Names = class _Names {
|
|
|
146
146
|
fork() {
|
|
147
147
|
return new _Names(new Set(this.taken));
|
|
148
148
|
}
|
|
149
|
+
/** `name`, chosen elsewhere, is not to be chosen again here. */
|
|
150
|
+
take(name) {
|
|
151
|
+
this.taken.add(name);
|
|
152
|
+
}
|
|
149
153
|
/** `base`, or `base2`, `base3`, ... — whichever is free first. */
|
|
150
154
|
fresh(base) {
|
|
151
155
|
let name = base;
|
|
@@ -163,6 +167,7 @@ var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\
|
|
|
163
167
|
var array = read("array.luau");
|
|
164
168
|
var string2 = read("string.luau");
|
|
165
169
|
var object = read("object.luau");
|
|
170
|
+
var json = read("json.luau");
|
|
166
171
|
var arrayMethods = implemented(array);
|
|
167
172
|
var stringMethods = implemented(string2);
|
|
168
173
|
var objectMethods = implemented(object);
|
|
@@ -195,7 +200,7 @@ function objectKeys(type) {
|
|
|
195
200
|
return { known, open: String(open) };
|
|
196
201
|
}
|
|
197
202
|
var plugin = {
|
|
198
|
-
runtime: { array, string: string2, object },
|
|
203
|
+
runtime: { array, string: string2, object, json },
|
|
199
204
|
methodCall({ method, receiver, use }) {
|
|
200
205
|
if (isString(receiver)) {
|
|
201
206
|
return stringMethods.has(method) ? { callee: `${use("string")}.${method}` } : void 0;
|
|
@@ -284,6 +289,141 @@ var plugin2 = {
|
|
|
284
289
|
var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
|
|
285
290
|
|
|
286
291
|
// src/lower.ts
|
|
292
|
+
function syntaxOf(lowerings) {
|
|
293
|
+
return new Set((lowerings ?? []).flatMap((l) => l.plugin.syntax ?? []));
|
|
294
|
+
}
|
|
295
|
+
var SharedRuntime = class {
|
|
296
|
+
constructor(names) {
|
|
297
|
+
this.names = names;
|
|
298
|
+
}
|
|
299
|
+
names;
|
|
300
|
+
byKey = /* @__PURE__ */ new Map();
|
|
301
|
+
/** Each group of names and the statements that declare them as locals,
|
|
302
|
+
* in the order they were first defined: each after what it uses. */
|
|
303
|
+
definitions = [];
|
|
304
|
+
defined = /* @__PURE__ */ new Set();
|
|
305
|
+
/** The name the helper `key` goes by, in every module. */
|
|
306
|
+
name(key, base) {
|
|
307
|
+
let name = this.byKey.get(key);
|
|
308
|
+
if (!name) {
|
|
309
|
+
name = this.names.fresh(base);
|
|
310
|
+
this.byKey.set(key, name);
|
|
311
|
+
}
|
|
312
|
+
return name;
|
|
313
|
+
}
|
|
314
|
+
/** `statements` declare `names`. Only the first module to say so is heard:
|
|
315
|
+
* the others' are the same. */
|
|
316
|
+
define(names, statements) {
|
|
317
|
+
if (names.every((name) => this.defined.has(name))) return;
|
|
318
|
+
for (const name of names) this.defined.add(name);
|
|
319
|
+
this.definitions.push({ names, statements });
|
|
320
|
+
}
|
|
321
|
+
/** Tree shaking: leave out of each runtime table the members nothing
|
|
322
|
+
* reads. `code` is everything else the output holds — the modules —
|
|
323
|
+
* and a member is read when it, or a member read already, names it:
|
|
324
|
+
* `tilua_array.filter(xs, f)` keeps `filter`, and whatever `filter`
|
|
325
|
+
* itself calls. A table used as anything but `table.member` — handed
|
|
326
|
+
* on, walked, indexed by what only the running program knows — keeps
|
|
327
|
+
* every member. */
|
|
328
|
+
shake(code) {
|
|
329
|
+
const names = new Set(this.definitions.flatMap((group) => group.names));
|
|
330
|
+
const read3 = /* @__PURE__ */ new Map();
|
|
331
|
+
const use = (name, key) => {
|
|
332
|
+
const now = read3.get(name);
|
|
333
|
+
if (now === "all") return;
|
|
334
|
+
if (key === void 0) {
|
|
335
|
+
read3.set(name, "all");
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
read3.set(name, (now ?? /* @__PURE__ */ new Set()).add(key));
|
|
339
|
+
};
|
|
340
|
+
const isRead = (name, key) => {
|
|
341
|
+
const now = read3.get(name);
|
|
342
|
+
return now === "all" || (now?.has(key) ?? false);
|
|
343
|
+
};
|
|
344
|
+
const scan = (value) => {
|
|
345
|
+
if (!value || typeof value !== "object") return;
|
|
346
|
+
if (Array.isArray(value)) return void value.forEach(scan);
|
|
347
|
+
const node = value;
|
|
348
|
+
const object2 = node.object;
|
|
349
|
+
if (object2?.type === "Identifier" && names.has(object2.name)) {
|
|
350
|
+
if (node.type === "MemberExpression") return use(object2.name, node.property.name);
|
|
351
|
+
if (node.type === "MethodCallExpression") {
|
|
352
|
+
use(object2.name, node.method.name);
|
|
353
|
+
return scan(node.arguments);
|
|
354
|
+
}
|
|
355
|
+
if (node.type === "IndexExpression") {
|
|
356
|
+
const index2 = node.index;
|
|
357
|
+
if (index2.type === "StringLiteral") return use(object2.name, String(index2.value));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (node.type === "Identifier" && names.has(node.name)) return use(node.name);
|
|
361
|
+
if (node.type === "LocalFunctionStatement") return scan(node.func);
|
|
362
|
+
for (const [key, child] of Object.entries(node)) {
|
|
363
|
+
if (key !== "line" && key !== "column") scan(child);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const defines = (statement, own) => {
|
|
367
|
+
if (statement.type === "FunctionDeclarationStatement") {
|
|
368
|
+
const target = statement.target;
|
|
369
|
+
if (own.includes(target.base.name) && target.path.length === 1 && !target.method) {
|
|
370
|
+
return { name: target.base.name, key: target.path[0].name, value: statement.func };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (statement.type === "AssignmentStatement" && statement.targets.length === 1 && statement.values.length === 1) {
|
|
374
|
+
const target = statement.targets[0];
|
|
375
|
+
if (target.object?.type === "Identifier" && own.includes(target.object.name)) {
|
|
376
|
+
if (target.type === "MemberExpression") return { name: target.object.name, key: target.property.name, value: statement.values[0] };
|
|
377
|
+
if (target.type === "IndexExpression" && target.index?.type === "StringLiteral") {
|
|
378
|
+
return { name: target.object.name, key: String(target.index.value), value: statement.values[0] };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return void 0;
|
|
383
|
+
};
|
|
384
|
+
scan(code);
|
|
385
|
+
const scanned = /* @__PURE__ */ new Set();
|
|
386
|
+
let more = true;
|
|
387
|
+
while (more) {
|
|
388
|
+
more = false;
|
|
389
|
+
for (const group of this.definitions) {
|
|
390
|
+
for (const statement of group.statements) {
|
|
391
|
+
if (scanned.has(statement)) continue;
|
|
392
|
+
const definition = defines(statement, group.names);
|
|
393
|
+
if (definition && !isRead(definition.name, definition.key)) continue;
|
|
394
|
+
scanned.add(statement);
|
|
395
|
+
more = true;
|
|
396
|
+
scan(definition ? definition.value : statement);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
for (const group of this.definitions) {
|
|
401
|
+
group.statements = group.statements.filter((statement) => {
|
|
402
|
+
const definition = defines(statement, group.names);
|
|
403
|
+
return !definition || isRead(definition.name, definition.key);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/** Luau setting up the helpers `used` for a chunk whose code names them:
|
|
408
|
+
* `declare` goes before that code, which reads each as an upvalue, and
|
|
409
|
+
* `define` after it — before any of it runs, and after what a helper
|
|
410
|
+
* reads as it is set up (the line map). Each definition runs in a
|
|
411
|
+
* function of its own, so what it keeps to itself stays there. */
|
|
412
|
+
chunk(used) {
|
|
413
|
+
const groups = this.definitions.filter((group) => group.names.some((name) => used.has(name)));
|
|
414
|
+
const all = groups.flatMap((group) => group.names);
|
|
415
|
+
const declare = [];
|
|
416
|
+
for (let i = 0; i < all.length; i += 100) declare.push(local(all.slice(i, i + 100), []));
|
|
417
|
+
const define = groups.map((group) => assign(
|
|
418
|
+
group.names.map((name) => identifier(name)),
|
|
419
|
+
[call(parenthesized(functionExpression(functionBody([], [
|
|
420
|
+
...group.statements,
|
|
421
|
+
returns(group.names.map((name) => identifier(name)))
|
|
422
|
+
]))), [])]
|
|
423
|
+
));
|
|
424
|
+
return { declare, define };
|
|
425
|
+
}
|
|
426
|
+
};
|
|
287
427
|
function lower(program2, scopes, options = {}) {
|
|
288
428
|
const result2 = new Lowerer(program2, scopes, options).run();
|
|
289
429
|
dropUnreachable(result2.statements);
|
|
@@ -314,7 +454,7 @@ var Lowerer = class {
|
|
|
314
454
|
this.source = source;
|
|
315
455
|
this.scopes = scopes;
|
|
316
456
|
this.options = options;
|
|
317
|
-
this.
|
|
457
|
+
this.syntax = syntaxOf(options.lowerings);
|
|
318
458
|
this.names = options.names ? options.names.fork() : Names.from(source);
|
|
319
459
|
this.exportsName = this.names.fresh("exports");
|
|
320
460
|
for (const binding of scopes.bindings.values()) {
|
|
@@ -339,6 +479,11 @@ var Lowerer = class {
|
|
|
339
479
|
names;
|
|
340
480
|
diagnostics = [];
|
|
341
481
|
helpers = /* @__PURE__ */ new Map();
|
|
482
|
+
/** The shared helpers this file names. */
|
|
483
|
+
sharedUsed = /* @__PURE__ */ new Set();
|
|
484
|
+
/** While a shared helper is written: it is defined outside every module,
|
|
485
|
+
* where none of a module's own names shadows a global. */
|
|
486
|
+
rawGlobals = false;
|
|
342
487
|
/** The binding each declaration node creates. */
|
|
343
488
|
bindingByDeclaration = /* @__PURE__ */ new Map();
|
|
344
489
|
/** tilua names that are Luau keywords, and what they are called instead. */
|
|
@@ -354,8 +499,8 @@ var Lowerer = class {
|
|
|
354
499
|
tryFrames = [];
|
|
355
500
|
/** The loops being lowered in the current function, innermost last. */
|
|
356
501
|
loops = [];
|
|
357
|
-
/**
|
|
358
|
-
|
|
502
|
+
/** What the output's Lua can write beyond Lua 5.1 (`syntaxOf`). */
|
|
503
|
+
syntax;
|
|
359
504
|
/** The source line each emitted statement came from. */
|
|
360
505
|
origins = /* @__PURE__ */ new WeakMap();
|
|
361
506
|
/** The name each class written as a value is known by. */
|
|
@@ -363,15 +508,19 @@ var Lowerer = class {
|
|
|
363
508
|
run() {
|
|
364
509
|
const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
|
|
365
510
|
const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
|
|
511
|
+
this.rawGlobals = this.options.shared !== void 0;
|
|
366
512
|
const helpers = this.helperDefinitions();
|
|
513
|
+
const classes = this.classRuntimeStatements();
|
|
514
|
+
this.rawGlobals = false;
|
|
367
515
|
const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
|
|
368
516
|
return {
|
|
369
|
-
statements: [...captured, ...helpers, ...scriptArgs, ...
|
|
517
|
+
statements: [...captured, ...helpers, ...scriptArgs, ...classes, ...body],
|
|
370
518
|
exportsName: this.exportsName,
|
|
371
519
|
module: this.info,
|
|
372
520
|
diagnostics: this.diagnostics,
|
|
373
521
|
origins: this.origins,
|
|
374
|
-
usesScriptArgs: this.usesScriptArgs
|
|
522
|
+
usesScriptArgs: this.usesScriptArgs,
|
|
523
|
+
shared: [...this.sharedUsed]
|
|
375
524
|
};
|
|
376
525
|
}
|
|
377
526
|
/** Whether the file reads `scriptArgs`, the language's own global. */
|
|
@@ -404,6 +553,7 @@ var Lowerer = class {
|
|
|
404
553
|
/** A global the generated code calls — `pairs`, `table`, ... — under a name
|
|
405
554
|
* the source cannot have shadowed. */
|
|
406
555
|
builtin(global) {
|
|
556
|
+
if (this.rawGlobals) return identifier(global);
|
|
407
557
|
let local2 = this.builtins.get(global);
|
|
408
558
|
if (!local2) {
|
|
409
559
|
const shadowed = [...this.scopes.bindings.values()].some((b) => b.name === global && b.kind !== "global");
|
|
@@ -415,16 +565,31 @@ var Lowerer = class {
|
|
|
415
565
|
}
|
|
416
566
|
/** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
|
|
417
567
|
move() {
|
|
418
|
-
return this.
|
|
568
|
+
return this.helper("move");
|
|
419
569
|
}
|
|
420
570
|
helper(kind) {
|
|
421
571
|
let name = this.helpers.get(kind);
|
|
422
572
|
if (!name) {
|
|
423
|
-
name = this.
|
|
573
|
+
name = this.sharedName(`helper:${kind}`, `tilua_${kind === "array" ? "asArray" : kind}`);
|
|
424
574
|
this.helpers.set(kind, name);
|
|
425
575
|
}
|
|
426
576
|
return identifier(name);
|
|
427
577
|
}
|
|
578
|
+
/** A fresh name for a helper — in a bundle, the one every module calls
|
|
579
|
+
* that helper by. */
|
|
580
|
+
sharedName(key, base) {
|
|
581
|
+
const shared = this.options.shared;
|
|
582
|
+
if (!shared) return this.names.fresh(base);
|
|
583
|
+
const name = shared.name(key, base);
|
|
584
|
+
this.names.take(name);
|
|
585
|
+
this.sharedUsed.add(name);
|
|
586
|
+
return name;
|
|
587
|
+
}
|
|
588
|
+
/** `statements` declare `names`: in this file, or once for the bundle. */
|
|
589
|
+
define(names, statements, out) {
|
|
590
|
+
if (this.options.shared) this.options.shared.define(names, statements);
|
|
591
|
+
else out.push(...statements);
|
|
592
|
+
}
|
|
428
593
|
// `typeof v` tells an array from an object, which Luau cannot: both are
|
|
429
594
|
// tables. So every array the program makes — `[a, b]`, a rest parameter,
|
|
430
595
|
// what `:map` answers — is written down as one, in a table that holds its
|
|
@@ -457,7 +622,8 @@ var Lowerer = class {
|
|
|
457
622
|
const key = `${index2}:${runtime2}`;
|
|
458
623
|
let name = this.loweringNames.get(key);
|
|
459
624
|
if (!name) {
|
|
460
|
-
|
|
625
|
+
const lines = this.lowerings[index2].plugin.runtime?.[runtime2]?.includes("__LINES__") ? this.options.module?.lines : "";
|
|
626
|
+
name = this.sharedName(`lowering:${key}:${lines}`, `tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
|
|
461
627
|
this.loweringNames.set(key, name);
|
|
462
628
|
this.loweringUsed.push({ index: index2, runtime: runtime2, name });
|
|
463
629
|
}
|
|
@@ -657,19 +823,25 @@ var Lowerer = class {
|
|
|
657
823
|
const runtimes = this.runtimeDefinitions();
|
|
658
824
|
if (this.helpers.has("concat")) this.helper("array");
|
|
659
825
|
if (this.helpers.has("concat")) this.move();
|
|
826
|
+
const unpack = this.helpers.get("unpack");
|
|
827
|
+
if (unpack) {
|
|
828
|
+
this.define([unpack], (0, import_luau_parser.parse)(
|
|
829
|
+
`local ${unpack} = ${this.builtin("table").name}.unpack or ${this.builtin("unpack").name}`
|
|
830
|
+
).body.statements, out);
|
|
831
|
+
}
|
|
660
832
|
const move = this.helpers.get("move");
|
|
661
833
|
if (move) {
|
|
662
|
-
|
|
663
|
-
local
|
|
834
|
+
this.define([move], (0, import_luau_parser.parse)(`
|
|
835
|
+
local ${move} = ${this.builtin("table").name}.move or function(from, first, last, at, to)
|
|
664
836
|
for i = first, last do
|
|
665
837
|
to[at + i - first] = from[i]
|
|
666
838
|
end
|
|
667
839
|
return to
|
|
668
|
-
end`).body.statements);
|
|
840
|
+
end`).body.statements, out);
|
|
669
841
|
}
|
|
670
842
|
const each = this.helpers.get("each");
|
|
671
843
|
if (each) {
|
|
672
|
-
|
|
844
|
+
this.define([each], (0, import_luau_parser.parse)(`
|
|
673
845
|
local function ${each}(source, ...)
|
|
674
846
|
if ${this.builtin("type").name}(source) == "function" then
|
|
675
847
|
return source, ...
|
|
@@ -679,7 +851,7 @@ local function ${each}(source, ...)
|
|
|
679
851
|
return meta.__iter(source)
|
|
680
852
|
end
|
|
681
853
|
return ${this.builtin("next").name}, source
|
|
682
|
-
end`).body.statements);
|
|
854
|
+
end`).body.statements, out);
|
|
683
855
|
}
|
|
684
856
|
const marker = this.helpers.get("array");
|
|
685
857
|
const typeOf = this.helpers.get("typeof");
|
|
@@ -687,19 +859,19 @@ end`).body.statements);
|
|
|
687
859
|
const arrays = this.arrays();
|
|
688
860
|
const own = this.helpers.get("arrays");
|
|
689
861
|
if (own) {
|
|
690
|
-
|
|
862
|
+
this.define([own], (0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements, out);
|
|
691
863
|
}
|
|
692
864
|
if (marker) {
|
|
693
|
-
|
|
865
|
+
this.define([marker], (0, import_luau_parser.parse)(`
|
|
694
866
|
local function ${marker}(t)
|
|
695
867
|
${arrays}[t] = true
|
|
696
868
|
return t
|
|
697
|
-
end`).body.statements);
|
|
869
|
+
end`).body.statements, out);
|
|
698
870
|
}
|
|
699
871
|
if (typeOf) {
|
|
700
|
-
|
|
872
|
+
this.define([typeOf], (0, import_luau_parser.parse)(`
|
|
701
873
|
local function ${typeOf}(value, known)
|
|
702
|
-
local name = ${this.builtin(
|
|
874
|
+
local name = (${this.builtin("typeof").name} or ${this.builtin("type").name})(value)
|
|
703
875
|
if name ~= "table" then
|
|
704
876
|
return name
|
|
705
877
|
end
|
|
@@ -716,22 +888,22 @@ local function ${typeOf}(value, known)
|
|
|
716
888
|
return "array"
|
|
717
889
|
end
|
|
718
890
|
return "object"
|
|
719
|
-
end`).body.statements);
|
|
891
|
+
end`).body.statements, out);
|
|
720
892
|
}
|
|
721
893
|
}
|
|
722
894
|
const add = this.helpers.get("add");
|
|
723
895
|
if (add) {
|
|
724
|
-
|
|
896
|
+
this.define([add], (0, import_luau_parser.parse)(`
|
|
725
897
|
local function ${add}(a, b)
|
|
726
898
|
if ${this.builtin("type").name}(a) == "string" or ${this.builtin("type").name}(b) == "string" then
|
|
727
899
|
return ${this.builtin("tostring").name}(a) .. ${this.builtin("tostring").name}(b)
|
|
728
900
|
end
|
|
729
901
|
return a + b
|
|
730
|
-
end`).body.statements);
|
|
902
|
+
end`).body.statements, out);
|
|
731
903
|
}
|
|
732
904
|
const instanceOf = this.helpers.get("instanceof");
|
|
733
905
|
if (instanceOf) {
|
|
734
|
-
|
|
906
|
+
this.define([instanceOf], (0, import_luau_parser.parse)(`
|
|
735
907
|
local function ${instanceOf}(value, class)
|
|
736
908
|
if ${this.builtin("type").name}(value) ~= "table" then
|
|
737
909
|
return false
|
|
@@ -744,11 +916,11 @@ local function ${instanceOf}(value, class)
|
|
|
744
916
|
current = current.ParentClass
|
|
745
917
|
end
|
|
746
918
|
return false
|
|
747
|
-
end`).body.statements);
|
|
919
|
+
end`).body.statements, out);
|
|
748
920
|
}
|
|
749
921
|
const accessors = this.helpers.get("accessors");
|
|
750
922
|
if (accessors) {
|
|
751
|
-
|
|
923
|
+
this.define([accessors], (0, import_luau_parser.parse)(`
|
|
752
924
|
local function ${accessors}(target, getters, setters)
|
|
753
925
|
return ${this.builtin("setmetatable").name}(target, {
|
|
754
926
|
-- Read by a spread, which copies what the getters give.
|
|
@@ -772,11 +944,11 @@ local function ${accessors}(target, getters, setters)
|
|
|
772
944
|
${this.builtin("rawset").name}(self, key, value)
|
|
773
945
|
end,
|
|
774
946
|
})
|
|
775
|
-
end`).body.statements);
|
|
947
|
+
end`).body.statements, out);
|
|
776
948
|
}
|
|
777
949
|
const assign2 = this.helpers.get("assign");
|
|
778
950
|
if (assign2) {
|
|
779
|
-
|
|
951
|
+
this.define([assign2], (0, import_luau_parser.parse)(`
|
|
780
952
|
local function ${assign2}(target, ...)
|
|
781
953
|
for i = 1, ${this.builtin("select").name}("#", ...) do
|
|
782
954
|
local source = (${this.builtin("select").name}(i, ...))
|
|
@@ -794,12 +966,12 @@ local function ${assign2}(target, ...)
|
|
|
794
966
|
end
|
|
795
967
|
end
|
|
796
968
|
return target
|
|
797
|
-
end`).body.statements);
|
|
969
|
+
end`).body.statements, out);
|
|
798
970
|
}
|
|
799
|
-
|
|
971
|
+
for (const { names, statements } of runtimes) this.define(names, statements, out);
|
|
800
972
|
const concat = this.helpers.get("concat");
|
|
801
973
|
if (concat) {
|
|
802
|
-
|
|
974
|
+
this.define([concat], [localFunction(concat, functionBody([], [
|
|
803
975
|
local(["result"], [this.array(table([]))]),
|
|
804
976
|
numericFor("i", number(1), selectCount(this.builtin("select")), [
|
|
805
977
|
local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
|
|
@@ -812,7 +984,7 @@ end`).body.statements);
|
|
|
812
984
|
]))
|
|
813
985
|
]),
|
|
814
986
|
returns([identifier("result")])
|
|
815
|
-
], true)));
|
|
987
|
+
], true))], out);
|
|
816
988
|
}
|
|
817
989
|
return out;
|
|
818
990
|
}
|
|
@@ -832,7 +1004,12 @@ end`).body.statements);
|
|
|
832
1004
|
}
|
|
833
1005
|
const lines = this.options.module?.lines ?? "nil";
|
|
834
1006
|
const fail = this.options.module?.fail ?? "nil";
|
|
835
|
-
|
|
1007
|
+
const group = {
|
|
1008
|
+
names: [name],
|
|
1009
|
+
statements: (0, import_luau_parser.parse)(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
|
|
1010
|
+
};
|
|
1011
|
+
if (index2 === 0 && runtime2 === "json") out.unshift(group);
|
|
1012
|
+
else out.push(group);
|
|
836
1013
|
}
|
|
837
1014
|
return out;
|
|
838
1015
|
}
|
|
@@ -847,7 +1024,8 @@ end`).body.statements);
|
|
|
847
1024
|
* util = require("util") -- the imports
|
|
848
1025
|
* ... -- the rest, in source order */
|
|
849
1026
|
module(context) {
|
|
850
|
-
const
|
|
1027
|
+
const shaken = this.options.shaken;
|
|
1028
|
+
const statements = shaken ? this.source.body.statements.filter((s) => !shaken.removed.has(s)) : this.source.body.statements;
|
|
851
1029
|
const exportsTable = () => identifier(this.exportsName);
|
|
852
1030
|
const { names, links, stars } = this.info;
|
|
853
1031
|
const locals = [];
|
|
@@ -886,13 +1064,24 @@ end`).body.statements);
|
|
|
886
1064
|
};
|
|
887
1065
|
for (const statement of statements) {
|
|
888
1066
|
if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
|
|
1067
|
+
if (!statement.defaultImport && !statement.namespaceImport && !statement.specifiers.length) {
|
|
1068
|
+
const key2 = resolve4(statement.source);
|
|
1069
|
+
if (key2 !== void 0) load(key2);
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
889
1072
|
const specifiers = [
|
|
890
1073
|
...statement.defaultImport ? [{ local: statement.defaultImport, imported: "default" }] : [],
|
|
891
1074
|
...statement.namespaceImport ? [{ local: statement.namespaceImport, imported: void 0 }] : [],
|
|
892
1075
|
...statement.specifiers.map((s) => ({ local: s.local, imported: s.imported.name }))
|
|
893
1076
|
];
|
|
894
|
-
const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references
|
|
895
|
-
if (!used.length)
|
|
1077
|
+
const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references ?? []).some((reference) => !shaken?.dead.has(reference)));
|
|
1078
|
+
if (!used.length) {
|
|
1079
|
+
if (shaken?.runs(statement.source.value)) {
|
|
1080
|
+
const key2 = resolve4(statement.source);
|
|
1081
|
+
if (key2 !== void 0) load(key2);
|
|
1082
|
+
}
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
896
1085
|
const key = resolve4(statement.source);
|
|
897
1086
|
if (key === void 0) continue;
|
|
898
1087
|
const local2 = moduleLocal(key);
|
|
@@ -934,6 +1123,7 @@ end`).body.statements);
|
|
|
934
1123
|
break;
|
|
935
1124
|
}
|
|
936
1125
|
case "ExportAllStatement": {
|
|
1126
|
+
if (shaken && !shaken.needed(statement.source.value)) break;
|
|
937
1127
|
const key = resolve4(statement.source);
|
|
938
1128
|
if (key === void 0) break;
|
|
939
1129
|
load(key);
|
|
@@ -1208,7 +1398,7 @@ end`).body.statements);
|
|
|
1208
1398
|
case "VariableDeclaration":
|
|
1209
1399
|
return this.variableDeclaration(node, "declare");
|
|
1210
1400
|
case "FunctionDeclaration":
|
|
1211
|
-
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.
|
|
1401
|
+
return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.syntax.has("attributes") ? node.attributes : void 0 }];
|
|
1212
1402
|
case "FunctionDeclarationStatement":
|
|
1213
1403
|
return [this.functionDeclarationStatement(node)];
|
|
1214
1404
|
case "ClassDeclaration":
|
|
@@ -1220,7 +1410,7 @@ end`).body.statements);
|
|
|
1220
1410
|
const join = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
|
|
1221
1411
|
if (join === "dynamic") return this.dynamicAddition(node);
|
|
1222
1412
|
const operator = join === "concat" ? "..=" : node.operator;
|
|
1223
|
-
if (this.
|
|
1413
|
+
if (!this.syntax.has("compoundAssignment")) return this.compoundAssignment51(node, operator);
|
|
1224
1414
|
return [{
|
|
1225
1415
|
type: "CompoundAssignmentStatement",
|
|
1226
1416
|
operator,
|
|
@@ -1258,10 +1448,10 @@ end`).body.statements);
|
|
|
1258
1448
|
case "RepeatStatement": {
|
|
1259
1449
|
const body = this.block(node.body);
|
|
1260
1450
|
const condition = this.expression(node.condition);
|
|
1261
|
-
if (this.
|
|
1451
|
+
if (!this.syntax.has("continue") && hasOwnJump(body.statements, "ContinueStatement")) {
|
|
1262
1452
|
const hidden = declaredNames(body.statements).filter((name) => mentions(condition, name));
|
|
1263
1453
|
if (hidden.length) {
|
|
1264
|
-
this.report(node, `'continue' in a 'repeat' whose 'until' reads ${hidden.map((n) => `'${n}'`).join(", ")} cannot be compiled
|
|
1454
|
+
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").`);
|
|
1265
1455
|
}
|
|
1266
1456
|
}
|
|
1267
1457
|
return [{ type: "RepeatStatement", body: this.loopBody51(body), condition, ...spanOf(node) }];
|
|
@@ -1302,8 +1492,8 @@ end`).body.statements);
|
|
|
1302
1492
|
let source = this.expression(node.iterator);
|
|
1303
1493
|
if (form.viaIter) source = methodCall(source, "__iter", []);
|
|
1304
1494
|
const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
|
|
1305
|
-
let iterators = form.walks === "iteration" ? [call(this.
|
|
1306
|
-
if (this.
|
|
1495
|
+
let iterators = form.walks === "iteration" ? [call(this.helper("unpack"), [source, number(1), number(3)])] : [source];
|
|
1496
|
+
if (!this.syntax.has("iteration") && form.walks === "values") {
|
|
1307
1497
|
const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
|
|
1308
1498
|
iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
|
|
1309
1499
|
}
|
|
@@ -1459,8 +1649,8 @@ end`).body.statements);
|
|
|
1459
1649
|
classHelpers;
|
|
1460
1650
|
classRuntime() {
|
|
1461
1651
|
return this.classHelpers ??= {
|
|
1462
|
-
build: this.
|
|
1463
|
-
accessors: this.
|
|
1652
|
+
build: this.sharedName("class:build", "tilua_class"),
|
|
1653
|
+
accessors: this.sharedName("class:accessors", "tilua_accessors")
|
|
1464
1654
|
};
|
|
1465
1655
|
}
|
|
1466
1656
|
/** The helpers, as Luau — emitted only in a file that declares a class.
|
|
@@ -1478,7 +1668,8 @@ end`).body.statements);
|
|
|
1478
1668
|
if (!this.classHelpers) return [];
|
|
1479
1669
|
const { build, accessors } = this.classHelpers;
|
|
1480
1670
|
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
1481
|
-
|
|
1671
|
+
const out = [];
|
|
1672
|
+
this.define([build, accessors], (0, import_luau_parser.parse)(`
|
|
1482
1673
|
local function ${build}(base, name)
|
|
1483
1674
|
local class = { __getters = {}, __setters = {} }
|
|
1484
1675
|
class.__index = class
|
|
@@ -1516,7 +1707,8 @@ local function ${accessors}(class)
|
|
|
1516
1707
|
rawset(this, key, value)
|
|
1517
1708
|
end
|
|
1518
1709
|
end
|
|
1519
|
-
`).body.statements;
|
|
1710
|
+
`).body.statements, out);
|
|
1711
|
+
return out;
|
|
1520
1712
|
}
|
|
1521
1713
|
/** The class being lowered, so `super` knows what to name. */
|
|
1522
1714
|
classContext;
|
|
@@ -1544,6 +1736,7 @@ end
|
|
|
1544
1736
|
string(called)
|
|
1545
1737
|
]);
|
|
1546
1738
|
const out = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
|
|
1739
|
+
const statics = [];
|
|
1547
1740
|
const previous = this.classContext;
|
|
1548
1741
|
this.classContext = node;
|
|
1549
1742
|
try {
|
|
@@ -1551,7 +1744,7 @@ end
|
|
|
1551
1744
|
switch (member2.type) {
|
|
1552
1745
|
case "ClassField":
|
|
1553
1746
|
if (member2.isStatic) {
|
|
1554
|
-
|
|
1747
|
+
statics.push(assign(
|
|
1555
1748
|
[member(self, member2.name.name)],
|
|
1556
1749
|
[member2.init ? this.expression(member2.init) : nil()]
|
|
1557
1750
|
));
|
|
@@ -1582,6 +1775,7 @@ end
|
|
|
1582
1775
|
out.push(this.classInit(node, self));
|
|
1583
1776
|
if (!node.isAbstract) out.push(this.classNew(node, self));
|
|
1584
1777
|
out.push(callStatement(call(identifier(accessors), [self])));
|
|
1778
|
+
out.push(...statics);
|
|
1585
1779
|
} finally {
|
|
1586
1780
|
this.classContext = previous;
|
|
1587
1781
|
}
|
|
@@ -1658,7 +1852,7 @@ end
|
|
|
1658
1852
|
isMethod,
|
|
1659
1853
|
func,
|
|
1660
1854
|
// `@native` and the like are Luau's.
|
|
1661
|
-
attributes: this.
|
|
1855
|
+
attributes: this.syntax.has("attributes") ? node.attributes : void 0,
|
|
1662
1856
|
...spanOf(node)
|
|
1663
1857
|
};
|
|
1664
1858
|
}
|
|
@@ -1898,7 +2092,7 @@ end
|
|
|
1898
2092
|
if (join === "concat") return binary("..", left, right);
|
|
1899
2093
|
if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1900
2094
|
}
|
|
1901
|
-
if (this.
|
|
2095
|
+
if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
|
|
1902
2096
|
return binary(node.operator, left, right);
|
|
1903
2097
|
}
|
|
1904
2098
|
case "UnaryExpression":
|
|
@@ -1932,7 +2126,7 @@ end
|
|
|
1932
2126
|
case "AsConstExpression":
|
|
1933
2127
|
return this.expression(node.expression);
|
|
1934
2128
|
case "IfElseExpression":
|
|
1935
|
-
if (this.
|
|
2129
|
+
if (!this.syntax.has("ifExpression")) return this.ifElse51(node);
|
|
1936
2130
|
return {
|
|
1937
2131
|
type: "IfElseExpression",
|
|
1938
2132
|
clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
|
|
@@ -1979,7 +2173,7 @@ end
|
|
|
1979
2173
|
const last2 = list.length - 1;
|
|
1980
2174
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1981
2175
|
}
|
|
1982
|
-
const unpack = (value) => call(this.
|
|
2176
|
+
const unpack = (value) => call(this.helper("unpack"), [value]);
|
|
1983
2177
|
const last = list[list.length - 1];
|
|
1984
2178
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1985
2179
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -2093,7 +2287,7 @@ end
|
|
|
2093
2287
|
const segments = this.segments(chain);
|
|
2094
2288
|
const base = this.expression(chain.base);
|
|
2095
2289
|
const last = chain.links[chain.links.length - 1];
|
|
2096
|
-
if (
|
|
2290
|
+
if (this.syntax.has("ifExpression") && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
2097
2291
|
return {
|
|
2098
2292
|
type: "IfElseExpression",
|
|
2099
2293
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -2120,7 +2314,7 @@ end
|
|
|
2120
2314
|
nullish(node) {
|
|
2121
2315
|
const left = this.keptToOne(this.expression(node.left));
|
|
2122
2316
|
const right = this.keptToOne(this.expression(node.right));
|
|
2123
|
-
if (
|
|
2317
|
+
if (this.syntax.has("ifExpression") && left.type === "Identifier") {
|
|
2124
2318
|
return {
|
|
2125
2319
|
type: "IfElseExpression",
|
|
2126
2320
|
clauses: [{ condition: binary("~=", left, nil()), body: left }],
|
|
@@ -2207,14 +2401,19 @@ end
|
|
|
2207
2401
|
let target;
|
|
2208
2402
|
if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
|
|
2209
2403
|
const object2 = this.expression(node.target.object);
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
target = member(identifier(objectName), node.target.property.name);
|
|
2404
|
+
const key = node.target.type === "IndexExpression" ? node.target.index : void 0;
|
|
2405
|
+
if (object2.type === "Identifier" && (key === void 0 || key.type === "StringLiteral" || key.type === "NumberLiteral")) {
|
|
2406
|
+
target = key === void 0 ? member(object2, node.target.property.name) : index(object2, this.expression(key));
|
|
2214
2407
|
} else {
|
|
2215
|
-
const
|
|
2216
|
-
before.push(local([
|
|
2217
|
-
target
|
|
2408
|
+
const objectName = this.names.fresh("target");
|
|
2409
|
+
before.push(local([objectName], [object2]));
|
|
2410
|
+
if (node.target.type === "MemberExpression") {
|
|
2411
|
+
target = member(identifier(objectName), node.target.property.name);
|
|
2412
|
+
} else {
|
|
2413
|
+
const keyName = this.names.fresh("key");
|
|
2414
|
+
before.push(local([keyName], [this.expression(node.target.index)]));
|
|
2415
|
+
target = index(identifier(objectName), identifier(keyName));
|
|
2416
|
+
}
|
|
2218
2417
|
}
|
|
2219
2418
|
} else {
|
|
2220
2419
|
target = this.expression(node.target);
|
|
@@ -2235,7 +2434,7 @@ end
|
|
|
2235
2434
|
* done, and breaks for real there. A body with no `continue` is left
|
|
2236
2435
|
* exactly as it was. */
|
|
2237
2436
|
loopBody51(body) {
|
|
2238
|
-
if (
|
|
2437
|
+
if (this.syntax.has("continue") || !hasOwnJump(body.statements, "ContinueStatement")) return body;
|
|
2239
2438
|
const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
|
|
2240
2439
|
const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
|
|
2241
2440
|
if (!flag) return block([once]);
|
|
@@ -2287,7 +2486,7 @@ end
|
|
|
2287
2486
|
* underscores and `0b1010` becomes the decimal it means. A hex literal
|
|
2288
2487
|
* keeps its base — 5.1 reads those. */
|
|
2289
2488
|
numberRaw(node) {
|
|
2290
|
-
if (
|
|
2489
|
+
if (this.syntax.has("numberLiterals")) return node.raw;
|
|
2291
2490
|
if (/^0[bB]/.test(node.raw)) return String(node.value);
|
|
2292
2491
|
return node.raw.replace(/_/g, "");
|
|
2293
2492
|
}
|
|
@@ -2540,10 +2739,10 @@ function declaredNames(statements) {
|
|
|
2540
2739
|
}
|
|
2541
2740
|
function mentions(expression, name) {
|
|
2542
2741
|
let found = false;
|
|
2543
|
-
const
|
|
2742
|
+
const walk2 = (value) => {
|
|
2544
2743
|
if (found || !value || typeof value !== "object") return;
|
|
2545
2744
|
if (Array.isArray(value)) {
|
|
2546
|
-
for (const item of value)
|
|
2745
|
+
for (const item of value) walk2(item);
|
|
2547
2746
|
return;
|
|
2548
2747
|
}
|
|
2549
2748
|
const node = value;
|
|
@@ -2552,10 +2751,10 @@ function mentions(expression, name) {
|
|
|
2552
2751
|
return;
|
|
2553
2752
|
}
|
|
2554
2753
|
for (const [key, child] of Object.entries(value)) {
|
|
2555
|
-
if (key !== "line" && key !== "column")
|
|
2754
|
+
if (key !== "line" && key !== "column") walk2(child);
|
|
2556
2755
|
}
|
|
2557
2756
|
};
|
|
2558
|
-
|
|
2757
|
+
walk2(expression);
|
|
2559
2758
|
return found;
|
|
2560
2759
|
}
|
|
2561
2760
|
function allNodes(root) {
|
|
@@ -2591,6 +2790,269 @@ var CLASS_METAMETHODS = [
|
|
|
2591
2790
|
"__iter"
|
|
2592
2791
|
];
|
|
2593
2792
|
|
|
2793
|
+
// src/treeshake.ts
|
|
2794
|
+
function walk(node, visit, parent) {
|
|
2795
|
+
if (!node || typeof node !== "object") return;
|
|
2796
|
+
if (Array.isArray(node)) {
|
|
2797
|
+
for (const item of node) walk(item, visit, parent);
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
const n = node;
|
|
2801
|
+
if (typeof n.type === "string" && visit(n, parent) === false) return;
|
|
2802
|
+
for (const [key, child] of Object.entries(n)) {
|
|
2803
|
+
if (key === "line" || key === "column") continue;
|
|
2804
|
+
walk(child, visit, typeof n.type === "string" ? n : parent);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
function pure(expression) {
|
|
2808
|
+
let ok = true;
|
|
2809
|
+
walk(expression, (node) => {
|
|
2810
|
+
if (!ok) return false;
|
|
2811
|
+
switch (node.type) {
|
|
2812
|
+
case "CallExpression":
|
|
2813
|
+
case "MethodCallExpression":
|
|
2814
|
+
case "ImportExpression":
|
|
2815
|
+
case "SuperExpression":
|
|
2816
|
+
ok = false;
|
|
2817
|
+
return false;
|
|
2818
|
+
case "FunctionExpression":
|
|
2819
|
+
case "FunctionBody":
|
|
2820
|
+
case "ClassMethod":
|
|
2821
|
+
case "ClassAccessor":
|
|
2822
|
+
case "ClassConstructor":
|
|
2823
|
+
return false;
|
|
2824
|
+
case "ClassField":
|
|
2825
|
+
if (!node.isStatic) return false;
|
|
2826
|
+
}
|
|
2827
|
+
});
|
|
2828
|
+
return ok;
|
|
2829
|
+
}
|
|
2830
|
+
function declarationOf(statement) {
|
|
2831
|
+
return statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
2832
|
+
}
|
|
2833
|
+
function removable(statement) {
|
|
2834
|
+
const declaration = declarationOf(statement);
|
|
2835
|
+
switch (declaration.type) {
|
|
2836
|
+
case "FunctionDeclaration":
|
|
2837
|
+
return true;
|
|
2838
|
+
case "ClassDeclaration":
|
|
2839
|
+
return pure(declaration);
|
|
2840
|
+
case "VariableDeclaration":
|
|
2841
|
+
return declaration.init === void 0 || pure(declaration.init);
|
|
2842
|
+
case "ExportDefaultStatement":
|
|
2843
|
+
return pure(declaration.declaration);
|
|
2844
|
+
default:
|
|
2845
|
+
return false;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
function structural(statement) {
|
|
2849
|
+
switch (statement.type) {
|
|
2850
|
+
case "TypeAliasStatement":
|
|
2851
|
+
case "ExportTypeAliasStatement":
|
|
2852
|
+
case "DeclareStatement":
|
|
2853
|
+
case "DeclareClassStatement":
|
|
2854
|
+
case "DeclareMetatableStatement":
|
|
2855
|
+
case "ImportStatement":
|
|
2856
|
+
case "ExportNamedStatement":
|
|
2857
|
+
case "ExportAllStatement":
|
|
2858
|
+
return true;
|
|
2859
|
+
default:
|
|
2860
|
+
return false;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
function exportedNames(statement) {
|
|
2864
|
+
switch (statement.type) {
|
|
2865
|
+
case "ExportStatement": {
|
|
2866
|
+
const declaration = statement.declaration;
|
|
2867
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return [declaration.name.name];
|
|
2868
|
+
const names = [];
|
|
2869
|
+
walk(declaration.name, (node) => {
|
|
2870
|
+
if (node.type === "IdentifierPattern") names.push(node.name);
|
|
2871
|
+
});
|
|
2872
|
+
return names;
|
|
2873
|
+
}
|
|
2874
|
+
case "ExportDefaultStatement":
|
|
2875
|
+
return ["default"];
|
|
2876
|
+
default:
|
|
2877
|
+
return [];
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
function factsOf(module2) {
|
|
2881
|
+
const tops = module2.program.body.statements;
|
|
2882
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
2883
|
+
for (const binding of module2.scopes.bindings.values()) {
|
|
2884
|
+
if (binding.declarationNode) byNode.set(binding.declarationNode, binding.id);
|
|
2885
|
+
}
|
|
2886
|
+
const declaredBy = /* @__PURE__ */ new Map();
|
|
2887
|
+
const imported = /* @__PURE__ */ new Map();
|
|
2888
|
+
for (const statement of tops) {
|
|
2889
|
+
const declaration = declarationOf(statement);
|
|
2890
|
+
const declare = (node) => {
|
|
2891
|
+
const id = node && byNode.get(node);
|
|
2892
|
+
if (id !== void 0) declaredBy.set(id, statement);
|
|
2893
|
+
};
|
|
2894
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") declare(declaration.name);
|
|
2895
|
+
else if (declaration.type === "VariableDeclaration") {
|
|
2896
|
+
walk(declaration.name, (node) => {
|
|
2897
|
+
if (node.type === "IdentifierPattern") declare(node);
|
|
2898
|
+
});
|
|
2899
|
+
} else if (declaration.type === "ImportStatement" && !declaration.isTypeOnly) {
|
|
2900
|
+
const target = module2.resolve(declaration.source.value);
|
|
2901
|
+
if (target === void 0) continue;
|
|
2902
|
+
const add = (node, name) => {
|
|
2903
|
+
const id = node && byNode.get(node);
|
|
2904
|
+
if (id !== void 0) imported.set(id, { target, name });
|
|
2905
|
+
};
|
|
2906
|
+
if (declaration.defaultImport) add(declaration.defaultImport, "default");
|
|
2907
|
+
if (declaration.namespaceImport) add(declaration.namespaceImport, "*");
|
|
2908
|
+
for (const s of declaration.specifiers) add(s.local, s.imported.name);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
const parents = /* @__PURE__ */ new WeakMap();
|
|
2912
|
+
walk(module2.program.body, (node, parent) => {
|
|
2913
|
+
if (parent) parents.set(node, parent);
|
|
2914
|
+
});
|
|
2915
|
+
const sideEffects = tops.some((s) => !structural(s) && !removable(s));
|
|
2916
|
+
return { module: module2, tops, declaredBy, imported, parents, sideEffects };
|
|
2917
|
+
}
|
|
2918
|
+
function treeShake(modules, entry) {
|
|
2919
|
+
const facts = new Map(modules.map((m) => [m.file, factsOf(m)]));
|
|
2920
|
+
const used = /* @__PURE__ */ new Map();
|
|
2921
|
+
const live = /* @__PURE__ */ new Map();
|
|
2922
|
+
const required = /* @__PURE__ */ new Set();
|
|
2923
|
+
let changed = true;
|
|
2924
|
+
const require2 = (file) => {
|
|
2925
|
+
if (required.has(file) || !facts.has(file)) return;
|
|
2926
|
+
required.add(file);
|
|
2927
|
+
changed = true;
|
|
2928
|
+
};
|
|
2929
|
+
const read3 = (file, name) => {
|
|
2930
|
+
if (!facts.has(file)) return;
|
|
2931
|
+
require2(file);
|
|
2932
|
+
const now = used.get(file);
|
|
2933
|
+
if (now === "all") return;
|
|
2934
|
+
if (name === "*") {
|
|
2935
|
+
used.set(file, "all");
|
|
2936
|
+
changed = true;
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
const names = now ?? /* @__PURE__ */ new Set();
|
|
2940
|
+
if (!names.has(name)) {
|
|
2941
|
+
names.add(name);
|
|
2942
|
+
used.set(file, names);
|
|
2943
|
+
changed = true;
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
const isRead = (file, name) => {
|
|
2947
|
+
const now = used.get(file);
|
|
2948
|
+
return now === "all" || (now?.has(name) ?? false);
|
|
2949
|
+
};
|
|
2950
|
+
require2(entry);
|
|
2951
|
+
used.set(entry, "all");
|
|
2952
|
+
while (changed) {
|
|
2953
|
+
changed = false;
|
|
2954
|
+
for (const file of [...required]) {
|
|
2955
|
+
const f = facts.get(file);
|
|
2956
|
+
const kept = live.get(file) ?? /* @__PURE__ */ new Set();
|
|
2957
|
+
live.set(file, kept);
|
|
2958
|
+
const queue = [];
|
|
2959
|
+
const keep = (statement) => {
|
|
2960
|
+
if (!statement || kept.has(statement)) return;
|
|
2961
|
+
kept.add(statement);
|
|
2962
|
+
queue.push(statement);
|
|
2963
|
+
changed = true;
|
|
2964
|
+
};
|
|
2965
|
+
for (const statement of f.tops) {
|
|
2966
|
+
if (!structural(statement) && !removable(statement)) keep(statement);
|
|
2967
|
+
if (exportedNames(statement).some((name) => isRead(file, name))) keep(statement);
|
|
2968
|
+
const declaration = declarationOf(statement);
|
|
2969
|
+
if (declaration.type === "ImportStatement" && !declaration.isTypeOnly && !declaration.defaultImport && !declaration.namespaceImport && !declaration.specifiers.length) {
|
|
2970
|
+
const target = f.module.resolve(declaration.source.value);
|
|
2971
|
+
if (target !== void 0) require2(target);
|
|
2972
|
+
}
|
|
2973
|
+
if (declaration.type === "ExportNamedStatement" && !declaration.typeOnly) {
|
|
2974
|
+
const target = declaration.source ? f.module.resolve(declaration.source.value) : void 0;
|
|
2975
|
+
for (const s of declaration.specifiers) {
|
|
2976
|
+
if (!isRead(file, s.exported.name)) continue;
|
|
2977
|
+
if (declaration.source) {
|
|
2978
|
+
if (target !== void 0) read3(target, s.local.name);
|
|
2979
|
+
continue;
|
|
2980
|
+
}
|
|
2981
|
+
const id = f.module.scopes.bindingOf.get(s.local);
|
|
2982
|
+
const own = [...f.declaredBy].find(([bid, st]) => (id !== void 0 ? bid === id : false) || exportedLocal(st, s.local.name));
|
|
2983
|
+
if (own) keep(own[1]);
|
|
2984
|
+
const from = id !== void 0 ? f.imported.get(id) : void 0;
|
|
2985
|
+
if (from) read3(from.target, from.name);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
if (declaration.type === "ExportAllStatement") {
|
|
2989
|
+
const target = f.module.resolve(declaration.source.value);
|
|
2990
|
+
if (target === void 0) continue;
|
|
2991
|
+
const mine = used.get(file);
|
|
2992
|
+
if (mine === "all") read3(target, "*");
|
|
2993
|
+
else for (const name of mine ?? []) if (!ownExport(f, name)) read3(target, name);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
for (const statement of [...kept]) queue.push(statement);
|
|
2997
|
+
while (queue.length) {
|
|
2998
|
+
const statement = queue.pop();
|
|
2999
|
+
walk(statement, (node) => {
|
|
3000
|
+
if (node.type === "ImportExpression") {
|
|
3001
|
+
const source = node.source;
|
|
3002
|
+
const target = source?.type === "StringLiteral" ? f.module.resolve(source.value) : void 0;
|
|
3003
|
+
if (target !== void 0) read3(target, "*");
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
if (node.type !== "Identifier") return;
|
|
3007
|
+
const id = f.module.scopes.bindingOf.get(node);
|
|
3008
|
+
if (id === void 0) return;
|
|
3009
|
+
const declaring = f.declaredBy.get(id);
|
|
3010
|
+
if (declaring && declaring !== statement) keep(declaring);
|
|
3011
|
+
const from = f.imported.get(id);
|
|
3012
|
+
if (!from) return;
|
|
3013
|
+
if (from.name !== "*") return read3(from.target, from.name);
|
|
3014
|
+
const parent = f.parents.get(node);
|
|
3015
|
+
if (parent?.type === "MemberExpression" && parent.object === node) read3(from.target, parent.property.name);
|
|
3016
|
+
else read3(from.target, "*");
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
for (const statement of f.tops) {
|
|
3020
|
+
if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
|
|
3021
|
+
const target = f.module.resolve(statement.source.value);
|
|
3022
|
+
if (target !== void 0 && facts.get(target)?.sideEffects) require2(target);
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
const shaken = /* @__PURE__ */ new Map();
|
|
3027
|
+
for (const [file, f] of facts) {
|
|
3028
|
+
const kept = live.get(file) ?? /* @__PURE__ */ new Set();
|
|
3029
|
+
const removed = new Set(f.tops.filter((s) => !kept.has(s) && removable(s)));
|
|
3030
|
+
const dead = /* @__PURE__ */ new Set();
|
|
3031
|
+
for (const statement of removed) walk(statement, (node) => {
|
|
3032
|
+
if (node.type === "Identifier") dead.add(node);
|
|
3033
|
+
});
|
|
3034
|
+
shaken.set(file, { removed, dead });
|
|
3035
|
+
}
|
|
3036
|
+
return {
|
|
3037
|
+
modules: shaken,
|
|
3038
|
+
sideEffects: new Set([...facts].filter(([, f]) => f.sideEffects).map(([file]) => file)),
|
|
3039
|
+
required
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
function exportedLocal(statement, name) {
|
|
3043
|
+
const declaration = declarationOf(statement);
|
|
3044
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.name.name === name;
|
|
3045
|
+
if (declaration.type !== "VariableDeclaration") return false;
|
|
3046
|
+
let found = false;
|
|
3047
|
+
walk(declaration.name, (node) => {
|
|
3048
|
+
if (node.type === "IdentifierPattern" && node.name === name) found = true;
|
|
3049
|
+
});
|
|
3050
|
+
return found;
|
|
3051
|
+
}
|
|
3052
|
+
function ownExport(f, name) {
|
|
3053
|
+
return f.tops.some((statement) => exportedNames(statement).includes(name) || statement.type === "ExportNamedStatement" && statement.specifiers.some((s) => s.exported.name === name));
|
|
3054
|
+
}
|
|
3055
|
+
|
|
2594
3056
|
// src/linemap.ts
|
|
2595
3057
|
var import_parser6 = require("@tilua/parser");
|
|
2596
3058
|
function statementsOf(node) {
|
|
@@ -2681,8 +3143,8 @@ async function bundle(options) {
|
|
|
2681
3143
|
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2682
3144
|
const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
|
|
2683
3145
|
for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
|
|
2684
|
-
const
|
|
2685
|
-
if (
|
|
3146
|
+
const target = (0, import_parser7.resolveModulePath)(file, specifier, config);
|
|
3147
|
+
if (target && !target.endsWith(".d.tilua")) queue.push(target);
|
|
2686
3148
|
}
|
|
2687
3149
|
}
|
|
2688
3150
|
const analysis = analyzeModules([...sources.values()], config);
|
|
@@ -2696,9 +3158,16 @@ async function bundle(options) {
|
|
|
2696
3158
|
const requireExpression = member(identifier(G), "require");
|
|
2697
3159
|
const importExpression = member(identifier(G), "import");
|
|
2698
3160
|
const mode = options.output ?? config?.output ?? "single";
|
|
2699
|
-
const
|
|
3161
|
+
const byPath = syntaxOf(lowerings).has("requireByPath");
|
|
2700
3162
|
const modules = /* @__PURE__ */ new Map();
|
|
2701
3163
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
3164
|
+
const shared = new SharedRuntime(names);
|
|
3165
|
+
const shaking = options.treeShaking ?? config?.treeShaking ?? false;
|
|
3166
|
+
const resolveIn = (file) => (specifier) => {
|
|
3167
|
+
const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
|
|
3168
|
+
return found !== void 0 && sources.has(found) ? found : void 0;
|
|
3169
|
+
};
|
|
3170
|
+
const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry) : void 0;
|
|
2702
3171
|
const pending = [entry];
|
|
2703
3172
|
while (pending.length) {
|
|
2704
3173
|
const file = pending.shift();
|
|
@@ -2711,10 +3180,21 @@ async function bundle(options) {
|
|
|
2711
3180
|
names,
|
|
2712
3181
|
types: analysis.types.get(file),
|
|
2713
3182
|
lowerings,
|
|
2714
|
-
target,
|
|
2715
3183
|
noLib: config?.noLib,
|
|
2716
3184
|
source: source.text,
|
|
2717
3185
|
file: shown,
|
|
3186
|
+
shared,
|
|
3187
|
+
shaken: shaken && {
|
|
3188
|
+
...shaken.modules.get(file),
|
|
3189
|
+
runs: (specifier) => {
|
|
3190
|
+
const target = resolveIn(file)(specifier);
|
|
3191
|
+
return target !== void 0 && shaken.sideEffects.has(target);
|
|
3192
|
+
},
|
|
3193
|
+
needed: (specifier) => {
|
|
3194
|
+
const target = resolveIn(file)(specifier);
|
|
3195
|
+
return target !== void 0 && shaken.required.has(target);
|
|
3196
|
+
}
|
|
3197
|
+
},
|
|
2718
3198
|
module: {
|
|
2719
3199
|
name: source.name,
|
|
2720
3200
|
require: requireExpression,
|
|
@@ -2745,6 +3225,7 @@ async function bundle(options) {
|
|
|
2745
3225
|
exportsName: lowered2.exportsName,
|
|
2746
3226
|
info: lowered2.module,
|
|
2747
3227
|
usesScriptArgs: lowered2.usesScriptArgs,
|
|
3228
|
+
shared: lowered2.shared,
|
|
2748
3229
|
imports,
|
|
2749
3230
|
dynamicImports
|
|
2750
3231
|
});
|
|
@@ -2770,16 +3251,17 @@ async function bundle(options) {
|
|
|
2770
3251
|
if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
|
|
2771
3252
|
return { files: [], modules: moduleNames, diagnostics };
|
|
2772
3253
|
}
|
|
3254
|
+
if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
|
|
2773
3255
|
const several = mode !== "single";
|
|
2774
3256
|
const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
|
|
2775
3257
|
const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
|
|
2776
3258
|
const elsewhere = /* @__PURE__ */ new Map();
|
|
2777
3259
|
for (const file of layout.slice(1)) {
|
|
2778
|
-
for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path,
|
|
3260
|
+
for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, byPath));
|
|
2779
3261
|
}
|
|
2780
3262
|
const program2 = (0, import_luau_parser2.parse)(runtime(G, {
|
|
2781
3263
|
several,
|
|
2782
|
-
|
|
3264
|
+
byPath,
|
|
2783
3265
|
files: elsewhere,
|
|
2784
3266
|
imports: lowered.some((m) => m.dynamicImports.size > 0)
|
|
2785
3267
|
}));
|
|
@@ -2794,6 +3276,8 @@ async function bundle(options) {
|
|
|
2794
3276
|
for (const module2 of layout[0].modules) {
|
|
2795
3277
|
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
|
|
2796
3278
|
}
|
|
3279
|
+
const helpers = shared.chunk(new Set(layout[0].modules.flatMap((m) => m.shared)));
|
|
3280
|
+
program2.body.statements.unshift(...helpers.declare);
|
|
2797
3281
|
const ok = names.fresh("ok");
|
|
2798
3282
|
const result2 = names.fresh("result");
|
|
2799
3283
|
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
@@ -2815,9 +3299,11 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
|
2815
3299
|
if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
|
|
2816
3300
|
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
|
|
2817
3301
|
}
|
|
3302
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
|
|
2818
3303
|
const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
|
|
2819
3304
|
for (const file of layout.slice(1)) {
|
|
2820
|
-
const
|
|
3305
|
+
const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
|
|
3306
|
+
const body = [...fileHelpers.declare];
|
|
2821
3307
|
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
2822
3308
|
for (const module2 of file.modules) {
|
|
2823
3309
|
body.push(assign(
|
|
@@ -2827,7 +3313,7 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
|
2827
3313
|
}
|
|
2828
3314
|
const chunk = program([returns([functionExpression(functionBody([G], body))])]);
|
|
2829
3315
|
const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
|
|
2830
|
-
body.push(...mapRegistration(G, file, lineMapSource(lines)));
|
|
3316
|
+
body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
|
|
2831
3317
|
files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
|
|
2832
3318
|
}
|
|
2833
3319
|
return { code: files[0].code, files, modules: moduleNames, diagnostics };
|
|
@@ -2889,9 +3375,9 @@ function layoutOf(mode, entry, modules, outFile) {
|
|
|
2889
3375
|
}
|
|
2890
3376
|
return files;
|
|
2891
3377
|
}
|
|
2892
|
-
function loadPath(entryPath, file,
|
|
3378
|
+
function loadPath(entryPath, file, byPath) {
|
|
2893
3379
|
const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
|
|
2894
|
-
if (
|
|
3380
|
+
if (!byPath) return path;
|
|
2895
3381
|
const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
|
|
2896
3382
|
return bare.startsWith("../") ? bare : `./${bare}`;
|
|
2897
3383
|
}
|
|
@@ -2904,8 +3390,8 @@ function mapRegistration(G, file, map) {
|
|
|
2904
3390
|
...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
|
|
2905
3391
|
].join("\n")).body.statements;
|
|
2906
3392
|
}
|
|
2907
|
-
function loader(G,
|
|
2908
|
-
if (
|
|
3393
|
+
function loader(G, byPath) {
|
|
3394
|
+
if (!byPath) {
|
|
2909
3395
|
return `
|
|
2910
3396
|
-- From beside this file, wherever that is: its own path says where.
|
|
2911
3397
|
local folder = ""
|
|
@@ -3113,7 +3599,7 @@ ${several ? `
|
|
|
3113
3599
|
if ${G}.loaded[file] then
|
|
3114
3600
|
return
|
|
3115
3601
|
end
|
|
3116
|
-
${G}.loaded[file] = true${loader(G, options.
|
|
3602
|
+
${G}.loaded[file] = true${loader(G, options.byPath)}
|
|
3117
3603
|
end,` : ""}${options.imports ? `
|
|
3118
3604
|
-- \`import("./x")\`: the module's exports, asked for where the code runs
|
|
3119
3605
|
-- rather than before its module does. \`require\` does the rest \u2014 and
|
|
@@ -3382,7 +3868,6 @@ async function compile(source, config) {
|
|
|
3382
3868
|
types: (0, import_parser8.analyzeTypes)(program2, scopes, { libs, noLib, diagnostics: false }),
|
|
3383
3869
|
noLib,
|
|
3384
3870
|
lowerings,
|
|
3385
|
-
target: resolved?.target,
|
|
3386
3871
|
source
|
|
3387
3872
|
});
|
|
3388
3873
|
diagnostics.push(...lowered.diagnostics);
|