@markw65/monkeyc-optimizer 1.0.2 → 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
@@ -18,4 +18,9 @@ Initial release
18
18
  - Better error reporting when something goes wrong internally
19
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
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
+
21
26
  ---
package/build/api.cjs CHANGED
@@ -217,169 +217,182 @@ function collectNamespaces(ast, state) {
217
217
  return [null, null];
218
218
  };
219
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
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 [];
242
228
  }
243
- case "ClassDeclaration":
244
- case "FunctionDeclaration":
245
- case "ModuleDeclaration":
246
- if (node.id || node.type == "BlockStatement") {
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": {
247
239
  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;
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] = [];
270
278
  }
271
- } else {
272
- parent.decls[elm.name] = [];
279
+ parent.decls[elm.name].push(elm);
273
280
  }
274
- parent.decls[elm.name].push(elm);
275
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));
276
293
  }
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] = [];
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] = [];
308
300
  }
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] = [];
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
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;
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
+ );
335
325
  }
336
- } else {
337
- name = m.name;
338
- prev += 1;
339
- if (!state.constants) {
340
- state.constants = {};
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
+ }
341
360
  }
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
- };
361
+ if (!hasProperty(values, name)) {
362
+ values[name] = [];
351
363
  }
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;
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
+ }
363
372
  }
373
+ if (state.pre) return state.pre(node);
374
+ } catch (e) {
375
+ handleException(state, node, e);
364
376
  }
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();
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);
376
392
  }
377
- return ret;
378
- } catch (e) {
379
- handleException(state, node, e);
380
393
  }
381
- }
382
- );
394
+ );
395
+ state.traverse(ast);
383
396
  if (state.stack.length != 1) {
384
397
  throw new Error("Invalid AST!");
385
398
  }
@@ -7685,34 +7685,24 @@ async function analyze(fileNames, buildConfig) {
7685
7685
  ? Object.fromEntries(buildConfig.excludeAnnotations.map((a) => [a, true]))
7686
7686
  : {};
7687
7687
 
7688
- const shouldExclude = (node) => {
7689
- if (node.attrs && node.attrs.attrs) {
7690
- if (
7691
- node.attrs.attrs.filter((attr) => {
7692
- if (attr.type != "UnaryExpression") return false;
7693
- if (attr.argument.type != "Identifier") return false;
7694
- return (0,external_api_cjs_namespaceObject.hasProperty)(excludeAnnotations, attr.argument.name);
7695
- }).length
7696
- ) {
7697
- return true;
7698
- }
7699
- }
7700
- };
7701
7688
  const allImports = [];
7702
7689
  const state = {
7703
7690
  allFunctions: [],
7704
7691
  allClasses: [],
7705
- pre(node) {
7706
- if (shouldExclude(node)) {
7707
- // don't visit any children, but do call post
7708
- return [];
7692
+ shouldExclude(node) {
7693
+ if (node.attrs && node.attrs.attrs) {
7694
+ if (
7695
+ node.attrs.attrs.filter((attr) => {
7696
+ if (attr.type != "UnaryExpression") return false;
7697
+ if (attr.argument.type != "Identifier") return false;
7698
+ return (0,external_api_cjs_namespaceObject.hasProperty)(excludeAnnotations, attr.argument.name);
7699
+ }).length
7700
+ ) {
7701
+ return true;
7702
+ }
7709
7703
  }
7710
7704
  },
7711
7705
  post(node) {
7712
- if (shouldExclude(node)) {
7713
- // delete the node.
7714
- return false;
7715
- }
7716
7706
  switch (node.type) {
7717
7707
  case "FunctionDeclaration":
7718
7708
  case "ClassDeclaration": {
@@ -7768,7 +7758,7 @@ async function analyze(fileNames, buildConfig) {
7768
7758
  (0,external_api_cjs_namespaceObject.collectNamespaces)(f.ast, state);
7769
7759
  });
7770
7760
 
7771
- delete state.pre;
7761
+ delete state.shouldExclude;
7772
7762
  delete state.post;
7773
7763
 
7774
7764
  processImports(allImports, state.lookup);
@@ -8054,6 +8044,49 @@ async function optimizeMonkeyC(fileNames, buildConfig) {
8054
8044
  state.calledFunctions = {};
8055
8045
  state.pre = (node) => {
8056
8046
  switch (node.type) {
8047
+ case "ConditionalExpression":
8048
+ case "IfStatement":
8049
+ case "DoWhileStatement":
8050
+ case "WhileStatement":
8051
+ state.traverse(node.test);
8052
+ const [value, type] = getNodeValue(node.test);
8053
+ if (value) {
8054
+ let result = null;
8055
+ if (type === "Null") {
8056
+ result = false;
8057
+ } else if (
8058
+ type === "Boolean" ||
8059
+ type === "Number" ||
8060
+ type === "Long"
8061
+ ) {
8062
+ result = !!value.value;
8063
+ }
8064
+ if (result !== null) {
8065
+ if (
8066
+ node.type === "IfStatement" ||
8067
+ node.type === "ConditionalExpression"
8068
+ ) {
8069
+ if (result === false) {
8070
+ node.consequent = null;
8071
+ } else {
8072
+ node.alternate = null;
8073
+ }
8074
+ node.test = result;
8075
+ } else if (node.type === "WhileStatement") {
8076
+ if (result === false) {
8077
+ node.body = null;
8078
+ }
8079
+ } else if (node.type === "DoWhileStatement") {
8080
+ if (result === false) {
8081
+ node.test = null;
8082
+ }
8083
+ } else {
8084
+ throw new Error("Unexpected Node type");
8085
+ }
8086
+ }
8087
+ }
8088
+ return;
8089
+
8057
8090
  case "EnumDeclaration":
8058
8091
  return false;
8059
8092
  case "VariableDeclarator":
@@ -8115,38 +8148,55 @@ async function optimizeMonkeyC(fileNames, buildConfig) {
8115
8148
  replace(node, opt);
8116
8149
  return;
8117
8150
  }
8118
- if (node.type == "CallExpression") {
8119
- const [name, callees] = state.lookup(node.callee);
8120
- if (!callees || !callees.length) {
8121
- const n =
8122
- name ||
8123
- node.callee.name ||
8124
- (node.callee.property && node.callee.property.name);
8125
- if (n) {
8126
- state.exposed[n] = true;
8127
- } else {
8128
- throw new Error("What?");
8151
+ switch (node.type) {
8152
+ case "ConditionalExpression":
8153
+ case "IfStatement":
8154
+ if (typeof node.test === "boolean") {
8155
+ const rep = node.test ? node.consequent : node.alternate;
8156
+ return rep || false;
8129
8157
  }
8130
- return;
8131
- }
8132
- if (callees.length == 1) {
8133
- const callee = callees[0].node;
8134
- if (
8135
- callee.optimizable &&
8136
- !callee.hasOverride &&
8137
- node.arguments.every((n) => getNodeValue(n)[0] !== null)
8138
- ) {
8139
- const ret = evaluateFunction(callee, node.arguments);
8140
- if (ret) {
8141
- replace(node, ret);
8142
- return;
8158
+ break;
8159
+ case "WhileStatement":
8160
+ if (!node.body) return false;
8161
+ break;
8162
+ case "DoWhileStatement":
8163
+ if (!node.test) return node.body;
8164
+ break;
8165
+
8166
+ case "CallExpression": {
8167
+ const [name, callees] = state.lookup(node.callee);
8168
+ if (!callees || !callees.length) {
8169
+ const n =
8170
+ name ||
8171
+ node.callee.name ||
8172
+ (node.callee.property && node.callee.property.name);
8173
+ if (n) {
8174
+ state.exposed[n] = true;
8175
+ } else {
8176
+ throw new Error("What?");
8143
8177
  }
8178
+ return;
8144
8179
  }
8180
+ if (callees.length == 1) {
8181
+ const callee = callees[0].node;
8182
+ if (
8183
+ callee.optimizable &&
8184
+ !callee.hasOverride &&
8185
+ node.arguments.every((n) => getNodeValue(n)[0] !== null)
8186
+ ) {
8187
+ const ret = evaluateFunction(callee, node.arguments);
8188
+ if (ret) {
8189
+ replace(node, ret);
8190
+ return;
8191
+ }
8192
+ }
8193
+ }
8194
+ if (!(0,external_api_cjs_namespaceObject.hasProperty)(state.calledFunctions, name)) {
8195
+ state.calledFunctions[name] = [];
8196
+ }
8197
+ callees.forEach((c) => state.calledFunctions[name].push(c.node));
8198
+ break;
8145
8199
  }
8146
- if (!(0,external_api_cjs_namespaceObject.hasProperty)(state.calledFunctions, name)) {
8147
- state.calledFunctions[name] = [];
8148
- }
8149
- callees.forEach((c) => state.calledFunctions[name].push(c.node));
8150
8200
  }
8151
8201
  };
8152
8202
  files.forEach((f) => {
@@ -8248,7 +8298,7 @@ async function getVSCodeSettings(path) {
8248
8298
  }
8249
8299
 
8250
8300
  const defaultConfig = {
8251
- outputPath: "optimized",
8301
+ outputPath: "bin/optimized",
8252
8302
  workspace: "./",
8253
8303
  };
8254
8304
 
@@ -8287,6 +8337,8 @@ async function buildOptimizedProject(product, options) {
8287
8337
  const config = await getConfig(options);
8288
8338
  if (product) {
8289
8339
  config.products = [product];
8340
+ } else if (!(0,external_api_cjs_namespaceObject.hasProperty)(config, "releaseBuild")) {
8341
+ config.releaseBuild = true;
8290
8342
  }
8291
8343
  const { jungleFiles, program } = await generateOptimizedProject(config);
8292
8344
  config.jungleFiles = jungleFiles;
@@ -8299,9 +8351,6 @@ async function buildOptimizedProject(product, options) {
8299
8351
  } else {
8300
8352
  bin = external_path_namespaceObject.join(bin, "exported");
8301
8353
  name = `${program}.iq`;
8302
- if (!(0,external_api_cjs_namespaceObject.hasProperty)(config, "releaseBuild")) {
8303
- config.releaseBuild = true;
8304
- }
8305
8354
  }
8306
8355
  config.program = external_path_namespaceObject.join(bin, name);
8307
8356
  return build_project(product, config);
@@ -8314,11 +8363,18 @@ async function generateOptimizedProject(options) {
8314
8363
  const { manifest, targets } = await get_jungle(config.jungleFiles, config);
8315
8364
  const buildConfigs = {};
8316
8365
  targets.forEach((p) => {
8317
- if (!(0,external_api_cjs_namespaceObject.hasProperty)(buildConfigs, p.group.key)) {
8318
- buildConfigs[p.group.key] = null;
8366
+ const key = p.group.key + (config.releaseBuild ? "-release" : "-debug");
8367
+ if (!(0,external_api_cjs_namespaceObject.hasProperty)(buildConfigs, key)) {
8368
+ p.group.dir = key;
8369
+ buildConfigs[key] = null;
8370
+ // Note that we exclude (:debug) in release builds, and we
8371
+ // exclude (:release) in debug builds. This isn't backwards!
8372
+ p.group.optimizerConfig["excludeAnnotations"].push(
8373
+ config.releaseBuild ? "debug" : "release"
8374
+ );
8319
8375
  }
8320
8376
  if (!options.products || options.products.includes(p.product)) {
8321
- buildConfigs[p.group.key] = p.group.optimizerConfig;
8377
+ buildConfigs[key] = p.group.optimizerConfig;
8322
8378
  }
8323
8379
  });
8324
8380
 
@@ -8358,7 +8414,7 @@ async function generateOptimizedProject(options) {
8358
8414
  const { product, qualifier, group } = jungle;
8359
8415
  const prefix = `${product}.`;
8360
8416
  process_field(prefix, qualifier, "sourcePath", (s) =>
8361
- external_path_namespaceObject.join(group.key.toString(), external_path_namespaceObject.relative(workspace, s))
8417
+ external_path_namespaceObject.join(group.dir, external_path_namespaceObject.relative(workspace, s))
8362
8418
  .replace(/(\/\*\*)\/\*/g, "$1")
8363
8419
  );
8364
8420
  process_field(prefix, qualifier, "resourcePath", relative_path);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@markw65/monkeyc-optimizer",
3
3
  "type": "module",
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "description": "Source to source optimizer for Garmin Monkey C code",
6
6
  "main": "build/optimizer.cjs",
7
7
  "exports": {