@markw65/monkeyc-optimizer 1.0.1 → 1.0.2

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,14 @@ 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
+
11
21
  ---
package/build/api.cjs ADDED
@@ -0,0 +1,487 @@
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
+ traverseAst(
221
+ ast,
222
+ (node) => {
223
+ try {
224
+ switch (node.type) {
225
+ case "Program":
226
+ if (state.stack.length != 1) {
227
+ throw new Error("Unexpected stack length for Program node");
228
+ }
229
+ if (node.source) {
230
+ state.stack[0].source = node.source;
231
+ }
232
+ break;
233
+ case "BlockStatement": {
234
+ const [parent] = state.stack.slice(-1);
235
+ if (
236
+ parent.type != "FunctionDeclaration" &&
237
+ parent.type != "BlockStatement"
238
+ ) {
239
+ break;
240
+ }
241
+ // fall through
242
+ }
243
+ case "ClassDeclaration":
244
+ case "FunctionDeclaration":
245
+ case "ModuleDeclaration":
246
+ if (node.id || node.type == "BlockStatement") {
247
+ const [parent] = state.stack.slice(-1);
248
+ const elm = {
249
+ type: node.type,
250
+ name: node.id && node.id.name,
251
+ node,
252
+ };
253
+ state.stack.push(elm);
254
+ elm.fullName = state.stack
255
+ .map((e) => e.name)
256
+ .filter((e) => e != null)
257
+ .join(".");
258
+ if (elm.name) {
259
+ if (!parent.decls) parent.decls = {};
260
+ if (hasProperty(parent.decls, elm.name)) {
261
+ const what =
262
+ node.type == "ModuleDeclaration" ? "type" : "node";
263
+ const e = parent.decls[elm.name].find(
264
+ (d) => d[what] == elm[what]
265
+ );
266
+ if (e != null) {
267
+ e.node = node;
268
+ state.stack.splice(-1, 1, e);
269
+ break;
270
+ }
271
+ } else {
272
+ parent.decls[elm.name] = [];
273
+ }
274
+ parent.decls[elm.name].push(elm);
275
+ }
276
+ }
277
+ break;
278
+ // an EnumDeclaration doesn't create a scope, but
279
+ // it does create a type (if it has a name)
280
+ case "EnumDeclaration": {
281
+ if (!node.id) break;
282
+ const [parent] = state.stack.slice(-1);
283
+ const name = (parent.fullName + "." + node.id.name).replace(
284
+ /^\$\./,
285
+ ""
286
+ );
287
+ node.body.members.forEach((m) => ((m.init || m).enumType = name));
288
+ }
289
+ // fall through
290
+ case "TypedefDeclaration": {
291
+ const [parent] = state.stack.slice(-1);
292
+ if (!parent.decls) parent.decls = {};
293
+ if (!hasProperty(parent.decls, node.id.name)) {
294
+ parent.decls[node.id.name] = [];
295
+ }
296
+ (0,external_util_cjs_namespaceObject.pushUnique)(
297
+ parent.decls[node.id.name],
298
+ node.ts ? formatAst(node.ts.argument) : node.id.name
299
+ );
300
+ break;
301
+ }
302
+ case "VariableDeclaration": {
303
+ const [parent] = state.stack.slice(-1);
304
+ if (!parent.decls) parent.decls = {};
305
+ node.declarations.forEach((decl) => {
306
+ if (!hasProperty(parent.decls, decl.id.name)) {
307
+ parent.decls[decl.id.name] = [];
308
+ }
309
+ if (node.kind == "const") {
310
+ (0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[decl.id.name], decl.init);
311
+ if (!hasProperty(state.index, decl.id.name)) {
312
+ state.index[decl.id.name] = [];
313
+ }
314
+ (0,external_util_cjs_namespaceObject.pushUnique)(state.index[decl.id.name], parent);
315
+ } else if (decl.id.ts) {
316
+ (0,external_util_cjs_namespaceObject.pushUnique)(
317
+ parent.decls[decl.id.name],
318
+ formatAst(decl.id.ts.argument)
319
+ );
320
+ }
321
+ });
322
+ break;
323
+ }
324
+ case "EnumStringBody": {
325
+ const [parent] = state.stack.slice(-1);
326
+ const values = parent.decls || (parent.decls = {});
327
+ let prev = -1;
328
+ node.members.forEach((m) => {
329
+ let name, init;
330
+ if (m.type == "EnumStringMember") {
331
+ name = m.id.name;
332
+ init = m.init;
333
+ if (init.type == "Literal" && LiteralIntegerRe.test(init.raw)) {
334
+ prev = init.value;
335
+ }
336
+ } else {
337
+ name = m.name;
338
+ prev += 1;
339
+ if (!state.constants) {
340
+ state.constants = {};
341
+ }
342
+ const key = m.enumType ? `${m.enumType}:${prev}` : prev;
343
+ init = state.constants[key];
344
+ if (!init) {
345
+ init = state.constants[key] = {
346
+ type: "Literal",
347
+ value: prev,
348
+ raw: prev.toString(),
349
+ enumType: m.enumType,
350
+ };
351
+ }
352
+ }
353
+ if (!hasProperty(values, name)) {
354
+ values[name] = [];
355
+ }
356
+ (0,external_util_cjs_namespaceObject.pushUnique)(values[name], init);
357
+ if (!hasProperty(state.index, name)) {
358
+ state.index[name] = [];
359
+ }
360
+ (0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
361
+ });
362
+ break;
363
+ }
364
+ }
365
+ if (state.pre) return state.pre(node);
366
+ } catch (e) {
367
+ handleException(state, node, e);
368
+ }
369
+ },
370
+ (node) => {
371
+ try {
372
+ let ret;
373
+ if (state.post) ret = state.post(node);
374
+ if (state.stack.slice(-1).pop().node === node) {
375
+ state.stack.pop();
376
+ }
377
+ return ret;
378
+ } catch (e) {
379
+ handleException(state, node, e);
380
+ }
381
+ }
382
+ );
383
+ if (state.stack.length != 1) {
384
+ throw new Error("Invalid AST!");
385
+ }
386
+ return state.stack[0];
387
+ }
388
+
389
+ /*
390
+ * Traverse the ast rooted at node, calling pre before
391
+ * visiting each node, and post after.
392
+ *
393
+ * - if pre returns false, the node is not traversed, and
394
+ * post is not called;
395
+ * - if pre returns a list of child nodes, only those will
396
+ * be traversed
397
+ * - otherwise all child nodes are traversed
398
+ *
399
+ * - if post returns false, the node it was called on is
400
+ * removed.
401
+ */
402
+ function traverseAst(node, pre, post) {
403
+ const nodes = pre && pre(node);
404
+ if (nodes === false) return;
405
+ for (const key of nodes || Object.keys(node)) {
406
+ const value = node[key];
407
+ if (!value) continue;
408
+ if (Array.isArray(value)) {
409
+ const deletions = value.reduce((state, obj, i) => {
410
+ const repl = traverseAst(obj, pre, post);
411
+ if (repl === false) {
412
+ if (!state) state = {};
413
+ state[i] = true;
414
+ } else if (repl != null) {
415
+ value[i] = repl;
416
+ }
417
+ return state;
418
+ }, null);
419
+ if (deletions) {
420
+ value.splice(
421
+ 0,
422
+ value.length,
423
+ ...value.filter((obj, i) => deletions[i] !== true)
424
+ );
425
+ }
426
+ } else if (typeof value == "object" && value.type) {
427
+ const repl = traverseAst(value, pre, post);
428
+ if (repl === false) {
429
+ delete node[key];
430
+ } else if (repl != null) {
431
+ node[key] = repl;
432
+ }
433
+ }
434
+ }
435
+ return post && post(node);
436
+ }
437
+
438
+ function formatAst(node, options) {
439
+ if (!node.monkeyCSource && node.comments) {
440
+ // Prettier inserts comments by using the source location to
441
+ // find the original comment, rather than using the contents
442
+ // of the comment as reported by the comment nodes themselves.
443
+ // If all we've got is the ast, rather than the actual
444
+ // source code, this goes horribly wrong, so just drop all
445
+ // the comments.
446
+ delete node.comments;
447
+ }
448
+ // If we *do* have the original source, pass that in ahead of the
449
+ // json. The parser knows to just treat the last line of the input
450
+ // as the ast itself, and the printers will find what they're
451
+ // looking for in the source.
452
+ const source =
453
+ (node.monkeyCSource ? node.monkeyCSource + "\n" : "") +
454
+ JSON.stringify(node);
455
+ return standalone_js_namespaceObject.format(source, {
456
+ ...(options || {}),
457
+ parser: "monkeyc-json",
458
+ plugins: [prettier_plugin_monkeyc_namespaceObject],
459
+ endOfLine: "lf",
460
+ });
461
+ }
462
+
463
+ function handleException(state, node, exception) {
464
+ let message;
465
+ try {
466
+ const fullName = state.stack
467
+ .map((e) => e.name)
468
+ .concat(node.name)
469
+ .filter((e) => e != null)
470
+ .join(".");
471
+ const location = state.stack[0].source
472
+ ? `${state.stack[0].source}:${node.start || 0}:${node.end || 0}`
473
+ : "<unknown>";
474
+ message = `Got exception \`${exception.toString()}' while processing node ${fullName}:${
475
+ node.type
476
+ } from ${location}`;
477
+ } catch {
478
+ throw exception;
479
+ }
480
+ throw new Error(message);
481
+ }
482
+
483
+ var __webpack_export_target__ = exports;
484
+ for(var i in __webpack_exports__) __webpack_export_target__[i] = __webpack_exports__[i];
485
+ if(__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_target__, "__esModule", { value: true });
486
+ /******/ })()
487
+ ;