@markw65/monkeyc-optimizer 1.0.28 → 1.0.31
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 +75 -0
- package/build/api.cjs +2901 -342
- package/build/optimizer.cjs +2775 -429
- package/build/src/api.d.ts +6 -4
- package/build/src/ast.d.ts +8 -0
- package/build/src/build.d.ts +1 -0
- package/build/src/control-flow.d.ts +21 -0
- package/build/src/function-info.d.ts +12 -0
- package/build/src/inliner.d.ts +4 -3
- package/build/src/jungles.d.ts +1 -0
- package/build/src/mc-rewrite.d.ts +3 -2
- package/build/src/optimizer-types.d.ts +188 -0
- package/build/src/optimizer.d.ts +4 -173
- package/build/src/pragma-checker.d.ts +2 -1
- package/build/src/pre.d.ts +1 -0
- package/build/src/unused-exprs.d.ts +3 -0
- package/build/src/variable-renamer.d.ts +1 -0
- package/build/src/visitor.d.ts +1 -0
- package/package.json +4 -2
package/build/api.cjs
CHANGED
|
@@ -1,8 +1,211 @@
|
|
|
1
|
-
0 && (module.exports = {collectNamespaces,findUsingForNode,formatAst,getApiMapping,hasProperty,isStateNode,sameLookupResult,traverseAst,variableDeclarationName,visitReferences});
|
|
1
|
+
0 && (module.exports = {collectNamespaces,findUsingForNode,formatAst,getApiFunctionInfo,getApiMapping,hasProperty,isLookupCandidate,isStateNode,markInvokeClassMethod,sameLookupResult,traverseAst,variableDeclarationName,visitReferences});
|
|
2
2
|
/******/ (() => { // webpackBootstrap
|
|
3
|
-
/******/
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/******/ var __webpack_modules__ = ({
|
|
4
|
+
|
|
5
|
+
/***/ 2789:
|
|
6
|
+
/***/ ((module) => {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Expose `PriorityQueue`.
|
|
10
|
+
*/
|
|
11
|
+
module.exports = PriorityQueue;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initializes a new empty `PriorityQueue` with the given `comparator(a, b)`
|
|
15
|
+
* function, uses `.DEFAULT_COMPARATOR()` when no function is provided.
|
|
16
|
+
*
|
|
17
|
+
* The comparator function must return a positive number when `a > b`, 0 when
|
|
18
|
+
* `a == b` and a negative number when `a < b`.
|
|
19
|
+
*
|
|
20
|
+
* @param {Function}
|
|
21
|
+
* @return {PriorityQueue}
|
|
22
|
+
* @api public
|
|
23
|
+
*/
|
|
24
|
+
function PriorityQueue(comparator) {
|
|
25
|
+
this._comparator = comparator || PriorityQueue.DEFAULT_COMPARATOR;
|
|
26
|
+
this._elements = [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compares `a` and `b`, when `a > b` it returns a positive number, when
|
|
31
|
+
* it returns 0 and when `a < b` it returns a negative number.
|
|
32
|
+
*
|
|
33
|
+
* @param {String|Number} a
|
|
34
|
+
* @param {String|Number} b
|
|
35
|
+
* @return {Number}
|
|
36
|
+
* @api public
|
|
37
|
+
*/
|
|
38
|
+
PriorityQueue.DEFAULT_COMPARATOR = function(a, b) {
|
|
39
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
40
|
+
return a - b;
|
|
41
|
+
} else {
|
|
42
|
+
a = a.toString();
|
|
43
|
+
b = b.toString();
|
|
44
|
+
|
|
45
|
+
if (a == b) return 0;
|
|
46
|
+
|
|
47
|
+
return (a > b) ? 1 : -1;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns whether the priority queue is empty or not.
|
|
53
|
+
*
|
|
54
|
+
* @return {Boolean}
|
|
55
|
+
* @api public
|
|
56
|
+
*/
|
|
57
|
+
PriorityQueue.prototype.isEmpty = function() {
|
|
58
|
+
return this.size() === 0;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Peeks at the top element of the priority queue.
|
|
63
|
+
*
|
|
64
|
+
* @return {Object}
|
|
65
|
+
* @throws {Error} when the queue is empty.
|
|
66
|
+
* @api public
|
|
67
|
+
*/
|
|
68
|
+
PriorityQueue.prototype.peek = function() {
|
|
69
|
+
if (this.isEmpty()) throw new Error('PriorityQueue is empty');
|
|
70
|
+
|
|
71
|
+
return this._elements[0];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Dequeues the top element of the priority queue.
|
|
76
|
+
*
|
|
77
|
+
* @return {Object}
|
|
78
|
+
* @throws {Error} when the queue is empty.
|
|
79
|
+
* @api public
|
|
80
|
+
*/
|
|
81
|
+
PriorityQueue.prototype.deq = function() {
|
|
82
|
+
var first = this.peek();
|
|
83
|
+
var last = this._elements.pop();
|
|
84
|
+
var size = this.size();
|
|
85
|
+
|
|
86
|
+
if (size === 0) return first;
|
|
87
|
+
|
|
88
|
+
this._elements[0] = last;
|
|
89
|
+
var current = 0;
|
|
90
|
+
|
|
91
|
+
while (current < size) {
|
|
92
|
+
var largest = current;
|
|
93
|
+
var left = (2 * current) + 1;
|
|
94
|
+
var right = (2 * current) + 2;
|
|
95
|
+
|
|
96
|
+
if (left < size && this._compare(left, largest) >= 0) {
|
|
97
|
+
largest = left;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (right < size && this._compare(right, largest) >= 0) {
|
|
101
|
+
largest = right;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (largest === current) break;
|
|
105
|
+
|
|
106
|
+
this._swap(largest, current);
|
|
107
|
+
current = largest;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return first;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Enqueues the `element` at the priority queue and returns its new size.
|
|
115
|
+
*
|
|
116
|
+
* @param {Object} element
|
|
117
|
+
* @return {Number}
|
|
118
|
+
* @api public
|
|
119
|
+
*/
|
|
120
|
+
PriorityQueue.prototype.enq = function(element) {
|
|
121
|
+
var size = this._elements.push(element);
|
|
122
|
+
var current = size - 1;
|
|
123
|
+
|
|
124
|
+
while (current > 0) {
|
|
125
|
+
var parent = Math.floor((current - 1) / 2);
|
|
126
|
+
|
|
127
|
+
if (this._compare(current, parent) <= 0) break;
|
|
128
|
+
|
|
129
|
+
this._swap(parent, current);
|
|
130
|
+
current = parent;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return size;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Returns the size of the priority queue.
|
|
138
|
+
*
|
|
139
|
+
* @return {Number}
|
|
140
|
+
* @api public
|
|
141
|
+
*/
|
|
142
|
+
PriorityQueue.prototype.size = function() {
|
|
143
|
+
return this._elements.length;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Iterates over queue elements
|
|
148
|
+
*
|
|
149
|
+
* @param {Function} fn
|
|
150
|
+
*/
|
|
151
|
+
PriorityQueue.prototype.forEach = function(fn) {
|
|
152
|
+
return this._elements.forEach(fn);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Compares the values at position `a` and `b` in the priority queue using its
|
|
157
|
+
* comparator function.
|
|
158
|
+
*
|
|
159
|
+
* @param {Number} a
|
|
160
|
+
* @param {Number} b
|
|
161
|
+
* @return {Number}
|
|
162
|
+
* @api private
|
|
163
|
+
*/
|
|
164
|
+
PriorityQueue.prototype._compare = function(a, b) {
|
|
165
|
+
return this._comparator(this._elements[a], this._elements[b]);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Swaps the values at position `a` and `b` in the priority queue.
|
|
170
|
+
*
|
|
171
|
+
* @param {Number} a
|
|
172
|
+
* @param {Number} b
|
|
173
|
+
* @api private
|
|
174
|
+
*/
|
|
175
|
+
PriorityQueue.prototype._swap = function(a, b) {
|
|
176
|
+
var aux = this._elements[a];
|
|
177
|
+
this._elements[a] = this._elements[b];
|
|
178
|
+
this._elements[b] = aux;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
/***/ })
|
|
183
|
+
|
|
184
|
+
/******/ });
|
|
185
|
+
/************************************************************************/
|
|
186
|
+
/******/ // The module cache
|
|
187
|
+
/******/ var __webpack_module_cache__ = {};
|
|
188
|
+
/******/
|
|
189
|
+
/******/ // The require function
|
|
190
|
+
/******/ function __webpack_require__(moduleId) {
|
|
191
|
+
/******/ // Check if module is in cache
|
|
192
|
+
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
193
|
+
/******/ if (cachedModule !== undefined) {
|
|
194
|
+
/******/ return cachedModule.exports;
|
|
195
|
+
/******/ }
|
|
196
|
+
/******/ // Create a new module (and put it into the cache)
|
|
197
|
+
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
198
|
+
/******/ // no module.id needed
|
|
199
|
+
/******/ // no module.loaded needed
|
|
200
|
+
/******/ exports: {}
|
|
201
|
+
/******/ };
|
|
202
|
+
/******/
|
|
203
|
+
/******/ // Execute the module function
|
|
204
|
+
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
205
|
+
/******/
|
|
206
|
+
/******/ // Return the exports of the module
|
|
207
|
+
/******/ return module.exports;
|
|
208
|
+
/******/ }
|
|
6
209
|
/******/
|
|
7
210
|
/************************************************************************/
|
|
8
211
|
/******/ /* webpack/runtime/compat get default export */
|
|
@@ -47,6 +250,9 @@
|
|
|
47
250
|
/******/
|
|
48
251
|
/************************************************************************/
|
|
49
252
|
var __webpack_exports__ = {};
|
|
253
|
+
// This entry need to be wrapped in an IIFE because it need to be in strict mode.
|
|
254
|
+
(() => {
|
|
255
|
+
"use strict";
|
|
50
256
|
// ESM COMPAT FLAG
|
|
51
257
|
__webpack_require__.r(__webpack_exports__);
|
|
52
258
|
|
|
@@ -55,9 +261,12 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
55
261
|
"collectNamespaces": () => (/* binding */ api_collectNamespaces),
|
|
56
262
|
"findUsingForNode": () => (/* binding */ findUsingForNode),
|
|
57
263
|
"formatAst": () => (/* binding */ api_formatAst),
|
|
264
|
+
"getApiFunctionInfo": () => (/* binding */ api_getApiFunctionInfo),
|
|
58
265
|
"getApiMapping": () => (/* binding */ api_getApiMapping),
|
|
59
|
-
"hasProperty": () => (/*
|
|
266
|
+
"hasProperty": () => (/* reexport */ ast_hasProperty),
|
|
267
|
+
"isLookupCandidate": () => (/* binding */ api_isLookupCandidate),
|
|
60
268
|
"isStateNode": () => (/* binding */ api_isStateNode),
|
|
269
|
+
"markInvokeClassMethod": () => (/* binding */ api_markInvokeClassMethod),
|
|
61
270
|
"sameLookupResult": () => (/* binding */ api_sameLookupResult),
|
|
62
271
|
"traverseAst": () => (/* reexport */ ast_traverseAst),
|
|
63
272
|
"variableDeclarationName": () => (/* binding */ api_variableDeclarationName),
|
|
@@ -86,62 +295,68 @@ function _check(x) {
|
|
|
86
295
|
x = y;
|
|
87
296
|
}
|
|
88
297
|
const mctreeTypeInfo = {
|
|
89
|
-
ArrayExpression: ["elements"],
|
|
90
|
-
AssignmentExpression: ["left", "right"],
|
|
91
|
-
AttributeList: ["attributes"],
|
|
92
|
-
Attributes: ["elements"],
|
|
93
|
-
BinaryExpression: ["left", "right"],
|
|
94
|
-
Block: [],
|
|
95
|
-
BlockStatement: ["body", "innerComments"],
|
|
96
|
-
BreakStatement: [],
|
|
97
|
-
CallExpression: ["callee", "arguments"],
|
|
98
|
-
CatchClause: ["param", "body"],
|
|
99
|
-
CatchClauses: ["catches"],
|
|
100
|
-
ClassBody: ["body"],
|
|
101
|
-
ClassDeclaration: ["attrs", "id", "superClass", "body"],
|
|
102
|
-
ClassElement: ["item"],
|
|
103
|
-
ConditionalExpression:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
298
|
+
ArrayExpression: { keys: ["elements"], expr: true },
|
|
299
|
+
AssignmentExpression: { keys: ["left", "right"], expr: true },
|
|
300
|
+
AttributeList: { keys: ["attributes"] },
|
|
301
|
+
Attributes: { keys: ["elements"] },
|
|
302
|
+
BinaryExpression: { keys: ["left", "right"], expr: true },
|
|
303
|
+
Block: { keys: [] },
|
|
304
|
+
BlockStatement: { keys: ["body", "innerComments"], stmt: true },
|
|
305
|
+
BreakStatement: { keys: [], stmt: true },
|
|
306
|
+
CallExpression: { keys: ["callee", "arguments"], expr: true },
|
|
307
|
+
CatchClause: { keys: ["param", "body"] },
|
|
308
|
+
CatchClauses: { keys: ["catches"] },
|
|
309
|
+
ClassBody: { keys: ["body"] },
|
|
310
|
+
ClassDeclaration: { keys: ["attrs", "id", "superClass", "body"], stmt: true },
|
|
311
|
+
ClassElement: { keys: ["item"] },
|
|
312
|
+
ConditionalExpression: {
|
|
313
|
+
keys: ["test", "consequent", "alternate"],
|
|
314
|
+
expr: true,
|
|
315
|
+
},
|
|
316
|
+
ContinueStatement: { keys: [], stmt: true },
|
|
317
|
+
DoWhileStatement: { keys: ["body", "test"], stmt: true },
|
|
318
|
+
EnumDeclaration: { keys: ["attrs", "id", "body"], stmt: true },
|
|
319
|
+
EnumStringBody: { keys: ["members"] },
|
|
320
|
+
EnumStringMember: { keys: ["id", "init"] },
|
|
321
|
+
ExpressionStatement: { keys: ["expression"], stmt: true },
|
|
322
|
+
ForStatement: { keys: ["init", "test", "body", "update"], stmt: true },
|
|
323
|
+
FunctionDeclaration: {
|
|
324
|
+
keys: ["attrs", "id", "params", "returnType", "body"],
|
|
325
|
+
stmt: true,
|
|
326
|
+
},
|
|
327
|
+
Identifier: { keys: [], expr: true },
|
|
328
|
+
IfStatement: { keys: ["test", "consequent", "alternate"], stmt: true },
|
|
329
|
+
ImportModule: { keys: ["id"] },
|
|
330
|
+
InstanceOfCase: { keys: ["id"] },
|
|
331
|
+
Line: { keys: [] },
|
|
332
|
+
Literal: { keys: [], expr: true },
|
|
333
|
+
LogicalExpression: { keys: ["left", "right"], expr: true },
|
|
334
|
+
MemberExpression: { keys: ["object", "property"], expr: true },
|
|
335
|
+
MethodDefinition: { keys: ["params", "returnType"] },
|
|
336
|
+
ModuleDeclaration: { keys: ["attrs", "id", "body"], stmt: true },
|
|
337
|
+
MultiLine: { keys: [] },
|
|
338
|
+
NewExpression: { keys: ["callee", "arguments"], expr: true },
|
|
339
|
+
ObjectExpression: { keys: ["properties"], expr: true },
|
|
340
|
+
ParenthesizedExpression: { keys: ["expression"], expr: true },
|
|
341
|
+
Program: { keys: ["body", "comments"] },
|
|
342
|
+
Property: { keys: ["key", "value"] },
|
|
343
|
+
ReturnStatement: { keys: ["argument"], stmt: true },
|
|
344
|
+
SequenceExpression: { keys: ["expressions"], expr: true },
|
|
345
|
+
SizedArrayExpression: { keys: ["size", "ts"], expr: true },
|
|
346
|
+
SwitchCase: { keys: ["test", "consequent"] },
|
|
347
|
+
SwitchStatement: { keys: ["discriminant", "cases"], stmt: true },
|
|
348
|
+
ThisExpression: { keys: [], expr: true },
|
|
349
|
+
ThrowStatement: { keys: ["argument"], stmt: true },
|
|
350
|
+
TryStatement: { keys: ["block", "handler", "finalizer"], stmt: true },
|
|
351
|
+
TypedefDeclaration: { keys: ["attrs", "id", "ts"], stmt: true },
|
|
352
|
+
TypeSpecList: { keys: ["ts"] },
|
|
353
|
+
TypeSpecPart: { keys: ["name", "body", "callspec", "generics"] },
|
|
354
|
+
UnaryExpression: { keys: ["argument"], expr: true },
|
|
355
|
+
UpdateExpression: { keys: ["argument"], expr: true },
|
|
356
|
+
Using: { keys: ["id", "as"] },
|
|
357
|
+
VariableDeclaration: { keys: ["attrs", "declarations"], stmt: true },
|
|
358
|
+
VariableDeclarator: { keys: ["id", "init"] },
|
|
359
|
+
WhileStatement: { keys: ["test", "body"], stmt: true },
|
|
145
360
|
};
|
|
146
361
|
function isMCTreeNode(node) {
|
|
147
362
|
return node ? typeof node === "object" && "type" in node : false;
|
|
@@ -166,7 +381,7 @@ function ast_traverseAst(node, pre, post) {
|
|
|
166
381
|
if (!mctreeTypeInfo[node.type]) {
|
|
167
382
|
throw new Error("what?");
|
|
168
383
|
}
|
|
169
|
-
for (const key of nodes || mctreeTypeInfo[node.type]) {
|
|
384
|
+
for (const key of nodes || mctreeTypeInfo[node.type].keys) {
|
|
170
385
|
const value = node[key];
|
|
171
386
|
if (!value)
|
|
172
387
|
continue;
|
|
@@ -193,13 +408,21 @@ function ast_traverseAst(node, pre, post) {
|
|
|
193
408
|
}
|
|
194
409
|
}
|
|
195
410
|
else if (isMCTreeNode(value)) {
|
|
196
|
-
|
|
411
|
+
let repl = ast_traverseAst(value, pre, post);
|
|
197
412
|
if (repl === false) {
|
|
198
413
|
delete node[key];
|
|
199
414
|
}
|
|
200
415
|
else if (repl != null) {
|
|
201
416
|
if (Array.isArray(repl)) {
|
|
202
|
-
|
|
417
|
+
if (ast_isStatement(value) && repl.every((s) => ast_isStatement(s))) {
|
|
418
|
+
repl = ast_withLoc({
|
|
419
|
+
type: "BlockStatement",
|
|
420
|
+
body: repl,
|
|
421
|
+
}, repl[0], repl[repl.length - 1]);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
throw new Error("Array returned by traverseAst in Node context");
|
|
425
|
+
}
|
|
203
426
|
}
|
|
204
427
|
node[key] = repl;
|
|
205
428
|
}
|
|
@@ -207,9 +430,187 @@ function ast_traverseAst(node, pre, post) {
|
|
|
207
430
|
}
|
|
208
431
|
return post && post(node);
|
|
209
432
|
}
|
|
433
|
+
function ast_isStatement(node) {
|
|
434
|
+
return ast_hasProperty(mctreeTypeInfo[node.type], "stmt");
|
|
435
|
+
}
|
|
436
|
+
function ast_isExpression(node) {
|
|
437
|
+
return ast_hasProperty(mctreeTypeInfo[node.type], "expr");
|
|
438
|
+
}
|
|
439
|
+
function ast_mayThrow(node) {
|
|
440
|
+
switch (node.type) {
|
|
441
|
+
case "BinaryExpression":
|
|
442
|
+
case "CallExpression":
|
|
443
|
+
case "ConditionalExpression":
|
|
444
|
+
case "LogicalExpression":
|
|
445
|
+
case "NewExpression":
|
|
446
|
+
case "ThrowStatement":
|
|
447
|
+
case "UnaryExpression":
|
|
448
|
+
case "UpdateExpression":
|
|
449
|
+
return true;
|
|
450
|
+
default:
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function ast_hasProperty(obj, prop) {
|
|
455
|
+
return obj ? Object.prototype.hasOwnProperty.call(obj, prop) : false;
|
|
456
|
+
}
|
|
457
|
+
function ast_withLoc(node, start, end) {
|
|
458
|
+
if (start && start.loc) {
|
|
459
|
+
node.start = start.start;
|
|
460
|
+
if (!node.end)
|
|
461
|
+
node.end = start.end;
|
|
462
|
+
node.loc = { ...(node.loc || start.loc), start: start.loc.start };
|
|
463
|
+
}
|
|
464
|
+
if (end && end.loc) {
|
|
465
|
+
node.end = end.end;
|
|
466
|
+
node.loc = { ...(node.loc || end.loc), end: end.loc.end };
|
|
467
|
+
}
|
|
468
|
+
return node;
|
|
469
|
+
}
|
|
470
|
+
function ast_withLocDeep(node, start, end, inplace) {
|
|
471
|
+
node = ast_withLoc(inplace ? node : { ...node }, start, end);
|
|
472
|
+
for (const key of mctreeTypeInfo[node.type].keys) {
|
|
473
|
+
const value = node[key];
|
|
474
|
+
if (!value)
|
|
475
|
+
continue;
|
|
476
|
+
const fix = (v) => isMCTreeNode(v) ? ast_withLocDeep(v, start, end, inplace) : v;
|
|
477
|
+
const repl = Array.isArray(value) ? value.map(fix) : fix(value);
|
|
478
|
+
inplace || (node[key] = repl);
|
|
479
|
+
}
|
|
480
|
+
return node;
|
|
481
|
+
}
|
|
482
|
+
function ast_cloneDeep(node) {
|
|
483
|
+
return ast_withLocDeep(node, null);
|
|
484
|
+
}
|
|
210
485
|
|
|
211
486
|
;// CONCATENATED MODULE: external "./api.cjs"
|
|
212
487
|
const external_api_cjs_namespaceObject = require("./api.cjs");
|
|
488
|
+
;// CONCATENATED MODULE: ./src/function-info.ts
|
|
489
|
+
|
|
490
|
+
function function_info_cloneSet(ae) {
|
|
491
|
+
return new Set(ae);
|
|
492
|
+
}
|
|
493
|
+
function function_info_mergeSet(a, b) {
|
|
494
|
+
b.forEach((event) => a.add(event));
|
|
495
|
+
}
|
|
496
|
+
function recordModifiedDecl(func, decl) {
|
|
497
|
+
if (!func.next_info) {
|
|
498
|
+
func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
|
|
499
|
+
}
|
|
500
|
+
func.next_info.modifiedDecls.add(decl);
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
function function_info_recordModifiedDecls(func, lookupDefs) {
|
|
504
|
+
lookupDefs.forEach((lookupDef) => lookupDef.results.forEach((result) => {
|
|
505
|
+
if (result.type == "VariableDeclarator" && result.node.kind === "var") {
|
|
506
|
+
recordModifiedDecl(func, result);
|
|
507
|
+
}
|
|
508
|
+
}));
|
|
509
|
+
}
|
|
510
|
+
function function_info_recordModifiedName(func, name) {
|
|
511
|
+
if (!func.next_info) {
|
|
512
|
+
func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
|
|
513
|
+
}
|
|
514
|
+
if (!func.next_info.modifiedNames) {
|
|
515
|
+
func.next_info.modifiedNames = new Set();
|
|
516
|
+
}
|
|
517
|
+
func.next_info.modifiedNames.add(name);
|
|
518
|
+
}
|
|
519
|
+
function function_info_recordModifiedUnknown(func) {
|
|
520
|
+
if (!func.next_info) {
|
|
521
|
+
func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
|
|
522
|
+
}
|
|
523
|
+
func.next_info.modifiedUnknown = true;
|
|
524
|
+
}
|
|
525
|
+
function recordCalledFunc(func, callee) {
|
|
526
|
+
if (!func.next_info) {
|
|
527
|
+
func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
|
|
528
|
+
}
|
|
529
|
+
func.next_info.calledFuncs.add(callee);
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
function function_info_recordCalledFuncs(func, callees) {
|
|
533
|
+
callees.forEach((callee) => {
|
|
534
|
+
recordCalledFunc(func, callee);
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
function function_info_functionMayModify(state, func, decl) {
|
|
538
|
+
const info = func.info;
|
|
539
|
+
if (!info || info.modifiedUnknown)
|
|
540
|
+
return true;
|
|
541
|
+
if (info.resolvedDecls) {
|
|
542
|
+
return info.resolvedDecls.has(decl);
|
|
543
|
+
}
|
|
544
|
+
if (info.modifiedNames?.has(decl.name))
|
|
545
|
+
return true;
|
|
546
|
+
if (info.modifiedDecls.has(decl))
|
|
547
|
+
return true;
|
|
548
|
+
const visited = new Set();
|
|
549
|
+
const resolved = new Set();
|
|
550
|
+
const resolveDecls = (f) => {
|
|
551
|
+
if (visited.has(f))
|
|
552
|
+
return true;
|
|
553
|
+
if (!f.info)
|
|
554
|
+
return false;
|
|
555
|
+
if (f.info.modifiedUnknown) {
|
|
556
|
+
info.modifiedUnknown = true;
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
if (f.info.modifiedNames) {
|
|
560
|
+
if (info.modifiedNames) {
|
|
561
|
+
function_info_mergeSet(info.modifiedNames, f.info.modifiedNames);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
info.modifiedNames = function_info_cloneSet(f.info.modifiedNames);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function_info_mergeSet(resolved, f.info.modifiedDecls);
|
|
568
|
+
visited.add(f);
|
|
569
|
+
const q = true;
|
|
570
|
+
if (q &&
|
|
571
|
+
f.info.callsExposed &&
|
|
572
|
+
state.exposed &&
|
|
573
|
+
!Object.keys(state.exposed).every((key) => !state.allFunctions[key] ||
|
|
574
|
+
state.allFunctions[key].every(resolveDecls))) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
return Array.from(f.info.calledFuncs).every(resolveDecls);
|
|
578
|
+
};
|
|
579
|
+
if (resolveDecls(func)) {
|
|
580
|
+
info.resolvedDecls = resolved;
|
|
581
|
+
return resolved.has(decl);
|
|
582
|
+
}
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
function function_info_findCallees(lookupDefs) {
|
|
586
|
+
const decls = lookupDefs.reduce((decls, r) => (decls ? decls.concat(r.results) : r.results), null);
|
|
587
|
+
return (decls &&
|
|
588
|
+
decls.filter((decl) => decl ? decl.type === "FunctionDeclaration" : false));
|
|
589
|
+
}
|
|
590
|
+
function function_info_findCalleesForNew(lookupDefs) {
|
|
591
|
+
const initializer = (decl) => {
|
|
592
|
+
if (hasProperty(decl.decls, "initialize")) {
|
|
593
|
+
return decl.decls["initialize"];
|
|
594
|
+
}
|
|
595
|
+
if (decl.superClass && decl.superClass !== true) {
|
|
596
|
+
return decl.superClass.reduce((cur, cls) => {
|
|
597
|
+
const init = initializer(cls);
|
|
598
|
+
if (init) {
|
|
599
|
+
if (!cur)
|
|
600
|
+
return init;
|
|
601
|
+
return cur.concat(init);
|
|
602
|
+
}
|
|
603
|
+
return cur;
|
|
604
|
+
}, null);
|
|
605
|
+
}
|
|
606
|
+
return null;
|
|
607
|
+
};
|
|
608
|
+
return lookupDefs.flatMap((r) => r.results
|
|
609
|
+
.filter((decl) => decl.type === "ClassDeclaration")
|
|
610
|
+
.flatMap(initializer)
|
|
611
|
+
.filter((decl) => decl ? decl.type === "FunctionDeclaration" : false));
|
|
612
|
+
}
|
|
613
|
+
|
|
213
614
|
;// CONCATENATED MODULE: ./src/variable-renamer.ts
|
|
214
615
|
|
|
215
616
|
|
|
@@ -270,6 +671,7 @@ function variable_renamer_renameVariable(state, locals, declName) {
|
|
|
270
671
|
|
|
271
672
|
|
|
272
673
|
|
|
674
|
+
|
|
273
675
|
function getArgSafety(state, func, args, requireAll) {
|
|
274
676
|
// determine whether decl might be changed by a function call
|
|
275
677
|
// or assignment during the evaluation of FunctionStateNode.
|
|
@@ -302,8 +704,9 @@ function getArgSafety(state, func, args, requireAll) {
|
|
|
302
704
|
}
|
|
303
705
|
};
|
|
304
706
|
const safeArgs = [];
|
|
707
|
+
const argDecls = [];
|
|
305
708
|
let allSafe = true;
|
|
306
|
-
if (!args.every((arg) => {
|
|
709
|
+
if (!args.every((arg, i) => {
|
|
307
710
|
switch (arg.type) {
|
|
308
711
|
case "Literal":
|
|
309
712
|
safeArgs.push(true);
|
|
@@ -317,13 +720,17 @@ function getArgSafety(state, func, args, requireAll) {
|
|
|
317
720
|
safeArgs.push(null);
|
|
318
721
|
return !requireAll;
|
|
319
722
|
}
|
|
320
|
-
const
|
|
723
|
+
const decl = results[0].results[0];
|
|
724
|
+
const safety = getSafety(decl);
|
|
321
725
|
safeArgs.push(safety);
|
|
322
726
|
if (!safety) {
|
|
323
727
|
allSafe = false;
|
|
324
728
|
if (safety === null) {
|
|
325
729
|
return !requireAll;
|
|
326
730
|
}
|
|
731
|
+
else if (decl.type === "VariableDeclarator") {
|
|
732
|
+
argDecls[i] = decl;
|
|
733
|
+
}
|
|
327
734
|
}
|
|
328
735
|
return true;
|
|
329
736
|
}
|
|
@@ -336,34 +743,91 @@ function getArgSafety(state, func, args, requireAll) {
|
|
|
336
743
|
}
|
|
337
744
|
if (allSafe && requireAll)
|
|
338
745
|
return true;
|
|
339
|
-
|
|
746
|
+
const callsSeen = new Set();
|
|
747
|
+
const modifiedDecls = new Set();
|
|
748
|
+
let modifiedUnknown = false;
|
|
340
749
|
const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
|
|
341
750
|
// look for uses of "unsafe" args that occur after a call.
|
|
342
751
|
// use post to do the checking, because arguments are evaluated
|
|
343
752
|
// prior to the call, so eg "return f(x.y);" is fine, but
|
|
344
753
|
// "return f()+x.y" is not.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
754
|
+
const { pre, post, stack } = state;
|
|
755
|
+
try {
|
|
756
|
+
delete state.pre;
|
|
757
|
+
state.post = (node) => {
|
|
758
|
+
switch (node.type) {
|
|
759
|
+
case "AssignmentExpression":
|
|
760
|
+
case "UpdateExpression": {
|
|
761
|
+
const v = node.type == "UpdateExpression" ? node.argument : node.left;
|
|
762
|
+
if (v.type === "Identifier" && hasProperty(params, v.name)) {
|
|
763
|
+
// If a parameter is modified, we can't just substitute the
|
|
764
|
+
// argument wherever the parameter is used.
|
|
765
|
+
safeArgs[params[v.name]] = null;
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
if (modifiedUnknown)
|
|
769
|
+
break;
|
|
770
|
+
const [, results] = state.lookup(v);
|
|
771
|
+
if (results) {
|
|
772
|
+
results.forEach((r) => r.results.forEach((decl) => decl.type === "VariableDeclarator" && modifiedDecls.add(decl)));
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
modifiedUnknown = true;
|
|
776
|
+
}
|
|
777
|
+
break;
|
|
352
778
|
}
|
|
779
|
+
case "CallExpression":
|
|
780
|
+
case "NewExpression":
|
|
781
|
+
if (!modifiedUnknown) {
|
|
782
|
+
const [, results] = state.lookup(node.callee, null,
|
|
783
|
+
// calls are looked up as non-locals, but new is not
|
|
784
|
+
node.type === "CallExpression" ? func.stack : state.stack);
|
|
785
|
+
if (!results) {
|
|
786
|
+
const callee_name = node.callee.type === "Identifier"
|
|
787
|
+
? node.callee
|
|
788
|
+
: node.callee.type === "MemberExpression"
|
|
789
|
+
? isLookupCandidate(node.callee)
|
|
790
|
+
: null;
|
|
791
|
+
if (callee_name) {
|
|
792
|
+
const callees = state.allFunctions[callee_name.name];
|
|
793
|
+
if (callees) {
|
|
794
|
+
callees.forEach((callee) => callsSeen.add(callee));
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
modifiedUnknown = true;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
const callees = node.type === "CallExpression"
|
|
803
|
+
? findCallees(results)
|
|
804
|
+
: findCalleesForNew(results);
|
|
805
|
+
if (callees) {
|
|
806
|
+
callees.forEach((callee) => callsSeen.add(callee));
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
break;
|
|
811
|
+
case "Identifier":
|
|
812
|
+
if (hasProperty(params, node.name) &&
|
|
813
|
+
!safeArgs[params[node.name]] &&
|
|
814
|
+
(modifiedUnknown ||
|
|
815
|
+
!argDecls[params[node.name]] ||
|
|
816
|
+
modifiedDecls.has(argDecls[params[node.name]]) ||
|
|
817
|
+
Array.from(callsSeen).some((callee) => functionMayModify(state, callee, argDecls[params[node.name]])))) {
|
|
818
|
+
safeArgs[params[node.name]] = null;
|
|
819
|
+
}
|
|
353
820
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
});
|
|
821
|
+
return null;
|
|
822
|
+
};
|
|
823
|
+
state.stack = func.stack;
|
|
824
|
+
state.traverse(func.node.body);
|
|
825
|
+
}
|
|
826
|
+
finally {
|
|
827
|
+
state.pre = pre;
|
|
828
|
+
state.post = post;
|
|
829
|
+
state.stack = stack;
|
|
830
|
+
}
|
|
367
831
|
return safeArgs;
|
|
368
832
|
}
|
|
369
833
|
function canInline(state, func, args) {
|
|
@@ -468,9 +932,6 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
468
932
|
state.pre = (node) => {
|
|
469
933
|
if (failed)
|
|
470
934
|
return [];
|
|
471
|
-
node.start = call.start;
|
|
472
|
-
node.end = call.end;
|
|
473
|
-
node.loc = call.loc;
|
|
474
935
|
if (replacements.has(node))
|
|
475
936
|
return false;
|
|
476
937
|
const result = pre(node, state);
|
|
@@ -485,6 +946,7 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
485
946
|
if (params[paramName] >= 0)
|
|
486
947
|
return null;
|
|
487
948
|
const name = renameVariable(state, locals, paramName) || paramName;
|
|
949
|
+
locals.map[name] = true;
|
|
488
950
|
return {
|
|
489
951
|
type: "VariableDeclarator",
|
|
490
952
|
id: { type: "Identifier", name },
|
|
@@ -503,31 +965,49 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
503
965
|
}
|
|
504
966
|
return result;
|
|
505
967
|
};
|
|
968
|
+
const fixId = (node) => {
|
|
969
|
+
if (state.inType)
|
|
970
|
+
return null;
|
|
971
|
+
if (hasProperty(params, node.name)) {
|
|
972
|
+
const ix = params[node.name];
|
|
973
|
+
if (ix >= 0) {
|
|
974
|
+
const replacement = { ...call.arguments[ix] };
|
|
975
|
+
replacements.add(replacement);
|
|
976
|
+
return replacement;
|
|
977
|
+
}
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
const replacement = fixNodeScope(state, node, func.stack);
|
|
981
|
+
if (!replacement) {
|
|
982
|
+
failed = true;
|
|
983
|
+
inlineDiagnostic(state, func, call, `Failed to resolve '${node.name}'`);
|
|
984
|
+
}
|
|
985
|
+
return replacement;
|
|
986
|
+
};
|
|
506
987
|
state.post = (node) => {
|
|
507
988
|
if (failed)
|
|
508
989
|
return post(node, state);
|
|
509
990
|
let replacement = null;
|
|
510
991
|
switch (node.type) {
|
|
511
|
-
case "
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (ix >= 0) {
|
|
517
|
-
replacement = call.arguments[ix];
|
|
518
|
-
replacements.add(replacement);
|
|
519
|
-
return replacement;
|
|
992
|
+
case "AssignmentExpression":
|
|
993
|
+
if (node.left.type === "Identifier") {
|
|
994
|
+
const rep = fixId(node.left);
|
|
995
|
+
if (rep) {
|
|
996
|
+
node.left = rep;
|
|
520
997
|
}
|
|
521
|
-
break;
|
|
522
998
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
999
|
+
break;
|
|
1000
|
+
case "UpdateExpression":
|
|
1001
|
+
if (node.argument.type === "Identifier") {
|
|
1002
|
+
const rep = fixId(node.argument);
|
|
1003
|
+
if (rep) {
|
|
1004
|
+
node.argument = rep;
|
|
1005
|
+
}
|
|
528
1006
|
}
|
|
529
1007
|
break;
|
|
530
|
-
|
|
1008
|
+
case "Identifier":
|
|
1009
|
+
replacement = fixId(node);
|
|
1010
|
+
break;
|
|
531
1011
|
}
|
|
532
1012
|
const ret = post(replacement || node, state);
|
|
533
1013
|
return ret === false || ret ? ret : replacement;
|
|
@@ -553,6 +1033,10 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
553
1033
|
}
|
|
554
1034
|
}
|
|
555
1035
|
function inliner_unused(expression, top) {
|
|
1036
|
+
const estmt = (expression) => withLoc({
|
|
1037
|
+
type: "ExpressionStatement",
|
|
1038
|
+
expression,
|
|
1039
|
+
}, expression);
|
|
556
1040
|
switch (expression.type) {
|
|
557
1041
|
case "Literal":
|
|
558
1042
|
return [];
|
|
@@ -564,9 +1048,50 @@ function inliner_unused(expression, top) {
|
|
|
564
1048
|
if (expression.operator === "as") {
|
|
565
1049
|
return inliner_unused(expression.left);
|
|
566
1050
|
}
|
|
567
|
-
// fall through
|
|
568
|
-
case "LogicalExpression":
|
|
569
1051
|
return inliner_unused(expression.left).concat(inliner_unused(expression.right));
|
|
1052
|
+
case "LogicalExpression": {
|
|
1053
|
+
const right = inliner_unused(expression.right);
|
|
1054
|
+
if (!right.length)
|
|
1055
|
+
return inliner_unused(expression.left);
|
|
1056
|
+
const consequent = withLoc({
|
|
1057
|
+
type: "BlockStatement",
|
|
1058
|
+
body: [estmt(expression.right)],
|
|
1059
|
+
}, expression.right);
|
|
1060
|
+
let alternate;
|
|
1061
|
+
if (expression.operator == "||") {
|
|
1062
|
+
alternate = { ...consequent };
|
|
1063
|
+
consequent.body = [];
|
|
1064
|
+
}
|
|
1065
|
+
return [
|
|
1066
|
+
withLoc({
|
|
1067
|
+
type: "IfStatement",
|
|
1068
|
+
test: expression.left,
|
|
1069
|
+
consequent,
|
|
1070
|
+
alternate,
|
|
1071
|
+
}, expression),
|
|
1072
|
+
];
|
|
1073
|
+
}
|
|
1074
|
+
case "ConditionalExpression": {
|
|
1075
|
+
const consequentExprs = inliner_unused(expression.consequent);
|
|
1076
|
+
const alternateExprs = inliner_unused(expression.alternate);
|
|
1077
|
+
if (!consequentExprs.length && !alternateExprs.length) {
|
|
1078
|
+
return inliner_unused(expression.test);
|
|
1079
|
+
}
|
|
1080
|
+
return [
|
|
1081
|
+
withLoc({
|
|
1082
|
+
type: "IfStatement",
|
|
1083
|
+
test: expression.test,
|
|
1084
|
+
consequent: withLoc({
|
|
1085
|
+
type: "BlockStatement",
|
|
1086
|
+
body: consequentExprs,
|
|
1087
|
+
}, expression.consequent),
|
|
1088
|
+
alternate: withLoc({
|
|
1089
|
+
type: "BlockStatement",
|
|
1090
|
+
body: alternateExprs,
|
|
1091
|
+
}, expression.alternate),
|
|
1092
|
+
}, expression),
|
|
1093
|
+
];
|
|
1094
|
+
}
|
|
570
1095
|
case "UnaryExpression":
|
|
571
1096
|
return inliner_unused(expression.argument);
|
|
572
1097
|
case "MemberExpression":
|
|
@@ -581,17 +1106,7 @@ function inliner_unused(expression, top) {
|
|
|
581
1106
|
.map((p) => inliner_unused(p.key).concat(inliner_unused(p.value)))
|
|
582
1107
|
.flat(1);
|
|
583
1108
|
}
|
|
584
|
-
return top
|
|
585
|
-
? null
|
|
586
|
-
: [
|
|
587
|
-
{
|
|
588
|
-
type: "ExpressionStatement",
|
|
589
|
-
expression,
|
|
590
|
-
start: expression.start,
|
|
591
|
-
end: expression.end,
|
|
592
|
-
loc: expression.loc,
|
|
593
|
-
},
|
|
594
|
-
];
|
|
1109
|
+
return top ? null : [estmt(expression)];
|
|
595
1110
|
}
|
|
596
1111
|
function inliner_diagnostic(state, loc, message, type = "INFO") {
|
|
597
1112
|
if (!loc || !loc.source)
|
|
@@ -624,6 +1139,10 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
624
1139
|
if (!func.node || !func.node.body) {
|
|
625
1140
|
return null;
|
|
626
1141
|
}
|
|
1142
|
+
const lastStmt = (block) => {
|
|
1143
|
+
const last = block.body.slice(-1)[0];
|
|
1144
|
+
return last.type === "BlockStatement" ? lastStmt(last) : [last, block];
|
|
1145
|
+
};
|
|
627
1146
|
let retStmtCount = 0;
|
|
628
1147
|
if (context.type === "ReturnStatement") {
|
|
629
1148
|
const last = func.node.body.body.slice(-1)[0];
|
|
@@ -639,21 +1158,25 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
639
1158
|
if (retStmtCount > 1) {
|
|
640
1159
|
inlineDiagnostic(state, func, call, "Function had more than one return statement");
|
|
641
1160
|
}
|
|
642
|
-
else if (context.type === "AssignmentExpression"
|
|
1161
|
+
else if ((context.type === "AssignmentExpression" ||
|
|
1162
|
+
context.type === "VariableDeclarator") &&
|
|
1163
|
+
retStmtCount !== 1) {
|
|
643
1164
|
inlineDiagnostic(state, func, call, "Function did not have a return statement");
|
|
644
1165
|
return null;
|
|
645
1166
|
}
|
|
646
1167
|
if (retStmtCount === 1) {
|
|
647
|
-
const last = func.node.body
|
|
1168
|
+
const [last] = lastStmt(func.node.body);
|
|
648
1169
|
if (!last ||
|
|
649
1170
|
last.type !== "ReturnStatement" ||
|
|
650
|
-
(context.type === "AssignmentExpression"
|
|
1171
|
+
((context.type === "AssignmentExpression" ||
|
|
1172
|
+
context.type === "VariableDeclarator") &&
|
|
1173
|
+
!last.argument)) {
|
|
651
1174
|
inlineDiagnostic(state, func, call, "There was a return statement, but not at the end of the function");
|
|
652
1175
|
return null;
|
|
653
1176
|
}
|
|
654
1177
|
}
|
|
655
1178
|
}
|
|
656
|
-
const body =
|
|
1179
|
+
const body = cloneDeep(func.node.body);
|
|
657
1180
|
const safeArgs = getArgSafety(state, func, call.arguments, false);
|
|
658
1181
|
const params = Object.fromEntries(func.node.params.map((param, i) => {
|
|
659
1182
|
const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
|
|
@@ -667,37 +1190,54 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
667
1190
|
}
|
|
668
1191
|
inliner_diagnostic(state, call.loc, null);
|
|
669
1192
|
if (context.type !== "ReturnStatement" && retStmtCount) {
|
|
670
|
-
const last = body
|
|
1193
|
+
const [last, block] = lastStmt(body);
|
|
671
1194
|
if (last.type != "ReturnStatement") {
|
|
672
1195
|
throw new Error("ReturnStatement got lost!");
|
|
673
1196
|
}
|
|
674
|
-
if (
|
|
675
|
-
context.
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1197
|
+
if (last.argument) {
|
|
1198
|
+
if (context.type === "AssignmentExpression") {
|
|
1199
|
+
context.right = last.argument;
|
|
1200
|
+
block.body[block.body.length - 1] = {
|
|
1201
|
+
type: "ExpressionStatement",
|
|
1202
|
+
expression: context,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
else if (context.type === "VariableDeclarator") {
|
|
1206
|
+
const { id, init: _init, kind: _kind, ...rest } = context;
|
|
1207
|
+
block.body[block.body.length - 1] = {
|
|
1208
|
+
...rest,
|
|
1209
|
+
type: "ExpressionStatement",
|
|
1210
|
+
expression: {
|
|
1211
|
+
...rest,
|
|
1212
|
+
type: "AssignmentExpression",
|
|
1213
|
+
operator: "=",
|
|
1214
|
+
left: id.type === "Identifier" ? id : id.left,
|
|
1215
|
+
right: last.argument,
|
|
1216
|
+
},
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
else {
|
|
1220
|
+
const side_exprs = inliner_unused(last.argument);
|
|
1221
|
+
block.body.splice(block.body.length - 1, 1, ...side_exprs);
|
|
1222
|
+
}
|
|
684
1223
|
}
|
|
685
1224
|
else {
|
|
686
|
-
--
|
|
1225
|
+
--block.body.length;
|
|
687
1226
|
}
|
|
688
1227
|
}
|
|
1228
|
+
withLocDeep(body, context, context, true);
|
|
689
1229
|
return body;
|
|
690
1230
|
}
|
|
691
1231
|
function inliner_inlineFunction(state, func, call, context) {
|
|
692
1232
|
if (context) {
|
|
693
1233
|
return inlineWithArgs(state, func, call, context);
|
|
694
1234
|
}
|
|
695
|
-
const retArg =
|
|
1235
|
+
const retArg = cloneDeep(func.node.body.body[0].argument);
|
|
696
1236
|
const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
|
|
697
1237
|
const map = fixupLocalsMap(state);
|
|
698
1238
|
const ret = processInlineBody(state, func, call, retArg, params);
|
|
699
1239
|
state.localsStack[state.localsStack.length - 1].map = map;
|
|
700
|
-
return ret;
|
|
1240
|
+
return ret && withLocDeep(ret, call, call, true);
|
|
701
1241
|
}
|
|
702
1242
|
function applyTypeIfNeeded(node) {
|
|
703
1243
|
if ("enumType" in node && node.enumType) {
|
|
@@ -799,11 +1339,1660 @@ function fixNodeScope(state, lookupNode, nodeStack) {
|
|
|
799
1339
|
loc: node.loc,
|
|
800
1340
|
}, node));
|
|
801
1341
|
}
|
|
802
|
-
return null;
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
;// CONCATENATED MODULE: ./src/pragma-checker.ts
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
|
|
1349
|
+
function pragma_checker_pragmaChecker(state, ast, diagnostics) {
|
|
1350
|
+
const comments = ast.comments;
|
|
1351
|
+
if (!comments)
|
|
1352
|
+
return;
|
|
1353
|
+
diagnostics = diagnostics
|
|
1354
|
+
?.slice()
|
|
1355
|
+
.sort((d1, d2) => d1.loc.start < d2.loc.start ? -1 : d1.loc.start == d2.loc.start ? 0 : 1);
|
|
1356
|
+
let diagIndex = 0;
|
|
1357
|
+
let index = -1;
|
|
1358
|
+
let comment;
|
|
1359
|
+
let matchers;
|
|
1360
|
+
const next = () => {
|
|
1361
|
+
while (++index < comments.length) {
|
|
1362
|
+
comment = comments[index];
|
|
1363
|
+
let match = comment.value.match(/^\s*@(match|expect)\s+(.+)/);
|
|
1364
|
+
if (!match)
|
|
1365
|
+
continue;
|
|
1366
|
+
const kind = match[1];
|
|
1367
|
+
let str = match[2];
|
|
1368
|
+
matchers = [];
|
|
1369
|
+
while ((match = str.match(/^([/%&#@"])(.+?(?<!\\)(?:\\{2})*)\1(\s+|$)/))) {
|
|
1370
|
+
matchers.push({ kind, quote: match[1], needle: match[2] });
|
|
1371
|
+
str = str.substring(match[0].length);
|
|
1372
|
+
if (!str.length)
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
if (!str.length)
|
|
1376
|
+
break;
|
|
1377
|
+
if (!matchers.length) {
|
|
1378
|
+
match = str.match(/^(\S+)\s+$/);
|
|
1379
|
+
if (match) {
|
|
1380
|
+
matchers.push({ kind, quote: '"', needle: match[1] });
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
diagnostic(state, comment.loc, `Build pragma '${comment.value}' is invalid`, "ERROR");
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
const matcher = (quote, needle, haystack) => {
|
|
1388
|
+
if (quote == '"') {
|
|
1389
|
+
return haystack.includes(needle);
|
|
1390
|
+
}
|
|
1391
|
+
const re = new RegExp(needle.replace(/@(\d+)/g, "(pre_)?$1(_\\d+)?"));
|
|
1392
|
+
return re.test(haystack);
|
|
1393
|
+
};
|
|
1394
|
+
next();
|
|
1395
|
+
traverseAst(ast, (node) => {
|
|
1396
|
+
if (index >= comments.length)
|
|
1397
|
+
return false;
|
|
1398
|
+
if (node.start && node.start >= (comment.end || Infinity)) {
|
|
1399
|
+
const { kind, quote, needle } = matchers.shift();
|
|
1400
|
+
if (kind === "match") {
|
|
1401
|
+
const haystack = formatAst(node).replace(/([\r\n]|\s)+/g, " ");
|
|
1402
|
+
if (!matcher(quote, needle, haystack)) {
|
|
1403
|
+
matcher(quote, needle, haystack);
|
|
1404
|
+
diagnostic(state, comment.loc, `Didn't find '${needle}' in '${haystack}'`, "ERROR");
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
else if (kind === "expect") {
|
|
1408
|
+
const locCmp = (a, b) => {
|
|
1409
|
+
if (!b)
|
|
1410
|
+
return -1;
|
|
1411
|
+
if (a.start.line < b.start.line)
|
|
1412
|
+
return -1;
|
|
1413
|
+
if (a.start.line === b.start.line &&
|
|
1414
|
+
a.start.column < b.start.column) {
|
|
1415
|
+
return -1;
|
|
1416
|
+
}
|
|
1417
|
+
if (a.end.line > b.end.line)
|
|
1418
|
+
return 1;
|
|
1419
|
+
if (a.end.line === b.end.line && a.end.column >= b.end.column) {
|
|
1420
|
+
return 1;
|
|
1421
|
+
}
|
|
1422
|
+
return 0;
|
|
1423
|
+
};
|
|
1424
|
+
let found = false;
|
|
1425
|
+
if (diagnostics) {
|
|
1426
|
+
while (true) {
|
|
1427
|
+
if (diagIndex >= diagnostics.length) {
|
|
1428
|
+
diagnostics = null;
|
|
1429
|
+
break;
|
|
1430
|
+
}
|
|
1431
|
+
const diag = diagnostics[diagIndex];
|
|
1432
|
+
const cmp = locCmp(diag.loc, node.loc);
|
|
1433
|
+
if (cmp > 0) {
|
|
1434
|
+
break;
|
|
1435
|
+
}
|
|
1436
|
+
diagIndex++;
|
|
1437
|
+
if (cmp < 0)
|
|
1438
|
+
continue;
|
|
1439
|
+
if (matcher(quote, needle, diag.message)) {
|
|
1440
|
+
found = true;
|
|
1441
|
+
diag.type = "INFO";
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (!found) {
|
|
1446
|
+
diagnostic(state, comment.loc, `Missing error message '${needle}`, "ERROR");
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (matchers.length) {
|
|
1450
|
+
// if we're checking a series of nodes, we need
|
|
1451
|
+
// to skip over this one.
|
|
1452
|
+
return false;
|
|
1453
|
+
}
|
|
1454
|
+
next();
|
|
1455
|
+
}
|
|
1456
|
+
return null;
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
;// CONCATENATED MODULE: external "./util.cjs"
|
|
1461
|
+
const external_util_cjs_namespaceObject = require("./util.cjs");
|
|
1462
|
+
;// CONCATENATED MODULE: ./src/control-flow.ts
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
const Terminals = {
|
|
1466
|
+
BreakStatement: "break",
|
|
1467
|
+
ContinueStatement: "continue",
|
|
1468
|
+
ReturnStatement: null,
|
|
1469
|
+
ThrowStatement: "throw",
|
|
1470
|
+
};
|
|
1471
|
+
class LocalState {
|
|
1472
|
+
constructor(func) {
|
|
1473
|
+
this.stack = [];
|
|
1474
|
+
this.info = new Map();
|
|
1475
|
+
this.curBlock = {};
|
|
1476
|
+
this.unreachable = false;
|
|
1477
|
+
this.push(func);
|
|
1478
|
+
}
|
|
1479
|
+
push(node) {
|
|
1480
|
+
const top = { node };
|
|
1481
|
+
this.stack.push(top);
|
|
1482
|
+
return top;
|
|
1483
|
+
}
|
|
1484
|
+
pop() {
|
|
1485
|
+
return this.stack.pop();
|
|
1486
|
+
}
|
|
1487
|
+
top(depth) {
|
|
1488
|
+
return this.stack[this.stack.length - (depth || 1)];
|
|
1489
|
+
}
|
|
1490
|
+
addEdge(from, to) {
|
|
1491
|
+
if (!from.succs) {
|
|
1492
|
+
from.succs = [to];
|
|
1493
|
+
}
|
|
1494
|
+
else {
|
|
1495
|
+
pushUnique(from.succs, to);
|
|
1496
|
+
}
|
|
1497
|
+
if (!to.preds) {
|
|
1498
|
+
to.preds = [from];
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
pushUnique(to.preds, from);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
newBlock(block) {
|
|
1505
|
+
if (!block)
|
|
1506
|
+
block = {};
|
|
1507
|
+
if (!this.unreachable) {
|
|
1508
|
+
this.addEdge(this.curBlock, block);
|
|
1509
|
+
}
|
|
1510
|
+
this.unreachable = false;
|
|
1511
|
+
for (let i = this.stack.length; i--;) {
|
|
1512
|
+
const si = this.stack[i];
|
|
1513
|
+
if (si.throw) {
|
|
1514
|
+
block.exsucc = si.throw;
|
|
1515
|
+
if (!si.throw.expreds) {
|
|
1516
|
+
si.throw.expreds = [block];
|
|
1517
|
+
}
|
|
1518
|
+
else {
|
|
1519
|
+
si.throw.expreds.push(block);
|
|
1520
|
+
}
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
return (this.curBlock = block);
|
|
1525
|
+
}
|
|
1526
|
+
terminal(type) {
|
|
1527
|
+
const re = Terminals[type];
|
|
1528
|
+
if (re) {
|
|
1529
|
+
for (let i = this.stack.length; i--;) {
|
|
1530
|
+
const target = this.stack[i][re];
|
|
1531
|
+
if (target) {
|
|
1532
|
+
this.addEdge(this.curBlock, target);
|
|
1533
|
+
break;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
this.unreachable = true;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function control_flow_buildReducedGraph(state, func, notice) {
|
|
1541
|
+
const { stack, pre, post } = state;
|
|
1542
|
+
try {
|
|
1543
|
+
const localState = new LocalState(func.node);
|
|
1544
|
+
const ret = localState.curBlock;
|
|
1545
|
+
state.stack = func.stack;
|
|
1546
|
+
const stmtStack = [func.node];
|
|
1547
|
+
let tryActive = 0;
|
|
1548
|
+
state.pre = (node) => {
|
|
1549
|
+
if (state.inType || localState.unreachable) {
|
|
1550
|
+
return [];
|
|
1551
|
+
}
|
|
1552
|
+
if (!localState.curBlock.node &&
|
|
1553
|
+
(isStatement(node) || isExpression(node))) {
|
|
1554
|
+
localState.curBlock.node = node;
|
|
1555
|
+
}
|
|
1556
|
+
if (isStatement(node)) {
|
|
1557
|
+
stmtStack.push(node);
|
|
1558
|
+
}
|
|
1559
|
+
switch (node.type) {
|
|
1560
|
+
case "AttributeList":
|
|
1561
|
+
return [];
|
|
1562
|
+
case "SwitchStatement": {
|
|
1563
|
+
const top = localState.push(node);
|
|
1564
|
+
top.break = {};
|
|
1565
|
+
state.traverse(node.discriminant);
|
|
1566
|
+
const testBlocks = [];
|
|
1567
|
+
let defaultSeen = false;
|
|
1568
|
+
node.cases.forEach((sc, i) => {
|
|
1569
|
+
if (sc.test) {
|
|
1570
|
+
state.traverse(sc.test);
|
|
1571
|
+
testBlocks[i] = localState.curBlock;
|
|
1572
|
+
localState.newBlock();
|
|
1573
|
+
}
|
|
1574
|
+
else {
|
|
1575
|
+
defaultSeen = true;
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
const endOfTests = localState.curBlock;
|
|
1579
|
+
if (!defaultSeen) {
|
|
1580
|
+
localState.addEdge(endOfTests, top.break);
|
|
1581
|
+
}
|
|
1582
|
+
localState.unreachable = true;
|
|
1583
|
+
node.cases.forEach((sc, i) => {
|
|
1584
|
+
localState.newBlock();
|
|
1585
|
+
localState.addEdge(testBlocks[i] || endOfTests, localState.curBlock);
|
|
1586
|
+
sc.consequent.every((s) => {
|
|
1587
|
+
state.traverse(s);
|
|
1588
|
+
return !localState.unreachable;
|
|
1589
|
+
});
|
|
1590
|
+
});
|
|
1591
|
+
localState.newBlock(top.break);
|
|
1592
|
+
localState.unreachable = !top.break.preds;
|
|
1593
|
+
return [];
|
|
1594
|
+
}
|
|
1595
|
+
case "DoWhileStatement":
|
|
1596
|
+
case "WhileStatement": {
|
|
1597
|
+
localState.push(node);
|
|
1598
|
+
const top = localState.top();
|
|
1599
|
+
top.break = {};
|
|
1600
|
+
top.continue = {};
|
|
1601
|
+
let head;
|
|
1602
|
+
if (node.type === "WhileStatement") {
|
|
1603
|
+
head = localState.newBlock(top.continue);
|
|
1604
|
+
state.traverse(node.test);
|
|
1605
|
+
localState.addEdge(localState.curBlock, top.break);
|
|
1606
|
+
localState.newBlock();
|
|
1607
|
+
}
|
|
1608
|
+
else {
|
|
1609
|
+
head = localState.newBlock();
|
|
1610
|
+
}
|
|
1611
|
+
state.traverse(node.body);
|
|
1612
|
+
if (node.type === "DoWhileStatement") {
|
|
1613
|
+
localState.newBlock(top.continue);
|
|
1614
|
+
state.traverse(node.test);
|
|
1615
|
+
localState.addEdge(localState.curBlock, top.break);
|
|
1616
|
+
}
|
|
1617
|
+
localState.addEdge(localState.curBlock, head);
|
|
1618
|
+
localState.curBlock = top.break;
|
|
1619
|
+
return [];
|
|
1620
|
+
}
|
|
1621
|
+
case "TryStatement": {
|
|
1622
|
+
const top = localState.push(node);
|
|
1623
|
+
const catches = (top.throw = {});
|
|
1624
|
+
// This edge shouldn't exist, but we can trigger
|
|
1625
|
+
// (incorrect) "variable may not be initialized" errors
|
|
1626
|
+
// in the monkey c compiler without it.
|
|
1627
|
+
// https://forums.garmin.com/developer/connect-iq/i/bug-reports/incorrect-maybe-uninitialized-error
|
|
1628
|
+
localState.addEdge(localState.curBlock, top.throw);
|
|
1629
|
+
localState.newBlock();
|
|
1630
|
+
tryActive++;
|
|
1631
|
+
state.traverse(node.block);
|
|
1632
|
+
tryActive--;
|
|
1633
|
+
delete top.throw;
|
|
1634
|
+
top.posttry = {};
|
|
1635
|
+
const tryFallsThrough = !localState.unreachable;
|
|
1636
|
+
if (node.finalizer) {
|
|
1637
|
+
tryActive++;
|
|
1638
|
+
top.throw = top.finally = {};
|
|
1639
|
+
// curBlock branches to finally, no matter how it exits.
|
|
1640
|
+
localState.addEdge(localState.curBlock, top.finally);
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
if (!localState.unreachable) {
|
|
1644
|
+
localState.addEdge(localState.curBlock, top.posttry);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
localState.unreachable = true;
|
|
1648
|
+
localState.newBlock(catches);
|
|
1649
|
+
if (node.handler) {
|
|
1650
|
+
state.traverse(node.handler);
|
|
1651
|
+
if (top.throw) {
|
|
1652
|
+
tryActive--;
|
|
1653
|
+
delete top.throw;
|
|
1654
|
+
}
|
|
1655
|
+
// Each "catch (ex instanceof Foo)" chains to the next,
|
|
1656
|
+
// but "catch (ex)" terminates the list. If the end
|
|
1657
|
+
// of the chain has a predecessor, its possible that
|
|
1658
|
+
// none of the conditions matched, so the exception
|
|
1659
|
+
// will propagate from there.
|
|
1660
|
+
if (localState.curBlock.preds) {
|
|
1661
|
+
localState.terminal("ThrowStatement");
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
if (top.throw) {
|
|
1665
|
+
tryActive--;
|
|
1666
|
+
delete top.throw;
|
|
1667
|
+
}
|
|
1668
|
+
if (node.finalizer) {
|
|
1669
|
+
localState.unreachable = true;
|
|
1670
|
+
localState.newBlock(top.finally);
|
|
1671
|
+
delete top.finally;
|
|
1672
|
+
state.traverse(node.finalizer);
|
|
1673
|
+
if (tryFallsThrough && !localState.unreachable) {
|
|
1674
|
+
localState.addEdge(localState.curBlock, top.posttry);
|
|
1675
|
+
}
|
|
1676
|
+
localState.terminal("ThrowStatement");
|
|
1677
|
+
}
|
|
1678
|
+
localState.unreachable = true;
|
|
1679
|
+
localState.newBlock(top.posttry);
|
|
1680
|
+
return [];
|
|
1681
|
+
}
|
|
1682
|
+
case "CatchClause": {
|
|
1683
|
+
const top = localState.top();
|
|
1684
|
+
if (!localState.curBlock.preds && !localState.curBlock.expreds) {
|
|
1685
|
+
return [];
|
|
1686
|
+
}
|
|
1687
|
+
const next = {};
|
|
1688
|
+
if (node.param && node.param.type === "BinaryExpression") {
|
|
1689
|
+
state.traverse(node.param);
|
|
1690
|
+
localState.addEdge(localState.curBlock, next);
|
|
1691
|
+
localState.newBlock();
|
|
1692
|
+
}
|
|
1693
|
+
state.traverse(node.body);
|
|
1694
|
+
if (top.finally) {
|
|
1695
|
+
// this edge exists even if this point is unreachable
|
|
1696
|
+
localState.addEdge(localState.curBlock, top.finally);
|
|
1697
|
+
}
|
|
1698
|
+
if (!localState.unreachable) {
|
|
1699
|
+
if (!top.posttry)
|
|
1700
|
+
top.posttry = {};
|
|
1701
|
+
localState.addEdge(localState.curBlock, top.posttry);
|
|
1702
|
+
}
|
|
1703
|
+
localState.unreachable = true;
|
|
1704
|
+
localState.newBlock(next);
|
|
1705
|
+
return [];
|
|
1706
|
+
}
|
|
1707
|
+
case "ForStatement": {
|
|
1708
|
+
const top = localState.push(node);
|
|
1709
|
+
if (node.init)
|
|
1710
|
+
state.traverse(node.init);
|
|
1711
|
+
const head = localState.newBlock();
|
|
1712
|
+
top.break = {};
|
|
1713
|
+
top.continue = {};
|
|
1714
|
+
if (node.test) {
|
|
1715
|
+
state.traverse(node.test);
|
|
1716
|
+
localState.addEdge(localState.curBlock, top.break);
|
|
1717
|
+
localState.newBlock();
|
|
1718
|
+
}
|
|
1719
|
+
state.traverse(node.body);
|
|
1720
|
+
localState.newBlock(top.continue);
|
|
1721
|
+
if (node.update) {
|
|
1722
|
+
state.traverse(node.update);
|
|
1723
|
+
}
|
|
1724
|
+
if (!localState.unreachable) {
|
|
1725
|
+
localState.addEdge(localState.curBlock, head);
|
|
1726
|
+
}
|
|
1727
|
+
// there is no fall through from the end of the loop
|
|
1728
|
+
// to the next block. The only way there is via break
|
|
1729
|
+
// or the test failing.
|
|
1730
|
+
localState.unreachable = true;
|
|
1731
|
+
localState.newBlock(top.break);
|
|
1732
|
+
if (!top.break.preds) {
|
|
1733
|
+
localState.unreachable = true;
|
|
1734
|
+
}
|
|
1735
|
+
return [];
|
|
1736
|
+
}
|
|
1737
|
+
case "IfStatement":
|
|
1738
|
+
case "ConditionalExpression": {
|
|
1739
|
+
state.traverse(node.test);
|
|
1740
|
+
const alternate = {};
|
|
1741
|
+
localState.addEdge(localState.curBlock, alternate);
|
|
1742
|
+
localState.newBlock();
|
|
1743
|
+
state.traverse(node.consequent);
|
|
1744
|
+
const consequent = localState.unreachable
|
|
1745
|
+
? null
|
|
1746
|
+
: localState.curBlock;
|
|
1747
|
+
localState.unreachable = true;
|
|
1748
|
+
localState.newBlock(alternate);
|
|
1749
|
+
if (node.alternate) {
|
|
1750
|
+
state.traverse(node.alternate);
|
|
1751
|
+
if (!localState.unreachable) {
|
|
1752
|
+
localState.newBlock();
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
if (consequent) {
|
|
1756
|
+
if (localState.unreachable) {
|
|
1757
|
+
localState.newBlock();
|
|
1758
|
+
}
|
|
1759
|
+
localState.addEdge(consequent, localState.curBlock);
|
|
1760
|
+
}
|
|
1761
|
+
return [];
|
|
1762
|
+
}
|
|
1763
|
+
case "LogicalExpression": {
|
|
1764
|
+
state.traverse(node.left);
|
|
1765
|
+
if (localState.unreachable)
|
|
1766
|
+
break;
|
|
1767
|
+
const mid = localState.curBlock;
|
|
1768
|
+
localState.newBlock();
|
|
1769
|
+
state.traverse(node.right);
|
|
1770
|
+
localState.newBlock();
|
|
1771
|
+
localState.addEdge(mid, localState.curBlock);
|
|
1772
|
+
return [];
|
|
1773
|
+
}
|
|
1774
|
+
case "VariableDeclarator":
|
|
1775
|
+
return ["init"];
|
|
1776
|
+
case "MemberExpression":
|
|
1777
|
+
if (!node.computed) {
|
|
1778
|
+
return ["object"];
|
|
1779
|
+
}
|
|
1780
|
+
break;
|
|
1781
|
+
case "UnaryExpression":
|
|
1782
|
+
if (node.operator === ":") {
|
|
1783
|
+
return [];
|
|
1784
|
+
}
|
|
1785
|
+
break;
|
|
1786
|
+
case "UpdateExpression":
|
|
1787
|
+
// We don't want to traverse the argument, since then it would
|
|
1788
|
+
// look like a ref, rather than a def. But if its a
|
|
1789
|
+
// MemberExpression, we *do* want to traverse the subexpressions
|
|
1790
|
+
// as potential refs.
|
|
1791
|
+
if (node.argument.type === "MemberExpression") {
|
|
1792
|
+
state.traverse(node.argument.object);
|
|
1793
|
+
if (node.argument.computed) {
|
|
1794
|
+
state.traverse(node.argument.property);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return [];
|
|
1798
|
+
case "AssignmentExpression":
|
|
1799
|
+
if (node.left.type === "MemberExpression") {
|
|
1800
|
+
state.traverse(node.left.object);
|
|
1801
|
+
if (node.left.computed) {
|
|
1802
|
+
state.traverse(node.left.property);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
return ["right"];
|
|
1806
|
+
case "ThrowStatement":
|
|
1807
|
+
case "ReturnStatement":
|
|
1808
|
+
if (node.argument) {
|
|
1809
|
+
state.traverse(node.argument);
|
|
1810
|
+
}
|
|
1811
|
+
// fall through
|
|
1812
|
+
case "BreakStatement":
|
|
1813
|
+
case "ContinueStatement":
|
|
1814
|
+
localState.terminal(node.type);
|
|
1815
|
+
return [];
|
|
1816
|
+
}
|
|
1817
|
+
return null;
|
|
1818
|
+
};
|
|
1819
|
+
const addEvent = (block, event) => {
|
|
1820
|
+
if (!block.events) {
|
|
1821
|
+
block.events = [event];
|
|
1822
|
+
}
|
|
1823
|
+
else {
|
|
1824
|
+
block.events.push(event);
|
|
1825
|
+
}
|
|
1826
|
+
};
|
|
1827
|
+
state.post = (node) => {
|
|
1828
|
+
const curStmt = stmtStack[stmtStack.length - 1];
|
|
1829
|
+
if (!state.inType) {
|
|
1830
|
+
const throws = tryActive > 0 && mayThrow(node);
|
|
1831
|
+
const event = notice(node, curStmt, throws);
|
|
1832
|
+
if (throws) {
|
|
1833
|
+
if (!event) {
|
|
1834
|
+
throw new Error("mayThrow expression in try/catch must generate an event");
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
else if (event) {
|
|
1838
|
+
event.mayThrow = false;
|
|
1839
|
+
}
|
|
1840
|
+
if (event) {
|
|
1841
|
+
if (event.mayThrow) {
|
|
1842
|
+
for (let i = localState.stack.length; i--;) {
|
|
1843
|
+
const target = localState.stack[i].throw;
|
|
1844
|
+
if (target) {
|
|
1845
|
+
if (localState.curBlock.exsucc) {
|
|
1846
|
+
if (localState.curBlock.exsucc !== target) {
|
|
1847
|
+
throw new Error(`Block has multiple throw targets`);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
else {
|
|
1851
|
+
localState.curBlock.exsucc = target;
|
|
1852
|
+
if (!target.expreds) {
|
|
1853
|
+
target.expreds = [localState.curBlock];
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1856
|
+
target.expreds.push(localState.curBlock);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
break;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
addEvent(localState.curBlock, event);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
if (curStmt === node) {
|
|
1867
|
+
stmtStack.pop();
|
|
1868
|
+
}
|
|
1869
|
+
if (localState.top().node === node) {
|
|
1870
|
+
localState.pop();
|
|
1871
|
+
}
|
|
1872
|
+
return null;
|
|
1873
|
+
};
|
|
1874
|
+
state.traverse(func.node);
|
|
1875
|
+
return cleanCfg(ret);
|
|
1876
|
+
}
|
|
1877
|
+
finally {
|
|
1878
|
+
state.pre = pre;
|
|
1879
|
+
state.post = post;
|
|
1880
|
+
state.stack = stack;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
function cleanCfg(head) {
|
|
1884
|
+
preOrderTraverse(head, (cur) => {
|
|
1885
|
+
if (cur.succs && cur.succs.length === 1) {
|
|
1886
|
+
const succ = cur.succs[0];
|
|
1887
|
+
if (succ !== head &&
|
|
1888
|
+
succ.preds.length === 1 &&
|
|
1889
|
+
(!cur.exsucc || cur.exsucc === succ.exsucc) &&
|
|
1890
|
+
(!succ.succs ||
|
|
1891
|
+
succ.succs.length === 1 ||
|
|
1892
|
+
(cur.preds && cur.preds.length === 1))) {
|
|
1893
|
+
if (cur.events) {
|
|
1894
|
+
if (succ.events) {
|
|
1895
|
+
cur.events.push(...succ.events);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
else if (succ.events) {
|
|
1899
|
+
cur.events = succ.events;
|
|
1900
|
+
}
|
|
1901
|
+
if (succ.exsucc) {
|
|
1902
|
+
const preds = succ.exsucc.expreds;
|
|
1903
|
+
for (let i = preds.length; i--;) {
|
|
1904
|
+
if (preds[i] === succ) {
|
|
1905
|
+
// If cur has an exsucc, we already
|
|
1906
|
+
// checked that its the same as succ's,
|
|
1907
|
+
// so we can just delete the edge.
|
|
1908
|
+
// Otherwise, we need to point it at cur.
|
|
1909
|
+
if (cur.exsucc) {
|
|
1910
|
+
preds.splice(i, 1);
|
|
1911
|
+
}
|
|
1912
|
+
else {
|
|
1913
|
+
preds[i] = cur;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
cur.exsucc = succ.exsucc;
|
|
1919
|
+
cur.succs = succ.succs;
|
|
1920
|
+
if (cur.succs) {
|
|
1921
|
+
cur.succs.forEach((s) => s.preds.forEach((p, i, arr) => {
|
|
1922
|
+
if (p === succ) {
|
|
1923
|
+
arr[i] = cur;
|
|
1924
|
+
}
|
|
1925
|
+
}));
|
|
1926
|
+
}
|
|
1927
|
+
if (!cur.node)
|
|
1928
|
+
cur.node = succ.node;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
return head;
|
|
1933
|
+
}
|
|
1934
|
+
function postOrderTraverse(head, visitor) {
|
|
1935
|
+
const visited = new Set();
|
|
1936
|
+
const helper = (cur) => {
|
|
1937
|
+
if (visited.has(cur))
|
|
1938
|
+
return;
|
|
1939
|
+
visited.add(cur);
|
|
1940
|
+
if (cur.succs) {
|
|
1941
|
+
cur.succs.forEach((block) => helper(block));
|
|
1942
|
+
}
|
|
1943
|
+
if (cur.exsucc)
|
|
1944
|
+
helper(cur.exsucc);
|
|
1945
|
+
visitor(cur);
|
|
1946
|
+
};
|
|
1947
|
+
helper(head);
|
|
1948
|
+
}
|
|
1949
|
+
function preOrderTraverse(head, visitor) {
|
|
1950
|
+
const visited = new Set();
|
|
1951
|
+
const helper = (cur) => {
|
|
1952
|
+
if (visited.has(cur))
|
|
1953
|
+
return;
|
|
1954
|
+
visited.add(cur);
|
|
1955
|
+
visitor(cur);
|
|
1956
|
+
if (cur.succs) {
|
|
1957
|
+
cur.succs.forEach((block) => helper(block));
|
|
1958
|
+
}
|
|
1959
|
+
if (cur.exsucc)
|
|
1960
|
+
helper(cur.exsucc);
|
|
1961
|
+
};
|
|
1962
|
+
helper(head);
|
|
1963
|
+
}
|
|
1964
|
+
function control_flow_getPostOrder(head) {
|
|
1965
|
+
const blocks = [];
|
|
1966
|
+
postOrderTraverse(head, (block) => blocks.push(block));
|
|
1967
|
+
return blocks;
|
|
1968
|
+
}
|
|
1969
|
+
function getPreOrder(head) {
|
|
1970
|
+
const blocks = [];
|
|
1971
|
+
postOrderTraverse(head, (block) => blocks.push(block));
|
|
1972
|
+
return blocks;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// EXTERNAL MODULE: ./node_modules/priorityqueuejs/index.js
|
|
1976
|
+
var priorityqueuejs = __webpack_require__(2789);
|
|
1977
|
+
;// CONCATENATED MODULE: ./src/pre.ts
|
|
1978
|
+
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
|
|
1982
|
+
|
|
1983
|
+
/**
|
|
1984
|
+
* This implements a pseudo Partial Redundancy Elimination
|
|
1985
|
+
* pass. It isn't quite like traditional PRE because we're
|
|
1986
|
+
* aiming to minimize size, not dynamic instructions. So
|
|
1987
|
+
* for us, its worthwhile to take something like:
|
|
1988
|
+
*
|
|
1989
|
+
* switch (x) {
|
|
1990
|
+
* case 1: foo(A.B); break;
|
|
1991
|
+
* case 2: foo(C); break;
|
|
1992
|
+
* case 3: bar(A.B); break;
|
|
1993
|
+
* }
|
|
1994
|
+
*
|
|
1995
|
+
* and rewrite it as
|
|
1996
|
+
*
|
|
1997
|
+
* var tmp = A.B;
|
|
1998
|
+
* switch (x) {
|
|
1999
|
+
* case 1: foo(tmp); break;
|
|
2000
|
+
* case 2: foo(C); break;
|
|
2001
|
+
* case 3: bar(tmp); break;
|
|
2002
|
+
* }
|
|
2003
|
+
*
|
|
2004
|
+
* because even though A.B wasn't used on all paths where we
|
|
2005
|
+
* inserted the temporary, we still reduced the code size.
|
|
2006
|
+
*/
|
|
2007
|
+
const logging = false;
|
|
2008
|
+
function declFullName(decl) {
|
|
2009
|
+
switch (decl.type) {
|
|
2010
|
+
case "Literal":
|
|
2011
|
+
return decl.raw || decl.value?.toString() || "null";
|
|
2012
|
+
case "VariableDeclarator":
|
|
2013
|
+
return decl.fullName;
|
|
2014
|
+
default:
|
|
2015
|
+
throw new Error(`Unexpected EventDecl type: ${decl.type}`);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
function declName(decl) {
|
|
2019
|
+
switch (decl.type) {
|
|
2020
|
+
case "Literal":
|
|
2021
|
+
return (decl.raw || decl.value?.toString() || "null").replace(/[^\w]/g, "_");
|
|
2022
|
+
case "VariableDeclarator":
|
|
2023
|
+
return decl.name;
|
|
2024
|
+
default:
|
|
2025
|
+
throw new Error(`Unexpected EventDecl type: ${decl.type}`);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
function logAntState(s, decl) {
|
|
2029
|
+
const defs = Array.from(s.ant).reduce((defs, event) => {
|
|
2030
|
+
if (event.type === "def" || event.type === "mod")
|
|
2031
|
+
defs++;
|
|
2032
|
+
return defs;
|
|
2033
|
+
}, 0);
|
|
2034
|
+
console.log(` - ${declFullName(decl)}: ${candidateCost(s)} bytes, ${s.ant.size - defs} refs, ${defs} defs, ${s.live ? "" : "!"}live, ${s.isIsolated ? "" : "!"}isolated`);
|
|
2035
|
+
console.log(` - members: ${Array.from(s.members)
|
|
2036
|
+
.map(([block, live]) => block.order + (live ? "t" : "f"))
|
|
2037
|
+
.join(", ")}`);
|
|
2038
|
+
}
|
|
2039
|
+
function logAntDecls(antDecls) {
|
|
2040
|
+
antDecls.forEach(logAntState);
|
|
2041
|
+
}
|
|
2042
|
+
function pre_sizeBasedPRE(state, func) {
|
|
2043
|
+
if (!func.node.body)
|
|
2044
|
+
return;
|
|
2045
|
+
if (!state.config ||
|
|
2046
|
+
!state.config.sizeBasedPRE ||
|
|
2047
|
+
(typeof state.config.sizeBasedPRE === "string" &&
|
|
2048
|
+
state.config.sizeBasedPRE !== func.fullName)) {
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
const { graph: head, identifiers } = buildPREGraph(state, func);
|
|
2052
|
+
const candidates = computeAttributes(state, head);
|
|
2053
|
+
if (candidates) {
|
|
2054
|
+
if (logging) {
|
|
2055
|
+
console.log(`Found ${candidates.size} candidates in ${func.fullName}`);
|
|
2056
|
+
logAntDecls(candidates);
|
|
2057
|
+
}
|
|
2058
|
+
const nodeMap = new Map();
|
|
2059
|
+
const declMap = new Map();
|
|
2060
|
+
const variableDecl = withLoc({
|
|
2061
|
+
type: "VariableDeclaration",
|
|
2062
|
+
declarations: [],
|
|
2063
|
+
kind: "var",
|
|
2064
|
+
}, func.node.body);
|
|
2065
|
+
variableDecl.end = variableDecl.start;
|
|
2066
|
+
variableDecl.loc.end = variableDecl.loc.start;
|
|
2067
|
+
candidates.forEach((s, decl) => {
|
|
2068
|
+
let name;
|
|
2069
|
+
let i = 0;
|
|
2070
|
+
do {
|
|
2071
|
+
name = `pre_${declName(decl)}${i ? "_" + i : ""}`;
|
|
2072
|
+
if (!identifiers.has(name)) {
|
|
2073
|
+
identifiers.add(name);
|
|
2074
|
+
break;
|
|
2075
|
+
}
|
|
2076
|
+
i++;
|
|
2077
|
+
} while (true);
|
|
2078
|
+
declMap.set(decl, name);
|
|
2079
|
+
variableDecl.declarations.push(withLoc({
|
|
2080
|
+
type: "VariableDeclarator",
|
|
2081
|
+
id: withLoc({ type: "Identifier", name }, variableDecl),
|
|
2082
|
+
kind: "var",
|
|
2083
|
+
}, variableDecl));
|
|
2084
|
+
s.ant.forEach((event) => {
|
|
2085
|
+
const events = nodeMap.get(event.node);
|
|
2086
|
+
if (!events) {
|
|
2087
|
+
nodeMap.set(event.node, [event]);
|
|
2088
|
+
}
|
|
2089
|
+
else {
|
|
2090
|
+
events.push(event);
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
2093
|
+
});
|
|
2094
|
+
applyReplacements(func.node, nodeMap, declMap);
|
|
2095
|
+
func.node.body.body.unshift(variableDecl);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
function unhandledExpression(node) {
|
|
2099
|
+
throw new Error(`Unhandled expression type: ${node.type}`);
|
|
2100
|
+
}
|
|
2101
|
+
function buildPREGraph(state, func) {
|
|
2102
|
+
const findDecl = (node) => {
|
|
2103
|
+
if (node.type === "Identifier" ||
|
|
2104
|
+
(node.type === "MemberExpression" && !node.computed)) {
|
|
2105
|
+
const [, results] = state.lookup(node);
|
|
2106
|
+
if (results &&
|
|
2107
|
+
results.length === 1 &&
|
|
2108
|
+
results[0].parent?.type != "BlockStatement" &&
|
|
2109
|
+
results[0].results.length === 1 &&
|
|
2110
|
+
results[0].results[0].type === "VariableDeclarator") {
|
|
2111
|
+
return results[0].results[0];
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
return null;
|
|
2115
|
+
};
|
|
2116
|
+
const literals = new Map();
|
|
2117
|
+
const identifiers = new Set();
|
|
2118
|
+
const liveDefs = new Map();
|
|
2119
|
+
const liveStmts = new Map();
|
|
2120
|
+
const liveDef = (def, stmt) => {
|
|
2121
|
+
let curNodes = liveDefs.get(def);
|
|
2122
|
+
if (!curNodes) {
|
|
2123
|
+
liveDefs.set(def, (curNodes = new Set()));
|
|
2124
|
+
}
|
|
2125
|
+
curNodes.add(stmt);
|
|
2126
|
+
let defs = liveStmts.get(stmt);
|
|
2127
|
+
if (!defs) {
|
|
2128
|
+
liveStmts.set(stmt, (defs = new Map()));
|
|
2129
|
+
}
|
|
2130
|
+
defs.set(def, (defs.get(def) || 0) + 1);
|
|
2131
|
+
};
|
|
2132
|
+
return {
|
|
2133
|
+
identifiers,
|
|
2134
|
+
graph: buildReducedGraph(state, func, (node, stmt, mayThrow) => {
|
|
2135
|
+
const defs = liveStmts.get(node);
|
|
2136
|
+
if (defs) {
|
|
2137
|
+
liveStmts.delete(node);
|
|
2138
|
+
defs.forEach((count, def) => {
|
|
2139
|
+
if (count > 1) {
|
|
2140
|
+
defs.set(def, count--);
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
const v = liveDefs.get(def);
|
|
2144
|
+
if (!v || !v.has(node)) {
|
|
2145
|
+
throw new Error(`No stmt in liveDef for ${def ? declFullName(def) : "null"}`);
|
|
2146
|
+
}
|
|
2147
|
+
v.delete(node);
|
|
2148
|
+
if (!v.size) {
|
|
2149
|
+
liveDefs.delete(def);
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
switch (node.type) {
|
|
2154
|
+
case "BinaryExpression":
|
|
2155
|
+
case "UnaryExpression":
|
|
2156
|
+
case "SizedArrayExpression":
|
|
2157
|
+
case "ArrayExpression":
|
|
2158
|
+
case "ObjectExpression":
|
|
2159
|
+
case "ThisExpression":
|
|
2160
|
+
case "LogicalExpression":
|
|
2161
|
+
case "ConditionalExpression":
|
|
2162
|
+
case "SequenceExpression":
|
|
2163
|
+
case "ParenthesizedExpression":
|
|
2164
|
+
break;
|
|
2165
|
+
case "Literal":
|
|
2166
|
+
if (refCost(node) > LocalRefCost) {
|
|
2167
|
+
let decl = literals.get(node.value);
|
|
2168
|
+
if (!decl) {
|
|
2169
|
+
decl = node;
|
|
2170
|
+
literals.set(node.value, decl);
|
|
2171
|
+
}
|
|
2172
|
+
return {
|
|
2173
|
+
type: "ref",
|
|
2174
|
+
node,
|
|
2175
|
+
decl: decl,
|
|
2176
|
+
mayThrow,
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
break;
|
|
2180
|
+
case "Identifier":
|
|
2181
|
+
identifiers.add(node.name);
|
|
2182
|
+
// fall through
|
|
2183
|
+
case "MemberExpression":
|
|
2184
|
+
{
|
|
2185
|
+
const decl = findDecl(node);
|
|
2186
|
+
if (decl && decl.type === "VariableDeclarator") {
|
|
2187
|
+
const defStmts = (decl.node.kind === "var" && liveDefs.get(null)) ||
|
|
2188
|
+
liveDefs.get(decl);
|
|
2189
|
+
if (defStmts) {
|
|
2190
|
+
break;
|
|
2191
|
+
/*
|
|
2192
|
+
// hold off on this for now. we need to communicate
|
|
2193
|
+
// which defs need to be fixed, which involves yet-another
|
|
2194
|
+
// table.
|
|
2195
|
+
|
|
2196
|
+
if (defStmts.size !== 1) break;
|
|
2197
|
+
const fixable = isFixableStmt([...defStmts][0]);
|
|
2198
|
+
if (fixable === false) break;
|
|
2199
|
+
cost += fixable;
|
|
2200
|
+
*/
|
|
2201
|
+
}
|
|
2202
|
+
return {
|
|
2203
|
+
type: "ref",
|
|
2204
|
+
node,
|
|
2205
|
+
decl,
|
|
2206
|
+
mayThrow,
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
break;
|
|
2211
|
+
case "VariableDeclarator": {
|
|
2212
|
+
const decl = findDecl(node.id.type === "BinaryExpression" ? node.id.left : node.id);
|
|
2213
|
+
if (decl) {
|
|
2214
|
+
liveDef(decl, stmt);
|
|
2215
|
+
return {
|
|
2216
|
+
type: "def",
|
|
2217
|
+
node,
|
|
2218
|
+
decl,
|
|
2219
|
+
mayThrow,
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
break;
|
|
2223
|
+
}
|
|
2224
|
+
case "AssignmentExpression": {
|
|
2225
|
+
const decl = findDecl(node.left);
|
|
2226
|
+
if (decl) {
|
|
2227
|
+
liveDef(decl, stmt);
|
|
2228
|
+
return {
|
|
2229
|
+
type: "def",
|
|
2230
|
+
node,
|
|
2231
|
+
decl,
|
|
2232
|
+
mayThrow,
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
break;
|
|
2236
|
+
}
|
|
2237
|
+
case "UpdateExpression": {
|
|
2238
|
+
const decl = findDecl(node.argument);
|
|
2239
|
+
if (decl) {
|
|
2240
|
+
liveDef(decl, stmt);
|
|
2241
|
+
return {
|
|
2242
|
+
type: "def",
|
|
2243
|
+
node,
|
|
2244
|
+
decl,
|
|
2245
|
+
mayThrow,
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
break;
|
|
2249
|
+
}
|
|
2250
|
+
case "NewExpression": {
|
|
2251
|
+
const [, results] = state.lookup(node.callee);
|
|
2252
|
+
const callees = results ? findCalleesForNew(results) : null;
|
|
2253
|
+
liveDef(null, stmt);
|
|
2254
|
+
return { type: "mod", node, mayThrow, callees };
|
|
2255
|
+
}
|
|
2256
|
+
case "CallExpression": {
|
|
2257
|
+
liveDef(null, stmt);
|
|
2258
|
+
const [, results] = state.lookup(node.callee);
|
|
2259
|
+
const callees = results ? findCallees(results) : null;
|
|
2260
|
+
return { type: "mod", node, mayThrow, callees };
|
|
2261
|
+
}
|
|
2262
|
+
default:
|
|
2263
|
+
if (!isExpression(node))
|
|
2264
|
+
break;
|
|
2265
|
+
unhandledExpression(node);
|
|
2266
|
+
}
|
|
2267
|
+
if (mayThrow) {
|
|
2268
|
+
return { type: "exn", node, mayThrow };
|
|
2269
|
+
}
|
|
2270
|
+
return null;
|
|
2271
|
+
}),
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
function anticipatedDecls() {
|
|
2275
|
+
return new Map();
|
|
2276
|
+
}
|
|
2277
|
+
function equalSet(a, b) {
|
|
2278
|
+
if (a.size != b.size)
|
|
2279
|
+
return false;
|
|
2280
|
+
for (const item of a) {
|
|
2281
|
+
if (!b.has(item))
|
|
2282
|
+
return false;
|
|
2283
|
+
}
|
|
2284
|
+
return true;
|
|
2285
|
+
}
|
|
2286
|
+
function equalMap(a, b) {
|
|
2287
|
+
if (a.size != b.size)
|
|
2288
|
+
return false;
|
|
2289
|
+
for (const [item, value] of a) {
|
|
2290
|
+
if (b.get(item) !== value)
|
|
2291
|
+
return false;
|
|
2292
|
+
}
|
|
2293
|
+
return true;
|
|
2294
|
+
}
|
|
2295
|
+
function anticipatedState(node, events) {
|
|
2296
|
+
return { ant: events || new Set(), live: true, node, members: new Map() };
|
|
2297
|
+
}
|
|
2298
|
+
function cloneAnticipatedState(as) {
|
|
2299
|
+
return {
|
|
2300
|
+
ant: cloneSet(as.ant),
|
|
2301
|
+
live: as.live,
|
|
2302
|
+
node: as.node,
|
|
2303
|
+
members: new Map(as.members),
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
function mergeAnticipatedState(ae, be) {
|
|
2307
|
+
mergeSet(ae.ant, be.ant);
|
|
2308
|
+
be.members.forEach((live, block) => ae.members.set(block, live));
|
|
2309
|
+
if (be.live)
|
|
2310
|
+
ae.live = true;
|
|
2311
|
+
}
|
|
2312
|
+
function cloneAnticipatedDecls(ad) {
|
|
2313
|
+
const copy = anticipatedDecls();
|
|
2314
|
+
for (const [k, v] of ad) {
|
|
2315
|
+
if (!v.isIsolated) {
|
|
2316
|
+
copy.set(k, cloneAnticipatedState(v));
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
return copy;
|
|
2320
|
+
}
|
|
2321
|
+
function mergeAnticipatedDecls(a, b) {
|
|
2322
|
+
for (const [k, v] of b) {
|
|
2323
|
+
if (v.isIsolated)
|
|
2324
|
+
continue;
|
|
2325
|
+
const ae = a.get(k);
|
|
2326
|
+
if (ae) {
|
|
2327
|
+
mergeAnticipatedState(ae, v);
|
|
2328
|
+
}
|
|
2329
|
+
else {
|
|
2330
|
+
a.set(k, cloneAnticipatedState(v));
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
function equalStates(a, b) {
|
|
2335
|
+
if (a.size !== b.size)
|
|
2336
|
+
return false;
|
|
2337
|
+
for (const [k, ae] of a) {
|
|
2338
|
+
const be = b.get(k);
|
|
2339
|
+
if (!be ||
|
|
2340
|
+
be.live != ae.live ||
|
|
2341
|
+
be.isIsolated != ae.isIsolated ||
|
|
2342
|
+
!equalSet(ae.ant, be.ant) ||
|
|
2343
|
+
!equalMap(ae.members, be.members)) {
|
|
2344
|
+
return false;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return true;
|
|
2348
|
+
}
|
|
2349
|
+
const LocalRefCost = 2;
|
|
2350
|
+
function refCost(node) {
|
|
2351
|
+
if (node.type === "Literal") {
|
|
2352
|
+
switch (typeof node.value) {
|
|
2353
|
+
case "string":
|
|
2354
|
+
return 5;
|
|
2355
|
+
case "bigint":
|
|
2356
|
+
case "number":
|
|
2357
|
+
return 5;
|
|
2358
|
+
case "boolean":
|
|
2359
|
+
return 2;
|
|
2360
|
+
default:
|
|
2361
|
+
if (node.value === null) {
|
|
2362
|
+
return 2;
|
|
2363
|
+
}
|
|
2364
|
+
return 0;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
// A read from a non-local identifier takes 8 bytes
|
|
2368
|
+
let cost = 8;
|
|
2369
|
+
if (node.type === "Identifier")
|
|
2370
|
+
return cost;
|
|
2371
|
+
while (true) {
|
|
2372
|
+
const next = node.object;
|
|
2373
|
+
if (next.type != "MemberExpression") {
|
|
2374
|
+
if (next.type != "ThisExpression") {
|
|
2375
|
+
cost += next.type === "Identifier" && next.name === "$" ? 4 : 6;
|
|
2376
|
+
}
|
|
2377
|
+
return cost;
|
|
2378
|
+
}
|
|
2379
|
+
node = next;
|
|
2380
|
+
cost += 6;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
function defCost(node) {
|
|
2384
|
+
return refCost(node) + 2;
|
|
2385
|
+
}
|
|
2386
|
+
function candidateBoundary(candState) {
|
|
2387
|
+
const boundary = new Set();
|
|
2388
|
+
candState.members.forEach((live, block) => {
|
|
2389
|
+
if (live && block !== candState.head) {
|
|
2390
|
+
if (block.preds) {
|
|
2391
|
+
block.preds.forEach((pred) => candState.members.has(pred) || boundary.add(pred));
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
});
|
|
2395
|
+
if (candState.live) {
|
|
2396
|
+
if (!candState.head) {
|
|
2397
|
+
throw new Error(`Missing head`);
|
|
2398
|
+
}
|
|
2399
|
+
boundary.add(candState.head);
|
|
2400
|
+
}
|
|
2401
|
+
return boundary;
|
|
2402
|
+
}
|
|
2403
|
+
function candidateCost(candState) {
|
|
2404
|
+
let cost = 0;
|
|
2405
|
+
candState.ant.forEach((event) => {
|
|
2406
|
+
if (event.type === "ref") {
|
|
2407
|
+
cost -= refCost(candState.node) - LocalRefCost;
|
|
2408
|
+
}
|
|
2409
|
+
else {
|
|
2410
|
+
cost += defCost(candState.node);
|
|
2411
|
+
}
|
|
2412
|
+
});
|
|
2413
|
+
const boundarySize = candidateBoundary(candState).size;
|
|
2414
|
+
cost += defCost(candState.node) * boundarySize;
|
|
2415
|
+
return cost;
|
|
2416
|
+
}
|
|
2417
|
+
function computeAttributes(state, head) {
|
|
2418
|
+
const order = getPostOrder(head);
|
|
2419
|
+
order.forEach((block, i) => {
|
|
2420
|
+
block.order = i;
|
|
2421
|
+
});
|
|
2422
|
+
if (logging) {
|
|
2423
|
+
order.forEach((block) => {
|
|
2424
|
+
console.log(block.order, `(${block.node ? block.node.loc?.start.line : "??"})`, `Preds: ${(block.preds || [])
|
|
2425
|
+
.map((block) => block.order)
|
|
2426
|
+
.join(", ")}`);
|
|
2427
|
+
if (block.events) {
|
|
2428
|
+
block.events.forEach((event) => event.type !== "exn" &&
|
|
2429
|
+
console.log(` ${event.type}: ${event.decl ? declFullName(event.decl) : "??"}`));
|
|
2430
|
+
}
|
|
2431
|
+
console.log(`Succs: ${(block.succs || [])
|
|
2432
|
+
.map((block) => block.order)
|
|
2433
|
+
.join(", ")} ExSucc: ${block.exsucc ? block.exsucc.order : ""}`);
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
const enqueued = new Set();
|
|
2437
|
+
const queue = new PriorityQueue((b, a) => (a.order || 0) - (b.order || 0));
|
|
2438
|
+
const enqueue = (block) => {
|
|
2439
|
+
if (!enqueued.has(block)) {
|
|
2440
|
+
enqueued.add(block);
|
|
2441
|
+
queue.enq(block);
|
|
2442
|
+
}
|
|
2443
|
+
};
|
|
2444
|
+
const dequeue = () => {
|
|
2445
|
+
const block = queue.deq();
|
|
2446
|
+
enqueued.delete(block);
|
|
2447
|
+
return block;
|
|
2448
|
+
};
|
|
2449
|
+
const blockStates = [];
|
|
2450
|
+
/*
|
|
2451
|
+
Algorithm
|
|
2452
|
+
=========
|
|
2453
|
+
|
|
2454
|
+
Process blocks in post-order, and the events in reverse
|
|
2455
|
+
order to collect the AnticipatedState at the start of each
|
|
2456
|
+
Block.
|
|
2457
|
+
|
|
2458
|
+
Then for each EventDecl find the best starting block.
|
|
2459
|
+
*/
|
|
2460
|
+
const modMap = new Map();
|
|
2461
|
+
const getMod = (event, decl, id) => {
|
|
2462
|
+
if (id.type !== "Identifier" && id.type !== "MemberExpression") {
|
|
2463
|
+
throw new Error("Trying to modify a non-variable");
|
|
2464
|
+
}
|
|
2465
|
+
let eventMap = modMap.get(event);
|
|
2466
|
+
if (!eventMap) {
|
|
2467
|
+
modMap.set(event, (eventMap = new Map()));
|
|
2468
|
+
}
|
|
2469
|
+
let result = eventMap.get(decl);
|
|
2470
|
+
if (!result) {
|
|
2471
|
+
result = {
|
|
2472
|
+
type: "mod",
|
|
2473
|
+
node: event.node,
|
|
2474
|
+
decl,
|
|
2475
|
+
id,
|
|
2476
|
+
mayThrow: event.mayThrow,
|
|
2477
|
+
};
|
|
2478
|
+
eventMap.set(decl, result);
|
|
2479
|
+
}
|
|
2480
|
+
return result;
|
|
2481
|
+
};
|
|
2482
|
+
order.forEach((block) => enqueue(block));
|
|
2483
|
+
while (queue.size()) {
|
|
2484
|
+
const top = dequeue();
|
|
2485
|
+
if (top.order === undefined) {
|
|
2486
|
+
throw new Error(`Unreachable block was visited!`);
|
|
2487
|
+
}
|
|
2488
|
+
const curState = (top.succs &&
|
|
2489
|
+
top.succs.reduce((blockState, succ) => {
|
|
2490
|
+
const succState = blockStates[succ.order];
|
|
2491
|
+
if (succState) {
|
|
2492
|
+
if (!blockState) {
|
|
2493
|
+
blockState = cloneAnticipatedDecls(succState);
|
|
2494
|
+
}
|
|
2495
|
+
else {
|
|
2496
|
+
mergeAnticipatedDecls(blockState, succState);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return blockState;
|
|
2500
|
+
}, null)) ||
|
|
2501
|
+
anticipatedDecls();
|
|
2502
|
+
if (top.events) {
|
|
2503
|
+
for (let i = top.events.length; i--;) {
|
|
2504
|
+
const event = top.events[i];
|
|
2505
|
+
if (event.mayThrow && top.exsucc) {
|
|
2506
|
+
const succState = blockStates[top.exsucc.order];
|
|
2507
|
+
if (succState) {
|
|
2508
|
+
mergeAnticipatedDecls(curState, succState);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
switch (event.type) {
|
|
2512
|
+
case "ref": {
|
|
2513
|
+
let candidates = curState.get(event.decl);
|
|
2514
|
+
if (!candidates) {
|
|
2515
|
+
candidates = anticipatedState(event.node);
|
|
2516
|
+
curState.set(event.decl, candidates);
|
|
2517
|
+
}
|
|
2518
|
+
candidates.ant.add(event);
|
|
2519
|
+
candidates.live = true;
|
|
2520
|
+
break;
|
|
2521
|
+
}
|
|
2522
|
+
case "mod": {
|
|
2523
|
+
curState.forEach((candidates, decl) => {
|
|
2524
|
+
if (decl.type === "VariableDeclarator" &&
|
|
2525
|
+
decl.node.kind === "var" &&
|
|
2526
|
+
candidates.live &&
|
|
2527
|
+
(!event.callees ||
|
|
2528
|
+
event.callees.some((callee) => functionMayModify(state, callee, decl)))) {
|
|
2529
|
+
candidates.ant.add(getMod(event, decl, candidates.node));
|
|
2530
|
+
candidates.live = false;
|
|
2531
|
+
}
|
|
2532
|
+
});
|
|
2533
|
+
break;
|
|
2534
|
+
}
|
|
2535
|
+
case "def": {
|
|
2536
|
+
let candidates = curState.get(event.decl);
|
|
2537
|
+
const isUpdate = event.node.type === "UpdateExpression" ||
|
|
2538
|
+
(event.node.type === "AssignmentExpression" &&
|
|
2539
|
+
event.node.operator !== "=");
|
|
2540
|
+
if (!candidates) {
|
|
2541
|
+
const target = event.node.type === "AssignmentExpression"
|
|
2542
|
+
? event.node.left
|
|
2543
|
+
: event.node.type === "UpdateExpression"
|
|
2544
|
+
? event.node.argument
|
|
2545
|
+
: event.node.id.type === "BinaryExpression"
|
|
2546
|
+
? event.node.id.left
|
|
2547
|
+
: event.node.id;
|
|
2548
|
+
candidates = anticipatedState(target);
|
|
2549
|
+
curState.set(event.decl, candidates);
|
|
2550
|
+
}
|
|
2551
|
+
if (isUpdate || candidates.live) {
|
|
2552
|
+
candidates.ant.add(event);
|
|
2553
|
+
}
|
|
2554
|
+
candidates.live = isUpdate;
|
|
2555
|
+
break;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
curState.forEach((antState) => {
|
|
2561
|
+
antState.head = top;
|
|
2562
|
+
antState.members.set(top, antState.live);
|
|
2563
|
+
if (!antState.live && candidateBoundary(antState).size === 0) {
|
|
2564
|
+
// we found a group that's isolated from the rest
|
|
2565
|
+
// of the function. Don't merge it with earlier
|
|
2566
|
+
// refs and defs, because we can take it or leave
|
|
2567
|
+
// it based on its own cost.
|
|
2568
|
+
antState.isIsolated = true;
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
const oldState = blockStates[top.order];
|
|
2572
|
+
if (oldState && equalStates(oldState, curState)) {
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
blockStates[top.order] = curState;
|
|
2576
|
+
if (logging) {
|
|
2577
|
+
console.log(`Updated block ${top.order}`);
|
|
2578
|
+
logAntDecls(curState);
|
|
2579
|
+
}
|
|
2580
|
+
if (top.preds) {
|
|
2581
|
+
top.preds.forEach((pred) => enqueue(pred));
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
const candidateDecls = anticipatedDecls();
|
|
2585
|
+
blockStates.forEach((blockState, i) => {
|
|
2586
|
+
blockState &&
|
|
2587
|
+
blockState.forEach((events, decl) => {
|
|
2588
|
+
const cost = candidateCost(events);
|
|
2589
|
+
if (cost >= 0)
|
|
2590
|
+
return;
|
|
2591
|
+
const existing = candidateDecls.get(decl);
|
|
2592
|
+
if (!existing ||
|
|
2593
|
+
existing.isIsolated ||
|
|
2594
|
+
candidateCost(existing) > cost) {
|
|
2595
|
+
const boundary = candidateBoundary(events);
|
|
2596
|
+
if (!Array.from(boundary).every((block) => {
|
|
2597
|
+
if (block !== events.head && block.events) {
|
|
2598
|
+
if (events.node.type === "Literal") {
|
|
2599
|
+
return false;
|
|
2600
|
+
}
|
|
2601
|
+
let i = block.events.length;
|
|
2602
|
+
while (i--) {
|
|
2603
|
+
const event = block.events[i];
|
|
2604
|
+
if (event.type === "def" || event.type === "mod") {
|
|
2605
|
+
events.ant.add({
|
|
2606
|
+
type: "mod",
|
|
2607
|
+
node: event.node,
|
|
2608
|
+
decl,
|
|
2609
|
+
id: events.node,
|
|
2610
|
+
mayThrow: false,
|
|
2611
|
+
});
|
|
2612
|
+
events.members.set(block, false);
|
|
2613
|
+
return true;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
const node = block.node;
|
|
2618
|
+
if (!node)
|
|
2619
|
+
return false;
|
|
2620
|
+
events.ant.add({
|
|
2621
|
+
type: "mod",
|
|
2622
|
+
node: node.type === "FunctionDeclaration" ? node.body : node,
|
|
2623
|
+
before: true,
|
|
2624
|
+
decl,
|
|
2625
|
+
id: events.node,
|
|
2626
|
+
mayThrow: false,
|
|
2627
|
+
});
|
|
2628
|
+
events.members.set(block, false);
|
|
2629
|
+
return true;
|
|
2630
|
+
})) {
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
events.live = false;
|
|
2634
|
+
if (existing && existing.isIsolated) {
|
|
2635
|
+
delete existing.isIsolated;
|
|
2636
|
+
mergeAnticipatedState(events, existing);
|
|
2637
|
+
}
|
|
2638
|
+
else if (candidateCost(events) != cost) {
|
|
2639
|
+
throw new Error(`cost of block ${i} changed`);
|
|
2640
|
+
}
|
|
2641
|
+
candidateDecls.set(decl, events);
|
|
2642
|
+
}
|
|
2643
|
+
});
|
|
2644
|
+
});
|
|
2645
|
+
if (candidateDecls.size) {
|
|
2646
|
+
return candidateDecls;
|
|
2647
|
+
}
|
|
2648
|
+
return null;
|
|
2649
|
+
}
|
|
2650
|
+
/*
|
|
2651
|
+
* Determine the cost of fixing a def under a statement.
|
|
2652
|
+
*
|
|
2653
|
+
* eg:
|
|
2654
|
+
*
|
|
2655
|
+
* if (foo()) {
|
|
2656
|
+
* bar(X.y);
|
|
2657
|
+
* } else {
|
|
2658
|
+
* baz(X.y);
|
|
2659
|
+
* }
|
|
2660
|
+
*
|
|
2661
|
+
* Here, we could pull out X.y as a local, but if foo might modify
|
|
2662
|
+
* X.y, we have nowhere to insert the temporary. But we can rewrite
|
|
2663
|
+
* it as:
|
|
2664
|
+
*
|
|
2665
|
+
* var tmp = foo();
|
|
2666
|
+
* if (tmp) {
|
|
2667
|
+
* bar(X.y);
|
|
2668
|
+
* } else {
|
|
2669
|
+
* baz(X.y);
|
|
2670
|
+
* }
|
|
2671
|
+
*
|
|
2672
|
+
* and now we can insert a temporary before the if, but it costs
|
|
2673
|
+
* 4 bytes to do so.
|
|
2674
|
+
*
|
|
2675
|
+
* We can do the same for switch statements unless (ugh!)
|
|
2676
|
+
* the cases might modify the decl too.
|
|
2677
|
+
*
|
|
2678
|
+
* eg
|
|
2679
|
+
*
|
|
2680
|
+
* switch (foo()) {
|
|
2681
|
+
* case bar(): ...
|
|
2682
|
+
* }
|
|
2683
|
+
*
|
|
2684
|
+
*/
|
|
2685
|
+
function _isFixableStmt(node) {
|
|
2686
|
+
switch (node.type) {
|
|
2687
|
+
case "IfStatement":
|
|
2688
|
+
return 4;
|
|
2689
|
+
case "SwitchStatement":
|
|
2690
|
+
if (node.cases.every((c) => !c.test ||
|
|
2691
|
+
c.test.type === "Literal" ||
|
|
2692
|
+
c.test.type === "Identifier" ||
|
|
2693
|
+
c.test.type === "InstanceOfCase" ||
|
|
2694
|
+
(c.test.type === "UnaryExpression" && c.test.operator === ":"))) {
|
|
2695
|
+
return 4;
|
|
2696
|
+
}
|
|
2697
|
+
break;
|
|
2698
|
+
}
|
|
2699
|
+
return false;
|
|
2700
|
+
}
|
|
2701
|
+
function applyReplacements(func, nodeMap, declMap) {
|
|
2702
|
+
const ident = (name, node) => {
|
|
2703
|
+
return withLoc({ type: "Identifier", name }, node);
|
|
2704
|
+
};
|
|
2705
|
+
const pendingMap = new Map();
|
|
2706
|
+
const stmtStack = [func];
|
|
2707
|
+
traverseAst(func, (node) => {
|
|
2708
|
+
if (isStatement(node)) {
|
|
2709
|
+
stmtStack.push(node);
|
|
2710
|
+
}
|
|
2711
|
+
}, (node) => {
|
|
2712
|
+
const stmt = stmtStack[stmtStack.length - 1];
|
|
2713
|
+
if (stmt === node)
|
|
2714
|
+
stmtStack.pop();
|
|
2715
|
+
const events = nodeMap.get(node);
|
|
2716
|
+
if (events) {
|
|
2717
|
+
const ret = events.reduce((ret, event) => {
|
|
2718
|
+
if (event.type === "ref") {
|
|
2719
|
+
if (ret) {
|
|
2720
|
+
throw new Error(`ref found when there was already a replacement for this node`);
|
|
2721
|
+
}
|
|
2722
|
+
if (node.type !== "Identifier" &&
|
|
2723
|
+
node.type !== "MemberExpression" &&
|
|
2724
|
+
node.type !== "Literal") {
|
|
2725
|
+
throw new Error(`Ref found, but wrong type of node: ${node.type}`);
|
|
2726
|
+
}
|
|
2727
|
+
const name = declMap.get(event.decl);
|
|
2728
|
+
if (!name) {
|
|
2729
|
+
throw new Error(`No replacement found for "${formatAst(node)}"`);
|
|
2730
|
+
}
|
|
2731
|
+
return ident(name, node);
|
|
2732
|
+
}
|
|
2733
|
+
if (event.type === "def") {
|
|
2734
|
+
if (ret) {
|
|
2735
|
+
throw new Error(`def found when there was already a replacement for this node`);
|
|
2736
|
+
}
|
|
2737
|
+
if (node.type !== "AssignmentExpression" &&
|
|
2738
|
+
node.type !== "UpdateExpression") {
|
|
2739
|
+
throw new Error(`Def found, but wrong type of node: ${node.type}`);
|
|
2740
|
+
}
|
|
2741
|
+
const target = node.type === "AssignmentExpression"
|
|
2742
|
+
? node.left
|
|
2743
|
+
: node.argument;
|
|
2744
|
+
const name = declMap.get(event.decl);
|
|
2745
|
+
if (!name) {
|
|
2746
|
+
throw new Error(`No replacement found for "${formatAst(target)}"`);
|
|
2747
|
+
}
|
|
2748
|
+
const id = ident(name, target);
|
|
2749
|
+
const assign = withLoc({
|
|
2750
|
+
type: "AssignmentExpression",
|
|
2751
|
+
left: target,
|
|
2752
|
+
right: { ...id },
|
|
2753
|
+
operator: "=",
|
|
2754
|
+
}, node);
|
|
2755
|
+
if (node.type === "AssignmentExpression") {
|
|
2756
|
+
node.left = id;
|
|
2757
|
+
}
|
|
2758
|
+
else {
|
|
2759
|
+
node.argument = id;
|
|
2760
|
+
}
|
|
2761
|
+
return withLoc({ type: "SequenceExpression", expressions: [node, assign] }, node);
|
|
2762
|
+
}
|
|
2763
|
+
if (event.type === "mod") {
|
|
2764
|
+
if (!event.decl) {
|
|
2765
|
+
throw new Error(`Unexpected null decl on mod event`);
|
|
2766
|
+
}
|
|
2767
|
+
let pending = pendingMap.get(stmt);
|
|
2768
|
+
if (!pending) {
|
|
2769
|
+
pendingMap.set(stmt, (pending = new Set()));
|
|
2770
|
+
}
|
|
2771
|
+
pending.add(event);
|
|
2772
|
+
}
|
|
2773
|
+
else {
|
|
2774
|
+
throw new Error(`Unexpected ${event.type} found`);
|
|
2775
|
+
}
|
|
2776
|
+
return ret;
|
|
2777
|
+
}, null);
|
|
2778
|
+
if (ret) {
|
|
2779
|
+
return ret;
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
const pending = pendingMap.get(node);
|
|
2783
|
+
if (node.type === "SequenceExpression") {
|
|
2784
|
+
if (pending) {
|
|
2785
|
+
throw new Error(`Unexpected pending list at SequenceExpression`);
|
|
2786
|
+
}
|
|
2787
|
+
for (let i = node.expressions.length; i--;) {
|
|
2788
|
+
const ni = node.expressions[i];
|
|
2789
|
+
if (ni.type === "SequenceExpression") {
|
|
2790
|
+
node.expressions.splice(i, 1, ...ni.expressions);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
const applyPending = (results, locNode) => {
|
|
2795
|
+
const target = results.length === 1 && results[0].type === "BlockStatement"
|
|
2796
|
+
? results[0]
|
|
2797
|
+
: null;
|
|
2798
|
+
pendingMap.delete(node);
|
|
2799
|
+
pending.forEach((event) => {
|
|
2800
|
+
const decl = event.decl;
|
|
2801
|
+
const name = declMap.get(decl);
|
|
2802
|
+
if (!name) {
|
|
2803
|
+
throw new Error(`No replacement found for "${declFullName(decl)}"`);
|
|
2804
|
+
}
|
|
2805
|
+
if (!event.id) {
|
|
2806
|
+
throw new Error(`Missing id for mod event for "${declFullName(decl)}"`);
|
|
2807
|
+
}
|
|
2808
|
+
const rhs = withLocDeep(event.id, locNode, locNode);
|
|
2809
|
+
rhs.end = rhs.start;
|
|
2810
|
+
if (rhs.loc) {
|
|
2811
|
+
rhs.loc.end = rhs.loc.start;
|
|
2812
|
+
}
|
|
2813
|
+
const insertion = withLoc({
|
|
2814
|
+
type: "ExpressionStatement",
|
|
2815
|
+
expression: withLoc({
|
|
2816
|
+
type: "AssignmentExpression",
|
|
2817
|
+
left: ident(name, rhs),
|
|
2818
|
+
right: rhs,
|
|
2819
|
+
operator: "=",
|
|
2820
|
+
}, rhs),
|
|
2821
|
+
}, rhs);
|
|
2822
|
+
if (event.type === "mod" && event.before) {
|
|
2823
|
+
if (target) {
|
|
2824
|
+
target.body.unshift(insertion);
|
|
2825
|
+
}
|
|
2826
|
+
else {
|
|
2827
|
+
results.unshift(insertion);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
else {
|
|
2831
|
+
results.push(insertion);
|
|
2832
|
+
}
|
|
2833
|
+
});
|
|
2834
|
+
return results.length === 1 ? null : results;
|
|
2835
|
+
};
|
|
2836
|
+
if (node.type === "ExpressionStatement" &&
|
|
2837
|
+
node.expression.type === "SequenceExpression") {
|
|
2838
|
+
const results = [];
|
|
2839
|
+
node.expression.expressions.forEach((expression) => {
|
|
2840
|
+
results.push({ ...node, expression });
|
|
2841
|
+
});
|
|
2842
|
+
if (!pending) {
|
|
2843
|
+
return results;
|
|
2844
|
+
}
|
|
2845
|
+
return applyPending(results, node);
|
|
2846
|
+
}
|
|
2847
|
+
if (pending) {
|
|
2848
|
+
return applyPending([node], node);
|
|
2849
|
+
}
|
|
2850
|
+
return null;
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
;// CONCATENATED MODULE: ./src/unused-exprs.ts
|
|
2855
|
+
|
|
2856
|
+
|
|
2857
|
+
|
|
2858
|
+
function unused_exprs_cleanupUnusedVars(state, node) {
|
|
2859
|
+
const [parent] = state.stack.slice(-1);
|
|
2860
|
+
if (parent.node !== node) {
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
if (parent.type != "BlockStatement") {
|
|
2864
|
+
throw new Error(`Unexpected parent type '${parent.type}' for local declaration`);
|
|
2865
|
+
}
|
|
2866
|
+
if (parent.decls) {
|
|
2867
|
+
let toRemove = null;
|
|
2868
|
+
Object.values(parent.decls).forEach((decls) => {
|
|
2869
|
+
if (decls.length === 1 &&
|
|
2870
|
+
decls[0].type === "VariableDeclarator" &&
|
|
2871
|
+
!decls[0].used) {
|
|
2872
|
+
if (!toRemove)
|
|
2873
|
+
toRemove = {};
|
|
2874
|
+
toRemove[decls[0].name] = decls[0];
|
|
2875
|
+
}
|
|
2876
|
+
});
|
|
2877
|
+
if (toRemove) {
|
|
2878
|
+
const varDeclarations = new Map();
|
|
2879
|
+
traverseAst(node, null, (node) => {
|
|
2880
|
+
switch (node.type) {
|
|
2881
|
+
case "VariableDeclaration": {
|
|
2882
|
+
node.declarations.forEach((decl, i) => {
|
|
2883
|
+
const name = variableDeclarationName(decl.id);
|
|
2884
|
+
if (hasProperty(toRemove, name)) {
|
|
2885
|
+
const indices = varDeclarations.get(node);
|
|
2886
|
+
if (indices) {
|
|
2887
|
+
indices.push(i);
|
|
2888
|
+
}
|
|
2889
|
+
else {
|
|
2890
|
+
varDeclarations.set(node, [i]);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
});
|
|
2894
|
+
break;
|
|
2895
|
+
}
|
|
2896
|
+
case "ExpressionStatement":
|
|
2897
|
+
if (node.expression.type === "AssignmentExpression") {
|
|
2898
|
+
if (node.expression.left.type === "Identifier" &&
|
|
2899
|
+
hasProperty(toRemove, node.expression.left.name)) {
|
|
2900
|
+
return unused(node.expression.right);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
else if (node.expression.type === "UpdateExpression" &&
|
|
2904
|
+
node.expression.argument.type === "Identifier" &&
|
|
2905
|
+
hasProperty(toRemove, node.expression.argument.name)) {
|
|
2906
|
+
return false;
|
|
2907
|
+
}
|
|
2908
|
+
break;
|
|
2909
|
+
case "SequenceExpression": {
|
|
2910
|
+
for (let i = node.expressions.length; i--;) {
|
|
2911
|
+
const expr = node.expressions[i];
|
|
2912
|
+
if (expr.type === "AssignmentExpression") {
|
|
2913
|
+
if (expr.left.type === "Identifier" &&
|
|
2914
|
+
hasProperty(toRemove, expr.left.name)) {
|
|
2915
|
+
const rep = unused(expr.right);
|
|
2916
|
+
if (!rep.length) {
|
|
2917
|
+
node.expressions.splice(i, 1);
|
|
2918
|
+
}
|
|
2919
|
+
else {
|
|
2920
|
+
// Sequence expressions can only be assignments
|
|
2921
|
+
// or update expressions. Even calls aren't allowed
|
|
2922
|
+
toRemove[expr.left.name] = null;
|
|
2923
|
+
expr.operator = "=";
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
else if (expr.type === "UpdateExpression" &&
|
|
2928
|
+
expr.argument.type === "Identifier" &&
|
|
2929
|
+
hasProperty(toRemove, expr.argument.name)) {
|
|
2930
|
+
node.expressions.splice(i, 1);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
break;
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
return null;
|
|
2937
|
+
});
|
|
2938
|
+
varDeclarations.forEach((indices, decl) => {
|
|
2939
|
+
let index = -1;
|
|
2940
|
+
for (let ii = indices.length, j = decl.declarations.length; ii--;) {
|
|
2941
|
+
const i = indices[ii];
|
|
2942
|
+
const vdecl = decl.declarations[i];
|
|
2943
|
+
const name = variableDeclarationName(vdecl.id);
|
|
2944
|
+
if (hasProperty(toRemove, name)) {
|
|
2945
|
+
const rep = vdecl.init ? unused(vdecl.init) : [];
|
|
2946
|
+
if (rep.length) {
|
|
2947
|
+
if (parent.node.type === "ForStatement") {
|
|
2948
|
+
// declarations whose inits have side effects
|
|
2949
|
+
// can't be deleted from for statements.
|
|
2950
|
+
continue;
|
|
2951
|
+
}
|
|
2952
|
+
if (index < 0) {
|
|
2953
|
+
index = parent.node.body.findIndex((s) => s === decl);
|
|
2954
|
+
if (index < 0) {
|
|
2955
|
+
throw new Error(`Failed to find variable declaration for ${variableDeclarationName(vdecl.id)}`);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
if (j > i + 1) {
|
|
2959
|
+
const tail = {
|
|
2960
|
+
...decl,
|
|
2961
|
+
declarations: decl.declarations.slice(i + 1, j),
|
|
2962
|
+
};
|
|
2963
|
+
if (decl.loc && vdecl.loc) {
|
|
2964
|
+
tail.loc = { ...decl.loc, start: vdecl.loc.end };
|
|
2965
|
+
tail.start = vdecl.end;
|
|
2966
|
+
}
|
|
2967
|
+
rep.push(tail);
|
|
2968
|
+
}
|
|
2969
|
+
if (decl.loc && vdecl.loc) {
|
|
2970
|
+
decl.loc = { ...decl.loc, end: vdecl.loc.start };
|
|
2971
|
+
decl.end = vdecl.start;
|
|
2972
|
+
}
|
|
2973
|
+
decl.declarations.splice(i);
|
|
2974
|
+
parent.node.body.splice(index + 1, 0, ...rep);
|
|
2975
|
+
j = i;
|
|
2976
|
+
continue;
|
|
2977
|
+
}
|
|
2978
|
+
if (toRemove[name]) {
|
|
2979
|
+
j--;
|
|
2980
|
+
decl.declarations.splice(i, 1);
|
|
2981
|
+
if (i === j && decl.loc && vdecl.loc) {
|
|
2982
|
+
decl.loc = { ...decl.loc, end: vdecl.loc.start };
|
|
2983
|
+
decl.end = vdecl.start;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
else {
|
|
2987
|
+
delete vdecl.init;
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
803
2994
|
}
|
|
804
2995
|
|
|
805
|
-
;// CONCATENATED MODULE: external "./util.cjs"
|
|
806
|
-
const external_util_cjs_namespaceObject = require("./util.cjs");
|
|
807
2996
|
;// CONCATENATED MODULE: ./src/visitor.ts
|
|
808
2997
|
|
|
809
2998
|
function visitor_visitReferences(state, ast, name, defn, callback) {
|
|
@@ -868,14 +3057,16 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
|
|
|
868
3057
|
return checkResults(state.lookup(node), node);
|
|
869
3058
|
}
|
|
870
3059
|
break;
|
|
871
|
-
case "MemberExpression":
|
|
872
|
-
|
|
873
|
-
|
|
3060
|
+
case "MemberExpression": {
|
|
3061
|
+
const property = (0,external_api_cjs_namespaceObject.isLookupCandidate)(node);
|
|
3062
|
+
if (property) {
|
|
3063
|
+
if (!name || property.name === name) {
|
|
874
3064
|
return checkResults(state.lookup(node), node) || ["object"];
|
|
875
3065
|
}
|
|
876
3066
|
return ["object"];
|
|
877
3067
|
}
|
|
878
3068
|
break;
|
|
3069
|
+
}
|
|
879
3070
|
case "MethodDefinition": {
|
|
880
3071
|
if (!state.inType) {
|
|
881
3072
|
throw new Error("Method definition outside of type!");
|
|
@@ -884,7 +3075,6 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
|
|
|
884
3075
|
node.params.forEach((param) => {
|
|
885
3076
|
if (param.type == "BinaryExpression") {
|
|
886
3077
|
state.traverse(param.right);
|
|
887
|
-
state.inType = true;
|
|
888
3078
|
}
|
|
889
3079
|
});
|
|
890
3080
|
}
|
|
@@ -906,6 +3096,10 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
|
|
|
906
3096
|
|
|
907
3097
|
|
|
908
3098
|
|
|
3099
|
+
|
|
3100
|
+
|
|
3101
|
+
|
|
3102
|
+
|
|
909
3103
|
function collectClassInfo(state) {
|
|
910
3104
|
const toybox = state.stack[0].decls["Toybox"][0];
|
|
911
3105
|
const lang = toybox.decls["Lang"][0];
|
|
@@ -965,8 +3159,7 @@ function collectClassInfo(state) {
|
|
|
965
3159
|
c.decls &&
|
|
966
3160
|
Object.values(c.decls).forEach((funcs) => {
|
|
967
3161
|
funcs.forEach((f) => {
|
|
968
|
-
if (
|
|
969
|
-
f.type === "FunctionDeclaration" &&
|
|
3162
|
+
if (f.type === "FunctionDeclaration" &&
|
|
970
3163
|
hasProperty(cls.decls, f.name)) {
|
|
971
3164
|
f.node.hasOverride = true;
|
|
972
3165
|
}
|
|
@@ -979,6 +3172,15 @@ function collectClassInfo(state) {
|
|
|
979
3172
|
state.allClasses.forEach((elm) => {
|
|
980
3173
|
if (elm.superClass)
|
|
981
3174
|
markOverrides(elm, elm.superClass);
|
|
3175
|
+
if (elm.hasInvoke && elm.decls) {
|
|
3176
|
+
Object.values(elm.decls).forEach((funcs) => {
|
|
3177
|
+
funcs.forEach((f) => {
|
|
3178
|
+
if (f.type === "FunctionDeclaration" && !f.isStatic) {
|
|
3179
|
+
markInvokeClassMethod(f);
|
|
3180
|
+
}
|
|
3181
|
+
});
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
982
3184
|
});
|
|
983
3185
|
}
|
|
984
3186
|
function getFileSources(fnMap) {
|
|
@@ -1018,7 +3220,7 @@ async function analyze(fnMap, barrelList, config) {
|
|
|
1018
3220
|
const preState = {
|
|
1019
3221
|
fnMap,
|
|
1020
3222
|
config,
|
|
1021
|
-
allFunctions:
|
|
3223
|
+
allFunctions: {},
|
|
1022
3224
|
allClasses: [],
|
|
1023
3225
|
shouldExclude(node) {
|
|
1024
3226
|
if ("attrs" in node &&
|
|
@@ -1048,11 +3250,6 @@ async function analyze(fnMap, barrelList, config) {
|
|
|
1048
3250
|
pre(node, state) {
|
|
1049
3251
|
switch (node.type) {
|
|
1050
3252
|
case "FunctionDeclaration":
|
|
1051
|
-
if (markApi) {
|
|
1052
|
-
node.body = null;
|
|
1053
|
-
break;
|
|
1054
|
-
}
|
|
1055
|
-
// falls through
|
|
1056
3253
|
case "ModuleDeclaration":
|
|
1057
3254
|
case "ClassDeclaration": {
|
|
1058
3255
|
const [scope] = state.stack.slice(-1);
|
|
@@ -1063,7 +3260,18 @@ async function analyze(fnMap, barrelList, config) {
|
|
|
1063
3260
|
(scope.node.attrs &&
|
|
1064
3261
|
scope.node.attrs.access &&
|
|
1065
3262
|
scope.node.attrs.access.includes("static"));
|
|
1066
|
-
|
|
3263
|
+
if (markApi) {
|
|
3264
|
+
node.body = null;
|
|
3265
|
+
scope.info = getApiFunctionInfo(scope);
|
|
3266
|
+
delete scope.stack;
|
|
3267
|
+
}
|
|
3268
|
+
const allFuncs = state.allFunctions;
|
|
3269
|
+
if (!hasProperty(allFuncs, scope.name)) {
|
|
3270
|
+
allFuncs[scope.name] = [scope];
|
|
3271
|
+
}
|
|
3272
|
+
else {
|
|
3273
|
+
allFuncs[scope.name].push(scope);
|
|
3274
|
+
}
|
|
1067
3275
|
}
|
|
1068
3276
|
else if (scope.type === "ClassDeclaration") {
|
|
1069
3277
|
state.allClasses.push(scope);
|
|
@@ -1088,7 +3296,7 @@ async function analyze(fnMap, barrelList, config) {
|
|
|
1088
3296
|
value.hasTests = hasTests;
|
|
1089
3297
|
});
|
|
1090
3298
|
delete state.shouldExclude;
|
|
1091
|
-
delete state.
|
|
3299
|
+
delete state.pre;
|
|
1092
3300
|
collectClassInfo(state);
|
|
1093
3301
|
const diagnosticType = config?.checkInvalidSymbols !== "OFF"
|
|
1094
3302
|
? config?.checkInvalidSymbols || "WARNING"
|
|
@@ -1111,6 +3319,8 @@ async function analyze(fnMap, barrelList, config) {
|
|
|
1111
3319
|
});
|
|
1112
3320
|
});
|
|
1113
3321
|
}
|
|
3322
|
+
state.exposed = state.nextExposed;
|
|
3323
|
+
state.nextExposed = {};
|
|
1114
3324
|
return state;
|
|
1115
3325
|
}
|
|
1116
3326
|
function compareLiteralLike(a, b) {
|
|
@@ -1156,15 +3366,12 @@ function getLiteralNode(node) {
|
|
|
1156
3366
|
if (node.argument.type != "Literal")
|
|
1157
3367
|
return null;
|
|
1158
3368
|
switch (node.operator) {
|
|
1159
|
-
case "-":
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
value: -node.argument.value,
|
|
1164
|
-
raw: "-" + node.argument.value,
|
|
1165
|
-
enumType: node.enumType,
|
|
1166
|
-
};
|
|
3369
|
+
case "-": {
|
|
3370
|
+
const [arg, type] = getNodeValue(node.argument);
|
|
3371
|
+
if (type === "Number" || type === "Long") {
|
|
3372
|
+
return replacementLiteral(arg, -arg.value, type);
|
|
1167
3373
|
}
|
|
3374
|
+
}
|
|
1168
3375
|
}
|
|
1169
3376
|
}
|
|
1170
3377
|
return null;
|
|
@@ -1183,31 +3390,114 @@ function getNodeValue(node) {
|
|
|
1183
3390
|
if (node.type != "Literal") {
|
|
1184
3391
|
return [null, null];
|
|
1185
3392
|
}
|
|
1186
|
-
|
|
3393
|
+
if (node.value === null) {
|
|
3394
|
+
return [node, "Null"];
|
|
3395
|
+
}
|
|
3396
|
+
const type = typeof node.value;
|
|
1187
3397
|
if (type === "number") {
|
|
1188
|
-
const match =
|
|
3398
|
+
const match = prettier_plugin_monkeyc_namespaceObject.LiteralIntegerRe.exec(node.raw);
|
|
1189
3399
|
if (match) {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
type = "Double";
|
|
3400
|
+
return match[2] === "l" || match[2] === "L"
|
|
3401
|
+
? [node, "Long"]
|
|
3402
|
+
: [node, "Number"];
|
|
1194
3403
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
3404
|
+
return [node, node.raw.endsWith("d") ? "Double" : "Float"];
|
|
3405
|
+
}
|
|
3406
|
+
if (type === "bigint") {
|
|
3407
|
+
return [node, "Long"];
|
|
3408
|
+
}
|
|
3409
|
+
if (type === "string") {
|
|
3410
|
+
return [node, "String"];
|
|
3411
|
+
}
|
|
3412
|
+
if (type === "boolean") {
|
|
3413
|
+
return [node, "Boolean"];
|
|
3414
|
+
}
|
|
3415
|
+
throw new Error(`Literal has unknown type '${type}'`);
|
|
3416
|
+
}
|
|
3417
|
+
function fullTypeName(state, tsp) {
|
|
3418
|
+
if (typeof tsp.name === "string") {
|
|
3419
|
+
return tsp.name;
|
|
3420
|
+
}
|
|
3421
|
+
const [, results] = state.lookupType(tsp.name);
|
|
3422
|
+
if (results && results.length === 1 && results[0].results.length === 1) {
|
|
3423
|
+
const result = results[0].results[0];
|
|
3424
|
+
if (isStateNode(result)) {
|
|
3425
|
+
return result.fullName;
|
|
1197
3426
|
}
|
|
1198
3427
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
3428
|
+
return null;
|
|
3429
|
+
}
|
|
3430
|
+
function isBooleanExpression(state, node) {
|
|
3431
|
+
switch (node.type) {
|
|
3432
|
+
case "Literal":
|
|
3433
|
+
return typeof node.value === "boolean";
|
|
3434
|
+
case "BinaryExpression":
|
|
3435
|
+
switch (node.operator) {
|
|
3436
|
+
case "==":
|
|
3437
|
+
case "!=":
|
|
3438
|
+
case "<=":
|
|
3439
|
+
case ">=":
|
|
3440
|
+
case "<":
|
|
3441
|
+
case ">":
|
|
3442
|
+
return true;
|
|
3443
|
+
case "as":
|
|
3444
|
+
return node.right.ts.length === 1 &&
|
|
3445
|
+
node.right.ts[0].type === "TypeSpecPart" &&
|
|
3446
|
+
node.right.ts[0].name &&
|
|
3447
|
+
fullTypeName(state, node.right.ts[0]) === "$.Toybox.Lang.Boolean"
|
|
3448
|
+
? true
|
|
3449
|
+
: false;
|
|
3450
|
+
}
|
|
3451
|
+
return false;
|
|
3452
|
+
case "LogicalExpression":
|
|
3453
|
+
return (isBooleanExpression(state, node.left) &&
|
|
3454
|
+
isBooleanExpression(state, node.right));
|
|
3455
|
+
case "UnaryExpression":
|
|
3456
|
+
return node.operator === "!" && isBooleanExpression(state, node.argument);
|
|
1201
3457
|
}
|
|
1202
|
-
|
|
3458
|
+
return false;
|
|
3459
|
+
}
|
|
3460
|
+
function replacementLiteral(arg, value, type) {
|
|
3461
|
+
if (typeof value === "boolean") {
|
|
1203
3462
|
type = "Boolean";
|
|
1204
3463
|
}
|
|
1205
|
-
else {
|
|
1206
|
-
|
|
3464
|
+
else if (type === "Number") {
|
|
3465
|
+
value = Number(BigInt.asIntN(32, BigInt(value)));
|
|
3466
|
+
}
|
|
3467
|
+
else if (type === "Long") {
|
|
3468
|
+
value = BigInt.asIntN(64, BigInt(value));
|
|
1207
3469
|
}
|
|
1208
|
-
return
|
|
3470
|
+
return {
|
|
3471
|
+
...arg,
|
|
3472
|
+
value,
|
|
3473
|
+
raw: value.toString() + (type === "Long" ? "l" : ""),
|
|
3474
|
+
};
|
|
1209
3475
|
}
|
|
1210
|
-
|
|
3476
|
+
const operators = {
|
|
3477
|
+
"+": (left, right) => left + right,
|
|
3478
|
+
"-": (left, right) => left - right,
|
|
3479
|
+
"*": (left, right) => left * right,
|
|
3480
|
+
"/": (left, right) => left / right,
|
|
3481
|
+
"%": (left, right) => left % right,
|
|
3482
|
+
"&": (left, right) => left & right,
|
|
3483
|
+
"|": (left, right) => left | right,
|
|
3484
|
+
"^": (left, right) => left ^ right,
|
|
3485
|
+
"<<": (left, right) => left << (right & 127n),
|
|
3486
|
+
">>": (left, right) => left >> (right & 127n),
|
|
3487
|
+
"==": (left, right) =>
|
|
3488
|
+
// two string literals will compare unequal, becuase string
|
|
3489
|
+
// equality is object equality.
|
|
3490
|
+
typeof left === "string" ? false : left === right,
|
|
3491
|
+
"!=": (left, right) => typeof left === "string" ? true : left !== right,
|
|
3492
|
+
"<=": (left, right) => left <= right,
|
|
3493
|
+
">=": (left, right) => left >= right,
|
|
3494
|
+
"<": (left, right) => left < right,
|
|
3495
|
+
">": (left, right) => left > right,
|
|
3496
|
+
as: null,
|
|
3497
|
+
instanceof: null,
|
|
3498
|
+
has: null,
|
|
3499
|
+
};
|
|
3500
|
+
function optimizeNode(state, node) {
|
|
1211
3501
|
switch (node.type) {
|
|
1212
3502
|
case "UnaryExpression": {
|
|
1213
3503
|
const [arg, type] = getNodeValue(node.argument);
|
|
@@ -1221,29 +3511,17 @@ function optimizeNode(node) {
|
|
|
1221
3511
|
break;
|
|
1222
3512
|
case "-":
|
|
1223
3513
|
if (type === "Number" || type === "Long") {
|
|
1224
|
-
return
|
|
1225
|
-
...arg,
|
|
1226
|
-
value: -arg.value,
|
|
1227
|
-
raw: (-arg.value).toString() + (type === "Long" ? "l" : ""),
|
|
1228
|
-
};
|
|
3514
|
+
return replacementLiteral(arg, -arg.value, type);
|
|
1229
3515
|
}
|
|
1230
3516
|
break;
|
|
1231
3517
|
case "!":
|
|
1232
3518
|
case "~":
|
|
1233
3519
|
{
|
|
1234
|
-
let value;
|
|
1235
3520
|
if (type === "Number" || type === "Long") {
|
|
1236
|
-
|
|
3521
|
+
return replacementLiteral(arg, ~BigInt(arg.value), type);
|
|
1237
3522
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
}
|
|
1241
|
-
if (value !== undefined) {
|
|
1242
|
-
return {
|
|
1243
|
-
...arg,
|
|
1244
|
-
value,
|
|
1245
|
-
raw: value.toString() + (type === "Long" ? "l" : ""),
|
|
1246
|
-
};
|
|
3523
|
+
if (type === "Boolean" && node.operator == "!") {
|
|
3524
|
+
return replacementLiteral(arg, !arg.value, type);
|
|
1247
3525
|
}
|
|
1248
3526
|
}
|
|
1249
3527
|
break;
|
|
@@ -1251,56 +3529,81 @@ function optimizeNode(node) {
|
|
|
1251
3529
|
break;
|
|
1252
3530
|
}
|
|
1253
3531
|
case "BinaryExpression": {
|
|
1254
|
-
const operators = {
|
|
1255
|
-
"+": (left, right) => left + right,
|
|
1256
|
-
"-": (left, right) => left - right,
|
|
1257
|
-
"*": (left, right) => left * right,
|
|
1258
|
-
"/": (left, right) => Math.trunc(left / right),
|
|
1259
|
-
"%": (left, right) => left % right,
|
|
1260
|
-
"&": (left, right, type) => type === "Number" ? left & right : null,
|
|
1261
|
-
"|": (left, right, type) => type === "Number" ? left | right : null,
|
|
1262
|
-
"<<": (left, right, type) => type === "Number" ? left << right : null,
|
|
1263
|
-
">>": (left, right, type) => type === "Number" ? left >> right : null,
|
|
1264
|
-
};
|
|
1265
3532
|
const op = operators[node.operator];
|
|
1266
3533
|
if (op) {
|
|
1267
3534
|
const [left, left_type] = getNodeValue(node.left);
|
|
1268
3535
|
const [right, right_type] = getNodeValue(node.right);
|
|
1269
3536
|
if (!left || !right)
|
|
1270
3537
|
break;
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
3538
|
+
let value = null;
|
|
3539
|
+
let type;
|
|
3540
|
+
if ((left_type != "Number" && left_type != "Long") ||
|
|
3541
|
+
left_type != right_type) {
|
|
3542
|
+
if (node.operator !== "==" && node.operator !== "!=") {
|
|
3543
|
+
break;
|
|
3544
|
+
}
|
|
3545
|
+
value = operators[node.operator](left.value, right.value);
|
|
3546
|
+
type = "Boolean";
|
|
3547
|
+
}
|
|
3548
|
+
else {
|
|
3549
|
+
type = left_type;
|
|
3550
|
+
value = op(BigInt(left.value), BigInt(right.value));
|
|
1274
3551
|
}
|
|
1275
|
-
const value = op(left.value, right.value, left_type);
|
|
1276
3552
|
if (value === null)
|
|
1277
3553
|
break;
|
|
1278
|
-
return
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
3554
|
+
return replacementLiteral(left, value, type);
|
|
3555
|
+
}
|
|
3556
|
+
break;
|
|
3557
|
+
}
|
|
3558
|
+
case "LogicalExpression": {
|
|
3559
|
+
const [left, left_type] = getNodeValue(node.left);
|
|
3560
|
+
if (!left)
|
|
3561
|
+
break;
|
|
3562
|
+
const falsy = left.value === false ||
|
|
3563
|
+
left.value === null ||
|
|
3564
|
+
((left_type === "Number" || left_type === "Long") &&
|
|
3565
|
+
(left.value === 0 || left.value === 0n));
|
|
3566
|
+
if (falsy === (node.operator === "&&")) {
|
|
3567
|
+
return left;
|
|
3568
|
+
}
|
|
3569
|
+
if (left_type !== "Boolean" &&
|
|
3570
|
+
left_type !== "Number" &&
|
|
3571
|
+
left_type !== "Long") {
|
|
3572
|
+
break;
|
|
3573
|
+
}
|
|
3574
|
+
const [right, right_type] = getNodeValue(node.right);
|
|
3575
|
+
if (right && right_type === left_type) {
|
|
3576
|
+
if (left_type === "Boolean" || node.operator === "||") {
|
|
3577
|
+
return right;
|
|
3578
|
+
}
|
|
3579
|
+
if (node.operator !== "&&") {
|
|
3580
|
+
throw new Error(`Unexpected operator "${node.operator}"`);
|
|
3581
|
+
}
|
|
3582
|
+
return { ...node, type: "BinaryExpression", operator: "&" };
|
|
3583
|
+
}
|
|
3584
|
+
if (left_type === "Boolean") {
|
|
3585
|
+
if (isBooleanExpression(state, node.right)) {
|
|
3586
|
+
return node.right;
|
|
3587
|
+
}
|
|
1283
3588
|
}
|
|
1284
3589
|
break;
|
|
1285
3590
|
}
|
|
1286
3591
|
case "FunctionDeclaration":
|
|
1287
|
-
if (node.body && evaluateFunction(node, null) !== false) {
|
|
3592
|
+
if (node.body && evaluateFunction(state, node, null) !== false) {
|
|
1288
3593
|
node.optimizable = true;
|
|
1289
3594
|
}
|
|
1290
3595
|
break;
|
|
1291
3596
|
}
|
|
1292
3597
|
return null;
|
|
1293
3598
|
}
|
|
1294
|
-
function evaluateFunction(func, args) {
|
|
3599
|
+
function evaluateFunction(state, func, args) {
|
|
1295
3600
|
if (!func.body || (args && args.length != func.params.length)) {
|
|
1296
3601
|
return false;
|
|
1297
3602
|
}
|
|
1298
3603
|
const paramValues = args &&
|
|
1299
3604
|
Object.fromEntries(func.params.map((p, i) => [variableDeclarationName(p), args[i]]));
|
|
1300
3605
|
let ret = null;
|
|
1301
|
-
const body = args
|
|
1302
|
-
? JSON.parse(JSON.stringify(func.body))
|
|
1303
|
-
: func.body;
|
|
3606
|
+
const body = args ? cloneDeep(func.body) : func.body;
|
|
1304
3607
|
try {
|
|
1305
3608
|
traverseAst(body, (node) => {
|
|
1306
3609
|
switch (node.type) {
|
|
@@ -1330,7 +3633,7 @@ function evaluateFunction(func, args) {
|
|
|
1330
3633
|
}
|
|
1331
3634
|
// fall through;
|
|
1332
3635
|
default: {
|
|
1333
|
-
const repl = optimizeNode(node);
|
|
3636
|
+
const repl = optimizeNode(state, node);
|
|
1334
3637
|
if (repl && repl.type === "Literal")
|
|
1335
3638
|
return repl;
|
|
1336
3639
|
throw new Error("Didn't optimize");
|
|
@@ -1351,12 +3654,10 @@ function markFunctionCalled(state, func) {
|
|
|
1351
3654
|
pushUnique(state.calledFunctions[func.id.name], func);
|
|
1352
3655
|
}
|
|
1353
3656
|
async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
1354
|
-
const state =
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
calledFunctions: {},
|
|
1359
|
-
};
|
|
3657
|
+
const state = (await analyze(fnMap, barrelList, config));
|
|
3658
|
+
state.localsStack = [{}];
|
|
3659
|
+
state.calledFunctions = {};
|
|
3660
|
+
state.usedByName = {};
|
|
1360
3661
|
const replace = (node, old) => {
|
|
1361
3662
|
if (node === false || node === null)
|
|
1362
3663
|
return node;
|
|
@@ -1390,10 +3691,19 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1390
3691
|
if (!objects) {
|
|
1391
3692
|
return false;
|
|
1392
3693
|
}
|
|
1393
|
-
|
|
3694
|
+
let obj = getLiteralFromDecls(objects);
|
|
1394
3695
|
if (!obj) {
|
|
1395
3696
|
return false;
|
|
1396
3697
|
}
|
|
3698
|
+
while (obj.type === "BinaryExpression") {
|
|
3699
|
+
if (obj.left.type === "BinaryExpression" && obj.left.operator === "as") {
|
|
3700
|
+
obj = { ...obj, left: obj.left.left };
|
|
3701
|
+
}
|
|
3702
|
+
else {
|
|
3703
|
+
obj = { ...obj, left: { ...obj.left } };
|
|
3704
|
+
break;
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
1397
3707
|
inPlaceReplacement(node, obj);
|
|
1398
3708
|
return true;
|
|
1399
3709
|
};
|
|
@@ -1436,6 +3746,58 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1436
3746
|
f.type == "FunctionDeclaration" &&
|
|
1437
3747
|
maybeCalled(f.node))) ||
|
|
1438
3748
|
(sc.superClass && checkInherited(sc, name))));
|
|
3749
|
+
const renamer = (idnode) => {
|
|
3750
|
+
const ident = idnode.type === "Identifier" ? idnode : idnode.left;
|
|
3751
|
+
const locals = topLocals();
|
|
3752
|
+
const { map } = locals;
|
|
3753
|
+
if (map) {
|
|
3754
|
+
const declName = ident.name;
|
|
3755
|
+
const name = renameVariable(state, locals, declName);
|
|
3756
|
+
if (name) {
|
|
3757
|
+
const [, results] = state.lookupValue(ident);
|
|
3758
|
+
if (!results) {
|
|
3759
|
+
throw new Error(`Didn't find local ${declName} which needed renaming`);
|
|
3760
|
+
}
|
|
3761
|
+
if (results.length !== 1) {
|
|
3762
|
+
throw new Error(`Lookup of local ${declName} found more than one result`);
|
|
3763
|
+
}
|
|
3764
|
+
const parent = results[0].parent;
|
|
3765
|
+
if (!parent) {
|
|
3766
|
+
throw new Error(`No parent in lookup of local ${declName}`);
|
|
3767
|
+
}
|
|
3768
|
+
const decls = parent.decls;
|
|
3769
|
+
if (!decls || !hasProperty(decls, declName)) {
|
|
3770
|
+
throw new Error(`Missing decls in lookup of local ${declName}`);
|
|
3771
|
+
}
|
|
3772
|
+
if (hasProperty(decls, name)) {
|
|
3773
|
+
throw new Error(`While renaming ${declName} to ${name}, there was already a variable ${name}`);
|
|
3774
|
+
}
|
|
3775
|
+
if (decls[declName].length === 1) {
|
|
3776
|
+
decls[name] = decls[declName];
|
|
3777
|
+
delete decls[declName];
|
|
3778
|
+
}
|
|
3779
|
+
else {
|
|
3780
|
+
let i = decls[declName].length;
|
|
3781
|
+
while (i--) {
|
|
3782
|
+
const decl = decls[declName][i];
|
|
3783
|
+
if (decl === idnode ||
|
|
3784
|
+
(decl.type === "VariableDeclarator" && decl.node.id === idnode)) {
|
|
3785
|
+
decls[declName].splice(i, 1);
|
|
3786
|
+
decls[name] = [decl];
|
|
3787
|
+
break;
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
if (i < 0) {
|
|
3791
|
+
throw new Error(`While renaming ${declName} to ${name}: Didn't find original declaration`);
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
ident.name = name;
|
|
3795
|
+
}
|
|
3796
|
+
else {
|
|
3797
|
+
map[declName] = true;
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
};
|
|
1439
3801
|
state.pre = (node) => {
|
|
1440
3802
|
switch (node.type) {
|
|
1441
3803
|
case "ConditionalExpression":
|
|
@@ -1456,7 +3818,11 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1456
3818
|
result = !!value.value;
|
|
1457
3819
|
}
|
|
1458
3820
|
if (result !== null) {
|
|
1459
|
-
node.test = {
|
|
3821
|
+
node.test = {
|
|
3822
|
+
type: "Literal",
|
|
3823
|
+
value: result,
|
|
3824
|
+
raw: result.toString(),
|
|
3825
|
+
};
|
|
1460
3826
|
if (node.type === "IfStatement" ||
|
|
1461
3827
|
node.type === "ConditionalExpression") {
|
|
1462
3828
|
return [result ? "consequent" : "alternate"];
|
|
@@ -1475,7 +3841,7 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1475
3841
|
return null;
|
|
1476
3842
|
}
|
|
1477
3843
|
case "EnumDeclaration":
|
|
1478
|
-
return
|
|
3844
|
+
return [];
|
|
1479
3845
|
case "ForStatement": {
|
|
1480
3846
|
const map = topLocals().map;
|
|
1481
3847
|
if (map) {
|
|
@@ -1484,43 +3850,13 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1484
3850
|
break;
|
|
1485
3851
|
}
|
|
1486
3852
|
case "VariableDeclarator": {
|
|
1487
|
-
|
|
1488
|
-
const { map } = locals;
|
|
1489
|
-
if (map) {
|
|
1490
|
-
const declName = variableDeclarationName(node.id);
|
|
1491
|
-
const name = renameVariable(state, locals, declName);
|
|
1492
|
-
if (name) {
|
|
1493
|
-
if (node.id.type === "Identifier") {
|
|
1494
|
-
node.id.name = name;
|
|
1495
|
-
}
|
|
1496
|
-
else {
|
|
1497
|
-
node.id.left.name = name;
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
else {
|
|
1501
|
-
map[declName] = true;
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
3853
|
+
renamer(node.id);
|
|
1504
3854
|
return ["init"];
|
|
1505
3855
|
}
|
|
1506
3856
|
case "CatchClause":
|
|
1507
3857
|
if (node.param) {
|
|
1508
3858
|
state.localsStack.push({ node, map: { ...(topLocals().map || {}) } });
|
|
1509
|
-
|
|
1510
|
-
const map = locals.map;
|
|
1511
|
-
const declName = variableDeclarationName(node.param);
|
|
1512
|
-
const name = renameVariable(state, locals, declName);
|
|
1513
|
-
if (name) {
|
|
1514
|
-
if (node.param.type === "Identifier") {
|
|
1515
|
-
node.param.name = name;
|
|
1516
|
-
}
|
|
1517
|
-
else {
|
|
1518
|
-
node.param.left.name = name;
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
else {
|
|
1522
|
-
map[declName] = true;
|
|
1523
|
-
}
|
|
3859
|
+
renamer(node.param);
|
|
1524
3860
|
return ["body"];
|
|
1525
3861
|
}
|
|
1526
3862
|
break;
|
|
@@ -1536,14 +3872,8 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1536
3872
|
break;
|
|
1537
3873
|
case "UnaryExpression":
|
|
1538
3874
|
if (node.operator == ":") {
|
|
1539
|
-
//
|
|
1540
|
-
//
|
|
1541
|
-
// indirectly, so we can't remove any enums or
|
|
1542
|
-
// constants with that name (we can still replace
|
|
1543
|
-
// uses of those constants though).
|
|
1544
|
-
state.exposed[node.argument.name] = true;
|
|
1545
|
-
// In any case, we can't replace *this* use of the
|
|
1546
|
-
// symbol with its value...
|
|
3875
|
+
// node.argument is not a normal identifier.
|
|
3876
|
+
// don't visit it.
|
|
1547
3877
|
return [];
|
|
1548
3878
|
}
|
|
1549
3879
|
break;
|
|
@@ -1555,29 +3885,73 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1555
3885
|
if (typeof name === "string") {
|
|
1556
3886
|
node.name = name;
|
|
1557
3887
|
}
|
|
3888
|
+
const [, results] = state.lookupValue(node);
|
|
3889
|
+
if (results) {
|
|
3890
|
+
if (results.length !== 1 || results[0].results.length !== 1) {
|
|
3891
|
+
throw new Error(`Local ${node.name} had multiple lookup results`);
|
|
3892
|
+
}
|
|
3893
|
+
const parent = results[0].parent;
|
|
3894
|
+
if (!parent) {
|
|
3895
|
+
throw new Error(`Local ${node.name} had no parent`);
|
|
3896
|
+
}
|
|
3897
|
+
const decl = results[0].results[0];
|
|
3898
|
+
if (parent.type === "FunctionDeclaration" ||
|
|
3899
|
+
decl.type !== "VariableDeclarator") {
|
|
3900
|
+
// we can't optimize away function or catch parameters
|
|
3901
|
+
return [];
|
|
3902
|
+
}
|
|
3903
|
+
if (parent.type !== "BlockStatement") {
|
|
3904
|
+
throw new Error(`Local ${node.name} was not declared at block scope(??)`);
|
|
3905
|
+
}
|
|
3906
|
+
decl.used = true;
|
|
3907
|
+
}
|
|
1558
3908
|
}
|
|
1559
3909
|
}
|
|
1560
3910
|
if (hasProperty(state.index, node.name)) {
|
|
1561
3911
|
if (!lookupAndReplace(node)) {
|
|
1562
|
-
state.
|
|
3912
|
+
state.usedByName[node.name] = true;
|
|
1563
3913
|
}
|
|
1564
3914
|
}
|
|
1565
3915
|
return [];
|
|
1566
3916
|
}
|
|
1567
|
-
case "MemberExpression":
|
|
1568
|
-
|
|
1569
|
-
|
|
3917
|
+
case "MemberExpression": {
|
|
3918
|
+
const property = isLookupCandidate(node);
|
|
3919
|
+
if (property) {
|
|
3920
|
+
if (hasProperty(state.index, property.name)) {
|
|
1570
3921
|
if (lookupAndReplace(node)) {
|
|
1571
3922
|
return false;
|
|
1572
3923
|
}
|
|
1573
3924
|
else {
|
|
1574
|
-
state.
|
|
3925
|
+
state.usedByName[property.name] = true;
|
|
1575
3926
|
}
|
|
1576
3927
|
}
|
|
1577
3928
|
// Don't optimize the property.
|
|
1578
3929
|
return ["object"];
|
|
1579
3930
|
}
|
|
1580
3931
|
break;
|
|
3932
|
+
}
|
|
3933
|
+
case "AssignmentExpression":
|
|
3934
|
+
case "UpdateExpression": {
|
|
3935
|
+
const lhs = node.type === "AssignmentExpression" ? node.left : node.argument;
|
|
3936
|
+
if (lhs.type === "Identifier") {
|
|
3937
|
+
const map = topLocals().map;
|
|
3938
|
+
if (map) {
|
|
3939
|
+
if (hasProperty(map, lhs.name)) {
|
|
3940
|
+
const name = map[lhs.name];
|
|
3941
|
+
if (typeof name === "string") {
|
|
3942
|
+
lhs.name = name;
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
else if (lhs.type === "MemberExpression") {
|
|
3948
|
+
state.traverse(lhs.object);
|
|
3949
|
+
if (lhs.computed) {
|
|
3950
|
+
state.traverse(lhs.property);
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
return node.type === "AssignmentExpression" ? ["right"] : [];
|
|
3954
|
+
}
|
|
1581
3955
|
case "BlockStatement": {
|
|
1582
3956
|
const map = topLocals().map;
|
|
1583
3957
|
if (map) {
|
|
@@ -1593,7 +3967,11 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1593
3967
|
node.params &&
|
|
1594
3968
|
node.params.forEach((p) => (map[variableDeclarationName(p)] = true));
|
|
1595
3969
|
state.localsStack.push({ node, map });
|
|
1596
|
-
const [parent] = state.stack.slice(-2);
|
|
3970
|
+
const [parent, self] = state.stack.slice(-2);
|
|
3971
|
+
if (state.currentFunction) {
|
|
3972
|
+
throw new Error(`Nested functions: ${self.fullName} was activated during processing of ${state.currentFunction.fullName}`);
|
|
3973
|
+
}
|
|
3974
|
+
state.currentFunction = self;
|
|
1597
3975
|
if (parent.type == "ClassDeclaration" && !maybeCalled(node)) {
|
|
1598
3976
|
let used = false;
|
|
1599
3977
|
if (node.id.name == "initialize") {
|
|
@@ -1611,14 +3989,33 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1611
3989
|
return null;
|
|
1612
3990
|
};
|
|
1613
3991
|
state.post = (node) => {
|
|
1614
|
-
|
|
3992
|
+
const locals = topLocals();
|
|
3993
|
+
if (locals.node === node) {
|
|
1615
3994
|
state.localsStack.pop();
|
|
1616
3995
|
}
|
|
1617
|
-
const opt = optimizeNode(node);
|
|
3996
|
+
const opt = optimizeNode(state, node);
|
|
1618
3997
|
if (opt) {
|
|
1619
3998
|
return replace(opt, node);
|
|
1620
3999
|
}
|
|
1621
4000
|
switch (node.type) {
|
|
4001
|
+
case "FunctionDeclaration":
|
|
4002
|
+
if (!state.currentFunction) {
|
|
4003
|
+
throw new Error(`Finished function ${state.stack.slice(-1)[0].fullName}, but it was not marked current`);
|
|
4004
|
+
}
|
|
4005
|
+
state.currentFunction.info = state.currentFunction.next_info;
|
|
4006
|
+
delete state.currentFunction.next_info;
|
|
4007
|
+
delete state.currentFunction;
|
|
4008
|
+
break;
|
|
4009
|
+
case "BlockStatement":
|
|
4010
|
+
if (node.body.length === 1 && node.body[0].type === "BlockStatement") {
|
|
4011
|
+
node.body.splice(0, 1, ...node.body[0].body);
|
|
4012
|
+
}
|
|
4013
|
+
// fall through
|
|
4014
|
+
case "ForStatement":
|
|
4015
|
+
if (locals.map) {
|
|
4016
|
+
cleanupUnusedVars(state, node);
|
|
4017
|
+
}
|
|
4018
|
+
break;
|
|
1622
4019
|
case "ConditionalExpression":
|
|
1623
4020
|
case "IfStatement":
|
|
1624
4021
|
if (node.test.type === "Literal" &&
|
|
@@ -1628,6 +4025,12 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1628
4025
|
return false;
|
|
1629
4026
|
return replace(rep, rep);
|
|
1630
4027
|
}
|
|
4028
|
+
else if (node.type === "IfStatement" &&
|
|
4029
|
+
node.alternate &&
|
|
4030
|
+
node.alternate.type === "BlockStatement" &&
|
|
4031
|
+
!node.alternate.body.length) {
|
|
4032
|
+
delete node.alternate;
|
|
4033
|
+
}
|
|
1631
4034
|
break;
|
|
1632
4035
|
case "WhileStatement":
|
|
1633
4036
|
if (node.test.type === "Literal" && node.test.value === false) {
|
|
@@ -1644,17 +4047,62 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1644
4047
|
return replace(optimizeCall(state, node.argument, node), node.argument);
|
|
1645
4048
|
}
|
|
1646
4049
|
break;
|
|
4050
|
+
case "NewExpression":
|
|
4051
|
+
if (state.currentFunction) {
|
|
4052
|
+
const [, results] = state.lookup(node.callee);
|
|
4053
|
+
if (results) {
|
|
4054
|
+
recordCalledFuncs(state.currentFunction, findCalleesForNew(results));
|
|
4055
|
+
}
|
|
4056
|
+
else {
|
|
4057
|
+
recordModifiedUnknown(state.currentFunction);
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
break;
|
|
1647
4061
|
case "CallExpression": {
|
|
1648
4062
|
return replace(optimizeCall(state, node, null), node);
|
|
1649
4063
|
}
|
|
1650
|
-
case "
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
node
|
|
1654
|
-
node.
|
|
1655
|
-
|
|
4064
|
+
case "VariableDeclaration": {
|
|
4065
|
+
const locals = topLocals();
|
|
4066
|
+
if (locals.map &&
|
|
4067
|
+
locals.node &&
|
|
4068
|
+
locals.node.type === "BlockStatement") {
|
|
4069
|
+
let results;
|
|
4070
|
+
const declarations = node.declarations;
|
|
4071
|
+
let i = 0;
|
|
4072
|
+
let j = 0;
|
|
4073
|
+
while (i < node.declarations.length) {
|
|
4074
|
+
const decl = declarations[i++];
|
|
4075
|
+
if (decl.init && decl.init.type === "CallExpression") {
|
|
4076
|
+
const inlined = replace(optimizeCall(state, decl.init, decl), decl.init);
|
|
4077
|
+
if (!inlined)
|
|
4078
|
+
continue;
|
|
4079
|
+
if (Array.isArray(inlined) || inlined.type != "BlockStatement") {
|
|
4080
|
+
throw new Error("Unexpected inlined result");
|
|
4081
|
+
}
|
|
4082
|
+
if (!results) {
|
|
4083
|
+
results = [];
|
|
4084
|
+
}
|
|
4085
|
+
delete decl.init;
|
|
4086
|
+
results.push(withLoc({
|
|
4087
|
+
...node,
|
|
4088
|
+
declarations: declarations.slice(j, i),
|
|
4089
|
+
}, j ? declarations[j] : null, decl.id));
|
|
4090
|
+
results.push(inlined);
|
|
4091
|
+
j = i;
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
if (results) {
|
|
4095
|
+
if (j < i) {
|
|
4096
|
+
results.push({
|
|
4097
|
+
...node,
|
|
4098
|
+
declarations: declarations.slice(j, i),
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
return results;
|
|
4102
|
+
}
|
|
1656
4103
|
}
|
|
1657
4104
|
break;
|
|
4105
|
+
}
|
|
1658
4106
|
case "ExpressionStatement":
|
|
1659
4107
|
if (node.expression.type === "CallExpression") {
|
|
1660
4108
|
return replace(optimizeCall(state, node.expression, node), node.expression);
|
|
@@ -1686,6 +4134,32 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1686
4134
|
}
|
|
1687
4135
|
}
|
|
1688
4136
|
break;
|
|
4137
|
+
case "AssignmentExpression":
|
|
4138
|
+
if (node.operator === "=" &&
|
|
4139
|
+
node.left.type === "Identifier" &&
|
|
4140
|
+
node.right.type === "Identifier" &&
|
|
4141
|
+
node.left.name === node.right.name) {
|
|
4142
|
+
return { type: "Literal", value: null, raw: "null" };
|
|
4143
|
+
}
|
|
4144
|
+
// fall through;
|
|
4145
|
+
case "UpdateExpression":
|
|
4146
|
+
if (state.currentFunction) {
|
|
4147
|
+
const lhs = node.type === "AssignmentExpression" ? node.left : node.argument;
|
|
4148
|
+
const [, results] = state.lookup(lhs);
|
|
4149
|
+
if (results) {
|
|
4150
|
+
recordModifiedDecls(state.currentFunction, results);
|
|
4151
|
+
}
|
|
4152
|
+
else {
|
|
4153
|
+
const id = lhs.type === "Identifier" ? lhs : isLookupCandidate(lhs);
|
|
4154
|
+
if (id) {
|
|
4155
|
+
recordModifiedName(state.currentFunction, id.name);
|
|
4156
|
+
}
|
|
4157
|
+
else {
|
|
4158
|
+
recordModifiedUnknown(state.currentFunction);
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
break;
|
|
1689
4163
|
}
|
|
1690
4164
|
return null;
|
|
1691
4165
|
};
|
|
@@ -1693,12 +4167,16 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1693
4167
|
collectNamespaces(f.ast, state);
|
|
1694
4168
|
});
|
|
1695
4169
|
state.calledFunctions = {};
|
|
1696
|
-
state.exposed =
|
|
4170
|
+
state.exposed = state.nextExposed;
|
|
4171
|
+
state.nextExposed = {};
|
|
1697
4172
|
Object.values(fnMap).forEach((f) => {
|
|
1698
4173
|
collectNamespaces(f.ast, state);
|
|
1699
4174
|
});
|
|
4175
|
+
state.exposed = state.nextExposed;
|
|
4176
|
+
state.nextExposed = {};
|
|
1700
4177
|
delete state.pre;
|
|
1701
4178
|
delete state.post;
|
|
4179
|
+
Object.values(state.allFunctions).forEach((fns) => fns.forEach((fn) => sizeBasedPRE(state, fn)));
|
|
1702
4180
|
const cleanup = (node) => {
|
|
1703
4181
|
switch (node.type) {
|
|
1704
4182
|
case "ThisExpression":
|
|
@@ -1708,7 +4186,8 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1708
4186
|
if (node.members.every((m) => {
|
|
1709
4187
|
const name = "name" in m ? m.name : m.id.name;
|
|
1710
4188
|
return (hasProperty(state.index, name) &&
|
|
1711
|
-
!hasProperty(state.exposed, name)
|
|
4189
|
+
!hasProperty(state.exposed, name) &&
|
|
4190
|
+
!hasProperty(state.usedByName, name));
|
|
1712
4191
|
})) {
|
|
1713
4192
|
node.enumType = [
|
|
1714
4193
|
...new Set(node.members.map((m) => {
|
|
@@ -1751,7 +4230,9 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1751
4230
|
case "VariableDeclaration": {
|
|
1752
4231
|
node.declarations = node.declarations.filter((d) => {
|
|
1753
4232
|
const name = variableDeclarationName(d.id);
|
|
1754
|
-
return (!hasProperty(state.index, name) ||
|
|
4233
|
+
return (!hasProperty(state.index, name) ||
|
|
4234
|
+
hasProperty(state.exposed, name) ||
|
|
4235
|
+
hasProperty(state.usedByName, name));
|
|
1755
4236
|
});
|
|
1756
4237
|
if (!node.declarations.length) {
|
|
1757
4238
|
return false;
|
|
@@ -1784,7 +4265,7 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1784
4265
|
}
|
|
1785
4266
|
return null;
|
|
1786
4267
|
};
|
|
1787
|
-
Object.
|
|
4268
|
+
Object.entries(fnMap).forEach(([name, f]) => {
|
|
1788
4269
|
traverseAst(f.ast, undefined, (node) => {
|
|
1789
4270
|
const ret = cleanup(node);
|
|
1790
4271
|
if (ret === false) {
|
|
@@ -1792,16 +4273,15 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
1792
4273
|
}
|
|
1793
4274
|
return ret;
|
|
1794
4275
|
});
|
|
4276
|
+
if (state.config && state.config.checkBuildPragmas) {
|
|
4277
|
+
pragmaChecker(state, f.ast, state.diagnostics?.[name]);
|
|
4278
|
+
}
|
|
1795
4279
|
});
|
|
1796
4280
|
return state.diagnostics;
|
|
1797
4281
|
}
|
|
1798
4282
|
function optimizeCall(state, node, context) {
|
|
1799
4283
|
const [name, results] = state.lookupNonlocal(node.callee);
|
|
1800
|
-
const callees = results
|
|
1801
|
-
results
|
|
1802
|
-
.map((r) => r.results)
|
|
1803
|
-
.flat()
|
|
1804
|
-
.filter((c) => c.type === "FunctionDeclaration");
|
|
4284
|
+
const callees = results ? findCallees(results) : null;
|
|
1805
4285
|
if (!callees || !callees.length) {
|
|
1806
4286
|
const n = name ||
|
|
1807
4287
|
("name" in node.callee && node.callee.name) ||
|
|
@@ -1810,21 +4290,31 @@ function optimizeCall(state, node, context) {
|
|
|
1810
4290
|
"name" in node.callee.property &&
|
|
1811
4291
|
node.callee.property.name);
|
|
1812
4292
|
if (n) {
|
|
1813
|
-
state.
|
|
4293
|
+
if (hasProperty(state.allFunctions, n)) {
|
|
4294
|
+
if (state.currentFunction) {
|
|
4295
|
+
recordCalledFuncs(state.currentFunction, state.allFunctions[n]);
|
|
4296
|
+
}
|
|
4297
|
+
state.allFunctions[n].forEach((fn) => markFunctionCalled(state, fn.node));
|
|
4298
|
+
}
|
|
1814
4299
|
}
|
|
1815
|
-
else {
|
|
1816
|
-
//
|
|
1817
|
-
//
|
|
4300
|
+
else if (state.currentFunction) {
|
|
4301
|
+
// I don't think this can happen: foo[x](args)
|
|
4302
|
+
// doesn't parse, so you can't even do things like
|
|
4303
|
+
// $.Toybox.Lang[:format]("fmt", [])
|
|
4304
|
+
recordModifiedUnknown(state.currentFunction);
|
|
1818
4305
|
}
|
|
1819
4306
|
return null;
|
|
1820
4307
|
}
|
|
4308
|
+
if (state.currentFunction) {
|
|
4309
|
+
recordCalledFuncs(state.currentFunction, callees);
|
|
4310
|
+
}
|
|
1821
4311
|
if (callees.length == 1 && callees[0].type === "FunctionDeclaration") {
|
|
1822
4312
|
const callee = callees[0].node;
|
|
1823
4313
|
if (!context &&
|
|
1824
4314
|
callee.optimizable &&
|
|
1825
4315
|
!callee.hasOverride &&
|
|
1826
4316
|
node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
|
|
1827
|
-
const ret = evaluateFunction(callee, node.arguments);
|
|
4317
|
+
const ret = evaluateFunction(state, callee, node.arguments);
|
|
1828
4318
|
if (ret) {
|
|
1829
4319
|
return ret;
|
|
1830
4320
|
}
|
|
@@ -1984,11 +4474,8 @@ async function api_getApiMapping(state, barrelList) {
|
|
|
1984
4474
|
return null;
|
|
1985
4475
|
}
|
|
1986
4476
|
}
|
|
1987
|
-
function api_hasProperty(obj, prop) {
|
|
1988
|
-
return obj ? Object.prototype.hasOwnProperty.call(obj, prop) : false;
|
|
1989
|
-
}
|
|
1990
4477
|
function api_isStateNode(node) {
|
|
1991
|
-
return
|
|
4478
|
+
return ast_hasProperty(node, "node");
|
|
1992
4479
|
}
|
|
1993
4480
|
function api_variableDeclarationName(node) {
|
|
1994
4481
|
return ("left" in node ? node.left : node).name;
|
|
@@ -2008,7 +4495,7 @@ function checkOne(state, ns, decls, node, isStatic) {
|
|
|
2008
4495
|
}
|
|
2009
4496
|
return cls.superClass.reduce((result, sup) => {
|
|
2010
4497
|
const sdecls = sup[decls];
|
|
2011
|
-
const next =
|
|
4498
|
+
const next = ast_hasProperty(sdecls, node.name)
|
|
2012
4499
|
? sdecls[node.name]
|
|
2013
4500
|
: superChain(sup);
|
|
2014
4501
|
return next ? (result ? result.concat(next) : next) : result;
|
|
@@ -2031,20 +4518,18 @@ function checkOne(state, ns, decls, node, isStatic) {
|
|
|
2031
4518
|
return next ? (result ? result.concat(next) : next) : result;
|
|
2032
4519
|
}, null);
|
|
2033
4520
|
};
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
return lookupInContext(ns) || false;
|
|
2047
|
-
}
|
|
4521
|
+
const ndecls = ns[decls];
|
|
4522
|
+
if (ast_hasProperty(ndecls, node.name)) {
|
|
4523
|
+
return ndecls[node.name];
|
|
4524
|
+
}
|
|
4525
|
+
switch (ns.type) {
|
|
4526
|
+
case "ClassDeclaration":
|
|
4527
|
+
if (!isStatic) {
|
|
4528
|
+
return superChain(ns) || superChainScopes(ns) || false;
|
|
4529
|
+
}
|
|
4530
|
+
// fall through
|
|
4531
|
+
case "ModuleDeclaration":
|
|
4532
|
+
return lookupInContext(ns) || false;
|
|
2048
4533
|
}
|
|
2049
4534
|
return null;
|
|
2050
4535
|
}
|
|
@@ -2067,6 +4552,13 @@ function sameLookupDefinition(a, b) {
|
|
|
2067
4552
|
function api_sameLookupResult(a, b) {
|
|
2068
4553
|
return (0,external_util_cjs_namespaceObject.sameArrays)(a, b, sameLookupDefinition);
|
|
2069
4554
|
}
|
|
4555
|
+
function api_isLookupCandidate(node) {
|
|
4556
|
+
return node.computed
|
|
4557
|
+
? node.property.type === "UnaryExpression" &&
|
|
4558
|
+
node.property.operator === ":" &&
|
|
4559
|
+
node.property.argument
|
|
4560
|
+
: node.property.type === "Identifier" && node.property;
|
|
4561
|
+
}
|
|
2070
4562
|
/**
|
|
2071
4563
|
*
|
|
2072
4564
|
* @param state - The ProgramState
|
|
@@ -2087,9 +4579,9 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
|
|
|
2087
4579
|
const stack = maybeStack || state.stack;
|
|
2088
4580
|
switch (node.type) {
|
|
2089
4581
|
case "MemberExpression": {
|
|
2090
|
-
|
|
4582
|
+
const property = api_isLookupCandidate(node);
|
|
4583
|
+
if (!property)
|
|
2091
4584
|
break;
|
|
2092
|
-
const property = node.property;
|
|
2093
4585
|
let result;
|
|
2094
4586
|
if (node.object.type === "ThisExpression") {
|
|
2095
4587
|
[, result] = lookup(state, decls, node.property, name, stack, true);
|
|
@@ -2103,6 +4595,9 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
|
|
|
2103
4595
|
result = results.reduce((current, lookupDef) => {
|
|
2104
4596
|
const items = lookupDef.results
|
|
2105
4597
|
.map((module) => {
|
|
4598
|
+
if (!api_isStateNode(module)) {
|
|
4599
|
+
return null;
|
|
4600
|
+
}
|
|
2106
4601
|
const res = checkOne(state, module, decls, property, false);
|
|
2107
4602
|
return res ? { parent: module, results: res } : null;
|
|
2108
4603
|
})
|
|
@@ -2187,6 +4682,8 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
|
|
|
2187
4682
|
}
|
|
2188
4683
|
function api_collectNamespaces(ast, stateIn) {
|
|
2189
4684
|
const state = (stateIn || {});
|
|
4685
|
+
if (!state.nextExposed)
|
|
4686
|
+
state.nextExposed = {};
|
|
2190
4687
|
if (!state.index)
|
|
2191
4688
|
state.index = {};
|
|
2192
4689
|
if (!state.stack) {
|
|
@@ -2222,7 +4719,7 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2222
4719
|
state.stackClone = () => state.stack.map((elm) => elm.type === "ModuleDeclaration" || elm.type === "Program"
|
|
2223
4720
|
? { ...elm }
|
|
2224
4721
|
: elm);
|
|
2225
|
-
state.inType =
|
|
4722
|
+
state.inType = 0;
|
|
2226
4723
|
state.traverse = (root) => ast_traverseAst(root, (node) => {
|
|
2227
4724
|
try {
|
|
2228
4725
|
if (state.shouldExclude && state.shouldExclude(node)) {
|
|
@@ -2230,6 +4727,13 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2230
4727
|
return [];
|
|
2231
4728
|
}
|
|
2232
4729
|
switch (node.type) {
|
|
4730
|
+
case "UnaryExpression":
|
|
4731
|
+
if (node.operator === ":" && !state.inType) {
|
|
4732
|
+
state.nextExposed[node.argument.name] = true;
|
|
4733
|
+
}
|
|
4734
|
+
break;
|
|
4735
|
+
case "AttributeList":
|
|
4736
|
+
return [];
|
|
2233
4737
|
case "Program":
|
|
2234
4738
|
if (state.stack.length != 1) {
|
|
2235
4739
|
throw new Error("Unexpected stack length for Program node");
|
|
@@ -2238,7 +4742,7 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2238
4742
|
break;
|
|
2239
4743
|
case "TypeSpecList":
|
|
2240
4744
|
case "TypeSpecPart":
|
|
2241
|
-
state.inType
|
|
4745
|
+
state.inType++;
|
|
2242
4746
|
break;
|
|
2243
4747
|
case "ImportModule":
|
|
2244
4748
|
case "Using": {
|
|
@@ -2284,6 +4788,16 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2284
4788
|
});
|
|
2285
4789
|
}
|
|
2286
4790
|
break;
|
|
4791
|
+
case "ForStatement":
|
|
4792
|
+
if (node.init && node.init.type === "VariableDeclaration") {
|
|
4793
|
+
state.stack.push({
|
|
4794
|
+
type: "BlockStatement",
|
|
4795
|
+
fullName: undefined,
|
|
4796
|
+
name: undefined,
|
|
4797
|
+
node: node,
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
break;
|
|
2287
4801
|
case "BlockStatement": {
|
|
2288
4802
|
const [parent] = state.stack.slice(-1);
|
|
2289
4803
|
if (parent.node === node ||
|
|
@@ -2313,7 +4827,7 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2313
4827
|
if (name) {
|
|
2314
4828
|
if (!parent.decls)
|
|
2315
4829
|
parent.decls = {};
|
|
2316
|
-
if (
|
|
4830
|
+
if (ast_hasProperty(parent.decls, name)) {
|
|
2317
4831
|
const what = node.type == "ModuleDeclaration" ? "type" : "node";
|
|
2318
4832
|
const e = parent.decls[name].find((d) => api_isStateNode(d) && d[what] == elm[what]);
|
|
2319
4833
|
if (e != null) {
|
|
@@ -2339,7 +4853,7 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2339
4853
|
elm.decls = { [name]: [elm] };
|
|
2340
4854
|
if (!parent.type_decls)
|
|
2341
4855
|
parent.type_decls = {};
|
|
2342
|
-
if (!
|
|
4856
|
+
if (!ast_hasProperty(parent.type_decls, name)) {
|
|
2343
4857
|
parent.type_decls[name] = [];
|
|
2344
4858
|
}
|
|
2345
4859
|
parent.type_decls[name].push(elm);
|
|
@@ -2350,20 +4864,22 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2350
4864
|
// an EnumDeclaration doesn't create a scope, but
|
|
2351
4865
|
// it does create a type (if it has a name)
|
|
2352
4866
|
case "EnumDeclaration": {
|
|
2353
|
-
if (!node.id)
|
|
4867
|
+
if (!node.id) {
|
|
4868
|
+
state.inType++;
|
|
2354
4869
|
break;
|
|
4870
|
+
}
|
|
2355
4871
|
const [parent] = state.stack.slice(-1);
|
|
2356
4872
|
const name = (parent.fullName + "." + node.id.name).replace(/^\$\./, "");
|
|
2357
4873
|
node.body.members.forEach((m) => (("init" in m ? m.init : m).enumType = name));
|
|
2358
4874
|
}
|
|
2359
4875
|
// fall through
|
|
2360
4876
|
case "TypedefDeclaration": {
|
|
2361
|
-
state.inType
|
|
4877
|
+
state.inType++;
|
|
2362
4878
|
const name = node.id.name;
|
|
2363
4879
|
const [parent] = state.stack.slice(-1);
|
|
2364
4880
|
if (!parent.type_decls)
|
|
2365
4881
|
parent.type_decls = {};
|
|
2366
|
-
if (!
|
|
4882
|
+
if (!ast_hasProperty(parent.type_decls, name)) {
|
|
2367
4883
|
parent.type_decls[name] = [];
|
|
2368
4884
|
}
|
|
2369
4885
|
else if (parent.type_decls[name].find((n) => (api_isStateNode(n) ? n.node : n) == node)) {
|
|
@@ -2387,7 +4903,7 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2387
4903
|
const stack = state.stackClone();
|
|
2388
4904
|
node.declarations.forEach((decl) => {
|
|
2389
4905
|
const name = api_variableDeclarationName(decl.id);
|
|
2390
|
-
if (!
|
|
4906
|
+
if (!ast_hasProperty(decls, name)) {
|
|
2391
4907
|
decls[name] = [];
|
|
2392
4908
|
}
|
|
2393
4909
|
else if (decls[name].find((n) => (api_isStateNode(n) ? n.node : n) == decl)) {
|
|
@@ -2402,7 +4918,7 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2402
4918
|
stack,
|
|
2403
4919
|
});
|
|
2404
4920
|
if (node.kind == "const") {
|
|
2405
|
-
if (!
|
|
4921
|
+
if (!ast_hasProperty(state.index, name)) {
|
|
2406
4922
|
state.index[name] = [];
|
|
2407
4923
|
}
|
|
2408
4924
|
(0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
|
|
@@ -2411,13 +4927,21 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2411
4927
|
break;
|
|
2412
4928
|
}
|
|
2413
4929
|
case "EnumStringBody": {
|
|
2414
|
-
state.inType
|
|
4930
|
+
if (state.inType !== 1) {
|
|
4931
|
+
throw new Error(`Expected inType to be 1 at EnumStringBody. Got ${state.inType}.`);
|
|
4932
|
+
}
|
|
4933
|
+
state.inType--;
|
|
2415
4934
|
const [parent] = state.stack.slice(-1);
|
|
2416
4935
|
const values = parent.decls || (parent.decls = {});
|
|
2417
4936
|
let prev = -1;
|
|
2418
4937
|
node.members.forEach((m, i) => {
|
|
2419
4938
|
if (m.type == "Identifier") {
|
|
2420
|
-
prev
|
|
4939
|
+
if (typeof prev === "bigint") {
|
|
4940
|
+
prev += 1n;
|
|
4941
|
+
}
|
|
4942
|
+
else {
|
|
4943
|
+
prev += 1;
|
|
4944
|
+
}
|
|
2421
4945
|
m = node.members[i] = {
|
|
2422
4946
|
type: "EnumStringMember",
|
|
2423
4947
|
loc: m.loc,
|
|
@@ -2427,7 +4951,7 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2427
4951
|
init: {
|
|
2428
4952
|
type: "Literal",
|
|
2429
4953
|
value: prev,
|
|
2430
|
-
raw: prev.toString(),
|
|
4954
|
+
raw: prev.toString() + (typeof prev === "bigint" ? "l" : ""),
|
|
2431
4955
|
enumType: m.enumType,
|
|
2432
4956
|
loc: m.loc,
|
|
2433
4957
|
start: m.start,
|
|
@@ -2448,11 +4972,11 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2448
4972
|
prettier_plugin_monkeyc_namespaceObject.LiteralIntegerRe.test(init.raw)) {
|
|
2449
4973
|
prev = init.value;
|
|
2450
4974
|
}
|
|
2451
|
-
if (!
|
|
4975
|
+
if (!ast_hasProperty(values, name)) {
|
|
2452
4976
|
values[name] = [];
|
|
2453
4977
|
}
|
|
2454
4978
|
(0,external_util_cjs_namespaceObject.pushUnique)(values[name], m);
|
|
2455
|
-
if (!
|
|
4979
|
+
if (!ast_hasProperty(state.index, name)) {
|
|
2456
4980
|
state.index[name] = [];
|
|
2457
4981
|
}
|
|
2458
4982
|
(0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
|
|
@@ -2479,22 +5003,22 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2479
5003
|
if (state.post)
|
|
2480
5004
|
ret = state.post(node, state);
|
|
2481
5005
|
switch (type) {
|
|
2482
|
-
|
|
2483
|
-
// generally occur in TypeSpecLists. But do clear it for
|
|
2484
|
-
// SizedArrayExpression, since thats the only place they
|
|
2485
|
-
// happen on their own.
|
|
2486
|
-
case "SizedArrayExpression":
|
|
5006
|
+
case "TypeSpecPart":
|
|
2487
5007
|
case "TypeSpecList":
|
|
2488
5008
|
case "TypedefDeclaration":
|
|
2489
5009
|
case "EnumDeclaration":
|
|
2490
|
-
state.inType
|
|
5010
|
+
state.inType--;
|
|
2491
5011
|
break;
|
|
2492
5012
|
case "EnumStringBody":
|
|
2493
|
-
state.inType
|
|
5013
|
+
state.inType++;
|
|
2494
5014
|
break;
|
|
2495
5015
|
}
|
|
2496
5016
|
const [parent] = state.stack.slice(-1);
|
|
2497
5017
|
if (parent.node === node ||
|
|
5018
|
+
// The pre function might cause node.body to be skipped,
|
|
5019
|
+
// so we need to check here, just in case.
|
|
5020
|
+
// (this actually happens with prettier-extenison-monkeyc's
|
|
5021
|
+
// findItemsByRange)
|
|
2498
5022
|
(node.type === "CatchClause" && parent.node === node.body)) {
|
|
2499
5023
|
delete parent.usings;
|
|
2500
5024
|
delete parent.imports;
|
|
@@ -2513,6 +5037,9 @@ function api_collectNamespaces(ast, stateIn) {
|
|
|
2513
5037
|
}
|
|
2514
5038
|
});
|
|
2515
5039
|
state.traverse(ast);
|
|
5040
|
+
if (state.inType) {
|
|
5041
|
+
throw new Error(`inType was non-zero on exit: ${state.inType}`);
|
|
5042
|
+
}
|
|
2516
5043
|
if (state.stack.length != 1) {
|
|
2517
5044
|
throw new Error("Invalid AST!");
|
|
2518
5045
|
}
|
|
@@ -2547,7 +5074,7 @@ function api_formatAst(node, monkeyCSource = null) {
|
|
|
2547
5074
|
// json. The parser knows to just treat the last line of the input
|
|
2548
5075
|
// as the ast itself, and the printers will find what they're
|
|
2549
5076
|
// looking for in the source.
|
|
2550
|
-
const source = (monkeyCSource || "") + "\n" +
|
|
5077
|
+
const source = (monkeyCSource || "") + "\n" + (0,prettier_plugin_monkeyc_namespaceObject.serializeMonkeyC)(node);
|
|
2551
5078
|
return external_prettier_namespaceObject.format(source, {
|
|
2552
5079
|
parser: "monkeyc-json",
|
|
2553
5080
|
plugins: [(prettier_plugin_monkeyc_default())],
|
|
@@ -2592,7 +5119,7 @@ function findUsing(state, stack, using) {
|
|
|
2592
5119
|
find(node.object);
|
|
2593
5120
|
name = node.property.name;
|
|
2594
5121
|
}
|
|
2595
|
-
if (
|
|
5122
|
+
if (ast_hasProperty(module.decls, name)) {
|
|
2596
5123
|
const decls = module.decls[name];
|
|
2597
5124
|
if (decls &&
|
|
2598
5125
|
decls.length === 1 &&
|
|
@@ -2615,7 +5142,7 @@ function findUsing(state, stack, using) {
|
|
|
2615
5142
|
function findUsingForNode(state, stack, i, node, isType) {
|
|
2616
5143
|
while (i >= 0) {
|
|
2617
5144
|
const si = stack[i--];
|
|
2618
|
-
if (
|
|
5145
|
+
if (ast_hasProperty(si.usings, node.name)) {
|
|
2619
5146
|
const using = si.usings[node.name];
|
|
2620
5147
|
const module = findUsing(state, stack, using);
|
|
2621
5148
|
return module && [module];
|
|
@@ -2625,7 +5152,7 @@ function findUsingForNode(state, stack, i, node, isType) {
|
|
|
2625
5152
|
const using = si.imports[j];
|
|
2626
5153
|
const module = findUsing(state, stack, using);
|
|
2627
5154
|
if (module) {
|
|
2628
|
-
if (
|
|
5155
|
+
if (ast_hasProperty(module.type_decls, node.name)) {
|
|
2629
5156
|
return module.type_decls[node.name];
|
|
2630
5157
|
}
|
|
2631
5158
|
}
|
|
@@ -2634,6 +5161,38 @@ function findUsingForNode(state, stack, i, node, isType) {
|
|
|
2634
5161
|
}
|
|
2635
5162
|
return null;
|
|
2636
5163
|
}
|
|
5164
|
+
const invokeInfo = {};
|
|
5165
|
+
const toyboxFnInfo = {};
|
|
5166
|
+
function api_getApiFunctionInfo(func) {
|
|
5167
|
+
if (func.fullName === "$.Toybox.Lang.Method.invoke" ||
|
|
5168
|
+
(func.node.params &&
|
|
5169
|
+
func.node.params.some((param) => param.type === "BinaryExpression" &&
|
|
5170
|
+
param.right.ts.some((tsp) => tsp.type === "TypeSpecPart" && tsp.callspec)))) {
|
|
5171
|
+
if (!invokeInfo.calledFuncs) {
|
|
5172
|
+
invokeInfo.modifiedDecls = new Set();
|
|
5173
|
+
invokeInfo.calledFuncs = new Set();
|
|
5174
|
+
invokeInfo.callsExposed = true;
|
|
5175
|
+
}
|
|
5176
|
+
if (func.name === "initialize") {
|
|
5177
|
+
const top = func.stack[func.stack.length - 1];
|
|
5178
|
+
if (top.type === "ClassDeclaration") {
|
|
5179
|
+
top.hasInvoke = true;
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
return invokeInfo;
|
|
5183
|
+
}
|
|
5184
|
+
if (!toyboxFnInfo.calledFuncs) {
|
|
5185
|
+
toyboxFnInfo.modifiedDecls = new Set();
|
|
5186
|
+
toyboxFnInfo.calledFuncs = new Set();
|
|
5187
|
+
toyboxFnInfo.resolvedDecls = new Set();
|
|
5188
|
+
}
|
|
5189
|
+
return toyboxFnInfo;
|
|
5190
|
+
}
|
|
5191
|
+
function api_markInvokeClassMethod(func) {
|
|
5192
|
+
func.info = invokeInfo;
|
|
5193
|
+
}
|
|
5194
|
+
|
|
5195
|
+
})();
|
|
2637
5196
|
|
|
2638
5197
|
var __webpack_export_target__ = exports;
|
|
2639
5198
|
for(var i in __webpack_exports__) __webpack_export_target__[i] = __webpack_exports__[i];
|