storm-lua-minify 0.3.0 → 0.9.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 +119 -41
- package/dist/aggregateSpecialization.js +406 -0
- package/dist/ast2lua.js +156 -68
- package/dist/astWalk.js +162 -0
- package/dist/callGraph.js +372 -0
- package/dist/cli.js +53 -58
- package/dist/cliOptions.js +36 -0
- package/dist/cliProgress.js +87 -0
- package/dist/config.js +73 -0
- package/dist/constantFold.js +798 -0
- package/dist/controlFlow.js +266 -0
- package/dist/functionRewrites.js +580 -0
- package/dist/generatedAst.js +108 -0
- package/dist/generatedNode.js +23 -0
- package/dist/interproceduralAnalysis.js +842 -0
- package/dist/interproceduralConstants.js +120 -0
- package/dist/luaString.js +157 -0
- package/dist/minifier.js +1178 -44
- package/dist/optimizerAnalysis.js +43 -0
- package/dist/optimizerDiagnostics.js +65 -0
- package/dist/optimizerFacts.js +529 -0
- package/dist/optimizerPass.js +96 -0
- package/dist/optimizerTransaction.js +56 -0
- package/dist/optimizerValueDomain.js +180 -0
- package/dist/options.js +233 -0
- package/dist/progress.js +2 -0
- package/dist/removeUnused.js +145 -0
- package/dist/renamer.js +223 -54
- package/dist/resolver.js +28 -11
- package/dist/runtimeEnvironment.js +105 -0
- package/dist/sourceMetadata.js +314 -0
- package/dist/statementDataflow.js +259 -0
- package/dist/statementScheduler.js +595 -0
- package/dist/symbolLiveness.js +92 -0
- package/dist/tableEffects.js +356 -0
- package/dist/transform.js +10 -371
- package/dist/valueFlow.js +409 -0
- package/dist/wholeProgramExports.js +646 -0
- package/dist/wholeProgramFieldRenames.js +583 -0
- package/dist/wholeProgramFields.js +672 -0
- package/dist/wholeProgramObjects.js +783 -0
- package/package.json +11 -2
- package/dist/index.js +0 -27
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.propagateInterproceduralConstants = propagateInterproceduralConstants;
|
|
4
|
+
/**
|
|
5
|
+
* Replace a direct, pure, single-return call with its proven scalar result.
|
|
6
|
+
*
|
|
7
|
+
* This is intentionally narrower than #76 inlining. It accepts only a one-statement return body,
|
|
8
|
+
* no actual arguments, a non-recursive target, and a literal no longer than the shortest possible
|
|
9
|
+
* renamed `a()` call. Thus evaluation can be removed without alpha conversion and without relying
|
|
10
|
+
* on later function DCE to make the local rewrite profitable.
|
|
11
|
+
*/
|
|
12
|
+
function propagateInterproceduralConstants(chunk, analysis) {
|
|
13
|
+
let changed = false;
|
|
14
|
+
const replacementOf = (expression) => {
|
|
15
|
+
if (expression.type !== "CallExpression" ||
|
|
16
|
+
expression.arguments.length !== 0 ||
|
|
17
|
+
expression.base.type !== "Identifier")
|
|
18
|
+
return undefined;
|
|
19
|
+
const call = analysis.callGraph.callSiteOf(expression);
|
|
20
|
+
if (!call || call.hasUnknownTarget || call.targets.size !== 1)
|
|
21
|
+
return undefined;
|
|
22
|
+
const target = [...call.targets][0];
|
|
23
|
+
if (analysis.callGraph.sccs.some((scc) => scc.recursive && scc.functions.includes(target)) ||
|
|
24
|
+
target.declaration.body.length !== 1 ||
|
|
25
|
+
target.declaration.body[0].type !== "ReturnStatement")
|
|
26
|
+
return undefined;
|
|
27
|
+
const summary = analysis.summaryOf(target);
|
|
28
|
+
if (summary.effects.length > 0 ||
|
|
29
|
+
summary.escapes.length > 0 ||
|
|
30
|
+
summary.externalEffects.length > 0 ||
|
|
31
|
+
summary.mayError ||
|
|
32
|
+
summary.mayInvokeMetamethod ||
|
|
33
|
+
summary.returns.prefix.length !== 1 ||
|
|
34
|
+
summary.returns.tail.kind !== "none")
|
|
35
|
+
return undefined;
|
|
36
|
+
const value = summary.returns.prefix[0];
|
|
37
|
+
if (value.unknownReasons.length > 0 || value.atoms.length !== 1)
|
|
38
|
+
return undefined;
|
|
39
|
+
const replacement = literalOf(value.atoms[0], expression);
|
|
40
|
+
if (!replacement || printedLength(replacement) > 3)
|
|
41
|
+
return undefined;
|
|
42
|
+
return replacement;
|
|
43
|
+
};
|
|
44
|
+
const rewriteBlock = (body) => {
|
|
45
|
+
body.forEach((statement) => {
|
|
46
|
+
if ((statement.type === "LocalStatement" ||
|
|
47
|
+
statement.type === "AssignmentStatement") &&
|
|
48
|
+
statement.variables.length === 1 &&
|
|
49
|
+
statement.init.length === 1) {
|
|
50
|
+
const replacement = replacementOf(statement.init[0]);
|
|
51
|
+
if (replacement) {
|
|
52
|
+
statement.init[0] = replacement;
|
|
53
|
+
changed = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
switch (statement.type) {
|
|
57
|
+
case "DoStatement":
|
|
58
|
+
case "WhileStatement":
|
|
59
|
+
case "RepeatStatement":
|
|
60
|
+
case "FunctionDeclaration":
|
|
61
|
+
case "ForNumericStatement":
|
|
62
|
+
case "ForGenericStatement":
|
|
63
|
+
rewriteBlock(statement.body);
|
|
64
|
+
return;
|
|
65
|
+
case "IfStatement":
|
|
66
|
+
statement.clauses.forEach((clause) => {
|
|
67
|
+
rewriteBlock(clause.body);
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
default:
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
rewriteBlock(chunk.body);
|
|
76
|
+
return changed;
|
|
77
|
+
}
|
|
78
|
+
function literalOf(atom, source) {
|
|
79
|
+
const metadata = {
|
|
80
|
+
...(source.loc ? { loc: source.loc } : {}),
|
|
81
|
+
...("range" in source
|
|
82
|
+
? { range: source.range }
|
|
83
|
+
: {}),
|
|
84
|
+
};
|
|
85
|
+
switch (atom.kind) {
|
|
86
|
+
case "nil":
|
|
87
|
+
return { type: "NilLiteral", value: null, raw: "nil", ...metadata };
|
|
88
|
+
case "boolean":
|
|
89
|
+
return {
|
|
90
|
+
type: "BooleanLiteral",
|
|
91
|
+
value: atom.value,
|
|
92
|
+
raw: atom.value ? "true" : "false",
|
|
93
|
+
...metadata,
|
|
94
|
+
};
|
|
95
|
+
case "number":
|
|
96
|
+
return {
|
|
97
|
+
type: "NumericLiteral",
|
|
98
|
+
value: Number(atom.raw),
|
|
99
|
+
raw: atom.raw,
|
|
100
|
+
...metadata,
|
|
101
|
+
};
|
|
102
|
+
case "string": {
|
|
103
|
+
const raw = JSON.stringify(atom.value);
|
|
104
|
+
return { type: "StringLiteral", value: atom.value, raw, ...metadata };
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function printedLength(expression) {
|
|
111
|
+
switch (expression.type) {
|
|
112
|
+
case "NilLiteral":
|
|
113
|
+
case "BooleanLiteral":
|
|
114
|
+
case "NumericLiteral":
|
|
115
|
+
case "StringLiteral":
|
|
116
|
+
return expression.raw.length;
|
|
117
|
+
default:
|
|
118
|
+
return Infinity;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.luaByteStringOfText = luaByteStringOfText;
|
|
4
|
+
exports.decodeLuaStringLiteral = decodeLuaStringLiteral;
|
|
5
|
+
exports.luaByteStringKey = luaByteStringKey;
|
|
6
|
+
exports.compareLuaByteStrings = compareLuaByteStrings;
|
|
7
|
+
exports.concatLuaByteStrings = concatLuaByteStrings;
|
|
8
|
+
exports.encodeLuaByteString = encodeLuaByteString;
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
function luaByteStringOfText(text) {
|
|
11
|
+
return { bytes: encoder.encode(text) };
|
|
12
|
+
}
|
|
13
|
+
function decodeLuaStringLiteral(node) {
|
|
14
|
+
const raw = node.raw;
|
|
15
|
+
const long = /^\[(=*)\[/.exec(raw);
|
|
16
|
+
if (long) {
|
|
17
|
+
const close = `]${long[1]}]`;
|
|
18
|
+
if (!raw.endsWith(close)) {
|
|
19
|
+
return { ok: false, reason: "invalid-delimiter" };
|
|
20
|
+
}
|
|
21
|
+
let value = raw.slice(long[0].length, -close.length);
|
|
22
|
+
value = value.replace(/\r\n|\r/g, "\n");
|
|
23
|
+
if (value.startsWith("\n"))
|
|
24
|
+
value = value.slice(1);
|
|
25
|
+
return { ok: true, value: luaByteStringOfText(value) };
|
|
26
|
+
}
|
|
27
|
+
const quote = raw[0];
|
|
28
|
+
if (raw.length < 2 ||
|
|
29
|
+
(quote !== '"' && quote !== "'") ||
|
|
30
|
+
raw[raw.length - 1] !== quote) {
|
|
31
|
+
return { ok: false, reason: "invalid-delimiter" };
|
|
32
|
+
}
|
|
33
|
+
const bytes = [];
|
|
34
|
+
const appendText = (text) => bytes.push(...encoder.encode(text));
|
|
35
|
+
for (let index = 1; index < raw.length - 1; index++) {
|
|
36
|
+
const current = raw[index];
|
|
37
|
+
if (current !== "\\") {
|
|
38
|
+
const codePoint = raw.codePointAt(index);
|
|
39
|
+
if (codePoint === undefined) {
|
|
40
|
+
return { ok: false, reason: "invalid-code-point" };
|
|
41
|
+
}
|
|
42
|
+
appendText(String.fromCodePoint(codePoint));
|
|
43
|
+
if (codePoint > 0xffff)
|
|
44
|
+
index++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
index++;
|
|
48
|
+
if (index >= raw.length - 1) {
|
|
49
|
+
return { ok: false, reason: "invalid-escape" };
|
|
50
|
+
}
|
|
51
|
+
const escaped = raw[index];
|
|
52
|
+
const simple = SIMPLE_ESCAPES[escaped];
|
|
53
|
+
if (simple !== undefined) {
|
|
54
|
+
bytes.push(simple);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (escaped === "\n" || escaped === "\r") {
|
|
58
|
+
if (escaped === "\r" && raw[index + 1] === "\n")
|
|
59
|
+
index++;
|
|
60
|
+
bytes.push(0x0a);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (escaped === "z") {
|
|
64
|
+
while (/\s/.test(raw[index + 1] ?? ""))
|
|
65
|
+
index++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (escaped === "x") {
|
|
69
|
+
const hex = raw.slice(index + 1, index + 3);
|
|
70
|
+
if (!/^[0-9a-fA-F]{2}$/.test(hex)) {
|
|
71
|
+
return { ok: false, reason: "invalid-escape" };
|
|
72
|
+
}
|
|
73
|
+
bytes.push(Number.parseInt(hex, 16));
|
|
74
|
+
index += 2;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (escaped === "u") {
|
|
78
|
+
const match = /^\{([0-9a-fA-F]+)\}/.exec(raw.slice(index + 1));
|
|
79
|
+
if (!match)
|
|
80
|
+
return { ok: false, reason: "invalid-escape" };
|
|
81
|
+
const codePoint = Number.parseInt(match[1], 16);
|
|
82
|
+
if (codePoint > 0x10ffff ||
|
|
83
|
+
(codePoint >= 0xd800 && codePoint <= 0xdfff)) {
|
|
84
|
+
return { ok: false, reason: "invalid-code-point" };
|
|
85
|
+
}
|
|
86
|
+
appendText(String.fromCodePoint(codePoint));
|
|
87
|
+
index += match[0].length;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (/\d/.test(escaped)) {
|
|
91
|
+
let digits = escaped;
|
|
92
|
+
while (digits.length < 3 && /\d/.test(raw[index + 1] ?? "")) {
|
|
93
|
+
digits += raw[++index];
|
|
94
|
+
}
|
|
95
|
+
const byte = Number.parseInt(digits, 10);
|
|
96
|
+
if (byte > 255)
|
|
97
|
+
return { ok: false, reason: "out-of-range-byte" };
|
|
98
|
+
bytes.push(byte);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
return { ok: false, reason: "invalid-escape" };
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, value: { bytes: Uint8Array.from(bytes) } };
|
|
104
|
+
}
|
|
105
|
+
function luaByteStringKey(value) {
|
|
106
|
+
return Array.from(value.bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
107
|
+
}
|
|
108
|
+
function compareLuaByteStrings(left, right) {
|
|
109
|
+
const length = Math.min(left.bytes.length, right.bytes.length);
|
|
110
|
+
for (let index = 0; index < length; index++) {
|
|
111
|
+
if (left.bytes[index] !== right.bytes[index]) {
|
|
112
|
+
return left.bytes[index] - right.bytes[index];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return left.bytes.length - right.bytes.length;
|
|
116
|
+
}
|
|
117
|
+
function concatLuaByteStrings(left, right) {
|
|
118
|
+
const bytes = new Uint8Array(left.bytes.length + right.bytes.length);
|
|
119
|
+
bytes.set(left.bytes);
|
|
120
|
+
bytes.set(right.bytes, left.bytes.length);
|
|
121
|
+
return { bytes };
|
|
122
|
+
}
|
|
123
|
+
/** 任意のLua byte列を、再decode可能な決定論的quoted literalへ戻す。 */
|
|
124
|
+
function encodeLuaByteString(value) {
|
|
125
|
+
const encodeWith = (quote) => {
|
|
126
|
+
let raw = String.fromCharCode(quote);
|
|
127
|
+
value.bytes.forEach((byte) => {
|
|
128
|
+
if (byte === quote || byte === 0x5c) {
|
|
129
|
+
raw += `\\${String.fromCharCode(byte)}`;
|
|
130
|
+
}
|
|
131
|
+
else if (byte >= 0x20 && byte <= 0x7e) {
|
|
132
|
+
raw += String.fromCharCode(byte);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
raw += `\\x${byte.toString(16).padStart(2, "0")}`;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
return raw + String.fromCharCode(quote);
|
|
139
|
+
};
|
|
140
|
+
const doubleQuoted = encodeWith(0x22);
|
|
141
|
+
const singleQuoted = encodeWith(0x27);
|
|
142
|
+
return singleQuoted.length < doubleQuoted.length
|
|
143
|
+
? singleQuoted
|
|
144
|
+
: doubleQuoted;
|
|
145
|
+
}
|
|
146
|
+
const SIMPLE_ESCAPES = {
|
|
147
|
+
a: 0x07,
|
|
148
|
+
b: 0x08,
|
|
149
|
+
f: 0x0c,
|
|
150
|
+
n: 0x0a,
|
|
151
|
+
r: 0x0d,
|
|
152
|
+
t: 0x09,
|
|
153
|
+
v: 0x0b,
|
|
154
|
+
"\\": 0x5c,
|
|
155
|
+
'"': 0x22,
|
|
156
|
+
"'": 0x27,
|
|
157
|
+
};
|