@markw65/monkeyc-optimizer 1.0.9 → 1.0.12

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/build/api.cjs CHANGED
@@ -1,10 +1,22 @@
1
- 0 && (module.exports = {LiteralIntegerRe,getApiMapping,hasProperty,collectNamespaces,traverseAst,formatAst});
1
+ 0 && (module.exports = {getApiMapping,hasProperty,isStateNode,variableDeclarationName,collectNamespaces,traverseAst,formatAst});
2
2
  /******/ (() => { // webpackBootstrap
3
3
  /******/ "use strict";
4
4
  /******/ // The require scope
5
5
  /******/ var __webpack_require__ = {};
6
6
  /******/
7
7
  /************************************************************************/
8
+ /******/ /* webpack/runtime/compat get default export */
9
+ /******/ (() => {
10
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
11
+ /******/ __webpack_require__.n = (module) => {
12
+ /******/ var getter = module && module.__esModule ?
13
+ /******/ () => (module['default']) :
14
+ /******/ () => (module);
15
+ /******/ __webpack_require__.d(getter, { a: getter });
16
+ /******/ return getter;
17
+ /******/ };
18
+ /******/ })();
19
+ /******/
8
20
  /******/ /* webpack/runtime/define property getters */
9
21
  /******/ (() => {
10
22
  /******/ // define getter functions for harmony exports
@@ -40,772 +52,827 @@ __webpack_require__.r(__webpack_exports__);
40
52
 
41
53
  // EXPORTS
42
54
  __webpack_require__.d(__webpack_exports__, {
43
- "LiteralIntegerRe": () => (/* binding */ api_LiteralIntegerRe),
44
55
  "collectNamespaces": () => (/* binding */ api_collectNamespaces),
45
56
  "formatAst": () => (/* binding */ formatAst),
46
57
  "getApiMapping": () => (/* binding */ api_getApiMapping),
47
58
  "hasProperty": () => (/* binding */ api_hasProperty),
48
- "traverseAst": () => (/* binding */ api_traverseAst)
59
+ "isStateNode": () => (/* binding */ api_isStateNode),
60
+ "traverseAst": () => (/* binding */ api_traverseAst),
61
+ "variableDeclarationName": () => (/* binding */ api_variableDeclarationName)
49
62
  });
50
63
 
51
64
  ;// CONCATENATED MODULE: external "@markw65/prettier-plugin-monkeyc"
52
65
  const prettier_plugin_monkeyc_namespaceObject = require("@markw65/prettier-plugin-monkeyc");
66
+ var prettier_plugin_monkeyc_default = /*#__PURE__*/__webpack_require__.n(prettier_plugin_monkeyc_namespaceObject);
53
67
  ;// CONCATENATED MODULE: external "fs/promises"
54
68
  const promises_namespaceObject = require("fs/promises");
55
69
  ;// CONCATENATED MODULE: external "prettier/standalone.js"
56
70
  const standalone_js_namespaceObject = require("prettier/standalone.js");
71
+ var standalone_js_default = /*#__PURE__*/__webpack_require__.n(standalone_js_namespaceObject);
57
72
  ;// CONCATENATED MODULE: external "./api.cjs"
58
73
  const external_api_cjs_namespaceObject = require("./api.cjs");
59
74
  ;// CONCATENATED MODULE: external "./util.cjs"
60
75
  const external_util_cjs_namespaceObject = require("./util.cjs");
61
- ;// CONCATENATED MODULE: ./src/mc-rewrite.js
62
-
76
+ ;// CONCATENATED MODULE: ./src/mc-rewrite.ts
63
77
 
64
78
 
65
79
 
66
80
 
67
81
  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);
82
+ allImports.forEach(({ node, stack }) => {
83
+ const [name, module] = lookup(node.id, "as" in node && node.as && node.as.name, stack);
84
+ if (name && module) {
85
+ const [parent] = stack.slice(-1);
86
+ if (!parent.decls)
87
+ parent.decls = {};
88
+ if (!hasProperty(parent.decls, name))
89
+ parent.decls[name] = [];
90
+ module.forEach((m) => {
91
+ if (isStateNode(m) && m.type == "ModuleDeclaration") {
92
+ pushUnique(parent.decls[name], m);
93
+ }
94
+ });
77
95
  }
78
- });
79
- }
80
- });
96
+ });
81
97
  }
82
-
83
98
  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
- }
99
+ state.allClasses.forEach((elm) => {
100
+ if (elm.node.superClass) {
101
+ const [, classes] = state.lookup(elm.node.superClass, null, elm.stack);
102
+ const superClass = classes &&
103
+ classes.filter((c) => isStateNode(c) && c.type === "ClassDeclaration");
104
+ // set it "true" if there is a superClass, but we can't find it.
105
+ elm.superClass = superClass && superClass.length ? superClass : true;
106
+ }
107
+ });
108
+ const markOverrides = (cls, scls) => {
109
+ if (scls === true)
110
+ return;
111
+ scls.forEach((c) => {
112
+ c.decls &&
113
+ Object.values(c.decls).forEach((funcs) => {
114
+ funcs.forEach((f) => {
115
+ if (isStateNode(f) &&
116
+ f.type === "FunctionDeclaration" &&
117
+ hasProperty(cls.decls, f.name)) {
118
+ f.node.hasOverride = true;
119
+ }
120
+ });
121
+ });
122
+ if (c.superClass)
123
+ markOverrides(cls, c.superClass);
105
124
  });
106
- if (c.superClass) markOverrides(cls, c.superClass);
125
+ };
126
+ state.allClasses.forEach((elm) => {
127
+ if (elm.superClass)
128
+ markOverrides(elm, elm.superClass);
107
129
  });
108
- };
109
-
110
- state.allClasses.forEach((elm) => {
111
- if (elm.superClass) markOverrides(elm, elm.superClass);
112
- });
113
130
  }
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;
131
+ function getFileSources(fnMap) {
132
+ return Promise.all(Object.entries(fnMap).map(([name, value]) => {
133
+ return (value.monkeyCSource ||
134
+ fs
135
+ .readFile(name)
136
+ .then((data) => (value.monkeyCSource = data.toString().replace(/\r\n/g, "\n"))));
137
+ })).then(() => { });
138
+ }
139
+ function getFileASTs(fnMap) {
140
+ return getFileSources(fnMap).then(() => Object.entries(fnMap).reduce((ok, [name, value]) => {
141
+ if (!value.ast) {
142
+ try {
143
+ value.ast = MonkeyC.parsers.monkeyc.parse(value.monkeyCSource, null, {
144
+ filepath: name,
145
+ });
146
+ }
147
+ catch (e) {
148
+ ok = false;
149
+ if (e instanceof Error) {
150
+ value.parserError = e;
151
+ }
152
+ else {
153
+ value.parserError = new Error("An unknown parser error occurred");
154
+ }
155
+ }
150
156
  }
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 };
157
+ return ok;
158
+ }, true));
201
159
  }
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;
160
+ async function analyze(fnMap) {
161
+ let excludeAnnotations;
162
+ let hasTests = false;
163
+ const allImports = [];
164
+ const state = {
165
+ allFunctions: [],
166
+ allClasses: [],
167
+ shouldExclude(node) {
168
+ if ("attrs" in node &&
169
+ node.attrs &&
170
+ "attrs" in node.attrs &&
171
+ node.attrs.attrs) {
172
+ return node.attrs.attrs.reduce((drop, attr) => {
173
+ if (attr.type != "UnaryExpression")
174
+ return drop;
175
+ if (attr.argument.type != "Identifier")
176
+ return drop;
177
+ if (hasProperty(excludeAnnotations, attr.argument.name)) {
178
+ return true;
179
+ }
180
+ if (attr.argument.name == "test") {
181
+ hasTests = true;
182
+ }
183
+ return drop;
184
+ }, false);
185
+ }
186
+ return null;
187
+ },
188
+ post(node) {
189
+ switch (node.type) {
190
+ case "FunctionDeclaration":
191
+ case "ClassDeclaration": {
192
+ const [scope] = state.stack.slice(-1);
193
+ const stack = state.stack.slice(0, -1);
194
+ scope.stack = stack;
195
+ if (scope.type == "FunctionDeclaration") {
196
+ state.allFunctions.push(scope);
197
+ }
198
+ else {
199
+ state.allClasses.push(scope);
200
+ }
201
+ return null;
202
+ }
203
+ case "Using":
204
+ case "ImportModule":
205
+ allImports.push({ node, stack: state.stack.slice() });
206
+ return null;
207
+ }
208
+ },
209
+ };
210
+ await getApiMapping(state);
211
+ // Mark all functions from api.mir as "special" by
212
+ // setting their bodies to null. In api.mir, they're
213
+ // all empty, which makes it look like they're
214
+ // do-nothing functions.
215
+ const markApi = (node) => {
216
+ if (node.type == "FunctionDeclaration") {
217
+ node.node.body = null;
216
218
  }
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
- };
219
+ if ("decls" in node) {
220
+ Object.values(node.decls).forEach((v) => v.forEach(markApi));
239
221
  }
240
- }
241
- }
222
+ };
223
+ markApi(state.stack[0]);
224
+ await getFileASTs(fnMap);
225
+ Object.entries(fnMap).forEach(([name, value]) => {
226
+ const { ast, parserError } = value;
227
+ if (!ast) {
228
+ throw parserError || new Error(`Failed to parse ${name}`);
229
+ }
230
+ excludeAnnotations = value.excludeAnnotations;
231
+ hasTests = false;
232
+ collectNamespaces(ast, state);
233
+ value.hasTests = hasTests;
234
+ });
235
+ delete state.shouldExclude;
236
+ delete state.post;
237
+ processImports(allImports, state.lookup);
238
+ collectClassInfo(state);
239
+ return state;
242
240
  }
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];
241
+ function compareLiteralLike(a, b) {
242
+ while (a.type === "BinaryExpression")
243
+ a = a.left;
244
+ while (b.type === "BinaryExpression")
245
+ b = b.left;
246
+ return a.type === "Literal" && b.type === "Literal" && a.value === b.value;
278
247
  }
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;
248
+ function getLiteralFromDecls(decls) {
249
+ if (!decls.length)
250
+ return null;
251
+ let result = null;
252
+ if (decls.every((d) => {
253
+ if (d.type === "EnumStringMember" ||
254
+ (d.type === "VariableDeclarator" && d.kind === "const")) {
255
+ const init = getLiteralNode(d.init);
256
+ if (!init)
257
+ return false;
258
+ if (!result) {
259
+ result = init;
260
+ return true;
308
261
  }
309
- if (value !== undefined) {
310
- return {
311
- ...arg,
312
- value,
313
- raw: value.toString() + (type === "Long" ? "l" : ""),
314
- };
262
+ else {
263
+ return compareLiteralLike(init, result);
315
264
  }
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
265
  }
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;
266
+ return false;
267
+ })) {
268
+ return result;
353
269
  }
354
- case "FunctionDeclaration":
355
- if (node.body && evaluateFunction(node, null) !== false) {
356
- node.optimizable = true;
357
- }
358
- break;
359
- }
270
+ return null;
360
271
  }
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");
272
+ function getLiteralNode(node) {
273
+ if (node.type == "Literal")
274
+ return node;
275
+ if (node.type == "BinaryExpression" && node.operator == "as") {
276
+ return getLiteralNode(node.left) && node;
277
+ }
278
+ if (node.type == "UnaryExpression") {
279
+ if (node.argument.type != "Literal")
280
+ return null;
281
+ switch (node.operator) {
282
+ case "-":
283
+ if (typeof node.argument.value == "number") {
284
+ return {
285
+ ...node.argument,
286
+ value: -node.argument.value,
287
+ raw: "-" + node.argument.value,
288
+ enumType: node.enumType,
289
+ };
290
+ }
384
291
  }
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
292
  }
420
- if (obj.enumType) {
421
- obj = {
422
- type: "BinaryExpression",
423
- operator: "as",
424
- left: obj,
425
- right: { type: "TypeSpecList", ts: [obj.enumType] },
426
- };
293
+ return null;
294
+ }
295
+ function getNodeValue(node) {
296
+ if (node.type == "BinaryExpression" &&
297
+ node.operator == "as" &&
298
+ node.right.type == "TypeSpecList" &&
299
+ node.right.ts.length == 1 &&
300
+ typeof node.right.ts[0] == "string") {
301
+ // this is a cast we inserted to retain the type of an enum
302
+ // any arithmetic on it will revert to "Number", or "Long",
303
+ // so just ignore it.
304
+ return getNodeValue(node.left);
427
305
  }
428
- for (const [k, v] of Object.entries(obj)) {
429
- node[k] = v;
306
+ if (node.type != "Literal") {
307
+ return [null, null];
430
308
  }
431
- };
432
- const lookupAndReplace = (node) => {
433
- const [, objects] = state.lookup(node);
434
- if (!objects) {
435
- return false;
309
+ let type = node.value === null ? "Null" : typeof node.value;
310
+ if (type === "number") {
311
+ const match = LiteralIntegerRe.exec(node.raw);
312
+ if (match) {
313
+ type = match[2] == "l" ? "Long" : "Number";
314
+ }
315
+ else if (node.raw.endsWith("d")) {
316
+ type = "Double";
317
+ }
318
+ else {
319
+ type = "Float";
320
+ }
436
321
  }
437
- const obj = getLiteralNode(objects);
438
- if (!obj) {
439
- return false;
322
+ else if (type === "string") {
323
+ type = "String";
440
324
  }
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;
325
+ else if (type === "boolean") {
326
+ type = "Boolean";
453
327
  }
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
- );
328
+ else {
329
+ type = "Unknown";
459
330
  }
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) => {
331
+ return [node, type];
332
+ }
333
+ function optimizeNode(node) {
480
334
  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");
335
+ case "UnaryExpression": {
336
+ const [arg, type] = getNodeValue(node.argument);
337
+ if (arg === null)
338
+ break;
339
+ switch (node.operator) {
340
+ case "+":
341
+ if (type === "Number" || type === "Long") {
342
+ return arg;
343
+ }
344
+ break;
345
+ case "-":
346
+ if (type === "Number" || type === "Long") {
347
+ return {
348
+ ...arg,
349
+ value: -arg.value,
350
+ raw: (-arg.value).toString() + (type === "Long" ? "l" : ""),
351
+ };
352
+ }
353
+ break;
354
+ case "!":
355
+ case "~":
356
+ {
357
+ let value;
358
+ if (type === "Number" || type === "Long") {
359
+ value = -arg.value - 1;
360
+ }
361
+ else if (type === "Boolean" && node.operator == "!") {
362
+ value = !arg.value;
363
+ }
364
+ if (value !== undefined) {
365
+ return {
366
+ ...arg,
367
+ value,
368
+ raw: value.toString() + (type === "Long" ? "l" : ""),
369
+ };
370
+ }
371
+ }
372
+ break;
519
373
  }
520
- }
374
+ break;
521
375
  }
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 } });
376
+ case "BinaryExpression": {
377
+ const operators = {
378
+ "+": (left, right) => left + right,
379
+ "-": (left, right) => left - right,
380
+ "*": (left, right) => left * right,
381
+ "/": (left, right) => Math.trunc(left / right),
382
+ "%": (left, right) => left % right,
383
+ "&": (left, right, type) => type === "Number" ? left & right : null,
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
+ };
388
+ const op = operators[node.operator];
389
+ if (op) {
390
+ const [left, left_type] = getNodeValue(node.left);
391
+ const [right, right_type] = getNodeValue(node.right);
392
+ if (!left || !right)
393
+ break;
394
+ if (left_type != right_type ||
395
+ (left_type != "Number" && left_type != "Long")) {
396
+ break;
397
+ }
398
+ const value = op(left.value, right.value, left_type);
399
+ if (value === null)
400
+ break;
401
+ return {
402
+ ...left,
403
+ value,
404
+ raw: value.toString() + (left_type === "Long" ? "l" : ""),
405
+ };
406
+ }
407
+ break;
530
408
  }
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;
409
+ case "FunctionDeclaration":
410
+ if (node.body && evaluateFunction(node, null) !== false) {
411
+ node.optimizable = true;
546
412
  }
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
- });
413
+ break;
414
+ }
415
+ return null;
416
+ }
417
+ function evaluateFunction(func, args) {
418
+ if (args && args.length != func.params.length) {
419
+ return false;
420
+ }
421
+ const paramValues = args &&
422
+ Object.fromEntries(func.params.map((p, i) => [variableDeclarationName(p), args[i]]));
423
+ let ret = null;
424
+ const body = args ? JSON.parse(JSON.stringify(func.body)) : func.body;
425
+ try {
426
+ traverseAst(body, (node) => {
427
+ switch (node.type) {
428
+ case "BlockStatement":
429
+ case "ReturnStatement":
430
+ case "UnaryExpression":
431
+ case "BinaryExpression":
432
+ case "Literal":
433
+ case "Identifier":
434
+ return;
435
+ default:
436
+ throw new Error("Bad node type");
556
437
  }
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;
438
+ }, args &&
439
+ ((node) => {
440
+ switch (node.type) {
441
+ case "ReturnStatement":
442
+ ret = node.argument;
443
+ return null;
444
+ case "BlockStatement":
445
+ case "Literal":
446
+ return null;
447
+ case "Identifier":
448
+ if (hasProperty(paramValues, node.name)) {
449
+ return paramValues[node.name];
450
+ }
451
+ // fall through;
452
+ default: {
453
+ const repl = optimizeNode(node);
454
+ if (repl && repl.type === "Literal")
455
+ return repl;
456
+ throw new Error("Didn't optimize");
574
457
  }
575
- } else if (elm.node.type === "FunctionDeclaration") {
576
- ok = true;
577
- }
578
458
  }
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;
459
+ }));
460
+ return ret;
461
+ }
462
+ catch (e) {
463
+ return false;
464
+ }
465
+ }
466
+ async function optimizeMonkeyC(fnMap) {
467
+ const state = await analyze(fnMap);
468
+ const replace = (node, obj) => {
469
+ for (const k of Object.keys(node)) {
470
+ delete node[k];
605
471
  }
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
- }
472
+ if (obj.enumType) {
473
+ obj = {
474
+ type: "BinaryExpression",
475
+ operator: "as",
476
+ left: obj,
477
+ right: { type: "TypeSpecList", ts: [obj.enumType] },
478
+ };
616
479
  }
617
- if (hasProperty(state.index, node.name)) {
618
- if (!lookupAndReplace(node)) {
619
- state.exposed[node.name] = true;
620
- }
480
+ for (const [k, v] of Object.entries(obj)) {
481
+ node[k] = v;
621
482
  }
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"];
483
+ };
484
+ const lookupAndReplace = (node) => {
485
+ const [, objects] = state.lookup(node);
486
+ if (!objects) {
487
+ return false;
635
488
  }
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
- });
489
+ const obj = getLiteralFromDecls(objects);
490
+ if (!obj) {
491
+ return false;
644
492
  }
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
- }
493
+ replace(node, obj);
494
+ return true;
495
+ };
496
+ /*
497
+ * Might this function be called from somewhere, including
498
+ * callbacks from the api (eg getSettingsView, etc).
499
+ */
500
+ const maybeCalled = (func) => {
501
+ if (!func.body) {
502
+ // this is an api.mir function. It can be called
503
+ return true;
665
504
  }
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);
505
+ if (hasProperty(state.exposed, func.id.name))
506
+ return true;
507
+ if (func.attrs &&
508
+ func.attrs.attrs &&
509
+ func.attrs.attrs.some((attr) => {
510
+ if (attr.type != "UnaryExpression")
511
+ return false;
512
+ if (attr.argument.type != "Identifier")
513
+ return false;
514
+ return attr.argument.name == "test";
515
+ })) {
516
+ return true;
685
517
  }
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;
518
+ if (hasProperty(state.calledFunctions, func.id.name)) {
519
+ return (state.calledFunctions[func.id.name].find((f) => f === func) !== null);
708
520
  }
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;
521
+ return false;
522
+ };
523
+ /*
524
+ * Does elm (a class) have a maybeCalled function called name,
525
+ * anywhere in its superClass chain.
526
+ */
527
+ const checkInherited = (elm, name) => elm.superClass === true ||
528
+ elm.superClass.some((sc) => (hasProperty(sc.decls, name) &&
529
+ sc.decls[name].some((f) => isStateNode(f) &&
530
+ f.type == "FunctionDeclaration" &&
531
+ maybeCalled(f.node))) ||
532
+ (sc.superClass && checkInherited(sc, name)));
533
+ state.localsStack = [{}];
534
+ state.exposed = {};
535
+ state.calledFunctions = {};
536
+ state.pre = (node) => {
537
+ switch (node.type) {
538
+ case "ConditionalExpression":
539
+ case "IfStatement":
540
+ case "DoWhileStatement":
541
+ case "WhileStatement":
542
+ state.traverse(node.test);
543
+ const [value, type] = getNodeValue(node.test);
544
+ if (value) {
545
+ let result = null;
546
+ if (type === "Null") {
547
+ result = false;
548
+ }
549
+ else if (type === "Boolean" ||
550
+ type === "Number" ||
551
+ type === "Long") {
552
+ result = !!value.value;
553
+ }
554
+ if (result !== null) {
555
+ if (node.type === "IfStatement" ||
556
+ node.type === "ConditionalExpression") {
557
+ if (result === false) {
558
+ node.consequent = null;
559
+ }
560
+ else {
561
+ node.alternate = null;
562
+ }
563
+ node.test = null;
564
+ }
565
+ else if (node.type === "WhileStatement") {
566
+ if (result === false) {
567
+ node.body = null;
568
+ }
569
+ }
570
+ else if (node.type === "DoWhileStatement") {
571
+ if (result === false) {
572
+ node.test = null;
573
+ }
574
+ }
575
+ else {
576
+ throw new Error("Unexpected Node type");
577
+ }
578
+ }
579
+ }
580
+ return null;
581
+ case "EnumDeclaration":
582
+ return false;
583
+ case "ForStatement": {
584
+ const map = state.localsStack.slice(-1).pop().map;
585
+ if (map) {
586
+ state.localsStack.push({ node, map: { ...map } });
587
+ }
588
+ break;
589
+ }
590
+ case "VariableDeclarator": {
591
+ const locals = state.localsStack.slice(-1).pop();
592
+ const { map } = locals;
593
+ if (map) {
594
+ const declName = variableDeclarationName(node.id);
595
+ if (hasProperty(map, declName)) {
596
+ // We already have a variable with this name in scope
597
+ // Recent monkeyc compilers complain, so rename it
598
+ let suffix = 0;
599
+ let node_name = declName;
600
+ const match = node_name.match(/^pmcr_(.*)_(\d+)$/);
601
+ if (match) {
602
+ node_name = match[1];
603
+ suffix = parseInt(match[2], 10) + 1;
604
+ }
605
+ if (!locals.inners) {
606
+ // find all the names declared in this scope, to avoid
607
+ // more conflicts
608
+ locals.inners = {};
609
+ traverseAst(locals.node, (node) => {
610
+ if (node.type === "VariableDeclarator") {
611
+ locals.inners[variableDeclarationName(node.id)] = true;
612
+ }
613
+ });
614
+ }
615
+ let name;
616
+ while (true) {
617
+ name = `pmcr_${node_name}_${suffix}`;
618
+ if (!hasProperty(map, name) &&
619
+ !hasProperty(locals.inners, name)) {
620
+ // we also need to ensure that we don't hide the name of
621
+ // an outer module, class, function, enum or variable,
622
+ // since someone might want to access it from this scope.
623
+ let ok = false;
624
+ let i;
625
+ for (i = state.stack.length; i--;) {
626
+ const elm = state.stack[i];
627
+ if (ok) {
628
+ if (hasProperty(elm.decls, name)) {
629
+ break;
630
+ }
631
+ }
632
+ else if (elm.node.type === "FunctionDeclaration") {
633
+ ok = true;
634
+ }
635
+ }
636
+ if (i < 0) {
637
+ break;
638
+ }
639
+ }
640
+ suffix++;
641
+ }
642
+ map[declName] = name;
643
+ map[name] = true;
644
+ if (node.id.type === "Identifier") {
645
+ node.id.name = name;
646
+ }
647
+ else {
648
+ node.id.left.name = name;
649
+ }
650
+ }
651
+ else {
652
+ map[declName] = true;
653
+ }
654
+ }
655
+ return ["init"];
656
+ }
657
+ case "UnaryExpression":
658
+ if (node.operator == ":") {
659
+ // If we produce a Symbol, for a given name,
660
+ // its possible that someone uses that symbol
661
+ // indirectly, so we can't remove any enums or
662
+ // constants with that name (we can still replace
663
+ // uses of those constants though).
664
+ state.exposed[node.argument.name] = true;
665
+ // In any case, we can't replace *this* use of the
666
+ // symbol with its value...
667
+ return false;
668
+ }
669
+ break;
670
+ case "Identifier": {
671
+ const map = state.localsStack.slice(-1).pop().map;
672
+ if (map) {
673
+ if (hasProperty(map, node.name)) {
674
+ const name = map[node.name];
675
+ if (name !== true) {
676
+ node.name = name;
677
+ }
678
+ }
679
+ }
680
+ if (hasProperty(state.index, node.name)) {
681
+ if (!lookupAndReplace(node)) {
682
+ state.exposed[node.name] = true;
683
+ }
684
+ }
685
+ return false;
686
+ }
687
+ case "MemberExpression":
688
+ if (node.property.type === "Identifier" && !node.computed) {
689
+ if (hasProperty(state.index, node.property.name)) {
690
+ if (lookupAndReplace(node)) {
691
+ return false;
692
+ }
693
+ else {
694
+ state.exposed[node.property.name] = true;
695
+ }
696
+ }
697
+ // Don't optimize the property.
698
+ return ["object"];
699
+ }
700
+ break;
701
+ case "BlockStatement": {
702
+ const map = state.localsStack.slice(-1).pop().map;
703
+ if (map) {
704
+ state.localsStack.push({
705
+ node,
706
+ map: { ...map },
707
+ });
708
+ }
709
+ break;
710
+ }
711
+ case "FunctionDeclaration": {
712
+ const map = {};
713
+ node.params &&
714
+ node.params.forEach((p) => (map[variableDeclarationName(p)] = true));
715
+ state.localsStack.push({ node, map });
716
+ const [parent] = state.stack.slice(-2);
717
+ if (parent.type == "ClassDeclaration" && !maybeCalled(node)) {
718
+ let used = false;
719
+ if (node.id.name == "initialize") {
720
+ used = true;
721
+ }
722
+ else if (parent.superClass) {
723
+ used = checkInherited(parent, node.id.name);
724
+ }
725
+ if (used) {
726
+ if (!hasProperty(state.calledFunctions, node.id.name)) {
727
+ state.calledFunctions[node.id.name] = [];
728
+ }
729
+ state.calledFunctions[node.id.name].push(node);
730
+ }
731
+ }
720
732
  }
721
- }
722
733
  }
723
- if (!hasProperty(state.calledFunctions, name)) {
724
- state.calledFunctions[name] = [];
734
+ return null;
735
+ };
736
+ state.post = (node) => {
737
+ if (state.localsStack.slice(-1).pop().node === node) {
738
+ state.localsStack.pop();
725
739
  }
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");
740
+ const opt = optimizeNode(node);
741
+ if (opt) {
742
+ replace(node, opt);
743
+ return null;
744
+ }
745
+ switch (node.type) {
746
+ case "ConditionalExpression":
747
+ case "IfStatement":
748
+ if (node.test === null) {
749
+ const rep = node.consequent || node.alternate;
750
+ if (!rep)
751
+ return false;
752
+ replace(node, rep);
753
+ }
754
+ break;
755
+ case "WhileStatement":
756
+ if (!node.body)
757
+ return false;
758
+ break;
759
+ case "DoWhileStatement":
760
+ if (!node.test)
761
+ return node.body;
762
+ break;
763
+ case "CallExpression": {
764
+ const [name, callees] = state.lookup(node.callee);
765
+ if (!callees || !callees.length) {
766
+ const n = name ||
767
+ ("name" in node.callee && node.callee.name) ||
768
+ ("property" in node.callee &&
769
+ node.callee.property &&
770
+ "name" in node.callee.property &&
771
+ node.callee.property.name);
772
+ if (n) {
773
+ state.exposed[n] = true;
774
+ }
775
+ else {
776
+ // There are unnamed CallExpressions, such as new [size]
777
+ // So there's nothing to do here.
778
+ }
779
+ return null;
780
+ }
781
+ if (callees.length == 1) {
782
+ const callee = isStateNode(callees[0]) && callees[0].node;
783
+ if (callee.type == "FunctionDeclaration" &&
784
+ callee.optimizable &&
785
+ !callee.hasOverride &&
786
+ node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
787
+ const ret = evaluateFunction(callee, node.arguments);
788
+ if (ret) {
789
+ replace(node, ret);
790
+ return null;
791
+ }
792
+ }
793
+ }
794
+ if (!hasProperty(state.calledFunctions, name)) {
795
+ state.calledFunctions[name] = [];
796
+ }
797
+ callees.forEach((c) => isStateNode(c) && state.calledFunctions[name].push(c.node));
798
+ break;
767
799
  }
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
800
  }
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
- }
801
+ return null;
802
+ };
803
+ Object.values(fnMap).forEach((f) => {
804
+ collectNamespaces(f.ast, state);
805
+ });
806
+ Object.values(fnMap).forEach((f) => {
807
+ traverseAst(f.ast, null, (node) => {
808
+ switch (node.type) {
809
+ case "EnumStringBody":
810
+ if (node.members.every((m) => {
811
+ const name = "name" in m ? m.name : m.id.name;
812
+ return (hasProperty(state.index, name) &&
813
+ !hasProperty(state.exposed, name));
814
+ })) {
815
+ node.enumType = [
816
+ ...new Set(node.members.map((m) => {
817
+ if (!("init" in m))
818
+ return "Number";
819
+ const [node, type] = getNodeValue(m.init);
820
+ if (!node) {
821
+ throw new Error("Failed to get type for eliminated enum");
822
+ }
823
+ return type;
824
+ })),
825
+ ].join(" or ");
826
+ node.members.splice(0);
827
+ }
828
+ break;
829
+ case "EnumDeclaration":
830
+ if (!node.body.members.length) {
831
+ if (!node.id)
832
+ return false;
833
+ if (!node.body["enumType"]) {
834
+ throw new Error("Missing enumType on optimized enum");
835
+ }
836
+ replace(node, {
837
+ type: "TypedefDeclaration",
838
+ id: node.id,
839
+ ts: {
840
+ type: "UnaryExpression",
841
+ argument: { type: "TypeSpecList", ts: [node.body.enumType] },
842
+ prefix: true,
843
+ operator: " as",
844
+ },
845
+ });
846
+ }
847
+ break;
848
+ case "VariableDeclaration": {
849
+ node.declarations = node.declarations.filter((d) => {
850
+ const name = variableDeclarationName(d.id);
851
+ return (!hasProperty(state.index, name) ||
852
+ hasProperty(state.exposed, name));
853
+ });
854
+ if (!node.declarations.length) {
855
+ return false;
856
+ }
857
+ break;
858
+ }
859
+ case "ClassElement":
860
+ if (!node.item) {
861
+ return false;
862
+ }
863
+ break;
864
+ case "FunctionDeclaration":
865
+ if (!maybeCalled(node)) {
866
+ return false;
867
+ }
868
+ break;
869
+ }
870
+ return null;
871
+ });
802
872
  });
803
- });
804
-
805
- return files;
806
873
  }
807
874
 
808
- ;// CONCATENATED MODULE: ./src/negative-fixups.js
875
+ ;// CONCATENATED MODULE: ./src/negative-fixups.ts
809
876
  /*
810
877
  * This is strange. It pretty much has to be a bug in the sdk,
811
878
  * but its the same in every sdk.
@@ -823,48 +890,47 @@ async function optimizeMonkeyC(fileNames, buildConfig) {
823
890
  * so we can fix them after reading api.mir.
824
891
  */
825
892
  const negativeFixups = [
826
- "Toybox.Communications.BLE_CONNECTION_UNAVAILABLE",
827
- "Toybox.Communications.INVALID_HTTP_BODY_IN_REQUEST",
828
- "Toybox.Communications.REQUEST_CANCELLED",
829
- "Toybox.Communications.UNSUPPORTED_CONTENT_TYPE_IN_RESPONSE",
830
- "Toybox.Communications.UNABLE_TO_PROCESS_IMAGE",
831
- "Toybox.Communications.NETWORK_RESPONSE_OUT_OF_MEMORY",
832
- "Toybox.Communications.BLE_REQUEST_CANCELLED",
833
- "Toybox.Communications.INVALID_HTTP_METHOD_IN_REQUEST",
834
- "Toybox.Communications.BLE_NO_DATA",
835
- "Toybox.Communications.INVALID_HTTP_HEADER_FIELDS_IN_REQUEST",
836
- "Toybox.Communications.BLE_ERROR",
837
- "Toybox.Communications.NETWORK_RESPONSE_TOO_LARGE",
838
- "Toybox.Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE",
839
- "Toybox.Communications.BLE_REQUEST_TOO_LARGE",
840
- "Toybox.Communications.UNABLE_TO_PROCESS_MEDIA",
841
- "Toybox.Communications.REQUEST_CONNECTION_DROPPED",
842
- "Toybox.Communications.BLE_UNKNOWN_SEND_ERROR",
843
- "Toybox.Communications.BLE_QUEUE_FULL",
844
- "Toybox.Communications.STORAGE_FULL",
845
- "Toybox.Communications.BLE_SERVER_TIMEOUT",
846
- "Toybox.Communications.INVALID_HTTP_HEADER_FIELDS_IN_NETWORK_RESPONSE",
847
- "Toybox.Communications.SECURE_CONNECTION_REQUIRED",
848
- "Toybox.Communications.NETWORK_REQUEST_TIMED_OUT",
849
- "Toybox.Communications.BLE_HOST_TIMEOUT",
850
- "Toybox.Communications.UNABLE_TO_PROCESS_HLS",
851
- "Toybox.Graphics.COLOR_TRANSPARENT",
852
- "Toybox.AntPlus.INVALID_SPEED",
853
- "Toybox.AntPlus.INVALID_CADENCE",
854
- "Toybox.WatchUi.LAYOUT_VALIGN_START",
855
- "Toybox.WatchUi.LAYOUT_VALIGN_TOP",
856
- "Toybox.WatchUi.LAYOUT_VALIGN_BOTTOM",
857
- "Toybox.WatchUi.LAYOUT_VALIGN_CENTER",
858
- "Toybox.WatchUi.LAYOUT_HALIGN_RIGHT",
859
- "Toybox.WatchUi.LAYOUT_HALIGN_CENTER",
860
- "Toybox.WatchUi.LAYOUT_HALIGN_START",
861
- "Toybox.WatchUi.LAYOUT_HALIGN_LEFT",
893
+ "Toybox.Communications.BLE_CONNECTION_UNAVAILABLE",
894
+ "Toybox.Communications.INVALID_HTTP_BODY_IN_REQUEST",
895
+ "Toybox.Communications.REQUEST_CANCELLED",
896
+ "Toybox.Communications.UNSUPPORTED_CONTENT_TYPE_IN_RESPONSE",
897
+ "Toybox.Communications.UNABLE_TO_PROCESS_IMAGE",
898
+ "Toybox.Communications.NETWORK_RESPONSE_OUT_OF_MEMORY",
899
+ "Toybox.Communications.BLE_REQUEST_CANCELLED",
900
+ "Toybox.Communications.INVALID_HTTP_METHOD_IN_REQUEST",
901
+ "Toybox.Communications.BLE_NO_DATA",
902
+ "Toybox.Communications.INVALID_HTTP_HEADER_FIELDS_IN_REQUEST",
903
+ "Toybox.Communications.BLE_ERROR",
904
+ "Toybox.Communications.NETWORK_RESPONSE_TOO_LARGE",
905
+ "Toybox.Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE",
906
+ "Toybox.Communications.BLE_REQUEST_TOO_LARGE",
907
+ "Toybox.Communications.UNABLE_TO_PROCESS_MEDIA",
908
+ "Toybox.Communications.REQUEST_CONNECTION_DROPPED",
909
+ "Toybox.Communications.BLE_UNKNOWN_SEND_ERROR",
910
+ "Toybox.Communications.BLE_QUEUE_FULL",
911
+ "Toybox.Communications.STORAGE_FULL",
912
+ "Toybox.Communications.BLE_SERVER_TIMEOUT",
913
+ "Toybox.Communications.INVALID_HTTP_HEADER_FIELDS_IN_NETWORK_RESPONSE",
914
+ "Toybox.Communications.SECURE_CONNECTION_REQUIRED",
915
+ "Toybox.Communications.NETWORK_REQUEST_TIMED_OUT",
916
+ "Toybox.Communications.BLE_HOST_TIMEOUT",
917
+ "Toybox.Communications.UNABLE_TO_PROCESS_HLS",
918
+ "Toybox.Graphics.COLOR_TRANSPARENT",
919
+ "Toybox.AntPlus.INVALID_SPEED",
920
+ "Toybox.AntPlus.INVALID_CADENCE",
921
+ "Toybox.WatchUi.LAYOUT_VALIGN_START",
922
+ "Toybox.WatchUi.LAYOUT_VALIGN_TOP",
923
+ "Toybox.WatchUi.LAYOUT_VALIGN_BOTTOM",
924
+ "Toybox.WatchUi.LAYOUT_VALIGN_CENTER",
925
+ "Toybox.WatchUi.LAYOUT_HALIGN_RIGHT",
926
+ "Toybox.WatchUi.LAYOUT_HALIGN_CENTER",
927
+ "Toybox.WatchUi.LAYOUT_HALIGN_START",
928
+ "Toybox.WatchUi.LAYOUT_HALIGN_LEFT",
862
929
  ];
863
930
 
864
931
  ;// CONCATENATED MODULE: external "./sdk-util.cjs"
865
932
  const external_sdk_util_cjs_namespaceObject = require("./sdk-util.cjs");
866
- ;// CONCATENATED MODULE: ./src/api.js
867
-
933
+ ;// CONCATENATED MODULE: ./src/api.ts
868
934
 
869
935
 
870
936
 
@@ -872,7 +938,6 @@ const external_sdk_util_cjs_namespaceObject = require("./sdk-util.cjs");
872
938
 
873
939
 
874
940
 
875
- const api_LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/i;
876
941
  /*
877
942
  * This is an unfortunate hack. I want to be able to extract things
878
943
  * like the types of all of a Class's variables (in particular the type
@@ -882,283 +947,307 @@ const api_LiteralIntegerRe = /^(0x[0-9a-f]+|\d+)(l)?$/i;
882
947
  * undocumented. The same could be said of compiler.json and simulator.json,
883
948
  * but those are at least in a standard format.
884
949
  */
885
-
886
950
  // Extract all enum values from api.mir
887
951
  async function api_getApiMapping(state) {
888
- // get the path to the currently active sdk
889
- const parser = prettier_plugin_monkeyc_namespaceObject.parsers.monkeyc;
890
-
891
- const sdk = await (0,external_sdk_util_cjs_namespaceObject.getSdkPath)();
892
-
893
- const api = (await promises_namespaceObject.readFile(`${sdk}bin/api.mir`))
894
- .toString()
895
- .replace(/\r\n/g, "\n")
896
- .replace(/^\s*\[.*?\]\s*$/gm, "")
897
- //.replace(/(COLOR_TRANSPARENT|LAYOUT_[HV]ALIGN_\w+) = (\d+)/gm, "$1 = -$2")
898
- .replace(/^(\s*type)\s/gm, "$1def ");
899
-
900
- try {
901
- const result = api_collectNamespaces(parser.parse(api, {}), state);
902
- negativeFixups.forEach((fixup) => {
903
- const value = fixup.split(".").reduce((state, part) => {
904
- const decls = state.decls[part];
905
- if (!Array.isArray(decls) || decls.length != 1 || !decls[0]) {
906
- throw `Failed to find and fix negative constant ${fixup}`;
907
- }
908
- return decls[0];
909
- }, result);
910
- if (value.type != "Literal") {
911
- throw `Negative constant ${fixup} was not a Literal`;
912
- }
913
- if (value.value > 0) {
914
- value.value = -value.value;
915
- value.raw = "-" + value.raw;
916
- } else {
917
- console.log(`Negative fixup ${fixup} was already negative!`);
918
- }
919
- });
920
- return result;
921
- } catch (e) {
922
- console.error(e.toString());
923
- }
952
+ // get the path to the currently active sdk
953
+ const parser = (prettier_plugin_monkeyc_default()).parsers.monkeyc;
954
+ const sdk = await (0,external_sdk_util_cjs_namespaceObject.getSdkPath)();
955
+ const api = (await promises_namespaceObject.readFile(`${sdk}bin/api.mir`))
956
+ .toString()
957
+ .replace(/\r\n/g, "\n")
958
+ .replace(/^\s*\[.*?\]\s*$/gm, "")
959
+ //.replace(/(COLOR_TRANSPARENT|LAYOUT_[HV]ALIGN_\w+) = (\d+)/gm, "$1 = -$2")
960
+ .replace(/^(\s*type)\s/gm, "$1def ");
961
+ try {
962
+ const result = api_collectNamespaces(parser.parse(api, null, {
963
+ filepath: "api.mir",
964
+ }), state);
965
+ negativeFixups.forEach((fixup) => {
966
+ const value = fixup.split(".").reduce((state, part) => {
967
+ const decls = state.decls[part];
968
+ if (!Array.isArray(decls) || decls.length != 1 || !decls[0]) {
969
+ throw `Failed to find and fix negative constant ${fixup}`;
970
+ }
971
+ return decls[0];
972
+ }, result);
973
+ if (value.type !== "EnumStringMember" &&
974
+ (value.type !== "VariableDeclarator" || value.kind != "const")) {
975
+ throw `Negative constant ${fixup} did not refer to a constant`;
976
+ }
977
+ const init = value.init;
978
+ if (init.type !== "Literal") {
979
+ throw `Negative constant ${fixup} was not a Literal`;
980
+ }
981
+ if (init.value > 0) {
982
+ init.value = -init.value;
983
+ init.raw = "-" + init.raw;
984
+ }
985
+ else {
986
+ console.log(`Negative fixup ${fixup} was already negative!`);
987
+ }
988
+ });
989
+ return result;
990
+ }
991
+ catch (e) {
992
+ console.error(e.toString());
993
+ return null;
994
+ }
924
995
  }
925
-
926
996
  function api_hasProperty(obj, prop) {
927
- return obj && Object.prototype.hasOwnProperty.call(obj, prop);
997
+ return obj && Object.prototype.hasOwnProperty.call(obj, prop);
998
+ }
999
+ function api_isStateNode(node) {
1000
+ return api_hasProperty(node, "node");
1001
+ }
1002
+ function api_variableDeclarationName(node) {
1003
+ return ("left" in node ? node.left : node).name;
928
1004
  }
929
-
930
1005
  function api_collectNamespaces(ast, state) {
931
- state = state || {};
932
- if (!state.index) state.index = {};
933
- if (!state.stack) {
934
- state.stack = [{ type: "Program", name: "$", fullName: "$" }];
935
- }
936
- const checkOne = (ns, name) => {
937
- if (api_hasProperty(ns.decls, name)) {
938
- return ns.decls[name];
1006
+ state = state || {};
1007
+ if (!state.index)
1008
+ state.index = {};
1009
+ if (!state.stack) {
1010
+ state.stack = [
1011
+ { type: "Program", name: "$", fullName: "$", node: undefined },
1012
+ ];
939
1013
  }
940
- return null;
941
- };
942
- state.lookup = (node, name, stack) => {
943
- stack || (stack = state.stack);
944
- switch (node.type) {
945
- case "MemberExpression": {
946
- if (node.property.type != "Identifier" || node.computed) break;
947
- const [, module] = state.lookup(node.object, name, stack);
948
- if (module && module.length === 1) {
949
- const result = checkOne(module[0], node.property.name);
950
- if (result) {
951
- return [name || node.property.name, result];
952
- }
953
- }
954
- break;
955
- }
956
- case "ThisExpression":
957
- return [name, stack.slice(-1)];
958
- case "Identifier": {
959
- if (node.name == "$") {
960
- return [name || node.name, [stack[0]]];
961
- }
962
- for (let i = stack.length; i--; ) {
963
- const result = checkOne(stack[i], node.name);
964
- if (result) {
965
- return [name || node.name, result];
966
- }
1014
+ const checkOne = (ns, name) => {
1015
+ if (api_isStateNode(ns) && api_hasProperty(ns.decls, name)) {
1016
+ return ns.decls[name];
967
1017
  }
968
- break;
969
- }
970
- }
971
- return [null, null];
972
- };
973
-
974
- state.traverse = (root) =>
975
- api_traverseAst(
976
- root,
977
- (node) => {
978
- try {
979
- if (state.shouldExclude && state.shouldExclude(node)) {
980
- // don't visit any children, but do call post
981
- return [];
982
- }
983
- switch (node.type) {
984
- case "Program":
985
- if (state.stack.length != 1) {
986
- throw new Error("Unexpected stack length for Program node");
987
- }
988
- if (node.source) {
989
- state.stack[0].source = node.source;
990
- }
991
- break;
992
- case "BlockStatement": {
993
- const [parent] = state.stack.slice(-1);
994
- if (
995
- parent.type != "FunctionDeclaration" &&
996
- parent.type != "BlockStatement"
997
- ) {
1018
+ return null;
1019
+ };
1020
+ state.lookup = (node, name, stack) => {
1021
+ stack || (stack = state.stack);
1022
+ switch (node.type) {
1023
+ case "MemberExpression": {
1024
+ if (node.property.type != "Identifier" || node.computed)
1025
+ break;
1026
+ const [, module, where] = state.lookup(node.object, name, stack);
1027
+ if (module && module.length === 1) {
1028
+ const result = checkOne(module[0], node.property.name);
1029
+ if (result) {
1030
+ return [
1031
+ name || node.property.name,
1032
+ result,
1033
+ where.concat(module[0]),
1034
+ ];
1035
+ }
1036
+ }
998
1037
  break;
999
- }
1000
- // fall through
1001
1038
  }
1002
- case "ClassDeclaration":
1003
- case "FunctionDeclaration":
1004
- case "ModuleDeclaration":
1005
- if (node.id || node.type == "BlockStatement") {
1006
- const [parent] = state.stack.slice(-1);
1007
- const elm = {
1008
- type: node.type,
1009
- name: node.id && node.id.name,
1010
- node,
1011
- };
1012
- state.stack.push(elm);
1013
- elm.fullName = state.stack
1014
- .map((e) => e.name)
1015
- .filter((e) => e != null)
1016
- .join(".");
1017
- if (elm.name) {
1018
- if (!parent.decls) parent.decls = {};
1019
- if (api_hasProperty(parent.decls, elm.name)) {
1020
- const what =
1021
- node.type == "ModuleDeclaration" ? "type" : "node";
1022
- const e = parent.decls[elm.name].find(
1023
- (d) => d[what] == elm[what]
1024
- );
1025
- if (e != null) {
1026
- e.node = node;
1027
- state.stack.splice(-1, 1, e);
1028
- break;
1039
+ case "ThisExpression": {
1040
+ for (let i = stack.length; i--;) {
1041
+ const si = stack[i];
1042
+ if (si.type == "ModuleDeclaration" ||
1043
+ si.type == "ClassDeclaration" ||
1044
+ !i) {
1045
+ return [name || si.name, [si], stack.slice(0, i)];
1029
1046
  }
1030
- } else {
1031
- parent.decls[elm.name] = [];
1032
- }
1033
- parent.decls[elm.name].push(elm);
1034
1047
  }
1035
- }
1036
- break;
1037
- // an EnumDeclaration doesn't create a scope, but
1038
- // it does create a type (if it has a name)
1039
- case "EnumDeclaration": {
1040
- if (!node.id) break;
1041
- const [parent] = state.stack.slice(-1);
1042
- const name = (parent.fullName + "." + node.id.name).replace(
1043
- /^\$\./,
1044
- ""
1045
- );
1046
- node.body.members.forEach((m) => ((m.init || m).enumType = name));
1047
- }
1048
- // fall through
1049
- case "TypedefDeclaration": {
1050
- const [parent] = state.stack.slice(-1);
1051
- if (!parent.decls) parent.decls = {};
1052
- if (!api_hasProperty(parent.decls, node.id.name)) {
1053
- parent.decls[node.id.name] = [];
1054
- }
1055
- (0,external_util_cjs_namespaceObject.pushUnique)(
1056
- parent.decls[node.id.name],
1057
- node.ts ? formatAst(node.ts.argument) : node.id.name
1058
- );
1059
- break;
1048
+ break;
1060
1049
  }
1061
- case "VariableDeclaration": {
1062
- const [parent] = state.stack.slice(-1);
1063
- if (!parent.decls) parent.decls = {};
1064
- node.declarations.forEach((decl) => {
1065
- if (!api_hasProperty(parent.decls, decl.id.name)) {
1066
- parent.decls[decl.id.name] = [];
1050
+ case "Identifier": {
1051
+ if (node.name == "$") {
1052
+ return [name || node.name, [stack[0]], []];
1067
1053
  }
1068
- if (node.kind == "const") {
1069
- (0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[decl.id.name], decl.init);
1070
- if (!api_hasProperty(state.index, decl.id.name)) {
1071
- state.index[decl.id.name] = [];
1072
- }
1073
- (0,external_util_cjs_namespaceObject.pushUnique)(state.index[decl.id.name], parent);
1074
- } else if (decl.id.ts) {
1075
- (0,external_util_cjs_namespaceObject.pushUnique)(
1076
- parent.decls[decl.id.name],
1077
- formatAst(decl.id.ts.argument)
1078
- );
1054
+ for (let i = stack.length; i--;) {
1055
+ const result = checkOne(stack[i], node.name);
1056
+ if (result) {
1057
+ return [name || node.name, result, stack.slice(0, i + 1)];
1058
+ }
1079
1059
  }
1080
- });
1081
- break;
1060
+ break;
1061
+ }
1062
+ }
1063
+ return [null, null, null];
1064
+ };
1065
+ state.traverse = (root) => api_traverseAst(root, (node) => {
1066
+ try {
1067
+ if (state.shouldExclude && state.shouldExclude(node)) {
1068
+ // don't visit any children, but do call post
1069
+ return [];
1082
1070
  }
1083
- case "EnumStringBody": {
1084
- const [parent] = state.stack.slice(-1);
1085
- const values = parent.decls || (parent.decls = {});
1086
- let prev = -1;
1087
- node.members.forEach((m) => {
1088
- let name, init;
1089
- if (m.type == "EnumStringMember") {
1090
- name = m.id.name;
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
- }
1098
- if (
1099
- init.type == "Literal" &&
1100
- api_LiteralIntegerRe.test(init.raw)
1101
- ) {
1102
- prev = init.value;
1103
- }
1104
- } else {
1105
- name = m.name;
1106
- prev += 1;
1107
- if (!state.constants) {
1108
- state.constants = {};
1109
- }
1110
- const key = m.enumType ? `${m.enumType}:${prev}` : prev;
1111
- init = state.constants[key];
1112
- if (!init) {
1113
- init = state.constants[key] = {
1114
- type: "Literal",
1115
- value: prev,
1116
- raw: prev.toString(),
1117
- enumType: m.enumType,
1071
+ switch (node.type) {
1072
+ case "Program":
1073
+ if (state.stack.length != 1) {
1074
+ throw new Error("Unexpected stack length for Program node");
1075
+ }
1076
+ break;
1077
+ case "BlockStatement": {
1078
+ const [parent] = state.stack.slice(-1);
1079
+ if (parent.type != "FunctionDeclaration" &&
1080
+ parent.type != "BlockStatement") {
1081
+ break;
1082
+ }
1083
+ // fall through
1084
+ }
1085
+ case "ClassDeclaration":
1086
+ case "FunctionDeclaration":
1087
+ case "ModuleDeclaration": {
1088
+ const [parent] = state.stack.slice(-1);
1089
+ const name = "id" in node ? node.id && node.id.name : null;
1090
+ const fullName = state.stack
1091
+ .map((e) => e.name)
1092
+ .concat(name)
1093
+ .filter((e) => e != null)
1094
+ .join(".");
1095
+ const elm = {
1096
+ type: node.type,
1097
+ name,
1098
+ fullName,
1099
+ node,
1118
1100
  };
1119
- }
1101
+ state.stack.push(elm);
1102
+ if (name) {
1103
+ if (!parent.decls)
1104
+ parent.decls = {};
1105
+ if (api_hasProperty(parent.decls, elm.name)) {
1106
+ const what = node.type == "ModuleDeclaration" ? "type" : "node";
1107
+ const e = parent.decls[name].find((d) => api_isStateNode(d) && d[what] == elm[what]);
1108
+ if (e != null) {
1109
+ e.node = node;
1110
+ state.stack.splice(-1, 1, e);
1111
+ break;
1112
+ }
1113
+ }
1114
+ else {
1115
+ parent.decls[elm.name] = [];
1116
+ }
1117
+ if (node.type === "FunctionDeclaration" &&
1118
+ node.params &&
1119
+ node.params.length) {
1120
+ elm.decls = {};
1121
+ node.params.forEach((p) => (elm.decls[api_variableDeclarationName(p)] = [p]));
1122
+ }
1123
+ parent.decls[elm.name].push(elm);
1124
+ }
1125
+ break;
1120
1126
  }
1121
- if (!api_hasProperty(values, name)) {
1122
- values[name] = [];
1127
+ // an EnumDeclaration doesn't create a scope, but
1128
+ // it does create a type (if it has a name)
1129
+ case "EnumDeclaration": {
1130
+ if (!node.id)
1131
+ break;
1132
+ const [parent] = state.stack.slice(-1);
1133
+ const name = (parent.fullName + "." + node.id.name).replace(/^\$\./, "");
1134
+ node.body.members.forEach((m) => (("init" in m ? m.init : m).enumType = name));
1123
1135
  }
1124
- (0,external_util_cjs_namespaceObject.pushUnique)(values[name], init);
1125
- if (!api_hasProperty(state.index, name)) {
1126
- state.index[name] = [];
1136
+ // fall through
1137
+ case "TypedefDeclaration": {
1138
+ const [parent] = state.stack.slice(-1);
1139
+ if (!parent.decls)
1140
+ parent.decls = {};
1141
+ if (!api_hasProperty(parent.decls, node.id.name)) {
1142
+ parent.decls[node.id.name] = [];
1143
+ }
1144
+ (0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[node.id.name], node);
1145
+ break;
1146
+ }
1147
+ case "VariableDeclaration": {
1148
+ const [parent] = state.stack.slice(-1);
1149
+ if (!parent.decls)
1150
+ parent.decls = {};
1151
+ node.declarations.forEach((decl) => {
1152
+ const name = api_variableDeclarationName(decl.id);
1153
+ if (!api_hasProperty(parent.decls, name)) {
1154
+ parent.decls[name] = [];
1155
+ }
1156
+ decl.kind = node.kind;
1157
+ (0,external_util_cjs_namespaceObject.pushUnique)(parent.decls[name], decl);
1158
+ if (node.kind == "const") {
1159
+ if (!api_hasProperty(state.index, name)) {
1160
+ state.index[name] = [];
1161
+ }
1162
+ (0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
1163
+ }
1164
+ });
1165
+ break;
1166
+ }
1167
+ case "EnumStringBody": {
1168
+ const [parent] = state.stack.slice(-1);
1169
+ const values = parent.decls || (parent.decls = {});
1170
+ let prev = -1;
1171
+ node.members.forEach((m, i) => {
1172
+ if (m.type == "Identifier") {
1173
+ prev += 1;
1174
+ m = node.members[i] = {
1175
+ type: "EnumStringMember",
1176
+ loc: m.loc,
1177
+ start: m.start,
1178
+ end: m.end,
1179
+ id: m,
1180
+ init: {
1181
+ type: "Literal",
1182
+ value: prev,
1183
+ raw: prev.toString(),
1184
+ enumType: m.enumType,
1185
+ loc: m.loc,
1186
+ start: m.start,
1187
+ end: m.end,
1188
+ },
1189
+ };
1190
+ }
1191
+ const name = m.id.name;
1192
+ const init = getLiteralNode(m.init);
1193
+ if (!init) {
1194
+ throw new Error("Unexpected enum initializer");
1195
+ }
1196
+ if (init != m.init) {
1197
+ m.init = init;
1198
+ }
1199
+ if (init.type == "Literal" && prettier_plugin_monkeyc_namespaceObject.LiteralIntegerRe.test(init.raw)) {
1200
+ prev = init.value;
1201
+ }
1202
+ if (!api_hasProperty(values, name)) {
1203
+ values[name] = [];
1204
+ }
1205
+ (0,external_util_cjs_namespaceObject.pushUnique)(values[name], m);
1206
+ if (!api_hasProperty(state.index, name)) {
1207
+ state.index[name] = [];
1208
+ }
1209
+ (0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
1210
+ });
1211
+ break;
1127
1212
  }
1128
- (0,external_util_cjs_namespaceObject.pushUnique)(state.index[name], parent);
1129
- });
1130
- break;
1131
1213
  }
1132
- }
1133
- if (state.pre) return state.pre(node);
1134
- } catch (e) {
1135
- handleException(state, node, e);
1214
+ if (state.pre)
1215
+ return state.pre(node);
1216
+ }
1217
+ catch (e) {
1218
+ handleException(state, node, e);
1136
1219
  }
1137
- },
1138
- (node) => {
1220
+ return null;
1221
+ }, (node) => {
1139
1222
  try {
1140
- let ret;
1141
- if (state.shouldExclude && state.shouldExclude(node)) {
1142
- // delete the node.
1143
- return false;
1144
- }
1145
- if (state.post) ret = state.post(node);
1146
- if (state.stack.slice(-1).pop().node === node) {
1147
- state.stack.pop();
1148
- }
1149
- return ret;
1150
- } catch (e) {
1151
- handleException(state, node, e);
1223
+ let ret;
1224
+ if (state.shouldExclude && state.shouldExclude(node)) {
1225
+ // delete the node.
1226
+ return false;
1227
+ }
1228
+ if (state.post)
1229
+ ret = state.post(node);
1230
+ if (state.stack.slice(-1).pop().node === node) {
1231
+ state.stack.pop();
1232
+ }
1233
+ return ret;
1234
+ }
1235
+ catch (e) {
1236
+ handleException(state, node, e);
1152
1237
  }
1153
- }
1154
- );
1155
- state.traverse(ast);
1156
- if (state.stack.length != 1) {
1157
- throw new Error("Invalid AST!");
1158
- }
1159
- return state.stack[0];
1238
+ });
1239
+ state.traverse(ast);
1240
+ if (state.stack.length != 1) {
1241
+ throw new Error("Invalid AST!");
1242
+ }
1243
+ if (state.stack[0].type != "Program") {
1244
+ throw new Error("Bottom of stack was not a Program!");
1245
+ }
1246
+ return state.stack[0];
1247
+ }
1248
+ function isMCTreeNode(node) {
1249
+ return node && typeof node === "object" && "type" in node;
1160
1250
  }
1161
-
1162
1251
  /*
1163
1252
  * Traverse the ast rooted at node, calling pre before
1164
1253
  * visiting each node, and post after.
@@ -1173,84 +1262,83 @@ function api_collectNamespaces(ast, state) {
1173
1262
  * removed.
1174
1263
  */
1175
1264
  function api_traverseAst(node, pre, post) {
1176
- const nodes = pre && pre(node);
1177
- if (nodes === false) return;
1178
- for (const key of nodes || Object.keys(node)) {
1179
- const value = node[key];
1180
- if (!value) continue;
1181
- if (Array.isArray(value)) {
1182
- const deletions = value.reduce((state, obj, i) => {
1183
- const repl = api_traverseAst(obj, pre, post);
1184
- if (repl === false) {
1185
- if (!state) state = {};
1186
- state[i] = true;
1187
- } else if (repl != null) {
1188
- value[i] = repl;
1265
+ const nodes = pre && pre(node);
1266
+ if (nodes === false)
1267
+ return;
1268
+ for (const key of nodes || Object.keys(node)) {
1269
+ const value = node[key];
1270
+ if (!value)
1271
+ continue;
1272
+ if (Array.isArray(value)) {
1273
+ const values = value;
1274
+ const deletions = values.reduce((state, obj, i) => {
1275
+ if (isMCTreeNode(obj)) {
1276
+ const repl = api_traverseAst(obj, pre, post);
1277
+ if (repl === false) {
1278
+ if (!state)
1279
+ state = {};
1280
+ state[i] = true;
1281
+ }
1282
+ else if (repl != null) {
1283
+ values[i] = repl;
1284
+ }
1285
+ }
1286
+ return state;
1287
+ }, null);
1288
+ if (deletions) {
1289
+ values.splice(0, values.length, ...values.filter((obj, i) => deletions[i] !== true));
1290
+ }
1291
+ }
1292
+ else if (isMCTreeNode(value)) {
1293
+ const repl = api_traverseAst(value, pre, post);
1294
+ if (repl === false) {
1295
+ delete node[key];
1296
+ }
1297
+ else if (repl != null) {
1298
+ node[key] = repl;
1299
+ }
1189
1300
  }
1190
- return state;
1191
- }, null);
1192
- if (deletions) {
1193
- value.splice(
1194
- 0,
1195
- value.length,
1196
- ...value.filter((obj, i) => deletions[i] !== true)
1197
- );
1198
- }
1199
- } else if (typeof value == "object" && value.type) {
1200
- const repl = api_traverseAst(value, pre, post);
1201
- if (repl === false) {
1202
- delete node[key];
1203
- } else if (repl != null) {
1204
- node[key] = repl;
1205
- }
1206
1301
  }
1207
- }
1208
- return post && post(node);
1302
+ return post && post(node);
1209
1303
  }
1210
-
1211
- function formatAst(node, options) {
1212
- if (!node.monkeyCSource && node.comments) {
1213
- // Prettier inserts comments by using the source location to
1214
- // find the original comment, rather than using the contents
1215
- // of the comment as reported by the comment nodes themselves.
1216
- // If all we've got is the ast, rather than the actual
1217
- // source code, this goes horribly wrong, so just drop all
1218
- // the comments.
1219
- delete node.comments;
1220
- }
1221
- // If we *do* have the original source, pass that in ahead of the
1222
- // json. The parser knows to just treat the last line of the input
1223
- // as the ast itself, and the printers will find what they're
1224
- // looking for in the source.
1225
- const source =
1226
- (node.monkeyCSource ? node.monkeyCSource + "\n" : "") +
1227
- JSON.stringify(node);
1228
- return standalone_js_namespaceObject.format(source, {
1229
- ...(options || {}),
1230
- parser: "monkeyc-json",
1231
- plugins: [prettier_plugin_monkeyc_namespaceObject],
1232
- endOfLine: "lf",
1233
- });
1304
+ function formatAst(node, monkeyCSource = null) {
1305
+ if ("comments" in node && !monkeyCSource) {
1306
+ // Prettier inserts comments by using the source location to
1307
+ // find the original comment, rather than using the contents
1308
+ // of the comment as reported by the comment nodes themselves.
1309
+ // If all we've got is the ast, rather than the actual
1310
+ // source code, this goes horribly wrong, so just drop all
1311
+ // the comments.
1312
+ delete node.comments;
1313
+ }
1314
+ // If we *do* have the original source, pass that in ahead of the
1315
+ // json. The parser knows to just treat the last line of the input
1316
+ // as the ast itself, and the printers will find what they're
1317
+ // looking for in the source.
1318
+ const source = (monkeyCSource || "") + "\n" + JSON.stringify(node);
1319
+ return standalone_js_default().format(source, {
1320
+ parser: "monkeyc-json",
1321
+ plugins: [(prettier_plugin_monkeyc_default())],
1322
+ endOfLine: "lf",
1323
+ });
1234
1324
  }
1235
-
1236
1325
  function handleException(state, node, exception) {
1237
- let message;
1238
- try {
1239
- const fullName = state.stack
1240
- .map((e) => e.name)
1241
- .concat(node.name)
1242
- .filter((e) => e != null)
1243
- .join(".");
1244
- const location = state.stack[0].source
1245
- ? `${state.stack[0].source}:${node.start || 0}:${node.end || 0}`
1246
- : "<unknown>";
1247
- message = `Got exception \`${exception.toString()}' while processing node ${fullName}:${
1248
- node.type
1249
- } from ${location}`;
1250
- } catch {
1251
- throw exception;
1252
- }
1253
- throw new Error(message);
1326
+ let message;
1327
+ try {
1328
+ const fullName = state.stack
1329
+ .map((e) => e.name)
1330
+ .concat("name" in node && typeof node.name === "string" ? node.name : null)
1331
+ .filter((e) => e != null)
1332
+ .join(".");
1333
+ const location = node.loc && node.loc.source
1334
+ ? `${node.loc.source}:${node.start || 0}:${node.end || 0}`
1335
+ : "<unknown>";
1336
+ message = `Got exception \`${exception.toString()}' while processing node ${fullName}:${node.type} from ${location}`;
1337
+ }
1338
+ catch {
1339
+ throw exception;
1340
+ }
1341
+ throw new Error(message);
1254
1342
  }
1255
1343
 
1256
1344
  var __webpack_export_target__ = exports;