@markw65/monkeyc-optimizer 1.0.3 → 1.0.7
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 +33 -0
- package/build/api.cjs +688 -23
- package/build/optimizer.cjs +254 -57
- package/build/util.cjs +1 -0
- package/package.json +2 -2
package/build/api.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
0 && (module.exports = {LiteralIntegerRe,getApiMapping,hasProperty,collectNamespaces,traverseAst,formatAst});
|
|
1
2
|
/******/ (() => { // webpackBootstrap
|
|
2
3
|
/******/ "use strict";
|
|
3
4
|
/******/ // The require scope
|
|
@@ -39,12 +40,12 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
39
40
|
|
|
40
41
|
// EXPORTS
|
|
41
42
|
__webpack_require__.d(__webpack_exports__, {
|
|
42
|
-
"LiteralIntegerRe": () => (/* binding */
|
|
43
|
-
"collectNamespaces": () => (/* binding */
|
|
43
|
+
"LiteralIntegerRe": () => (/* binding */ api_LiteralIntegerRe),
|
|
44
|
+
"collectNamespaces": () => (/* binding */ api_collectNamespaces),
|
|
44
45
|
"formatAst": () => (/* binding */ formatAst),
|
|
45
|
-
"getApiMapping": () => (/* binding */
|
|
46
|
-
"hasProperty": () => (/* binding */
|
|
47
|
-
"traverseAst": () => (/* binding */
|
|
46
|
+
"getApiMapping": () => (/* binding */ api_getApiMapping),
|
|
47
|
+
"hasProperty": () => (/* binding */ api_hasProperty),
|
|
48
|
+
"traverseAst": () => (/* binding */ api_traverseAst)
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
;// CONCATENATED MODULE: external "@markw65/prettier-plugin-monkeyc"
|
|
@@ -111,6 +112,663 @@ const negativeFixups = [
|
|
|
111
112
|
"Toybox.WatchUi.LAYOUT_HALIGN_LEFT",
|
|
112
113
|
];
|
|
113
114
|
|
|
115
|
+
;// CONCATENATED MODULE: external "./api.cjs"
|
|
116
|
+
const external_api_cjs_namespaceObject = require("./api.cjs");
|
|
117
|
+
;// CONCATENATED MODULE: ./src/mc-rewrite.js
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
function processImports(allImports, lookup) {
|
|
124
|
+
allImports.forEach(({ node, stack }) => {
|
|
125
|
+
const [name, module] = lookup(node.id, node.as && node.as.name, stack);
|
|
126
|
+
if (name && module) {
|
|
127
|
+
const [parent] = stack.slice(-1);
|
|
128
|
+
if (!parent.decls) parent.decls = {};
|
|
129
|
+
if (!hasProperty(parent.decls, name)) parent.decls[name] = [];
|
|
130
|
+
module.forEach((m) => {
|
|
131
|
+
if (m.type == "ModuleDeclaration") {
|
|
132
|
+
pushUnique(parent.decls[name], m);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function collectClassInfo(state) {
|
|
140
|
+
state.allClasses.forEach((elm) => {
|
|
141
|
+
if (elm.node.superClass) {
|
|
142
|
+
const [, classes] = state.lookup(elm.node.superClass, null, elm.stack);
|
|
143
|
+
if (classes) {
|
|
144
|
+
elm.superClass = classes.filter((c) => c.type == "ClassDeclaration");
|
|
145
|
+
}
|
|
146
|
+
// set it "true" if there is a superClass, but we can't find it.
|
|
147
|
+
if (!elm.superClass || !elm.superClass.length) elm.superClass = true;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const markOverrides = (cls, scls) => {
|
|
152
|
+
if (scls === true) return;
|
|
153
|
+
scls.forEach((c) => {
|
|
154
|
+
c.decls &&
|
|
155
|
+
Object.values(c.decls).forEach((f) => {
|
|
156
|
+
if (f.type == "FunctionDeclaration") {
|
|
157
|
+
if (hasProperty(cls.decls, f.name)) {
|
|
158
|
+
f.hasOverride = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (c.superClass) markOverrides(cls, c.superClass);
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
state.allClasses.forEach((elm) => {
|
|
167
|
+
if (elm.superClass) markOverrides(elm, elm.superClass);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function analyze(fileNames, buildConfig) {
|
|
172
|
+
const excludeAnnotations =
|
|
173
|
+
buildConfig && buildConfig.excludeAnnotations
|
|
174
|
+
? Object.fromEntries(buildConfig.excludeAnnotations.map((a) => [a, true]))
|
|
175
|
+
: {};
|
|
176
|
+
|
|
177
|
+
const allImports = [];
|
|
178
|
+
const state = {
|
|
179
|
+
allFunctions: [],
|
|
180
|
+
allClasses: [],
|
|
181
|
+
shouldExclude(node) {
|
|
182
|
+
if (node.attrs && node.attrs.attrs) {
|
|
183
|
+
if (
|
|
184
|
+
node.attrs.attrs.filter((attr) => {
|
|
185
|
+
if (attr.type != "UnaryExpression") return false;
|
|
186
|
+
if (attr.argument.type != "Identifier") return false;
|
|
187
|
+
return hasProperty(excludeAnnotations, attr.argument.name);
|
|
188
|
+
}).length
|
|
189
|
+
) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
post(node) {
|
|
195
|
+
switch (node.type) {
|
|
196
|
+
case "FunctionDeclaration":
|
|
197
|
+
case "ClassDeclaration": {
|
|
198
|
+
const [scope] = state.stack.slice(-1);
|
|
199
|
+
const stack = state.stack.slice(0, -1);
|
|
200
|
+
scope.stack = stack;
|
|
201
|
+
(node.type == "FunctionDeclaration"
|
|
202
|
+
? state.allFunctions
|
|
203
|
+
: state.allClasses
|
|
204
|
+
).push(scope);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
case "Using":
|
|
208
|
+
case "ImportModule":
|
|
209
|
+
allImports.push({ node, stack: state.stack.slice() });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
await getApiMapping(state);
|
|
216
|
+
|
|
217
|
+
// Mark all functions from api.mir as "special" by
|
|
218
|
+
// setting their bodies to null. In api.mir, they're
|
|
219
|
+
// all empty, which makes it look like they're
|
|
220
|
+
// do-nothing functions.
|
|
221
|
+
const markApi = (node) => {
|
|
222
|
+
if (node.type == "FunctionDeclaration") {
|
|
223
|
+
node.node.body = null;
|
|
224
|
+
}
|
|
225
|
+
if (node.decls) {
|
|
226
|
+
Object.values(node.decls).forEach(markApi);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
markApi(state.stack[0]);
|
|
230
|
+
|
|
231
|
+
const files = await Promise.all(
|
|
232
|
+
fileNames.map(async (name) => ({
|
|
233
|
+
name,
|
|
234
|
+
monkeyCSource: (await fs.readFile(name))
|
|
235
|
+
.toString()
|
|
236
|
+
.replace(/\r\n/g, "\n"),
|
|
237
|
+
}))
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
files.forEach((f) => {
|
|
241
|
+
f.ast = MonkeyC.parsers.monkeyc.parse(f.monkeyCSource, {
|
|
242
|
+
grammarSource: f.name,
|
|
243
|
+
});
|
|
244
|
+
f.ast.source = f.name;
|
|
245
|
+
f.ast.monkeyCSource = f.monkeyCSource;
|
|
246
|
+
delete f.monkeyCSource;
|
|
247
|
+
collectNamespaces(f.ast, state);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
delete state.shouldExclude;
|
|
251
|
+
delete state.post;
|
|
252
|
+
|
|
253
|
+
processImports(allImports, state.lookup);
|
|
254
|
+
collectClassInfo(state);
|
|
255
|
+
|
|
256
|
+
return { files, state };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getLiteralNode(node) {
|
|
260
|
+
if (Array.isArray(node)) {
|
|
261
|
+
if (!node.length) return null;
|
|
262
|
+
if (node.length === 1) return getLiteralNode(node[0]);
|
|
263
|
+
let result;
|
|
264
|
+
if (
|
|
265
|
+
node.every((n) => {
|
|
266
|
+
const lit = getLiteralNode(n);
|
|
267
|
+
if (!lit) return false;
|
|
268
|
+
if (!result) {
|
|
269
|
+
result = lit;
|
|
270
|
+
} else {
|
|
271
|
+
if (lit.value !== result.value) return false;
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
})
|
|
275
|
+
) {
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
if (node.type == "Literal") return node;
|
|
281
|
+
if (node.type == "BinaryExpression" && node.operator == "as") {
|
|
282
|
+
return getLiteralNode(node.left) && node;
|
|
283
|
+
}
|
|
284
|
+
if (node.type == "UnaryExpression") {
|
|
285
|
+
if (node.argument.type != "Literal") return null;
|
|
286
|
+
switch (node.operator) {
|
|
287
|
+
case "-":
|
|
288
|
+
if (typeof node.argument.value == "number") {
|
|
289
|
+
return {
|
|
290
|
+
...node.argument,
|
|
291
|
+
value: -node.argument.value,
|
|
292
|
+
raw: "-" + node.argument.value,
|
|
293
|
+
enumType: node.enumType,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function getNodeValue(node) {
|
|
301
|
+
if (
|
|
302
|
+
node.type == "BinaryExpression" &&
|
|
303
|
+
node.operator == "as" &&
|
|
304
|
+
node.right.type == "TypeSpecList" &&
|
|
305
|
+
node.right.ts.length == 1 &&
|
|
306
|
+
typeof node.right.ts[0] == "string"
|
|
307
|
+
) {
|
|
308
|
+
// this is a cast we inserted to retain the type of an enum
|
|
309
|
+
// any arithmetic on it will revert to "Number", or "Long",
|
|
310
|
+
// so just ignore it.
|
|
311
|
+
return getNodeValue(node.left);
|
|
312
|
+
}
|
|
313
|
+
if (node.type != "Literal") {
|
|
314
|
+
return [null, null];
|
|
315
|
+
}
|
|
316
|
+
let type = node.value === null ? "Null" : typeof node.value;
|
|
317
|
+
if (type === "number") {
|
|
318
|
+
const match = LiteralIntegerRe.exec(node.raw);
|
|
319
|
+
if (match) {
|
|
320
|
+
type = match[2] == "l" ? "Long" : "Number";
|
|
321
|
+
} else if (node.raw.endsWith("d")) {
|
|
322
|
+
type = "Double";
|
|
323
|
+
} else {
|
|
324
|
+
type = "Float";
|
|
325
|
+
}
|
|
326
|
+
} else if (type === "string") {
|
|
327
|
+
type = "String";
|
|
328
|
+
} else if (type === "boolean") {
|
|
329
|
+
type = "Boolean";
|
|
330
|
+
} else {
|
|
331
|
+
type = "Unknown";
|
|
332
|
+
}
|
|
333
|
+
return [node, type];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function optimizeNode(node) {
|
|
337
|
+
switch (node.type) {
|
|
338
|
+
case "UnaryExpression": {
|
|
339
|
+
const [arg, type] = getNodeValue(node.argument);
|
|
340
|
+
if (arg === null) break;
|
|
341
|
+
switch (node.operator) {
|
|
342
|
+
case "+":
|
|
343
|
+
if (type === "Number" || type === "Long") {
|
|
344
|
+
return arg;
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
case "-":
|
|
348
|
+
if (type === "Number" || type === "Long") {
|
|
349
|
+
return {
|
|
350
|
+
...arg,
|
|
351
|
+
value: -arg.value,
|
|
352
|
+
raw: (-arg.value).toString() + (type === "Long" ? "l" : ""),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
case "!":
|
|
357
|
+
case "~":
|
|
358
|
+
{
|
|
359
|
+
let value;
|
|
360
|
+
if (type === "Number" || type === "Long") {
|
|
361
|
+
value = -arg.value - 1;
|
|
362
|
+
} else if (type === "Boolean" && node.operator == "!") {
|
|
363
|
+
value = !arg.value;
|
|
364
|
+
}
|
|
365
|
+
if (value !== undefined) {
|
|
366
|
+
return {
|
|
367
|
+
...arg,
|
|
368
|
+
value,
|
|
369
|
+
raw: value.toString() + (type === "Long" ? "l" : ""),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
case "BinaryExpression": {
|
|
378
|
+
const operators = {
|
|
379
|
+
"+": (left, right) => left + right,
|
|
380
|
+
"-": (left, right) => left - right,
|
|
381
|
+
"*": (left, right) => left * right,
|
|
382
|
+
"/": (left, right) => Math.trunc(left / right),
|
|
383
|
+
"%": (left, right) => left % right,
|
|
384
|
+
"&": (left, right, type) => (type === "Number" ? left & right : null),
|
|
385
|
+
"|": (left, right, type) => (type === "Number" ? left | right : null),
|
|
386
|
+
"<<": (left, right, type) => (type === "Number" ? left << right : null),
|
|
387
|
+
">>": (left, right, type) => (type === "Number" ? left >> right : null),
|
|
388
|
+
};
|
|
389
|
+
const op = operators[node.operator];
|
|
390
|
+
if (op) {
|
|
391
|
+
const [left, left_type] = getNodeValue(node.left);
|
|
392
|
+
const [right, right_type] = getNodeValue(node.right);
|
|
393
|
+
if (!left || !right) break;
|
|
394
|
+
if (
|
|
395
|
+
left_type != right_type ||
|
|
396
|
+
(left_type != "Number" && left_type != "Long")
|
|
397
|
+
) {
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
const value = op(left.value, right.value, left_type);
|
|
401
|
+
if (value === null) break;
|
|
402
|
+
return {
|
|
403
|
+
...left,
|
|
404
|
+
value,
|
|
405
|
+
raw: value.toString() + (left_type === "Long" ? "l" : ""),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
case "FunctionDeclaration":
|
|
411
|
+
if (node.body && evaluateFunction(node, null) !== false) {
|
|
412
|
+
node.optimizable = true;
|
|
413
|
+
}
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function evaluateFunction(func, args) {
|
|
419
|
+
if (args && args.length != func.params.length) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
const paramValues =
|
|
423
|
+
args && Object.fromEntries(func.params.map((p, i) => [p.name, args[i]]));
|
|
424
|
+
let ret = null;
|
|
425
|
+
const body = args ? JSON.parse(JSON.stringify(func.body)) : func.body;
|
|
426
|
+
try {
|
|
427
|
+
traverseAst(
|
|
428
|
+
body,
|
|
429
|
+
(node) => {
|
|
430
|
+
switch (node.type) {
|
|
431
|
+
case "BlockStatement":
|
|
432
|
+
case "ReturnStatement":
|
|
433
|
+
case "UnaryExpression":
|
|
434
|
+
case "BinaryExpression":
|
|
435
|
+
case "Literal":
|
|
436
|
+
case "Identifier":
|
|
437
|
+
return;
|
|
438
|
+
default:
|
|
439
|
+
throw new Error("Bad node type");
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
args &&
|
|
443
|
+
((node) => {
|
|
444
|
+
switch (node.type) {
|
|
445
|
+
case "ReturnStatement":
|
|
446
|
+
ret = node.argument;
|
|
447
|
+
return;
|
|
448
|
+
case "BlockStatement":
|
|
449
|
+
case "Literal":
|
|
450
|
+
return;
|
|
451
|
+
case "Identifier":
|
|
452
|
+
if (hasProperty(paramValues, node.name)) {
|
|
453
|
+
return paramValues[node.name];
|
|
454
|
+
}
|
|
455
|
+
// fall through;
|
|
456
|
+
default: {
|
|
457
|
+
const repl = optimizeNode(node);
|
|
458
|
+
if (repl && repl.type === "Literal") return repl;
|
|
459
|
+
throw new Error("Didn't optimize");
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
return ret;
|
|
465
|
+
} catch (e) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async function optimizeMonkeyC(fileNames, buildConfig) {
|
|
471
|
+
const { files, state } = await analyze(fileNames, buildConfig);
|
|
472
|
+
const replace = (node, obj) => {
|
|
473
|
+
for (const k of Object.keys(node)) {
|
|
474
|
+
delete node[k];
|
|
475
|
+
}
|
|
476
|
+
if (obj.enumType) {
|
|
477
|
+
obj = {
|
|
478
|
+
type: "BinaryExpression",
|
|
479
|
+
operator: "as",
|
|
480
|
+
left: obj,
|
|
481
|
+
right: { type: "TypeSpecList", ts: [obj.enumType] },
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
485
|
+
node[k] = v;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const lookupAndReplace = (node) => {
|
|
489
|
+
const [, objects] = state.lookup(node);
|
|
490
|
+
if (!objects) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
const obj = getLiteralNode(objects);
|
|
494
|
+
if (!obj) {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
replace(node, obj);
|
|
498
|
+
return true;
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
/*
|
|
502
|
+
* Might this function be called from somewhere, including
|
|
503
|
+
* callbacks from the api (eg getSettingsView, etc).
|
|
504
|
+
*/
|
|
505
|
+
const maybeCalled = (func) => {
|
|
506
|
+
if (!func.body) {
|
|
507
|
+
// this is an api.mir function. It can be called
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
if (hasProperty(state.exposed, func.id.name)) return true;
|
|
511
|
+
if (hasProperty(state.calledFunctions, func.id.name)) {
|
|
512
|
+
return (
|
|
513
|
+
state.calledFunctions[func.id.name].find((f) => f === func) !== null
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
/*
|
|
518
|
+
* Does elm (a class) have a maybeCalled function called name,
|
|
519
|
+
* anywhere in its superClass chain.
|
|
520
|
+
*/
|
|
521
|
+
const checkInherited = (elm, name) =>
|
|
522
|
+
elm.superClass === true ||
|
|
523
|
+
elm.superClass.some(
|
|
524
|
+
(sc) =>
|
|
525
|
+
(hasProperty(sc.decls, name) &&
|
|
526
|
+
sc.decls[name].some(
|
|
527
|
+
(f) => f.type == "FunctionDeclaration" && maybeCalled(f)
|
|
528
|
+
)) ||
|
|
529
|
+
(sc.superClass && checkInherited(sc, name))
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
state.exposed = {};
|
|
533
|
+
state.calledFunctions = {};
|
|
534
|
+
state.pre = (node) => {
|
|
535
|
+
switch (node.type) {
|
|
536
|
+
case "ConditionalExpression":
|
|
537
|
+
case "IfStatement":
|
|
538
|
+
case "DoWhileStatement":
|
|
539
|
+
case "WhileStatement":
|
|
540
|
+
state.traverse(node.test);
|
|
541
|
+
const [value, type] = getNodeValue(node.test);
|
|
542
|
+
if (value) {
|
|
543
|
+
let result = null;
|
|
544
|
+
if (type === "Null") {
|
|
545
|
+
result = false;
|
|
546
|
+
} else if (
|
|
547
|
+
type === "Boolean" ||
|
|
548
|
+
type === "Number" ||
|
|
549
|
+
type === "Long"
|
|
550
|
+
) {
|
|
551
|
+
result = !!value.value;
|
|
552
|
+
}
|
|
553
|
+
if (result !== null) {
|
|
554
|
+
if (
|
|
555
|
+
node.type === "IfStatement" ||
|
|
556
|
+
node.type === "ConditionalExpression"
|
|
557
|
+
) {
|
|
558
|
+
if (result === false) {
|
|
559
|
+
node.consequent = null;
|
|
560
|
+
} else {
|
|
561
|
+
node.alternate = null;
|
|
562
|
+
}
|
|
563
|
+
node.test = result;
|
|
564
|
+
} else if (node.type === "WhileStatement") {
|
|
565
|
+
if (result === false) {
|
|
566
|
+
node.body = null;
|
|
567
|
+
}
|
|
568
|
+
} else if (node.type === "DoWhileStatement") {
|
|
569
|
+
if (result === false) {
|
|
570
|
+
node.test = null;
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
throw new Error("Unexpected Node type");
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return;
|
|
578
|
+
|
|
579
|
+
case "EnumDeclaration":
|
|
580
|
+
return false;
|
|
581
|
+
case "VariableDeclarator":
|
|
582
|
+
return ["init"];
|
|
583
|
+
case "UnaryExpression":
|
|
584
|
+
if (node.operator == ":") {
|
|
585
|
+
// If we produce a Symbol, for a given name,
|
|
586
|
+
// its possible that someone uses that symbol
|
|
587
|
+
// indirectly, so we can't remove any enums or
|
|
588
|
+
// constants with that name (we can still replace
|
|
589
|
+
// uses of those constants though).
|
|
590
|
+
state.exposed[node.argument.name] = true;
|
|
591
|
+
// In any case, we can't replace *this* use of the
|
|
592
|
+
// symbol with its value...
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
case "Identifier": {
|
|
597
|
+
if (hasProperty(state.index, node.name)) {
|
|
598
|
+
if (!lookupAndReplace(node)) {
|
|
599
|
+
state.exposed[node.name] = true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
case "MemberExpression":
|
|
605
|
+
if (node.property.type === "Identifier" && !node.computed) {
|
|
606
|
+
if (hasProperty(state.index, node.property.name)) {
|
|
607
|
+
if (lookupAndReplace(node)) {
|
|
608
|
+
return false;
|
|
609
|
+
} else {
|
|
610
|
+
state.exposed[node.property.name] = true;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// Don't optimize the property.
|
|
614
|
+
return ["object"];
|
|
615
|
+
}
|
|
616
|
+
break;
|
|
617
|
+
case "FunctionDeclaration": {
|
|
618
|
+
const [parent] = state.stack.slice(-2);
|
|
619
|
+
if (parent.type == "ClassDeclaration" && !maybeCalled(node)) {
|
|
620
|
+
let used = false;
|
|
621
|
+
if (node.id.name == "initialize") {
|
|
622
|
+
used = true;
|
|
623
|
+
} else if (parent.superClass) {
|
|
624
|
+
used = checkInherited(parent, node.id.name);
|
|
625
|
+
}
|
|
626
|
+
if (used) {
|
|
627
|
+
if (!hasProperty(state.calledFunctions, node.id.name)) {
|
|
628
|
+
state.calledFunctions[node.id.name] = [];
|
|
629
|
+
}
|
|
630
|
+
state.calledFunctions[node.id.name].push(node);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
state.post = (node) => {
|
|
637
|
+
const opt = optimizeNode(node);
|
|
638
|
+
if (opt) {
|
|
639
|
+
replace(node, opt);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
switch (node.type) {
|
|
643
|
+
case "ConditionalExpression":
|
|
644
|
+
case "IfStatement":
|
|
645
|
+
if (typeof node.test === "boolean") {
|
|
646
|
+
const rep = node.test ? node.consequent : node.alternate;
|
|
647
|
+
if (!rep) return false;
|
|
648
|
+
replace(node, rep);
|
|
649
|
+
}
|
|
650
|
+
break;
|
|
651
|
+
case "WhileStatement":
|
|
652
|
+
if (!node.body) return false;
|
|
653
|
+
break;
|
|
654
|
+
case "DoWhileStatement":
|
|
655
|
+
if (!node.test) return node.body;
|
|
656
|
+
break;
|
|
657
|
+
|
|
658
|
+
case "CallExpression": {
|
|
659
|
+
const [name, callees] = state.lookup(node.callee);
|
|
660
|
+
if (!callees || !callees.length) {
|
|
661
|
+
const n =
|
|
662
|
+
name ||
|
|
663
|
+
node.callee.name ||
|
|
664
|
+
(node.callee.property && node.callee.property.name);
|
|
665
|
+
if (n) {
|
|
666
|
+
state.exposed[n] = true;
|
|
667
|
+
} else {
|
|
668
|
+
// There are unnamed CallExpressions, such as new [size]
|
|
669
|
+
// So there's nothing to do here.
|
|
670
|
+
}
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (callees.length == 1) {
|
|
674
|
+
const callee = callees[0].node;
|
|
675
|
+
if (
|
|
676
|
+
callee.optimizable &&
|
|
677
|
+
!callee.hasOverride &&
|
|
678
|
+
node.arguments.every((n) => getNodeValue(n)[0] !== null)
|
|
679
|
+
) {
|
|
680
|
+
const ret = evaluateFunction(callee, node.arguments);
|
|
681
|
+
if (ret) {
|
|
682
|
+
replace(node, ret);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (!hasProperty(state.calledFunctions, name)) {
|
|
688
|
+
state.calledFunctions[name] = [];
|
|
689
|
+
}
|
|
690
|
+
callees.forEach((c) => state.calledFunctions[name].push(c.node));
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
files.forEach((f) => {
|
|
696
|
+
collectNamespaces(f.ast, state);
|
|
697
|
+
});
|
|
698
|
+
files.forEach((f) => {
|
|
699
|
+
traverseAst(f.ast, null, (node) => {
|
|
700
|
+
switch (node.type) {
|
|
701
|
+
case "EnumStringBody":
|
|
702
|
+
if (
|
|
703
|
+
node.members.every((m) => {
|
|
704
|
+
const name = m.name || m.id.name;
|
|
705
|
+
return (
|
|
706
|
+
hasProperty(state.index, name) &&
|
|
707
|
+
!hasProperty(state.exposed, name)
|
|
708
|
+
);
|
|
709
|
+
})
|
|
710
|
+
) {
|
|
711
|
+
node.enumType = [
|
|
712
|
+
...new Set(
|
|
713
|
+
node.members.map((m) => {
|
|
714
|
+
if (!m.init) return "Number";
|
|
715
|
+
const [node, type] = getNodeValue(m.init);
|
|
716
|
+
if (!node) {
|
|
717
|
+
throw new Error("Failed to get type for eliminated enum");
|
|
718
|
+
}
|
|
719
|
+
return type;
|
|
720
|
+
})
|
|
721
|
+
),
|
|
722
|
+
].join(" or ");
|
|
723
|
+
node.members.splice(0);
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
case "EnumDeclaration":
|
|
727
|
+
if (!node.body.members.length) {
|
|
728
|
+
if (!node.id) return false;
|
|
729
|
+
if (!node.body.enumType) {
|
|
730
|
+
throw new Error("Missing enumType on optimized enum");
|
|
731
|
+
}
|
|
732
|
+
replace(node, {
|
|
733
|
+
type: "TypedefDeclaration",
|
|
734
|
+
id: node.id,
|
|
735
|
+
ts: {
|
|
736
|
+
type: "UnaryExpression",
|
|
737
|
+
argument: { type: "TypeSpecList", ts: [node.body.enumType] },
|
|
738
|
+
prefix: true,
|
|
739
|
+
operator: " as",
|
|
740
|
+
},
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
break;
|
|
744
|
+
case "VariableDeclaration": {
|
|
745
|
+
node.declarations = node.declarations.filter(
|
|
746
|
+
(d) =>
|
|
747
|
+
!hasProperty(state.index, d.id.name) ||
|
|
748
|
+
hasProperty(state.exposed, d.id.name)
|
|
749
|
+
);
|
|
750
|
+
if (!node.declarations.length) {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
case "ClassElement":
|
|
756
|
+
if (!node.item) {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
break;
|
|
760
|
+
case "FunctionDeclaration":
|
|
761
|
+
if (!maybeCalled(node)) {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
return files;
|
|
770
|
+
}
|
|
771
|
+
|
|
114
772
|
;// CONCATENATED MODULE: ./src/api.js
|
|
115
773
|
|
|
116
774
|
|
|
@@ -118,7 +776,8 @@ const negativeFixups = [
|
|
|
118
776
|
|
|
119
777
|
|
|
120
778
|
|
|
121
|
-
|
|
779
|
+
|
|
780
|
+
const api_LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/;
|
|
122
781
|
/*
|
|
123
782
|
* This is an unfortunate hack. I want to be able to extract things
|
|
124
783
|
* like the types of all of a Class's variables (in particular the type
|
|
@@ -130,7 +789,7 @@ const LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/;
|
|
|
130
789
|
*/
|
|
131
790
|
|
|
132
791
|
// Extract all enum values from api.mir
|
|
133
|
-
async function
|
|
792
|
+
async function api_getApiMapping(state) {
|
|
134
793
|
// get the path to the currently active sdk
|
|
135
794
|
const parser = prettier_plugin_monkeyc_namespaceObject.parsers.monkeyc;
|
|
136
795
|
|
|
@@ -144,7 +803,7 @@ async function getApiMapping(state) {
|
|
|
144
803
|
.replace(/^(\s*type)\s/gm, "$1def ");
|
|
145
804
|
|
|
146
805
|
try {
|
|
147
|
-
const result =
|
|
806
|
+
const result = api_collectNamespaces(parser.parse(api, {}), state);
|
|
148
807
|
negativeFixups.forEach((fixup) => {
|
|
149
808
|
const value = fixup.split(".").reduce((state, part) => {
|
|
150
809
|
const decls = state.decls[part];
|
|
@@ -169,18 +828,18 @@ async function getApiMapping(state) {
|
|
|
169
828
|
}
|
|
170
829
|
}
|
|
171
830
|
|
|
172
|
-
function
|
|
831
|
+
function api_hasProperty(obj, prop) {
|
|
173
832
|
return obj && Object.prototype.hasOwnProperty.call(obj, prop);
|
|
174
833
|
}
|
|
175
834
|
|
|
176
|
-
function
|
|
835
|
+
function api_collectNamespaces(ast, state) {
|
|
177
836
|
state = state || {};
|
|
178
837
|
if (!state.index) state.index = {};
|
|
179
838
|
if (!state.stack) {
|
|
180
839
|
state.stack = [{ type: "Program", name: "$", fullName: "$" }];
|
|
181
840
|
}
|
|
182
841
|
const checkOne = (ns, name) => {
|
|
183
|
-
if (
|
|
842
|
+
if (api_hasProperty(ns.decls, name)) {
|
|
184
843
|
return ns.decls[name];
|
|
185
844
|
}
|
|
186
845
|
return null;
|
|
@@ -218,7 +877,7 @@ function collectNamespaces(ast, state) {
|
|
|
218
877
|
};
|
|
219
878
|
|
|
220
879
|
state.traverse = (root) =>
|
|
221
|
-
|
|
880
|
+
api_traverseAst(
|
|
222
881
|
root,
|
|
223
882
|
(node) => {
|
|
224
883
|
try {
|
|
@@ -262,7 +921,7 @@ function collectNamespaces(ast, state) {
|
|
|
262
921
|
.join(".");
|
|
263
922
|
if (elm.name) {
|
|
264
923
|
if (!parent.decls) parent.decls = {};
|
|
265
|
-
if (
|
|
924
|
+
if (api_hasProperty(parent.decls, elm.name)) {
|
|
266
925
|
const what =
|
|
267
926
|
node.type == "ModuleDeclaration" ? "type" : "node";
|
|
268
927
|
const e = parent.decls[elm.name].find(
|
|
@@ -295,7 +954,7 @@ function collectNamespaces(ast, state) {
|
|
|
295
954
|
case "TypedefDeclaration": {
|
|
296
955
|
const [parent] = state.stack.slice(-1);
|
|
297
956
|
if (!parent.decls) parent.decls = {};
|
|
298
|
-
if (!
|
|
957
|
+
if (!api_hasProperty(parent.decls, node.id.name)) {
|
|
299
958
|
parent.decls[node.id.name] = [];
|
|
300
959
|
}
|
|
301
960
|
(0,external_util_cjs_namespaceObject.pushUnique)(
|
|
@@ -308,12 +967,12 @@ function collectNamespaces(ast, state) {
|
|
|
308
967
|
const [parent] = state.stack.slice(-1);
|
|
309
968
|
if (!parent.decls) parent.decls = {};
|
|
310
969
|
node.declarations.forEach((decl) => {
|
|
311
|
-
if (!
|
|
970
|
+
if (!api_hasProperty(parent.decls, decl.id.name)) {
|
|
312
971
|
parent.decls[decl.id.name] = [];
|
|
313
972
|
}
|
|
314
973
|
if (node.kind == "const") {
|
|
315
974
|
(0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[decl.id.name], decl.init);
|
|
316
|
-
if (!
|
|
975
|
+
if (!api_hasProperty(state.index, decl.id.name)) {
|
|
317
976
|
state.index[decl.id.name] = [];
|
|
318
977
|
}
|
|
319
978
|
(0,external_util_cjs_namespaceObject.pushUnique)(state.index[decl.id.name], parent);
|
|
@@ -334,10 +993,16 @@ function collectNamespaces(ast, state) {
|
|
|
334
993
|
let name, init;
|
|
335
994
|
if (m.type == "EnumStringMember") {
|
|
336
995
|
name = m.id.name;
|
|
337
|
-
init = m.init;
|
|
996
|
+
init = getLiteralNode(m.init);
|
|
997
|
+
if (!init) {
|
|
998
|
+
throw new Error("Unexpected enum initializer");
|
|
999
|
+
}
|
|
1000
|
+
if (init != m.init) {
|
|
1001
|
+
m.init = init;
|
|
1002
|
+
}
|
|
338
1003
|
if (
|
|
339
1004
|
init.type == "Literal" &&
|
|
340
|
-
|
|
1005
|
+
api_LiteralIntegerRe.test(init.raw)
|
|
341
1006
|
) {
|
|
342
1007
|
prev = init.value;
|
|
343
1008
|
}
|
|
@@ -358,11 +1023,11 @@ function collectNamespaces(ast, state) {
|
|
|
358
1023
|
};
|
|
359
1024
|
}
|
|
360
1025
|
}
|
|
361
|
-
if (!
|
|
1026
|
+
if (!api_hasProperty(values, name)) {
|
|
362
1027
|
values[name] = [];
|
|
363
1028
|
}
|
|
364
1029
|
(0,external_util_cjs_namespaceObject.pushUnique)(values[name], init);
|
|
365
|
-
if (!
|
|
1030
|
+
if (!api_hasProperty(state.index, name)) {
|
|
366
1031
|
state.index[name] = [];
|
|
367
1032
|
}
|
|
368
1033
|
(0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
|
|
@@ -412,7 +1077,7 @@ function collectNamespaces(ast, state) {
|
|
|
412
1077
|
* - if post returns false, the node it was called on is
|
|
413
1078
|
* removed.
|
|
414
1079
|
*/
|
|
415
|
-
function
|
|
1080
|
+
function api_traverseAst(node, pre, post) {
|
|
416
1081
|
const nodes = pre && pre(node);
|
|
417
1082
|
if (nodes === false) return;
|
|
418
1083
|
for (const key of nodes || Object.keys(node)) {
|
|
@@ -420,7 +1085,7 @@ function traverseAst(node, pre, post) {
|
|
|
420
1085
|
if (!value) continue;
|
|
421
1086
|
if (Array.isArray(value)) {
|
|
422
1087
|
const deletions = value.reduce((state, obj, i) => {
|
|
423
|
-
const repl =
|
|
1088
|
+
const repl = api_traverseAst(obj, pre, post);
|
|
424
1089
|
if (repl === false) {
|
|
425
1090
|
if (!state) state = {};
|
|
426
1091
|
state[i] = true;
|
|
@@ -437,7 +1102,7 @@ function traverseAst(node, pre, post) {
|
|
|
437
1102
|
);
|
|
438
1103
|
}
|
|
439
1104
|
} else if (typeof value == "object" && value.type) {
|
|
440
|
-
const repl =
|
|
1105
|
+
const repl = api_traverseAst(value, pre, post);
|
|
441
1106
|
if (repl === false) {
|
|
442
1107
|
delete node[key];
|
|
443
1108
|
} else if (repl != null) {
|