@markw65/monkeyc-optimizer 1.0.6 → 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 +18 -0
- package/build/api.cjs +687 -23
- package/build/optimizer.cjs +232 -50
- package/package.json +1 -1
package/build/api.cjs
CHANGED
|
@@ -40,12 +40,12 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
40
40
|
|
|
41
41
|
// EXPORTS
|
|
42
42
|
__webpack_require__.d(__webpack_exports__, {
|
|
43
|
-
"LiteralIntegerRe": () => (/* binding */
|
|
44
|
-
"collectNamespaces": () => (/* binding */
|
|
43
|
+
"LiteralIntegerRe": () => (/* binding */ api_LiteralIntegerRe),
|
|
44
|
+
"collectNamespaces": () => (/* binding */ api_collectNamespaces),
|
|
45
45
|
"formatAst": () => (/* binding */ formatAst),
|
|
46
|
-
"getApiMapping": () => (/* binding */
|
|
47
|
-
"hasProperty": () => (/* binding */
|
|
48
|
-
"traverseAst": () => (/* binding */
|
|
46
|
+
"getApiMapping": () => (/* binding */ api_getApiMapping),
|
|
47
|
+
"hasProperty": () => (/* binding */ api_hasProperty),
|
|
48
|
+
"traverseAst": () => (/* binding */ api_traverseAst)
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
;// CONCATENATED MODULE: external "@markw65/prettier-plugin-monkeyc"
|
|
@@ -112,6 +112,663 @@ const negativeFixups = [
|
|
|
112
112
|
"Toybox.WatchUi.LAYOUT_HALIGN_LEFT",
|
|
113
113
|
];
|
|
114
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
|
+
|
|
115
772
|
;// CONCATENATED MODULE: ./src/api.js
|
|
116
773
|
|
|
117
774
|
|
|
@@ -119,7 +776,8 @@ const negativeFixups = [
|
|
|
119
776
|
|
|
120
777
|
|
|
121
778
|
|
|
122
|
-
|
|
779
|
+
|
|
780
|
+
const api_LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/;
|
|
123
781
|
/*
|
|
124
782
|
* This is an unfortunate hack. I want to be able to extract things
|
|
125
783
|
* like the types of all of a Class's variables (in particular the type
|
|
@@ -131,7 +789,7 @@ const LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/;
|
|
|
131
789
|
*/
|
|
132
790
|
|
|
133
791
|
// Extract all enum values from api.mir
|
|
134
|
-
async function
|
|
792
|
+
async function api_getApiMapping(state) {
|
|
135
793
|
// get the path to the currently active sdk
|
|
136
794
|
const parser = prettier_plugin_monkeyc_namespaceObject.parsers.monkeyc;
|
|
137
795
|
|
|
@@ -145,7 +803,7 @@ async function getApiMapping(state) {
|
|
|
145
803
|
.replace(/^(\s*type)\s/gm, "$1def ");
|
|
146
804
|
|
|
147
805
|
try {
|
|
148
|
-
const result =
|
|
806
|
+
const result = api_collectNamespaces(parser.parse(api, {}), state);
|
|
149
807
|
negativeFixups.forEach((fixup) => {
|
|
150
808
|
const value = fixup.split(".").reduce((state, part) => {
|
|
151
809
|
const decls = state.decls[part];
|
|
@@ -170,18 +828,18 @@ async function getApiMapping(state) {
|
|
|
170
828
|
}
|
|
171
829
|
}
|
|
172
830
|
|
|
173
|
-
function
|
|
831
|
+
function api_hasProperty(obj, prop) {
|
|
174
832
|
return obj && Object.prototype.hasOwnProperty.call(obj, prop);
|
|
175
833
|
}
|
|
176
834
|
|
|
177
|
-
function
|
|
835
|
+
function api_collectNamespaces(ast, state) {
|
|
178
836
|
state = state || {};
|
|
179
837
|
if (!state.index) state.index = {};
|
|
180
838
|
if (!state.stack) {
|
|
181
839
|
state.stack = [{ type: "Program", name: "$", fullName: "$" }];
|
|
182
840
|
}
|
|
183
841
|
const checkOne = (ns, name) => {
|
|
184
|
-
if (
|
|
842
|
+
if (api_hasProperty(ns.decls, name)) {
|
|
185
843
|
return ns.decls[name];
|
|
186
844
|
}
|
|
187
845
|
return null;
|
|
@@ -219,7 +877,7 @@ function collectNamespaces(ast, state) {
|
|
|
219
877
|
};
|
|
220
878
|
|
|
221
879
|
state.traverse = (root) =>
|
|
222
|
-
|
|
880
|
+
api_traverseAst(
|
|
223
881
|
root,
|
|
224
882
|
(node) => {
|
|
225
883
|
try {
|
|
@@ -263,7 +921,7 @@ function collectNamespaces(ast, state) {
|
|
|
263
921
|
.join(".");
|
|
264
922
|
if (elm.name) {
|
|
265
923
|
if (!parent.decls) parent.decls = {};
|
|
266
|
-
if (
|
|
924
|
+
if (api_hasProperty(parent.decls, elm.name)) {
|
|
267
925
|
const what =
|
|
268
926
|
node.type == "ModuleDeclaration" ? "type" : "node";
|
|
269
927
|
const e = parent.decls[elm.name].find(
|
|
@@ -296,7 +954,7 @@ function collectNamespaces(ast, state) {
|
|
|
296
954
|
case "TypedefDeclaration": {
|
|
297
955
|
const [parent] = state.stack.slice(-1);
|
|
298
956
|
if (!parent.decls) parent.decls = {};
|
|
299
|
-
if (!
|
|
957
|
+
if (!api_hasProperty(parent.decls, node.id.name)) {
|
|
300
958
|
parent.decls[node.id.name] = [];
|
|
301
959
|
}
|
|
302
960
|
(0,external_util_cjs_namespaceObject.pushUnique)(
|
|
@@ -309,12 +967,12 @@ function collectNamespaces(ast, state) {
|
|
|
309
967
|
const [parent] = state.stack.slice(-1);
|
|
310
968
|
if (!parent.decls) parent.decls = {};
|
|
311
969
|
node.declarations.forEach((decl) => {
|
|
312
|
-
if (!
|
|
970
|
+
if (!api_hasProperty(parent.decls, decl.id.name)) {
|
|
313
971
|
parent.decls[decl.id.name] = [];
|
|
314
972
|
}
|
|
315
973
|
if (node.kind == "const") {
|
|
316
974
|
(0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[decl.id.name], decl.init);
|
|
317
|
-
if (!
|
|
975
|
+
if (!api_hasProperty(state.index, decl.id.name)) {
|
|
318
976
|
state.index[decl.id.name] = [];
|
|
319
977
|
}
|
|
320
978
|
(0,external_util_cjs_namespaceObject.pushUnique)(state.index[decl.id.name], parent);
|
|
@@ -335,10 +993,16 @@ function collectNamespaces(ast, state) {
|
|
|
335
993
|
let name, init;
|
|
336
994
|
if (m.type == "EnumStringMember") {
|
|
337
995
|
name = m.id.name;
|
|
338
|
-
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
|
+
}
|
|
339
1003
|
if (
|
|
340
1004
|
init.type == "Literal" &&
|
|
341
|
-
|
|
1005
|
+
api_LiteralIntegerRe.test(init.raw)
|
|
342
1006
|
) {
|
|
343
1007
|
prev = init.value;
|
|
344
1008
|
}
|
|
@@ -359,11 +1023,11 @@ function collectNamespaces(ast, state) {
|
|
|
359
1023
|
};
|
|
360
1024
|
}
|
|
361
1025
|
}
|
|
362
|
-
if (!
|
|
1026
|
+
if (!api_hasProperty(values, name)) {
|
|
363
1027
|
values[name] = [];
|
|
364
1028
|
}
|
|
365
1029
|
(0,external_util_cjs_namespaceObject.pushUnique)(values[name], init);
|
|
366
|
-
if (!
|
|
1030
|
+
if (!api_hasProperty(state.index, name)) {
|
|
367
1031
|
state.index[name] = [];
|
|
368
1032
|
}
|
|
369
1033
|
(0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
|
|
@@ -413,7 +1077,7 @@ function collectNamespaces(ast, state) {
|
|
|
413
1077
|
* - if post returns false, the node it was called on is
|
|
414
1078
|
* removed.
|
|
415
1079
|
*/
|
|
416
|
-
function
|
|
1080
|
+
function api_traverseAst(node, pre, post) {
|
|
417
1081
|
const nodes = pre && pre(node);
|
|
418
1082
|
if (nodes === false) return;
|
|
419
1083
|
for (const key of nodes || Object.keys(node)) {
|
|
@@ -421,7 +1085,7 @@ function traverseAst(node, pre, post) {
|
|
|
421
1085
|
if (!value) continue;
|
|
422
1086
|
if (Array.isArray(value)) {
|
|
423
1087
|
const deletions = value.reduce((state, obj, i) => {
|
|
424
|
-
const repl =
|
|
1088
|
+
const repl = api_traverseAst(obj, pre, post);
|
|
425
1089
|
if (repl === false) {
|
|
426
1090
|
if (!state) state = {};
|
|
427
1091
|
state[i] = true;
|
|
@@ -438,7 +1102,7 @@ function traverseAst(node, pre, post) {
|
|
|
438
1102
|
);
|
|
439
1103
|
}
|
|
440
1104
|
} else if (typeof value == "object" && value.type) {
|
|
441
|
-
const repl =
|
|
1105
|
+
const repl = api_traverseAst(value, pre, post);
|
|
442
1106
|
if (repl === false) {
|
|
443
1107
|
delete node[key];
|
|
444
1108
|
} else if (repl != null) {
|