@markw65/monkeyc-optimizer 1.0.6 → 1.0.9
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 +54 -1
- package/build/api.cjs +783 -24
- package/build/optimizer.cjs +581 -122
- package/build/sdk-util.cjs +7245 -0
- package/build/util.cjs +25 -25
- package/package.json +5 -3
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"
|
|
@@ -54,8 +54,757 @@ const prettier_plugin_monkeyc_namespaceObject = require("@markw65/prettier-plugi
|
|
|
54
54
|
const promises_namespaceObject = require("fs/promises");
|
|
55
55
|
;// CONCATENATED MODULE: external "prettier/standalone.js"
|
|
56
56
|
const standalone_js_namespaceObject = require("prettier/standalone.js");
|
|
57
|
+
;// CONCATENATED MODULE: external "./api.cjs"
|
|
58
|
+
const external_api_cjs_namespaceObject = require("./api.cjs");
|
|
57
59
|
;// CONCATENATED MODULE: external "./util.cjs"
|
|
58
60
|
const external_util_cjs_namespaceObject = require("./util.cjs");
|
|
61
|
+
;// CONCATENATED MODULE: ./src/mc-rewrite.js
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
function processImports(allImports, lookup) {
|
|
68
|
+
allImports.forEach(({ node, stack }) => {
|
|
69
|
+
const [name, module] = lookup(node.id, node.as && node.as.name, stack);
|
|
70
|
+
if (name && module) {
|
|
71
|
+
const [parent] = stack.slice(-1);
|
|
72
|
+
if (!parent.decls) parent.decls = {};
|
|
73
|
+
if (!hasProperty(parent.decls, name)) parent.decls[name] = [];
|
|
74
|
+
module.forEach((m) => {
|
|
75
|
+
if (m.type == "ModuleDeclaration") {
|
|
76
|
+
pushUnique(parent.decls[name], m);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function collectClassInfo(state) {
|
|
84
|
+
state.allClasses.forEach((elm) => {
|
|
85
|
+
if (elm.node.superClass) {
|
|
86
|
+
const [, classes] = state.lookup(elm.node.superClass, null, elm.stack);
|
|
87
|
+
if (classes) {
|
|
88
|
+
elm.superClass = classes.filter((c) => c.type == "ClassDeclaration");
|
|
89
|
+
}
|
|
90
|
+
// set it "true" if there is a superClass, but we can't find it.
|
|
91
|
+
if (!elm.superClass || !elm.superClass.length) elm.superClass = true;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const markOverrides = (cls, scls) => {
|
|
96
|
+
if (scls === true) return;
|
|
97
|
+
scls.forEach((c) => {
|
|
98
|
+
c.decls &&
|
|
99
|
+
Object.values(c.decls).forEach((f) => {
|
|
100
|
+
if (f.type == "FunctionDeclaration") {
|
|
101
|
+
if (hasProperty(cls.decls, f.name)) {
|
|
102
|
+
f.hasOverride = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
if (c.superClass) markOverrides(cls, c.superClass);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
state.allClasses.forEach((elm) => {
|
|
111
|
+
if (elm.superClass) markOverrides(elm, elm.superClass);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function analyze(fileNames, buildConfig) {
|
|
116
|
+
const excludeAnnotations =
|
|
117
|
+
buildConfig && buildConfig.excludeAnnotations
|
|
118
|
+
? Object.fromEntries(buildConfig.excludeAnnotations.map((a) => [a, true]))
|
|
119
|
+
: {};
|
|
120
|
+
|
|
121
|
+
const allImports = [];
|
|
122
|
+
const state = {
|
|
123
|
+
allFunctions: [],
|
|
124
|
+
allClasses: [],
|
|
125
|
+
shouldExclude(node) {
|
|
126
|
+
if (node.attrs && node.attrs.attrs) {
|
|
127
|
+
if (
|
|
128
|
+
node.attrs.attrs.filter((attr) => {
|
|
129
|
+
if (attr.type != "UnaryExpression") return false;
|
|
130
|
+
if (attr.argument.type != "Identifier") return false;
|
|
131
|
+
return hasProperty(excludeAnnotations, attr.argument.name);
|
|
132
|
+
}).length
|
|
133
|
+
) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
post(node) {
|
|
139
|
+
switch (node.type) {
|
|
140
|
+
case "FunctionDeclaration":
|
|
141
|
+
case "ClassDeclaration": {
|
|
142
|
+
const [scope] = state.stack.slice(-1);
|
|
143
|
+
const stack = state.stack.slice(0, -1);
|
|
144
|
+
scope.stack = stack;
|
|
145
|
+
(node.type == "FunctionDeclaration"
|
|
146
|
+
? state.allFunctions
|
|
147
|
+
: state.allClasses
|
|
148
|
+
).push(scope);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
case "Using":
|
|
152
|
+
case "ImportModule":
|
|
153
|
+
allImports.push({ node, stack: state.stack.slice() });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
await getApiMapping(state);
|
|
160
|
+
|
|
161
|
+
// Mark all functions from api.mir as "special" by
|
|
162
|
+
// setting their bodies to null. In api.mir, they're
|
|
163
|
+
// all empty, which makes it look like they're
|
|
164
|
+
// do-nothing functions.
|
|
165
|
+
const markApi = (node) => {
|
|
166
|
+
if (node.type == "FunctionDeclaration") {
|
|
167
|
+
node.node.body = null;
|
|
168
|
+
}
|
|
169
|
+
if (node.decls) {
|
|
170
|
+
Object.values(node.decls).forEach(markApi);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
markApi(state.stack[0]);
|
|
174
|
+
|
|
175
|
+
const files = await Promise.all(
|
|
176
|
+
fileNames.map(async (name) => ({
|
|
177
|
+
name,
|
|
178
|
+
monkeyCSource: (await fs.readFile(name))
|
|
179
|
+
.toString()
|
|
180
|
+
.replace(/\r\n/g, "\n"),
|
|
181
|
+
}))
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
files.forEach((f) => {
|
|
185
|
+
f.ast = MonkeyC.parsers.monkeyc.parse(f.monkeyCSource, {
|
|
186
|
+
grammarSource: f.name,
|
|
187
|
+
});
|
|
188
|
+
f.ast.source = f.name;
|
|
189
|
+
f.ast.monkeyCSource = f.monkeyCSource;
|
|
190
|
+
delete f.monkeyCSource;
|
|
191
|
+
collectNamespaces(f.ast, state);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
delete state.shouldExclude;
|
|
195
|
+
delete state.post;
|
|
196
|
+
|
|
197
|
+
processImports(allImports, state.lookup);
|
|
198
|
+
collectClassInfo(state);
|
|
199
|
+
|
|
200
|
+
return { files, state };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getLiteralNode(node) {
|
|
204
|
+
if (Array.isArray(node)) {
|
|
205
|
+
if (!node.length) return null;
|
|
206
|
+
if (node.length === 1) return getLiteralNode(node[0]);
|
|
207
|
+
let result;
|
|
208
|
+
if (
|
|
209
|
+
node.every((n) => {
|
|
210
|
+
const lit = getLiteralNode(n);
|
|
211
|
+
if (!lit) return false;
|
|
212
|
+
if (!result) {
|
|
213
|
+
result = lit;
|
|
214
|
+
} else {
|
|
215
|
+
if (lit.value !== result.value) return false;
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
})
|
|
219
|
+
) {
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
if (node.type == "Literal") return node;
|
|
225
|
+
if (node.type == "BinaryExpression" && node.operator == "as") {
|
|
226
|
+
return getLiteralNode(node.left) && node;
|
|
227
|
+
}
|
|
228
|
+
if (node.type == "UnaryExpression") {
|
|
229
|
+
if (node.argument.type != "Literal") return null;
|
|
230
|
+
switch (node.operator) {
|
|
231
|
+
case "-":
|
|
232
|
+
if (typeof node.argument.value == "number") {
|
|
233
|
+
return {
|
|
234
|
+
...node.argument,
|
|
235
|
+
value: -node.argument.value,
|
|
236
|
+
raw: "-" + node.argument.value,
|
|
237
|
+
enumType: node.enumType,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getNodeValue(node) {
|
|
245
|
+
if (
|
|
246
|
+
node.type == "BinaryExpression" &&
|
|
247
|
+
node.operator == "as" &&
|
|
248
|
+
node.right.type == "TypeSpecList" &&
|
|
249
|
+
node.right.ts.length == 1 &&
|
|
250
|
+
typeof node.right.ts[0] == "string"
|
|
251
|
+
) {
|
|
252
|
+
// this is a cast we inserted to retain the type of an enum
|
|
253
|
+
// any arithmetic on it will revert to "Number", or "Long",
|
|
254
|
+
// so just ignore it.
|
|
255
|
+
return getNodeValue(node.left);
|
|
256
|
+
}
|
|
257
|
+
if (node.type != "Literal") {
|
|
258
|
+
return [null, null];
|
|
259
|
+
}
|
|
260
|
+
let type = node.value === null ? "Null" : typeof node.value;
|
|
261
|
+
if (type === "number") {
|
|
262
|
+
const match = LiteralIntegerRe.exec(node.raw);
|
|
263
|
+
if (match) {
|
|
264
|
+
type = match[2] == "l" ? "Long" : "Number";
|
|
265
|
+
} else if (node.raw.endsWith("d")) {
|
|
266
|
+
type = "Double";
|
|
267
|
+
} else {
|
|
268
|
+
type = "Float";
|
|
269
|
+
}
|
|
270
|
+
} else if (type === "string") {
|
|
271
|
+
type = "String";
|
|
272
|
+
} else if (type === "boolean") {
|
|
273
|
+
type = "Boolean";
|
|
274
|
+
} else {
|
|
275
|
+
type = "Unknown";
|
|
276
|
+
}
|
|
277
|
+
return [node, type];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function optimizeNode(node) {
|
|
281
|
+
switch (node.type) {
|
|
282
|
+
case "UnaryExpression": {
|
|
283
|
+
const [arg, type] = getNodeValue(node.argument);
|
|
284
|
+
if (arg === null) break;
|
|
285
|
+
switch (node.operator) {
|
|
286
|
+
case "+":
|
|
287
|
+
if (type === "Number" || type === "Long") {
|
|
288
|
+
return arg;
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
case "-":
|
|
292
|
+
if (type === "Number" || type === "Long") {
|
|
293
|
+
return {
|
|
294
|
+
...arg,
|
|
295
|
+
value: -arg.value,
|
|
296
|
+
raw: (-arg.value).toString() + (type === "Long" ? "l" : ""),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
case "!":
|
|
301
|
+
case "~":
|
|
302
|
+
{
|
|
303
|
+
let value;
|
|
304
|
+
if (type === "Number" || type === "Long") {
|
|
305
|
+
value = -arg.value - 1;
|
|
306
|
+
} else if (type === "Boolean" && node.operator == "!") {
|
|
307
|
+
value = !arg.value;
|
|
308
|
+
}
|
|
309
|
+
if (value !== undefined) {
|
|
310
|
+
return {
|
|
311
|
+
...arg,
|
|
312
|
+
value,
|
|
313
|
+
raw: value.toString() + (type === "Long" ? "l" : ""),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case "BinaryExpression": {
|
|
322
|
+
const operators = {
|
|
323
|
+
"+": (left, right) => left + right,
|
|
324
|
+
"-": (left, right) => left - right,
|
|
325
|
+
"*": (left, right) => left * right,
|
|
326
|
+
"/": (left, right) => Math.trunc(left / right),
|
|
327
|
+
"%": (left, right) => left % right,
|
|
328
|
+
"&": (left, right, type) => (type === "Number" ? left & right : null),
|
|
329
|
+
"|": (left, right, type) => (type === "Number" ? left | right : null),
|
|
330
|
+
"<<": (left, right, type) => (type === "Number" ? left << right : null),
|
|
331
|
+
">>": (left, right, type) => (type === "Number" ? left >> right : null),
|
|
332
|
+
};
|
|
333
|
+
const op = operators[node.operator];
|
|
334
|
+
if (op) {
|
|
335
|
+
const [left, left_type] = getNodeValue(node.left);
|
|
336
|
+
const [right, right_type] = getNodeValue(node.right);
|
|
337
|
+
if (!left || !right) break;
|
|
338
|
+
if (
|
|
339
|
+
left_type != right_type ||
|
|
340
|
+
(left_type != "Number" && left_type != "Long")
|
|
341
|
+
) {
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
const value = op(left.value, right.value, left_type);
|
|
345
|
+
if (value === null) break;
|
|
346
|
+
return {
|
|
347
|
+
...left,
|
|
348
|
+
value,
|
|
349
|
+
raw: value.toString() + (left_type === "Long" ? "l" : ""),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case "FunctionDeclaration":
|
|
355
|
+
if (node.body && evaluateFunction(node, null) !== false) {
|
|
356
|
+
node.optimizable = true;
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function evaluateFunction(func, args) {
|
|
363
|
+
if (args && args.length != func.params.length) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
const paramValues =
|
|
367
|
+
args && Object.fromEntries(func.params.map((p, i) => [p.name, args[i]]));
|
|
368
|
+
let ret = null;
|
|
369
|
+
const body = args ? JSON.parse(JSON.stringify(func.body)) : func.body;
|
|
370
|
+
try {
|
|
371
|
+
traverseAst(
|
|
372
|
+
body,
|
|
373
|
+
(node) => {
|
|
374
|
+
switch (node.type) {
|
|
375
|
+
case "BlockStatement":
|
|
376
|
+
case "ReturnStatement":
|
|
377
|
+
case "UnaryExpression":
|
|
378
|
+
case "BinaryExpression":
|
|
379
|
+
case "Literal":
|
|
380
|
+
case "Identifier":
|
|
381
|
+
return;
|
|
382
|
+
default:
|
|
383
|
+
throw new Error("Bad node type");
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
args &&
|
|
387
|
+
((node) => {
|
|
388
|
+
switch (node.type) {
|
|
389
|
+
case "ReturnStatement":
|
|
390
|
+
ret = node.argument;
|
|
391
|
+
return;
|
|
392
|
+
case "BlockStatement":
|
|
393
|
+
case "Literal":
|
|
394
|
+
return;
|
|
395
|
+
case "Identifier":
|
|
396
|
+
if (hasProperty(paramValues, node.name)) {
|
|
397
|
+
return paramValues[node.name];
|
|
398
|
+
}
|
|
399
|
+
// fall through;
|
|
400
|
+
default: {
|
|
401
|
+
const repl = optimizeNode(node);
|
|
402
|
+
if (repl && repl.type === "Literal") return repl;
|
|
403
|
+
throw new Error("Didn't optimize");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
return ret;
|
|
409
|
+
} catch (e) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function optimizeMonkeyC(fileNames, buildConfig) {
|
|
415
|
+
const { files, state } = await analyze(fileNames, buildConfig);
|
|
416
|
+
const replace = (node, obj) => {
|
|
417
|
+
for (const k of Object.keys(node)) {
|
|
418
|
+
delete node[k];
|
|
419
|
+
}
|
|
420
|
+
if (obj.enumType) {
|
|
421
|
+
obj = {
|
|
422
|
+
type: "BinaryExpression",
|
|
423
|
+
operator: "as",
|
|
424
|
+
left: obj,
|
|
425
|
+
right: { type: "TypeSpecList", ts: [obj.enumType] },
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
429
|
+
node[k] = v;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
const lookupAndReplace = (node) => {
|
|
433
|
+
const [, objects] = state.lookup(node);
|
|
434
|
+
if (!objects) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
const obj = getLiteralNode(objects);
|
|
438
|
+
if (!obj) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
replace(node, obj);
|
|
442
|
+
return true;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/*
|
|
446
|
+
* Might this function be called from somewhere, including
|
|
447
|
+
* callbacks from the api (eg getSettingsView, etc).
|
|
448
|
+
*/
|
|
449
|
+
const maybeCalled = (func) => {
|
|
450
|
+
if (!func.body) {
|
|
451
|
+
// this is an api.mir function. It can be called
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
if (hasProperty(state.exposed, func.id.name)) return true;
|
|
455
|
+
if (hasProperty(state.calledFunctions, func.id.name)) {
|
|
456
|
+
return (
|
|
457
|
+
state.calledFunctions[func.id.name].find((f) => f === func) !== null
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
/*
|
|
462
|
+
* Does elm (a class) have a maybeCalled function called name,
|
|
463
|
+
* anywhere in its superClass chain.
|
|
464
|
+
*/
|
|
465
|
+
const checkInherited = (elm, name) =>
|
|
466
|
+
elm.superClass === true ||
|
|
467
|
+
elm.superClass.some(
|
|
468
|
+
(sc) =>
|
|
469
|
+
(hasProperty(sc.decls, name) &&
|
|
470
|
+
sc.decls[name].some(
|
|
471
|
+
(f) => f.type == "FunctionDeclaration" && maybeCalled(f)
|
|
472
|
+
)) ||
|
|
473
|
+
(sc.superClass && checkInherited(sc, name))
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
state.localsStack = [{}];
|
|
477
|
+
state.exposed = {};
|
|
478
|
+
state.calledFunctions = {};
|
|
479
|
+
state.pre = (node) => {
|
|
480
|
+
switch (node.type) {
|
|
481
|
+
case "ConditionalExpression":
|
|
482
|
+
case "IfStatement":
|
|
483
|
+
case "DoWhileStatement":
|
|
484
|
+
case "WhileStatement":
|
|
485
|
+
state.traverse(node.test);
|
|
486
|
+
const [value, type] = getNodeValue(node.test);
|
|
487
|
+
if (value) {
|
|
488
|
+
let result = null;
|
|
489
|
+
if (type === "Null") {
|
|
490
|
+
result = false;
|
|
491
|
+
} else if (
|
|
492
|
+
type === "Boolean" ||
|
|
493
|
+
type === "Number" ||
|
|
494
|
+
type === "Long"
|
|
495
|
+
) {
|
|
496
|
+
result = !!value.value;
|
|
497
|
+
}
|
|
498
|
+
if (result !== null) {
|
|
499
|
+
if (
|
|
500
|
+
node.type === "IfStatement" ||
|
|
501
|
+
node.type === "ConditionalExpression"
|
|
502
|
+
) {
|
|
503
|
+
if (result === false) {
|
|
504
|
+
node.consequent = null;
|
|
505
|
+
} else {
|
|
506
|
+
node.alternate = null;
|
|
507
|
+
}
|
|
508
|
+
node.test = result;
|
|
509
|
+
} else if (node.type === "WhileStatement") {
|
|
510
|
+
if (result === false) {
|
|
511
|
+
node.body = null;
|
|
512
|
+
}
|
|
513
|
+
} else if (node.type === "DoWhileStatement") {
|
|
514
|
+
if (result === false) {
|
|
515
|
+
node.test = null;
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
throw new Error("Unexpected Node type");
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return;
|
|
523
|
+
|
|
524
|
+
case "EnumDeclaration":
|
|
525
|
+
return false;
|
|
526
|
+
case "ForStatement": {
|
|
527
|
+
const map = state.localsStack.slice(-1).pop().map;
|
|
528
|
+
if (map) {
|
|
529
|
+
state.localsStack.push({ node, map: { ...map } });
|
|
530
|
+
}
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
case "VariableDeclarator": {
|
|
534
|
+
const locals = state.localsStack.slice(-1).pop();
|
|
535
|
+
const { map } = locals;
|
|
536
|
+
if (map) {
|
|
537
|
+
if (hasProperty(map, node.id.name)) {
|
|
538
|
+
// We already have a variable with this name in scope
|
|
539
|
+
// Recent monkeyc compilers complain, so rename it
|
|
540
|
+
let suffix = 0;
|
|
541
|
+
let node_name = node.id.name;
|
|
542
|
+
const match = node_name.match(/^pmcr_(.*)_(\d+)$/);
|
|
543
|
+
if (match) {
|
|
544
|
+
node_name = match[1];
|
|
545
|
+
suffix = parseInt(match[2], 10) + 1;
|
|
546
|
+
}
|
|
547
|
+
if (!locals.inners) {
|
|
548
|
+
// find all the names declared in this scope, to avoid
|
|
549
|
+
// more conflicts
|
|
550
|
+
locals.inners = {};
|
|
551
|
+
traverseAst(locals.node, (node) => {
|
|
552
|
+
if (node.type === "VariableDeclarator") {
|
|
553
|
+
locals.inners[node.id.name] = true;
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
let name;
|
|
558
|
+
while (true) {
|
|
559
|
+
name = `pmcr_${node_name}_${suffix}`;
|
|
560
|
+
if (
|
|
561
|
+
!hasProperty(map, name) &&
|
|
562
|
+
!hasProperty(locals.inners, name)
|
|
563
|
+
) {
|
|
564
|
+
// we also need to ensure that we don't hide the name of
|
|
565
|
+
// an outer module, class, function, enum or variable,
|
|
566
|
+
// since someone might want to access it from this scope.
|
|
567
|
+
let ok = false;
|
|
568
|
+
let i;
|
|
569
|
+
for (i = state.stack.length; i--; ) {
|
|
570
|
+
const elm = state.stack[i];
|
|
571
|
+
if (ok) {
|
|
572
|
+
if (hasProperty(elm.decls, name)) {
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
} else if (elm.node.type === "FunctionDeclaration") {
|
|
576
|
+
ok = true;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (i < 0) {
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
suffix++;
|
|
584
|
+
}
|
|
585
|
+
map[node.id.name] = name;
|
|
586
|
+
map[name] = true;
|
|
587
|
+
node.id.name = name;
|
|
588
|
+
} else {
|
|
589
|
+
map[node.id.name] = true;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return ["init"];
|
|
593
|
+
}
|
|
594
|
+
case "UnaryExpression":
|
|
595
|
+
if (node.operator == ":") {
|
|
596
|
+
// If we produce a Symbol, for a given name,
|
|
597
|
+
// its possible that someone uses that symbol
|
|
598
|
+
// indirectly, so we can't remove any enums or
|
|
599
|
+
// constants with that name (we can still replace
|
|
600
|
+
// uses of those constants though).
|
|
601
|
+
state.exposed[node.argument.name] = true;
|
|
602
|
+
// In any case, we can't replace *this* use of the
|
|
603
|
+
// symbol with its value...
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
break;
|
|
607
|
+
case "Identifier": {
|
|
608
|
+
const map = state.localsStack.slice(-1).pop().map;
|
|
609
|
+
if (map) {
|
|
610
|
+
if (hasProperty(map, node.name)) {
|
|
611
|
+
const name = map[node.name];
|
|
612
|
+
if (name !== true) {
|
|
613
|
+
node.name = name;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (hasProperty(state.index, node.name)) {
|
|
618
|
+
if (!lookupAndReplace(node)) {
|
|
619
|
+
state.exposed[node.name] = true;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
case "MemberExpression":
|
|
625
|
+
if (node.property.type === "Identifier" && !node.computed) {
|
|
626
|
+
if (hasProperty(state.index, node.property.name)) {
|
|
627
|
+
if (lookupAndReplace(node)) {
|
|
628
|
+
return false;
|
|
629
|
+
} else {
|
|
630
|
+
state.exposed[node.property.name] = true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Don't optimize the property.
|
|
634
|
+
return ["object"];
|
|
635
|
+
}
|
|
636
|
+
break;
|
|
637
|
+
case "BlockStatement": {
|
|
638
|
+
const map = state.localsStack.slice(-1).pop().map;
|
|
639
|
+
if (map) {
|
|
640
|
+
state.localsStack.push({
|
|
641
|
+
node,
|
|
642
|
+
map: { ...map },
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
case "FunctionDeclaration": {
|
|
648
|
+
const map = {};
|
|
649
|
+
node.params && node.params.forEach((p) => (map[p.name] = true));
|
|
650
|
+
state.localsStack.push({ node, map });
|
|
651
|
+
const [parent] = state.stack.slice(-2);
|
|
652
|
+
if (parent.type == "ClassDeclaration" && !maybeCalled(node)) {
|
|
653
|
+
let used = false;
|
|
654
|
+
if (node.id.name == "initialize") {
|
|
655
|
+
used = true;
|
|
656
|
+
} else if (parent.superClass) {
|
|
657
|
+
used = checkInherited(parent, node.id.name);
|
|
658
|
+
}
|
|
659
|
+
if (used) {
|
|
660
|
+
if (!hasProperty(state.calledFunctions, node.id.name)) {
|
|
661
|
+
state.calledFunctions[node.id.name] = [];
|
|
662
|
+
}
|
|
663
|
+
state.calledFunctions[node.id.name].push(node);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
state.post = (node) => {
|
|
670
|
+
if (state.localsStack.slice(-1).pop().node === node) {
|
|
671
|
+
state.localsStack.pop();
|
|
672
|
+
}
|
|
673
|
+
const opt = optimizeNode(node);
|
|
674
|
+
if (opt) {
|
|
675
|
+
replace(node, opt);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
switch (node.type) {
|
|
679
|
+
case "ConditionalExpression":
|
|
680
|
+
case "IfStatement":
|
|
681
|
+
if (typeof node.test === "boolean") {
|
|
682
|
+
const rep = node.test ? node.consequent : node.alternate;
|
|
683
|
+
if (!rep) return false;
|
|
684
|
+
replace(node, rep);
|
|
685
|
+
}
|
|
686
|
+
break;
|
|
687
|
+
case "WhileStatement":
|
|
688
|
+
if (!node.body) return false;
|
|
689
|
+
break;
|
|
690
|
+
case "DoWhileStatement":
|
|
691
|
+
if (!node.test) return node.body;
|
|
692
|
+
break;
|
|
693
|
+
|
|
694
|
+
case "CallExpression": {
|
|
695
|
+
const [name, callees] = state.lookup(node.callee);
|
|
696
|
+
if (!callees || !callees.length) {
|
|
697
|
+
const n =
|
|
698
|
+
name ||
|
|
699
|
+
node.callee.name ||
|
|
700
|
+
(node.callee.property && node.callee.property.name);
|
|
701
|
+
if (n) {
|
|
702
|
+
state.exposed[n] = true;
|
|
703
|
+
} else {
|
|
704
|
+
// There are unnamed CallExpressions, such as new [size]
|
|
705
|
+
// So there's nothing to do here.
|
|
706
|
+
}
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (callees.length == 1) {
|
|
710
|
+
const callee = callees[0].node;
|
|
711
|
+
if (
|
|
712
|
+
callee.optimizable &&
|
|
713
|
+
!callee.hasOverride &&
|
|
714
|
+
node.arguments.every((n) => getNodeValue(n)[0] !== null)
|
|
715
|
+
) {
|
|
716
|
+
const ret = evaluateFunction(callee, node.arguments);
|
|
717
|
+
if (ret) {
|
|
718
|
+
replace(node, ret);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (!hasProperty(state.calledFunctions, name)) {
|
|
724
|
+
state.calledFunctions[name] = [];
|
|
725
|
+
}
|
|
726
|
+
callees.forEach((c) => state.calledFunctions[name].push(c.node));
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
files.forEach((f) => {
|
|
732
|
+
collectNamespaces(f.ast, state);
|
|
733
|
+
});
|
|
734
|
+
files.forEach((f) => {
|
|
735
|
+
traverseAst(f.ast, null, (node) => {
|
|
736
|
+
switch (node.type) {
|
|
737
|
+
case "EnumStringBody":
|
|
738
|
+
if (
|
|
739
|
+
node.members.every((m) => {
|
|
740
|
+
const name = m.name || m.id.name;
|
|
741
|
+
return (
|
|
742
|
+
hasProperty(state.index, name) &&
|
|
743
|
+
!hasProperty(state.exposed, name)
|
|
744
|
+
);
|
|
745
|
+
})
|
|
746
|
+
) {
|
|
747
|
+
node.enumType = [
|
|
748
|
+
...new Set(
|
|
749
|
+
node.members.map((m) => {
|
|
750
|
+
if (!m.init) return "Number";
|
|
751
|
+
const [node, type] = getNodeValue(m.init);
|
|
752
|
+
if (!node) {
|
|
753
|
+
throw new Error("Failed to get type for eliminated enum");
|
|
754
|
+
}
|
|
755
|
+
return type;
|
|
756
|
+
})
|
|
757
|
+
),
|
|
758
|
+
].join(" or ");
|
|
759
|
+
node.members.splice(0);
|
|
760
|
+
}
|
|
761
|
+
break;
|
|
762
|
+
case "EnumDeclaration":
|
|
763
|
+
if (!node.body.members.length) {
|
|
764
|
+
if (!node.id) return false;
|
|
765
|
+
if (!node.body.enumType) {
|
|
766
|
+
throw new Error("Missing enumType on optimized enum");
|
|
767
|
+
}
|
|
768
|
+
replace(node, {
|
|
769
|
+
type: "TypedefDeclaration",
|
|
770
|
+
id: node.id,
|
|
771
|
+
ts: {
|
|
772
|
+
type: "UnaryExpression",
|
|
773
|
+
argument: { type: "TypeSpecList", ts: [node.body.enumType] },
|
|
774
|
+
prefix: true,
|
|
775
|
+
operator: " as",
|
|
776
|
+
},
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
break;
|
|
780
|
+
case "VariableDeclaration": {
|
|
781
|
+
node.declarations = node.declarations.filter(
|
|
782
|
+
(d) =>
|
|
783
|
+
!hasProperty(state.index, d.id.name) ||
|
|
784
|
+
hasProperty(state.exposed, d.id.name)
|
|
785
|
+
);
|
|
786
|
+
if (!node.declarations.length) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
case "ClassElement":
|
|
792
|
+
if (!node.item) {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
break;
|
|
796
|
+
case "FunctionDeclaration":
|
|
797
|
+
if (!maybeCalled(node)) {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
return files;
|
|
806
|
+
}
|
|
807
|
+
|
|
59
808
|
;// CONCATENATED MODULE: ./src/negative-fixups.js
|
|
60
809
|
/*
|
|
61
810
|
* This is strange. It pretty much has to be a bug in the sdk,
|
|
@@ -112,6 +861,8 @@ const negativeFixups = [
|
|
|
112
861
|
"Toybox.WatchUi.LAYOUT_HALIGN_LEFT",
|
|
113
862
|
];
|
|
114
863
|
|
|
864
|
+
;// CONCATENATED MODULE: external "./sdk-util.cjs"
|
|
865
|
+
const external_sdk_util_cjs_namespaceObject = require("./sdk-util.cjs");
|
|
115
866
|
;// CONCATENATED MODULE: ./src/api.js
|
|
116
867
|
|
|
117
868
|
|
|
@@ -119,7 +870,9 @@ const negativeFixups = [
|
|
|
119
870
|
|
|
120
871
|
|
|
121
872
|
|
|
122
|
-
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
const api_LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/i;
|
|
123
876
|
/*
|
|
124
877
|
* This is an unfortunate hack. I want to be able to extract things
|
|
125
878
|
* like the types of all of a Class's variables (in particular the type
|
|
@@ -131,11 +884,11 @@ const LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/;
|
|
|
131
884
|
*/
|
|
132
885
|
|
|
133
886
|
// Extract all enum values from api.mir
|
|
134
|
-
async function
|
|
887
|
+
async function api_getApiMapping(state) {
|
|
135
888
|
// get the path to the currently active sdk
|
|
136
889
|
const parser = prettier_plugin_monkeyc_namespaceObject.parsers.monkeyc;
|
|
137
890
|
|
|
138
|
-
const sdk = await (0,
|
|
891
|
+
const sdk = await (0,external_sdk_util_cjs_namespaceObject.getSdkPath)();
|
|
139
892
|
|
|
140
893
|
const api = (await promises_namespaceObject.readFile(`${sdk}bin/api.mir`))
|
|
141
894
|
.toString()
|
|
@@ -145,7 +898,7 @@ async function getApiMapping(state) {
|
|
|
145
898
|
.replace(/^(\s*type)\s/gm, "$1def ");
|
|
146
899
|
|
|
147
900
|
try {
|
|
148
|
-
const result =
|
|
901
|
+
const result = api_collectNamespaces(parser.parse(api, {}), state);
|
|
149
902
|
negativeFixups.forEach((fixup) => {
|
|
150
903
|
const value = fixup.split(".").reduce((state, part) => {
|
|
151
904
|
const decls = state.decls[part];
|
|
@@ -170,18 +923,18 @@ async function getApiMapping(state) {
|
|
|
170
923
|
}
|
|
171
924
|
}
|
|
172
925
|
|
|
173
|
-
function
|
|
926
|
+
function api_hasProperty(obj, prop) {
|
|
174
927
|
return obj && Object.prototype.hasOwnProperty.call(obj, prop);
|
|
175
928
|
}
|
|
176
929
|
|
|
177
|
-
function
|
|
930
|
+
function api_collectNamespaces(ast, state) {
|
|
178
931
|
state = state || {};
|
|
179
932
|
if (!state.index) state.index = {};
|
|
180
933
|
if (!state.stack) {
|
|
181
934
|
state.stack = [{ type: "Program", name: "$", fullName: "$" }];
|
|
182
935
|
}
|
|
183
936
|
const checkOne = (ns, name) => {
|
|
184
|
-
if (
|
|
937
|
+
if (api_hasProperty(ns.decls, name)) {
|
|
185
938
|
return ns.decls[name];
|
|
186
939
|
}
|
|
187
940
|
return null;
|
|
@@ -219,7 +972,7 @@ function collectNamespaces(ast, state) {
|
|
|
219
972
|
};
|
|
220
973
|
|
|
221
974
|
state.traverse = (root) =>
|
|
222
|
-
|
|
975
|
+
api_traverseAst(
|
|
223
976
|
root,
|
|
224
977
|
(node) => {
|
|
225
978
|
try {
|
|
@@ -263,7 +1016,7 @@ function collectNamespaces(ast, state) {
|
|
|
263
1016
|
.join(".");
|
|
264
1017
|
if (elm.name) {
|
|
265
1018
|
if (!parent.decls) parent.decls = {};
|
|
266
|
-
if (
|
|
1019
|
+
if (api_hasProperty(parent.decls, elm.name)) {
|
|
267
1020
|
const what =
|
|
268
1021
|
node.type == "ModuleDeclaration" ? "type" : "node";
|
|
269
1022
|
const e = parent.decls[elm.name].find(
|
|
@@ -296,7 +1049,7 @@ function collectNamespaces(ast, state) {
|
|
|
296
1049
|
case "TypedefDeclaration": {
|
|
297
1050
|
const [parent] = state.stack.slice(-1);
|
|
298
1051
|
if (!parent.decls) parent.decls = {};
|
|
299
|
-
if (!
|
|
1052
|
+
if (!api_hasProperty(parent.decls, node.id.name)) {
|
|
300
1053
|
parent.decls[node.id.name] = [];
|
|
301
1054
|
}
|
|
302
1055
|
(0,external_util_cjs_namespaceObject.pushUnique)(
|
|
@@ -309,12 +1062,12 @@ function collectNamespaces(ast, state) {
|
|
|
309
1062
|
const [parent] = state.stack.slice(-1);
|
|
310
1063
|
if (!parent.decls) parent.decls = {};
|
|
311
1064
|
node.declarations.forEach((decl) => {
|
|
312
|
-
if (!
|
|
1065
|
+
if (!api_hasProperty(parent.decls, decl.id.name)) {
|
|
313
1066
|
parent.decls[decl.id.name] = [];
|
|
314
1067
|
}
|
|
315
1068
|
if (node.kind == "const") {
|
|
316
1069
|
(0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[decl.id.name], decl.init);
|
|
317
|
-
if (!
|
|
1070
|
+
if (!api_hasProperty(state.index, decl.id.name)) {
|
|
318
1071
|
state.index[decl.id.name] = [];
|
|
319
1072
|
}
|
|
320
1073
|
(0,external_util_cjs_namespaceObject.pushUnique)(state.index[decl.id.name], parent);
|
|
@@ -335,10 +1088,16 @@ function collectNamespaces(ast, state) {
|
|
|
335
1088
|
let name, init;
|
|
336
1089
|
if (m.type == "EnumStringMember") {
|
|
337
1090
|
name = m.id.name;
|
|
338
|
-
init = m.init;
|
|
1091
|
+
init = getLiteralNode(m.init);
|
|
1092
|
+
if (!init) {
|
|
1093
|
+
throw new Error("Unexpected enum initializer");
|
|
1094
|
+
}
|
|
1095
|
+
if (init != m.init) {
|
|
1096
|
+
m.init = init;
|
|
1097
|
+
}
|
|
339
1098
|
if (
|
|
340
1099
|
init.type == "Literal" &&
|
|
341
|
-
|
|
1100
|
+
api_LiteralIntegerRe.test(init.raw)
|
|
342
1101
|
) {
|
|
343
1102
|
prev = init.value;
|
|
344
1103
|
}
|
|
@@ -359,11 +1118,11 @@ function collectNamespaces(ast, state) {
|
|
|
359
1118
|
};
|
|
360
1119
|
}
|
|
361
1120
|
}
|
|
362
|
-
if (!
|
|
1121
|
+
if (!api_hasProperty(values, name)) {
|
|
363
1122
|
values[name] = [];
|
|
364
1123
|
}
|
|
365
1124
|
(0,external_util_cjs_namespaceObject.pushUnique)(values[name], init);
|
|
366
|
-
if (!
|
|
1125
|
+
if (!api_hasProperty(state.index, name)) {
|
|
367
1126
|
state.index[name] = [];
|
|
368
1127
|
}
|
|
369
1128
|
(0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
|
|
@@ -413,7 +1172,7 @@ function collectNamespaces(ast, state) {
|
|
|
413
1172
|
* - if post returns false, the node it was called on is
|
|
414
1173
|
* removed.
|
|
415
1174
|
*/
|
|
416
|
-
function
|
|
1175
|
+
function api_traverseAst(node, pre, post) {
|
|
417
1176
|
const nodes = pre && pre(node);
|
|
418
1177
|
if (nodes === false) return;
|
|
419
1178
|
for (const key of nodes || Object.keys(node)) {
|
|
@@ -421,7 +1180,7 @@ function traverseAst(node, pre, post) {
|
|
|
421
1180
|
if (!value) continue;
|
|
422
1181
|
if (Array.isArray(value)) {
|
|
423
1182
|
const deletions = value.reduce((state, obj, i) => {
|
|
424
|
-
const repl =
|
|
1183
|
+
const repl = api_traverseAst(obj, pre, post);
|
|
425
1184
|
if (repl === false) {
|
|
426
1185
|
if (!state) state = {};
|
|
427
1186
|
state[i] = true;
|
|
@@ -438,7 +1197,7 @@ function traverseAst(node, pre, post) {
|
|
|
438
1197
|
);
|
|
439
1198
|
}
|
|
440
1199
|
} else if (typeof value == "object" && value.type) {
|
|
441
|
-
const repl =
|
|
1200
|
+
const repl = api_traverseAst(value, pre, post);
|
|
442
1201
|
if (repl === false) {
|
|
443
1202
|
delete node[key];
|
|
444
1203
|
} else if (repl != null) {
|