@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
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;
|
|
@@ -1660,7 +1852,7 @@ end
|
|
|
1660
1852
|
isMethod,
|
|
1661
1853
|
func,
|
|
1662
1854
|
// `@native` and the like are Luau's.
|
|
1663
|
-
attributes: this.
|
|
1855
|
+
attributes: this.syntax.has("attributes") ? node.attributes : void 0,
|
|
1664
1856
|
...spanOf(node)
|
|
1665
1857
|
};
|
|
1666
1858
|
}
|
|
@@ -1900,7 +2092,7 @@ end
|
|
|
1900
2092
|
if (join === "concat") return binary("..", left, right);
|
|
1901
2093
|
if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
1902
2094
|
}
|
|
1903
|
-
if (this.
|
|
2095
|
+
if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
|
|
1904
2096
|
return binary(node.operator, left, right);
|
|
1905
2097
|
}
|
|
1906
2098
|
case "UnaryExpression":
|
|
@@ -1934,7 +2126,7 @@ end
|
|
|
1934
2126
|
case "AsConstExpression":
|
|
1935
2127
|
return this.expression(node.expression);
|
|
1936
2128
|
case "IfElseExpression":
|
|
1937
|
-
if (this.
|
|
2129
|
+
if (!this.syntax.has("ifExpression")) return this.ifElse51(node);
|
|
1938
2130
|
return {
|
|
1939
2131
|
type: "IfElseExpression",
|
|
1940
2132
|
clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
|
|
@@ -1981,7 +2173,7 @@ end
|
|
|
1981
2173
|
const last2 = list.length - 1;
|
|
1982
2174
|
return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
|
|
1983
2175
|
}
|
|
1984
|
-
const unpack = (value) => call(this.
|
|
2176
|
+
const unpack = (value) => call(this.helper("unpack"), [value]);
|
|
1985
2177
|
const last = list[list.length - 1];
|
|
1986
2178
|
if (spreads.length === 1 && last.type === "SpreadElement") {
|
|
1987
2179
|
return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
|
|
@@ -2095,7 +2287,7 @@ end
|
|
|
2095
2287
|
const segments = this.segments(chain);
|
|
2096
2288
|
const base = this.expression(chain.base);
|
|
2097
2289
|
const last = chain.links[chain.links.length - 1];
|
|
2098
|
-
if (
|
|
2290
|
+
if (this.syntax.has("ifExpression") && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
|
|
2099
2291
|
return {
|
|
2100
2292
|
type: "IfElseExpression",
|
|
2101
2293
|
clauses: [{ condition: binary("==", base, nil()), body: nil() }],
|
|
@@ -2122,7 +2314,7 @@ end
|
|
|
2122
2314
|
nullish(node) {
|
|
2123
2315
|
const left = this.keptToOne(this.expression(node.left));
|
|
2124
2316
|
const right = this.keptToOne(this.expression(node.right));
|
|
2125
|
-
if (
|
|
2317
|
+
if (this.syntax.has("ifExpression") && left.type === "Identifier") {
|
|
2126
2318
|
return {
|
|
2127
2319
|
type: "IfElseExpression",
|
|
2128
2320
|
clauses: [{ condition: binary("~=", left, nil()), body: left }],
|
|
@@ -2209,14 +2401,19 @@ end
|
|
|
2209
2401
|
let target;
|
|
2210
2402
|
if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
|
|
2211
2403
|
const object2 = this.expression(node.target.object);
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
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));
|
|
2216
2407
|
} else {
|
|
2217
|
-
const
|
|
2218
|
-
before.push(local([
|
|
2219
|
-
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
|
+
}
|
|
2220
2417
|
}
|
|
2221
2418
|
} else {
|
|
2222
2419
|
target = this.expression(node.target);
|
|
@@ -2237,7 +2434,7 @@ end
|
|
|
2237
2434
|
* done, and breaks for real there. A body with no `continue` is left
|
|
2238
2435
|
* exactly as it was. */
|
|
2239
2436
|
loopBody51(body) {
|
|
2240
|
-
if (
|
|
2437
|
+
if (this.syntax.has("continue") || !hasOwnJump(body.statements, "ContinueStatement")) return body;
|
|
2241
2438
|
const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
|
|
2242
2439
|
const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
|
|
2243
2440
|
if (!flag) return block([once]);
|
|
@@ -2289,7 +2486,7 @@ end
|
|
|
2289
2486
|
* underscores and `0b1010` becomes the decimal it means. A hex literal
|
|
2290
2487
|
* keeps its base — 5.1 reads those. */
|
|
2291
2488
|
numberRaw(node) {
|
|
2292
|
-
if (
|
|
2489
|
+
if (this.syntax.has("numberLiterals")) return node.raw;
|
|
2293
2490
|
if (/^0[bB]/.test(node.raw)) return String(node.value);
|
|
2294
2491
|
return node.raw.replace(/_/g, "");
|
|
2295
2492
|
}
|
|
@@ -2542,10 +2739,10 @@ function declaredNames(statements) {
|
|
|
2542
2739
|
}
|
|
2543
2740
|
function mentions(expression, name) {
|
|
2544
2741
|
let found = false;
|
|
2545
|
-
const
|
|
2742
|
+
const walk2 = (value) => {
|
|
2546
2743
|
if (found || !value || typeof value !== "object") return;
|
|
2547
2744
|
if (Array.isArray(value)) {
|
|
2548
|
-
for (const item of value)
|
|
2745
|
+
for (const item of value) walk2(item);
|
|
2549
2746
|
return;
|
|
2550
2747
|
}
|
|
2551
2748
|
const node = value;
|
|
@@ -2554,10 +2751,10 @@ function mentions(expression, name) {
|
|
|
2554
2751
|
return;
|
|
2555
2752
|
}
|
|
2556
2753
|
for (const [key, child] of Object.entries(value)) {
|
|
2557
|
-
if (key !== "line" && key !== "column")
|
|
2754
|
+
if (key !== "line" && key !== "column") walk2(child);
|
|
2558
2755
|
}
|
|
2559
2756
|
};
|
|
2560
|
-
|
|
2757
|
+
walk2(expression);
|
|
2561
2758
|
return found;
|
|
2562
2759
|
}
|
|
2563
2760
|
function allNodes(root) {
|
|
@@ -2593,6 +2790,269 @@ var CLASS_METAMETHODS = [
|
|
|
2593
2790
|
"__iter"
|
|
2594
2791
|
];
|
|
2595
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
|
+
|
|
2596
3056
|
// src/linemap.ts
|
|
2597
3057
|
var import_parser6 = require("@tilua/parser");
|
|
2598
3058
|
function statementsOf(node) {
|
|
@@ -2683,8 +3143,8 @@ async function bundle(options) {
|
|
|
2683
3143
|
sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
|
|
2684
3144
|
const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
|
|
2685
3145
|
for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
|
|
2686
|
-
const
|
|
2687
|
-
if (
|
|
3146
|
+
const target = (0, import_parser7.resolveModulePath)(file, specifier, config);
|
|
3147
|
+
if (target && !target.endsWith(".d.tilua")) queue.push(target);
|
|
2688
3148
|
}
|
|
2689
3149
|
}
|
|
2690
3150
|
const analysis = analyzeModules([...sources.values()], config);
|
|
@@ -2698,9 +3158,16 @@ async function bundle(options) {
|
|
|
2698
3158
|
const requireExpression = member(identifier(G), "require");
|
|
2699
3159
|
const importExpression = member(identifier(G), "import");
|
|
2700
3160
|
const mode = options.output ?? config?.output ?? "single";
|
|
2701
|
-
const
|
|
3161
|
+
const byPath = syntaxOf(lowerings).has("requireByPath");
|
|
2702
3162
|
const modules = /* @__PURE__ */ new Map();
|
|
2703
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;
|
|
2704
3171
|
const pending = [entry];
|
|
2705
3172
|
while (pending.length) {
|
|
2706
3173
|
const file = pending.shift();
|
|
@@ -2713,10 +3180,21 @@ async function bundle(options) {
|
|
|
2713
3180
|
names,
|
|
2714
3181
|
types: analysis.types.get(file),
|
|
2715
3182
|
lowerings,
|
|
2716
|
-
target,
|
|
2717
3183
|
noLib: config?.noLib,
|
|
2718
3184
|
source: source.text,
|
|
2719
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
|
+
},
|
|
2720
3198
|
module: {
|
|
2721
3199
|
name: source.name,
|
|
2722
3200
|
require: requireExpression,
|
|
@@ -2747,6 +3225,7 @@ async function bundle(options) {
|
|
|
2747
3225
|
exportsName: lowered2.exportsName,
|
|
2748
3226
|
info: lowered2.module,
|
|
2749
3227
|
usesScriptArgs: lowered2.usesScriptArgs,
|
|
3228
|
+
shared: lowered2.shared,
|
|
2750
3229
|
imports,
|
|
2751
3230
|
dynamicImports
|
|
2752
3231
|
});
|
|
@@ -2772,16 +3251,17 @@ async function bundle(options) {
|
|
|
2772
3251
|
if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
|
|
2773
3252
|
return { files: [], modules: moduleNames, diagnostics };
|
|
2774
3253
|
}
|
|
3254
|
+
if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
|
|
2775
3255
|
const several = mode !== "single";
|
|
2776
3256
|
const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
|
|
2777
3257
|
const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
|
|
2778
3258
|
const elsewhere = /* @__PURE__ */ new Map();
|
|
2779
3259
|
for (const file of layout.slice(1)) {
|
|
2780
|
-
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));
|
|
2781
3261
|
}
|
|
2782
3262
|
const program2 = (0, import_luau_parser2.parse)(runtime(G, {
|
|
2783
3263
|
several,
|
|
2784
|
-
|
|
3264
|
+
byPath,
|
|
2785
3265
|
files: elsewhere,
|
|
2786
3266
|
imports: lowered.some((m) => m.dynamicImports.size > 0)
|
|
2787
3267
|
}));
|
|
@@ -2796,6 +3276,8 @@ async function bundle(options) {
|
|
|
2796
3276
|
for (const module2 of layout[0].modules) {
|
|
2797
3277
|
modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
|
|
2798
3278
|
}
|
|
3279
|
+
const helpers = shared.chunk(new Set(layout[0].modules.flatMap((m) => m.shared)));
|
|
3280
|
+
program2.body.statements.unshift(...helpers.declare);
|
|
2799
3281
|
const ok = names.fresh("ok");
|
|
2800
3282
|
const result2 = names.fresh("result");
|
|
2801
3283
|
const entryStatements = (0, import_luau_parser2.parse)(`
|
|
@@ -2817,9 +3299,11 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
|
2817
3299
|
if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
|
|
2818
3300
|
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
|
|
2819
3301
|
}
|
|
3302
|
+
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
|
|
2820
3303
|
const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
|
|
2821
3304
|
for (const file of layout.slice(1)) {
|
|
2822
|
-
const
|
|
3305
|
+
const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
|
|
3306
|
+
const body = [...fileHelpers.declare];
|
|
2823
3307
|
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
2824
3308
|
for (const module2 of file.modules) {
|
|
2825
3309
|
body.push(assign(
|
|
@@ -2829,7 +3313,7 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
|
|
|
2829
3313
|
}
|
|
2830
3314
|
const chunk = program([returns([functionExpression(functionBody([G], body))])]);
|
|
2831
3315
|
const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
|
|
2832
|
-
body.push(...mapRegistration(G, file, lineMapSource(lines)));
|
|
3316
|
+
body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
|
|
2833
3317
|
files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
|
|
2834
3318
|
}
|
|
2835
3319
|
return { code: files[0].code, files, modules: moduleNames, diagnostics };
|
|
@@ -2891,9 +3375,9 @@ function layoutOf(mode, entry, modules, outFile) {
|
|
|
2891
3375
|
}
|
|
2892
3376
|
return files;
|
|
2893
3377
|
}
|
|
2894
|
-
function loadPath(entryPath, file,
|
|
3378
|
+
function loadPath(entryPath, file, byPath) {
|
|
2895
3379
|
const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
|
|
2896
|
-
if (
|
|
3380
|
+
if (!byPath) return path;
|
|
2897
3381
|
const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
|
|
2898
3382
|
return bare.startsWith("../") ? bare : `./${bare}`;
|
|
2899
3383
|
}
|
|
@@ -2906,8 +3390,8 @@ function mapRegistration(G, file, map) {
|
|
|
2906
3390
|
...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
|
|
2907
3391
|
].join("\n")).body.statements;
|
|
2908
3392
|
}
|
|
2909
|
-
function loader(G,
|
|
2910
|
-
if (
|
|
3393
|
+
function loader(G, byPath) {
|
|
3394
|
+
if (!byPath) {
|
|
2911
3395
|
return `
|
|
2912
3396
|
-- From beside this file, wherever that is: its own path says where.
|
|
2913
3397
|
local folder = ""
|
|
@@ -3115,7 +3599,7 @@ ${several ? `
|
|
|
3115
3599
|
if ${G}.loaded[file] then
|
|
3116
3600
|
return
|
|
3117
3601
|
end
|
|
3118
|
-
${G}.loaded[file] = true${loader(G, options.
|
|
3602
|
+
${G}.loaded[file] = true${loader(G, options.byPath)}
|
|
3119
3603
|
end,` : ""}${options.imports ? `
|
|
3120
3604
|
-- \`import("./x")\`: the module's exports, asked for where the code runs
|
|
3121
3605
|
-- rather than before its module does. \`require\` does the rest \u2014 and
|
|
@@ -3384,7 +3868,6 @@ async function compile(source, config) {
|
|
|
3384
3868
|
types: (0, import_parser8.analyzeTypes)(program2, scopes, { libs, noLib, diagnostics: false }),
|
|
3385
3869
|
noLib,
|
|
3386
3870
|
lowerings,
|
|
3387
|
-
target: resolved?.target,
|
|
3388
3871
|
source
|
|
3389
3872
|
});
|
|
3390
3873
|
diagnostics.push(...lowered.diagnostics);
|