@markw65/monkeyc-optimizer 1.0.1 → 1.0.4

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 CHANGED
@@ -8,4 +8,25 @@ This module is a set of utilities for optimizing Garmin Monkey-C code. Its the e
8
8
 
9
9
  Initial release
10
10
 
11
+ #### 1.0.1
12
+
13
+ - Make a better attempt to fix all the negative constants in api.mir
14
+ - Make export explicitly do a release build by default.
15
+
16
+ #### 1.0.2
17
+
18
+ - Better error reporting when something goes wrong internally
19
+ - Fix an order dependency when processing imports. Previously, if the import statement was seen before the module being imported, we would fail to properly handle the import.
20
+
21
+ ### 1.0.3
22
+
23
+ - Split the build into release and debug, so we can exclude code based on (:release) and (:debug)
24
+ - Optimize away `if (constant)`, `while (false)` and `constant ? E1 : E2`. Convert `do BODY while(false)` to `BODY`
25
+
26
+ ### 1.0.4
27
+
28
+ - Fix a bug resulting in a failure to fully optimize constants initialized by constant conditional expressions
29
+ - Make the generated .cjs files work better with es modules (vscode doesn't work with es modules, so prettier-extension-monkeyc doesn't care - but for other projects importing this package it improves the behavior)
30
+ - Generate separate debug/release jungle files.
31
+
11
32
  ---
package/build/api.cjs ADDED
@@ -0,0 +1,501 @@
1
+ 0 && (module.exports = {LiteralIntegerRe,getApiMapping,hasProperty,collectNamespaces,traverseAst,formatAst});
2
+ /******/ (() => { // webpackBootstrap
3
+ /******/ "use strict";
4
+ /******/ // The require scope
5
+ /******/ var __webpack_require__ = {};
6
+ /******/
7
+ /************************************************************************/
8
+ /******/ /* webpack/runtime/define property getters */
9
+ /******/ (() => {
10
+ /******/ // define getter functions for harmony exports
11
+ /******/ __webpack_require__.d = (exports, definition) => {
12
+ /******/ for(var key in definition) {
13
+ /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
14
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
15
+ /******/ }
16
+ /******/ }
17
+ /******/ };
18
+ /******/ })();
19
+ /******/
20
+ /******/ /* webpack/runtime/hasOwnProperty shorthand */
21
+ /******/ (() => {
22
+ /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
23
+ /******/ })();
24
+ /******/
25
+ /******/ /* webpack/runtime/make namespace object */
26
+ /******/ (() => {
27
+ /******/ // define __esModule on exports
28
+ /******/ __webpack_require__.r = (exports) => {
29
+ /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
30
+ /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
31
+ /******/ }
32
+ /******/ Object.defineProperty(exports, '__esModule', { value: true });
33
+ /******/ };
34
+ /******/ })();
35
+ /******/
36
+ /************************************************************************/
37
+ var __webpack_exports__ = {};
38
+ // ESM COMPAT FLAG
39
+ __webpack_require__.r(__webpack_exports__);
40
+
41
+ // EXPORTS
42
+ __webpack_require__.d(__webpack_exports__, {
43
+ "LiteralIntegerRe": () => (/* binding */ LiteralIntegerRe),
44
+ "collectNamespaces": () => (/* binding */ collectNamespaces),
45
+ "formatAst": () => (/* binding */ formatAst),
46
+ "getApiMapping": () => (/* binding */ getApiMapping),
47
+ "hasProperty": () => (/* binding */ hasProperty),
48
+ "traverseAst": () => (/* binding */ traverseAst)
49
+ });
50
+
51
+ ;// CONCATENATED MODULE: external "@markw65/prettier-plugin-monkeyc"
52
+ const prettier_plugin_monkeyc_namespaceObject = require("@markw65/prettier-plugin-monkeyc");
53
+ ;// CONCATENATED MODULE: external "fs/promises"
54
+ const promises_namespaceObject = require("fs/promises");
55
+ ;// CONCATENATED MODULE: external "prettier/standalone.js"
56
+ const standalone_js_namespaceObject = require("prettier/standalone.js");
57
+ ;// CONCATENATED MODULE: external "./util.cjs"
58
+ const external_util_cjs_namespaceObject = require("./util.cjs");
59
+ ;// CONCATENATED MODULE: ./src/negative-fixups.js
60
+ /*
61
+ * This is strange. It pretty much has to be a bug in the sdk,
62
+ * but its the same in every sdk.
63
+ *
64
+ * ${sdk}/bin/api.mir describes the Toybox api down to every module,
65
+ * class, function, enum and constant, together with the enum
66
+ * and constant values.
67
+ *
68
+ * The problem is that every enum or constant with a negative
69
+ * value, appears with the corresponding positive value in api.mir.
70
+ * So eg Graphics.COLOR_TRANSPARENT should be -1, but instead, it has
71
+ * the value 1.
72
+ *
73
+ * This is a (currently somewhat ad-hoc) list of the negative constants
74
+ * so we can fix them after reading api.mir.
75
+ */
76
+ const negativeFixups = [
77
+ "Toybox.Communications.BLE_CONNECTION_UNAVAILABLE",
78
+ "Toybox.Communications.INVALID_HTTP_BODY_IN_REQUEST",
79
+ "Toybox.Communications.REQUEST_CANCELLED",
80
+ "Toybox.Communications.UNSUPPORTED_CONTENT_TYPE_IN_RESPONSE",
81
+ "Toybox.Communications.UNABLE_TO_PROCESS_IMAGE",
82
+ "Toybox.Communications.NETWORK_RESPONSE_OUT_OF_MEMORY",
83
+ "Toybox.Communications.BLE_REQUEST_CANCELLED",
84
+ "Toybox.Communications.INVALID_HTTP_METHOD_IN_REQUEST",
85
+ "Toybox.Communications.BLE_NO_DATA",
86
+ "Toybox.Communications.INVALID_HTTP_HEADER_FIELDS_IN_REQUEST",
87
+ "Toybox.Communications.BLE_ERROR",
88
+ "Toybox.Communications.NETWORK_RESPONSE_TOO_LARGE",
89
+ "Toybox.Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE",
90
+ "Toybox.Communications.BLE_REQUEST_TOO_LARGE",
91
+ "Toybox.Communications.UNABLE_TO_PROCESS_MEDIA",
92
+ "Toybox.Communications.REQUEST_CONNECTION_DROPPED",
93
+ "Toybox.Communications.BLE_UNKNOWN_SEND_ERROR",
94
+ "Toybox.Communications.BLE_QUEUE_FULL",
95
+ "Toybox.Communications.STORAGE_FULL",
96
+ "Toybox.Communications.BLE_SERVER_TIMEOUT",
97
+ "Toybox.Communications.INVALID_HTTP_HEADER_FIELDS_IN_NETWORK_RESPONSE",
98
+ "Toybox.Communications.SECURE_CONNECTION_REQUIRED",
99
+ "Toybox.Communications.NETWORK_REQUEST_TIMED_OUT",
100
+ "Toybox.Communications.BLE_HOST_TIMEOUT",
101
+ "Toybox.Communications.UNABLE_TO_PROCESS_HLS",
102
+ "Toybox.Graphics.COLOR_TRANSPARENT",
103
+ "Toybox.AntPlus.INVALID_SPEED",
104
+ "Toybox.AntPlus.INVALID_CADENCE",
105
+ "Toybox.WatchUi.LAYOUT_VALIGN_START",
106
+ "Toybox.WatchUi.LAYOUT_VALIGN_TOP",
107
+ "Toybox.WatchUi.LAYOUT_VALIGN_BOTTOM",
108
+ "Toybox.WatchUi.LAYOUT_VALIGN_CENTER",
109
+ "Toybox.WatchUi.LAYOUT_HALIGN_RIGHT",
110
+ "Toybox.WatchUi.LAYOUT_HALIGN_CENTER",
111
+ "Toybox.WatchUi.LAYOUT_HALIGN_START",
112
+ "Toybox.WatchUi.LAYOUT_HALIGN_LEFT",
113
+ ];
114
+
115
+ ;// CONCATENATED MODULE: ./src/api.js
116
+
117
+
118
+
119
+
120
+
121
+
122
+ const LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/;
123
+ /*
124
+ * This is an unfortunate hack. I want to be able to extract things
125
+ * like the types of all of a Class's variables (in particular the type
126
+ * of each member of Activity.Info), and also map things like enum names
127
+ * to their values (eg to take font names, and color names to their values).
128
+ * The only place I can find this information is in api.mir, which is totally
129
+ * undocumented. The same could be said of compiler.json and simulator.json,
130
+ * but those are at least in a standard format.
131
+ */
132
+
133
+ // Extract all enum values from api.mir
134
+ async function getApiMapping(state) {
135
+ // get the path to the currently active sdk
136
+ const parser = prettier_plugin_monkeyc_namespaceObject.parsers.monkeyc;
137
+
138
+ const sdk = await (0,external_util_cjs_namespaceObject.getSdkPath)();
139
+
140
+ const api = (await promises_namespaceObject.readFile(`${sdk}bin/api.mir`))
141
+ .toString()
142
+ .replace(/\r\n/g, "\n")
143
+ .replace(/^\s*\[.*?\]\s*$/gm, "")
144
+ //.replace(/(COLOR_TRANSPARENT|LAYOUT_[HV]ALIGN_\w+) = (\d+)/gm, "$1 = -$2")
145
+ .replace(/^(\s*type)\s/gm, "$1def ");
146
+
147
+ try {
148
+ const result = collectNamespaces(parser.parse(api, {}), state);
149
+ negativeFixups.forEach((fixup) => {
150
+ const value = fixup.split(".").reduce((state, part) => {
151
+ const decls = state.decls[part];
152
+ if (!Array.isArray(decls) || decls.length != 1 || !decls[0]) {
153
+ throw `Failed to find and fix negative constant ${fixup}`;
154
+ }
155
+ return decls[0];
156
+ }, result);
157
+ if (value.type != "Literal") {
158
+ throw `Negative constant ${fixup} was not a Literal`;
159
+ }
160
+ if (value.value > 0) {
161
+ value.value = -value.value;
162
+ value.raw = "-" + value.raw;
163
+ } else {
164
+ console.log(`Negative fixup ${fixup} was already negative!`);
165
+ }
166
+ });
167
+ return result;
168
+ } catch (e) {
169
+ console.error(e.toString());
170
+ }
171
+ }
172
+
173
+ function hasProperty(obj, prop) {
174
+ return obj && Object.prototype.hasOwnProperty.call(obj, prop);
175
+ }
176
+
177
+ function collectNamespaces(ast, state) {
178
+ state = state || {};
179
+ if (!state.index) state.index = {};
180
+ if (!state.stack) {
181
+ state.stack = [{ type: "Program", name: "$", fullName: "$" }];
182
+ }
183
+ const checkOne = (ns, name) => {
184
+ if (hasProperty(ns.decls, name)) {
185
+ return ns.decls[name];
186
+ }
187
+ return null;
188
+ };
189
+ state.lookup = (node, name, stack) => {
190
+ stack || (stack = state.stack);
191
+ switch (node.type) {
192
+ case "MemberExpression": {
193
+ if (node.property.type != "Identifier" || node.computed) break;
194
+ const [, module] = state.lookup(node.object, name, stack);
195
+ if (module && module.length === 1) {
196
+ const result = checkOne(module[0], node.property.name);
197
+ if (result) {
198
+ return [name || node.property.name, result];
199
+ }
200
+ }
201
+ break;
202
+ }
203
+ case "ThisExpression":
204
+ return [name, stack.slice(-1)];
205
+ case "Identifier": {
206
+ if (node.name == "$") {
207
+ return [name || node.name, [stack[0]]];
208
+ }
209
+ for (let i = stack.length; i--; ) {
210
+ const result = checkOne(stack[i], node.name);
211
+ if (result) {
212
+ return [name || node.name, result];
213
+ }
214
+ }
215
+ break;
216
+ }
217
+ }
218
+ return [null, null];
219
+ };
220
+
221
+ state.traverse = (root) =>
222
+ traverseAst(
223
+ root,
224
+ (node) => {
225
+ try {
226
+ if (state.shouldExclude && state.shouldExclude(node)) {
227
+ // don't visit any children, but do call post
228
+ return [];
229
+ }
230
+ switch (node.type) {
231
+ case "Program":
232
+ if (state.stack.length != 1) {
233
+ throw new Error("Unexpected stack length for Program node");
234
+ }
235
+ if (node.source) {
236
+ state.stack[0].source = node.source;
237
+ }
238
+ break;
239
+ case "BlockStatement": {
240
+ const [parent] = state.stack.slice(-1);
241
+ if (
242
+ parent.type != "FunctionDeclaration" &&
243
+ parent.type != "BlockStatement"
244
+ ) {
245
+ break;
246
+ }
247
+ // fall through
248
+ }
249
+ case "ClassDeclaration":
250
+ case "FunctionDeclaration":
251
+ case "ModuleDeclaration":
252
+ if (node.id || node.type == "BlockStatement") {
253
+ const [parent] = state.stack.slice(-1);
254
+ const elm = {
255
+ type: node.type,
256
+ name: node.id && node.id.name,
257
+ node,
258
+ };
259
+ state.stack.push(elm);
260
+ elm.fullName = state.stack
261
+ .map((e) => e.name)
262
+ .filter((e) => e != null)
263
+ .join(".");
264
+ if (elm.name) {
265
+ if (!parent.decls) parent.decls = {};
266
+ if (hasProperty(parent.decls, elm.name)) {
267
+ const what =
268
+ node.type == "ModuleDeclaration" ? "type" : "node";
269
+ const e = parent.decls[elm.name].find(
270
+ (d) => d[what] == elm[what]
271
+ );
272
+ if (e != null) {
273
+ e.node = node;
274
+ state.stack.splice(-1, 1, e);
275
+ break;
276
+ }
277
+ } else {
278
+ parent.decls[elm.name] = [];
279
+ }
280
+ parent.decls[elm.name].push(elm);
281
+ }
282
+ }
283
+ break;
284
+ // an EnumDeclaration doesn't create a scope, but
285
+ // it does create a type (if it has a name)
286
+ case "EnumDeclaration": {
287
+ if (!node.id) break;
288
+ const [parent] = state.stack.slice(-1);
289
+ const name = (parent.fullName + "." + node.id.name).replace(
290
+ /^\$\./,
291
+ ""
292
+ );
293
+ node.body.members.forEach((m) => ((m.init || m).enumType = name));
294
+ }
295
+ // fall through
296
+ case "TypedefDeclaration": {
297
+ const [parent] = state.stack.slice(-1);
298
+ if (!parent.decls) parent.decls = {};
299
+ if (!hasProperty(parent.decls, node.id.name)) {
300
+ parent.decls[node.id.name] = [];
301
+ }
302
+ (0,external_util_cjs_namespaceObject.pushUnique)(
303
+ parent.decls[node.id.name],
304
+ node.ts ? formatAst(node.ts.argument) : node.id.name
305
+ );
306
+ break;
307
+ }
308
+ case "VariableDeclaration": {
309
+ const [parent] = state.stack.slice(-1);
310
+ if (!parent.decls) parent.decls = {};
311
+ node.declarations.forEach((decl) => {
312
+ if (!hasProperty(parent.decls, decl.id.name)) {
313
+ parent.decls[decl.id.name] = [];
314
+ }
315
+ if (node.kind == "const") {
316
+ (0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[decl.id.name], decl.init);
317
+ if (!hasProperty(state.index, decl.id.name)) {
318
+ state.index[decl.id.name] = [];
319
+ }
320
+ (0,external_util_cjs_namespaceObject.pushUnique)(state.index[decl.id.name], parent);
321
+ } else if (decl.id.ts) {
322
+ (0,external_util_cjs_namespaceObject.pushUnique)(
323
+ parent.decls[decl.id.name],
324
+ formatAst(decl.id.ts.argument)
325
+ );
326
+ }
327
+ });
328
+ break;
329
+ }
330
+ case "EnumStringBody": {
331
+ const [parent] = state.stack.slice(-1);
332
+ const values = parent.decls || (parent.decls = {});
333
+ let prev = -1;
334
+ node.members.forEach((m) => {
335
+ let name, init;
336
+ if (m.type == "EnumStringMember") {
337
+ name = m.id.name;
338
+ init = m.init;
339
+ if (
340
+ init.type == "Literal" &&
341
+ LiteralIntegerRe.test(init.raw)
342
+ ) {
343
+ prev = init.value;
344
+ }
345
+ } else {
346
+ name = m.name;
347
+ prev += 1;
348
+ if (!state.constants) {
349
+ state.constants = {};
350
+ }
351
+ const key = m.enumType ? `${m.enumType}:${prev}` : prev;
352
+ init = state.constants[key];
353
+ if (!init) {
354
+ init = state.constants[key] = {
355
+ type: "Literal",
356
+ value: prev,
357
+ raw: prev.toString(),
358
+ enumType: m.enumType,
359
+ };
360
+ }
361
+ }
362
+ if (!hasProperty(values, name)) {
363
+ values[name] = [];
364
+ }
365
+ (0,external_util_cjs_namespaceObject.pushUnique)(values[name], init);
366
+ if (!hasProperty(state.index, name)) {
367
+ state.index[name] = [];
368
+ }
369
+ (0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
370
+ });
371
+ break;
372
+ }
373
+ }
374
+ if (state.pre) return state.pre(node);
375
+ } catch (e) {
376
+ handleException(state, node, e);
377
+ }
378
+ },
379
+ (node) => {
380
+ try {
381
+ let ret;
382
+ if (state.shouldExclude && state.shouldExclude(node)) {
383
+ // delete the node.
384
+ return false;
385
+ }
386
+ if (state.post) ret = state.post(node);
387
+ if (state.stack.slice(-1).pop().node === node) {
388
+ state.stack.pop();
389
+ }
390
+ return ret;
391
+ } catch (e) {
392
+ handleException(state, node, e);
393
+ }
394
+ }
395
+ );
396
+ state.traverse(ast);
397
+ if (state.stack.length != 1) {
398
+ throw new Error("Invalid AST!");
399
+ }
400
+ return state.stack[0];
401
+ }
402
+
403
+ /*
404
+ * Traverse the ast rooted at node, calling pre before
405
+ * visiting each node, and post after.
406
+ *
407
+ * - if pre returns false, the node is not traversed, and
408
+ * post is not called;
409
+ * - if pre returns a list of child nodes, only those will
410
+ * be traversed
411
+ * - otherwise all child nodes are traversed
412
+ *
413
+ * - if post returns false, the node it was called on is
414
+ * removed.
415
+ */
416
+ function traverseAst(node, pre, post) {
417
+ const nodes = pre && pre(node);
418
+ if (nodes === false) return;
419
+ for (const key of nodes || Object.keys(node)) {
420
+ const value = node[key];
421
+ if (!value) continue;
422
+ if (Array.isArray(value)) {
423
+ const deletions = value.reduce((state, obj, i) => {
424
+ const repl = traverseAst(obj, pre, post);
425
+ if (repl === false) {
426
+ if (!state) state = {};
427
+ state[i] = true;
428
+ } else if (repl != null) {
429
+ value[i] = repl;
430
+ }
431
+ return state;
432
+ }, null);
433
+ if (deletions) {
434
+ value.splice(
435
+ 0,
436
+ value.length,
437
+ ...value.filter((obj, i) => deletions[i] !== true)
438
+ );
439
+ }
440
+ } else if (typeof value == "object" && value.type) {
441
+ const repl = traverseAst(value, pre, post);
442
+ if (repl === false) {
443
+ delete node[key];
444
+ } else if (repl != null) {
445
+ node[key] = repl;
446
+ }
447
+ }
448
+ }
449
+ return post && post(node);
450
+ }
451
+
452
+ function formatAst(node, options) {
453
+ if (!node.monkeyCSource && node.comments) {
454
+ // Prettier inserts comments by using the source location to
455
+ // find the original comment, rather than using the contents
456
+ // of the comment as reported by the comment nodes themselves.
457
+ // If all we've got is the ast, rather than the actual
458
+ // source code, this goes horribly wrong, so just drop all
459
+ // the comments.
460
+ delete node.comments;
461
+ }
462
+ // If we *do* have the original source, pass that in ahead of the
463
+ // json. The parser knows to just treat the last line of the input
464
+ // as the ast itself, and the printers will find what they're
465
+ // looking for in the source.
466
+ const source =
467
+ (node.monkeyCSource ? node.monkeyCSource + "\n" : "") +
468
+ JSON.stringify(node);
469
+ return standalone_js_namespaceObject.format(source, {
470
+ ...(options || {}),
471
+ parser: "monkeyc-json",
472
+ plugins: [prettier_plugin_monkeyc_namespaceObject],
473
+ endOfLine: "lf",
474
+ });
475
+ }
476
+
477
+ function handleException(state, node, exception) {
478
+ let message;
479
+ try {
480
+ const fullName = state.stack
481
+ .map((e) => e.name)
482
+ .concat(node.name)
483
+ .filter((e) => e != null)
484
+ .join(".");
485
+ const location = state.stack[0].source
486
+ ? `${state.stack[0].source}:${node.start || 0}:${node.end || 0}`
487
+ : "<unknown>";
488
+ message = `Got exception \`${exception.toString()}' while processing node ${fullName}:${
489
+ node.type
490
+ } from ${location}`;
491
+ } catch {
492
+ throw exception;
493
+ }
494
+ throw new Error(message);
495
+ }
496
+
497
+ var __webpack_export_target__ = exports;
498
+ for(var i in __webpack_exports__) __webpack_export_target__[i] = __webpack_exports__[i];
499
+ if(__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_target__, "__esModule", { value: true });
500
+ /******/ })()
501
+ ;