storm-lua-minify 0.1.3 → 0.3.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.
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAvailable = isAvailable;
4
+ exports.generateCandidate = generateCandidate;
5
+ exports.assignRenames = assignRenames;
6
+ const ast2lua_1 = require("./ast2lua");
7
+ function isAvailable(id, reserved) {
8
+ return id !== "self" && !(0, ast2lua_1.isKeyword)(id) && !reserved.has(id);
9
+ }
10
+ // 0始まりのカウンタから短縮名候補を生成する(バイジェクティブ基数記数法)。
11
+ // 通常の位取り記数法と違い同じ文字列を2つのカウンタ値が指すことがないため、
12
+ // カウンタを増やし続けるだけで重複なく識別子候補を列挙できる。
13
+ function generateCandidate(counter) {
14
+ const l = ast2lua_1.IDENTIFIER_PARTS.length;
15
+ let num = counter + 1;
16
+ let id = "";
17
+ while (num > 0) {
18
+ const rem = (num - 1) % l;
19
+ id = ast2lua_1.IDENTIFIER_PARTS[rem] + id;
20
+ num = Math.floor((num - 1) / l);
21
+ }
22
+ return id;
23
+ }
24
+ /**
25
+ * スコープ木のDFSでシンボルごとにスロット番号を割り当てる。
26
+ * `active`は祖先スコープ(自分を含む)で既に使われているスロットの集合。
27
+ * 兄弟スコープには同じ`active`のコピーが渡されるため、互いの割当は影響しない。
28
+ */
29
+ function assignSlots(scope, active) {
30
+ const slotOf = new Map();
31
+ const used = new Set(active);
32
+ scope.symbols.forEach((symbol) => {
33
+ let slot = 0;
34
+ while (used.has(slot)) {
35
+ slot++;
36
+ }
37
+ slotOf.set(symbol, slot);
38
+ used.add(slot);
39
+ });
40
+ scope.children.forEach((child) => {
41
+ assignSlots(child, used).forEach((slot, symbol) => {
42
+ slotOf.set(symbol, slot);
43
+ });
44
+ });
45
+ return slotOf;
46
+ }
47
+ function assignRenames(resolveResult, reserved,
48
+ // #8a: プログラム全体を横断して決定されたグローバル識別子の短縮名。
49
+ // 全モジュールに対して同じマップを渡すことで、モジュールをまたいで
50
+ // 共有される1つのランタイム束縛に一貫した短縮名を割り当てられる。
51
+ globalRenames) {
52
+ const slotOf = assignSlots(resolveResult.chunkScope, new Set());
53
+ // スロットの通算参照回数(宣言自体も1回として数える)を集計する。
54
+ const weightOfSlot = new Map();
55
+ resolveResult.symbols.forEach((symbol) => {
56
+ const slot = slotOf.get(symbol);
57
+ if (slot === undefined) {
58
+ return;
59
+ }
60
+ const weight = symbol.references.length + 1;
61
+ weightOfSlot.set(slot, (weightOfSlot.get(slot) ?? 0) + weight);
62
+ });
63
+ const orderedSlots = [...weightOfSlot.keys()].sort((a, b) => (weightOfSlot.get(b) ?? 0) - (weightOfSlot.get(a) ?? 0));
64
+ const nameOfSlot = new Map();
65
+ let counter = 0;
66
+ orderedSlots.forEach((slot) => {
67
+ let candidate;
68
+ do {
69
+ candidate = generateCandidate(counter++);
70
+ } while (!isAvailable(candidate, reserved));
71
+ nameOfSlot.set(slot, candidate);
72
+ });
73
+ const nameOfSymbol = new Map();
74
+ slotOf.forEach((slot, symbol) => {
75
+ const name = nameOfSlot.get(slot);
76
+ if (name !== undefined) {
77
+ nameOfSymbol.set(symbol, name);
78
+ }
79
+ });
80
+ return {
81
+ nameOf: (identifier) => {
82
+ // メソッド定義の暗黙のselfパラメータは慣習的な名前のため常に維持する
83
+ // (呼び出し側から見える名前ではないため短縮しても安全ではあるが、
84
+ // 可読性のために元の名前のままにする)。
85
+ if (identifier.name === "self") {
86
+ return undefined;
87
+ }
88
+ const symbol = resolveResult.symbolOf(identifier);
89
+ if (symbol) {
90
+ return nameOfSymbol.get(symbol);
91
+ }
92
+ // フィールド名(`.foo`)はここに来るが、これらはisGlobalReferenceが
93
+ // falseになるため名前文字列がたまたま一致しても誤ってリネームしない。
94
+ if (globalRenames && resolveResult.isGlobalReference(identifier)) {
95
+ return globalRenames.get(identifier.name);
96
+ }
97
+ return undefined;
98
+ },
99
+ usedNames: new Set(nameOfSlot.values()),
100
+ };
101
+ }
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveScopes = resolveScopes;
4
+ function resolveScopes(chunk) {
5
+ let nextSymbolId = 0;
6
+ const allSymbols = [];
7
+ const globals = new Map();
8
+ const identifierSymbols = new WeakMap();
9
+ const globalReferenceNodes = new WeakSet();
10
+ function createScope(kind, parent) {
11
+ const scope = {
12
+ kind,
13
+ parent,
14
+ children: [],
15
+ symbols: [],
16
+ bindings: new Map(),
17
+ labels: new Map(),
18
+ };
19
+ parent?.children.push(scope);
20
+ return scope;
21
+ }
22
+ function declare(scope, node, kind) {
23
+ const symbol = {
24
+ id: nextSymbolId++,
25
+ name: node.name,
26
+ kind,
27
+ scope,
28
+ declaration: node,
29
+ references: [],
30
+ };
31
+ // 同名の再宣言はスコープ内の以後の参照から見た束縛を上書きする(Luaの通常のシャドーイング)
32
+ scope.symbols.push(symbol);
33
+ scope.bindings.set(node.name, symbol);
34
+ allSymbols.push(symbol);
35
+ identifierSymbols.set(node, symbol);
36
+ return symbol;
37
+ }
38
+ function declareLabel(scope, node) {
39
+ const symbol = {
40
+ id: nextSymbolId++,
41
+ name: node.name,
42
+ kind: "label",
43
+ scope,
44
+ declaration: node,
45
+ references: [],
46
+ };
47
+ scope.symbols.push(symbol);
48
+ scope.labels.set(node.name, symbol);
49
+ allSymbols.push(symbol);
50
+ identifierSymbols.set(node, symbol);
51
+ return symbol;
52
+ }
53
+ function lookupBinding(scope, name) {
54
+ for (let s = scope; s; s = s.parent) {
55
+ const found = s.bindings.get(name);
56
+ if (found) {
57
+ return found;
58
+ }
59
+ }
60
+ return undefined;
61
+ }
62
+ function lookupLabel(scope, name) {
63
+ for (let s = scope; s; s = s.parent) {
64
+ const found = s.labels.get(name);
65
+ if (found) {
66
+ return found;
67
+ }
68
+ }
69
+ return undefined;
70
+ }
71
+ function reference(scope, node, isWrite = false) {
72
+ const symbol = lookupBinding(scope, node.name);
73
+ if (symbol) {
74
+ symbol.references.push(node);
75
+ identifierSymbols.set(node, symbol);
76
+ return;
77
+ }
78
+ let binding = globals.get(node.name);
79
+ if (!binding) {
80
+ binding = { name: node.name, references: [], writes: [] };
81
+ globals.set(node.name, binding);
82
+ }
83
+ binding.references.push(node);
84
+ if (isWrite) {
85
+ binding.writes.push(node);
86
+ }
87
+ globalReferenceNodes.add(node);
88
+ }
89
+ // ラベルはブロック内での宣言位置に関わらずブロック全体から参照できる
90
+ // (前方goto)ため、通常の宣言文と違い先読みで登録する。
91
+ function hoistLabels(body, scope) {
92
+ body.forEach((statement) => {
93
+ if (statement.type === "LabelStatement") {
94
+ declareLabel(scope, statement.label);
95
+ }
96
+ });
97
+ }
98
+ function resolveBlock(body, scope) {
99
+ hoistLabels(body, scope);
100
+ body.forEach((statement) => {
101
+ resolveStatement(statement, scope);
102
+ });
103
+ }
104
+ function resolveStatement(statement, scope) {
105
+ switch (statement.type) {
106
+ case "LocalStatement":
107
+ // 初期化式は新しいローカルが宣言される前の束縛で解決する
108
+ // (`local x = x` の右辺は外側のxを指す)
109
+ statement.init.forEach((expr) => {
110
+ resolveExpression(expr, scope);
111
+ });
112
+ statement.variables.forEach((v) => declare(scope, v, "local"));
113
+ return;
114
+ case "AssignmentStatement":
115
+ statement.init.forEach((expr) => {
116
+ resolveExpression(expr, scope);
117
+ });
118
+ statement.variables.forEach((v) => {
119
+ if (v.type === "Identifier") {
120
+ reference(scope, v, true);
121
+ }
122
+ else {
123
+ resolveExpression(v, scope);
124
+ }
125
+ });
126
+ return;
127
+ case "CallStatement":
128
+ resolveExpression(statement.expression, scope);
129
+ return;
130
+ case "DoStatement": {
131
+ const inner = createScope("block", scope);
132
+ resolveBlock(statement.body, inner);
133
+ return;
134
+ }
135
+ case "WhileStatement": {
136
+ resolveExpression(statement.condition, scope);
137
+ const inner = createScope("block", scope);
138
+ resolveBlock(statement.body, inner);
139
+ return;
140
+ }
141
+ case "RepeatStatement": {
142
+ // `until`の条件式は本体で宣言されたローカルを参照できる
143
+ const inner = createScope("block", scope);
144
+ resolveBlock(statement.body, inner);
145
+ resolveExpression(statement.condition, inner);
146
+ return;
147
+ }
148
+ case "IfStatement":
149
+ statement.clauses.forEach((clause) => {
150
+ if (clause.type !== "ElseClause") {
151
+ resolveExpression(clause.condition, scope);
152
+ }
153
+ const inner = createScope("block", scope);
154
+ resolveBlock(clause.body, inner);
155
+ });
156
+ return;
157
+ case "ForNumericStatement": {
158
+ resolveExpression(statement.start, scope);
159
+ resolveExpression(statement.end, scope);
160
+ if (statement.step) {
161
+ resolveExpression(statement.step, scope);
162
+ }
163
+ const inner = createScope("block", scope);
164
+ declare(inner, statement.variable, "for");
165
+ resolveBlock(statement.body, inner);
166
+ return;
167
+ }
168
+ case "ForGenericStatement": {
169
+ statement.iterators.forEach((iterator) => {
170
+ resolveExpression(iterator, scope);
171
+ });
172
+ const inner = createScope("block", scope);
173
+ statement.variables.forEach((v) => declare(inner, v, "for"));
174
+ resolveBlock(statement.body, inner);
175
+ return;
176
+ }
177
+ case "FunctionDeclaration":
178
+ resolveFunctionDeclaration(statement, scope);
179
+ return;
180
+ case "ReturnStatement":
181
+ statement.arguments.forEach((argument) => {
182
+ resolveExpression(argument, scope);
183
+ });
184
+ return;
185
+ case "BreakStatement":
186
+ return;
187
+ case "LabelStatement":
188
+ // hoistLabelsで宣言済みのため、ここでは何もしない
189
+ return;
190
+ case "GotoStatement": {
191
+ const symbol = lookupLabel(scope, statement.label.name);
192
+ if (symbol) {
193
+ symbol.references.push(statement.label);
194
+ identifierSymbols.set(statement.label, symbol);
195
+ }
196
+ return;
197
+ }
198
+ default: {
199
+ const exhaustive = statement;
200
+ throw new TypeError("Unknown statement type: `" + JSON.stringify(exhaustive) + "`");
201
+ }
202
+ }
203
+ }
204
+ function resolveFunctionDeclaration(fn, scope) {
205
+ if (fn.identifier) {
206
+ if (fn.identifier.type === "Identifier") {
207
+ if (fn.isLocal) {
208
+ // `local function`は再帰呼び出しのため、本体を解決する前に自身を宣言する
209
+ declare(scope, fn.identifier, "local");
210
+ }
211
+ else {
212
+ // 非local: 既存のローカル/グローバルへの代入として扱う(新規宣言ではない)
213
+ reference(scope, fn.identifier, true);
214
+ }
215
+ }
216
+ else {
217
+ resolveExpression(fn.identifier, scope);
218
+ }
219
+ }
220
+ const inner = createScope("function", scope);
221
+ fn.parameters.forEach((parameter) => {
222
+ if (parameter.type === "Identifier") {
223
+ declare(inner, parameter, "param");
224
+ }
225
+ });
226
+ resolveBlock(fn.body, inner);
227
+ }
228
+ function resolveExpression(expr, scope) {
229
+ switch (expr.type) {
230
+ case "Identifier":
231
+ reference(scope, expr);
232
+ return;
233
+ case "StringLiteral":
234
+ case "NumericLiteral":
235
+ case "BooleanLiteral":
236
+ case "NilLiteral":
237
+ case "VarargLiteral":
238
+ return;
239
+ case "LogicalExpression":
240
+ case "BinaryExpression":
241
+ resolveExpression(expr.left, scope);
242
+ resolveExpression(expr.right, scope);
243
+ return;
244
+ case "UnaryExpression":
245
+ resolveExpression(expr.argument, scope);
246
+ return;
247
+ case "CallExpression":
248
+ resolveExpression(expr.base, scope);
249
+ expr.arguments.forEach((argument) => {
250
+ resolveExpression(argument, scope);
251
+ });
252
+ return;
253
+ case "TableCallExpression":
254
+ resolveExpression(expr.base, scope);
255
+ resolveExpression(expr.arguments, scope);
256
+ return;
257
+ case "StringCallExpression":
258
+ resolveExpression(expr.base, scope);
259
+ resolveExpression(expr.argument, scope);
260
+ return;
261
+ case "IndexExpression":
262
+ resolveExpression(expr.base, scope);
263
+ resolveExpression(expr.index, scope);
264
+ return;
265
+ case "MemberExpression":
266
+ resolveExpression(expr.base, scope);
267
+ // フィールド名(`.identifier`)は変数参照ではないため解決しない
268
+ return;
269
+ case "FunctionDeclaration": {
270
+ const inner = createScope("function", scope);
271
+ expr.parameters.forEach((parameter) => {
272
+ if (parameter.type === "Identifier") {
273
+ declare(inner, parameter, "param");
274
+ }
275
+ });
276
+ resolveBlock(expr.body, inner);
277
+ return;
278
+ }
279
+ case "TableConstructorExpression":
280
+ expr.fields.forEach((field) => {
281
+ if (field.type === "TableKey") {
282
+ resolveExpression(field.key, scope);
283
+ resolveExpression(field.value, scope);
284
+ }
285
+ else if (field.type === "TableValue") {
286
+ resolveExpression(field.value, scope);
287
+ }
288
+ else {
289
+ // TableKeyString: キー名(`{ key = value }`のkey)は変数参照ではない
290
+ resolveExpression(field.value, scope);
291
+ }
292
+ });
293
+ return;
294
+ default: {
295
+ const exhaustive = expr;
296
+ throw new TypeError("Unknown expression type: `" + JSON.stringify(exhaustive) + "`");
297
+ }
298
+ }
299
+ }
300
+ const chunkScope = createScope("chunk", null);
301
+ resolveBlock(chunk.body, chunkScope);
302
+ return {
303
+ chunkScope,
304
+ symbols: allSymbols,
305
+ globals,
306
+ symbolOf: (identifier) => identifierSymbols.get(identifier),
307
+ isGlobalReference: (identifier) => globalReferenceNodes.has(identifier),
308
+ };
309
+ }