@markw65/monkeyc-optimizer 1.0.0 → 1.0.3

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