@markw65/monkeyc-optimizer 1.0.18 → 1.0.19

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
@@ -198,3 +198,25 @@ More fixes found via open source projects.
198
198
 
199
199
  - Bug Fixes
200
200
  - The new inliner was too agressive at constant propagating literal parameters to their point of use.
201
+
202
+ ### 1.0.19
203
+
204
+ - Upgrade to @markw65/prettier-plugin-monkeyc@1.0.22
205
+
206
+ - fixes some minor typing issues for mctree
207
+ - special handling for certain parenthesized expressions.
208
+
209
+ - Optimizer
210
+
211
+ - Handle more unused expressions, add tests, and prettify the OptimizerTests project
212
+ - Allow statement-style inlining in assignent and return contexts
213
+ - Add diagnostics for failure to inline
214
+
215
+ - Tests
216
+
217
+ - More tweaks to pragma-checker
218
+ - Add launch and task configs for building/running tests
219
+
220
+ - Code cleanup
221
+ - Properly type the results of JSON.parse
222
+ - Switch over to using ParenthesizedExpression for formatAst (depends on @markw65/prettier-plugin-monkeyc@1.0.22)
package/build/api.cjs CHANGED
@@ -256,15 +256,30 @@ function inliningLooksUseful(func, node) {
256
256
  }
257
257
  return false;
258
258
  }
259
- var inliner_InlineStatus;
259
+ var InlineStatus;
260
260
  (function (InlineStatus) {
261
261
  InlineStatus[InlineStatus["Never"] = 0] = "Never";
262
262
  InlineStatus[InlineStatus["AsExpression"] = 1] = "AsExpression";
263
263
  InlineStatus[InlineStatus["AsStatement"] = 2] = "AsStatement";
264
- })(inliner_InlineStatus || (inliner_InlineStatus = {}));
265
- function inliner_shouldInline(state, func, args) {
264
+ })(InlineStatus || (InlineStatus = {}));
265
+ function inlineRequested(state, func) {
266
+ const excludeAnnotations = (func.node.loc?.source &&
267
+ state.fnMap[func.node.loc?.source]?.excludeAnnotations) ||
268
+ {};
269
+ if (func.node.attrs &&
270
+ func.node.attrs.attrs &&
271
+ func.node.attrs.attrs.some((attr) => attr.type === "UnaryExpression" &&
272
+ (attr.argument.name === "inline" ||
273
+ (attr.argument.name.startsWith("inline_") &&
274
+ hasProperty(excludeAnnotations, attr.argument.name.substring(7)))))) {
275
+ return true;
276
+ }
277
+ return false;
278
+ }
279
+ function inliner_shouldInline(state, func, call, context) {
266
280
  let autoInline = false;
267
281
  let inlineAsExpression = false;
282
+ const args = call.arguments;
268
283
  if (func.node.body &&
269
284
  func.node.body.body.length === 1 &&
270
285
  func.node.body.body[0].type === "ReturnStatement" &&
@@ -274,39 +289,30 @@ function inliner_shouldInline(state, func, args) {
274
289
  autoInline = inliningLooksUseful(func.node, func.node.body.body[0].argument);
275
290
  }
276
291
  if (autoInline === 1) {
277
- return inliner_InlineStatus.AsExpression;
292
+ return true;
278
293
  }
279
- const excludeAnnotations = (func.node.loc?.source &&
280
- state.fnMap[func.node.loc?.source]?.excludeAnnotations) ||
281
- {};
282
- const inlineRequested = func.node.attrs &&
283
- func.node.attrs.attrs &&
284
- func.node.attrs.attrs.some((attr) => attr.type === "UnaryExpression" &&
285
- (attr.argument.name === "inline" ||
286
- (attr.argument.name.startsWith("inline_") &&
287
- hasProperty(excludeAnnotations, attr.argument.name.substring(7)))));
288
- if (autoInline || inlineRequested) {
289
- return inlineAsExpression && canInline(state, func, args)
290
- ? inliner_InlineStatus.AsExpression
291
- : inliner_InlineStatus.AsStatement;
294
+ const requested = inlineRequested(state, func);
295
+ if (autoInline || requested) {
296
+ if (inlineAsExpression) {
297
+ if (canInline(state, func, args)) {
298
+ return true;
299
+ }
300
+ }
301
+ if (!context && requested) {
302
+ inlineDiagnostic(state, func, call, "This function can only be inlined in statement, assignment, or return contexts");
303
+ }
304
+ return context != null;
292
305
  }
293
- return inliner_InlineStatus.Never;
306
+ return false;
294
307
  }
295
308
  function processInlineBody(state, func, call, root, insertedVariableDecls, params) {
296
- if (!params) {
297
- const safeArgs = getArgSafety(state, func, call.arguments, false);
298
- params = Object.fromEntries(func.node.params.map((param, i) => {
299
- const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
300
- ? i
301
- : -1;
302
- const name = variableDeclarationName(param);
303
- return [name, argnum];
304
- }));
305
- }
309
+ let failed = false;
306
310
  const pre = state.pre;
307
311
  const post = state.post;
308
312
  try {
309
313
  state.pre = (node) => {
314
+ if (failed)
315
+ return [];
310
316
  node.start = call.start;
311
317
  node.end = call.end;
312
318
  node.loc = call.loc;
@@ -318,6 +324,11 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
318
324
  const { map } = locals;
319
325
  if (!map)
320
326
  throw new Error("No local variable map!");
327
+ // We still need to keep track of every local name that was
328
+ // already in use, but we don't want to use any of its renames.
329
+ // We also want to know whether a local is from the function being
330
+ // inlined, or the calling function, so set every element to false.
331
+ Object.keys(map).forEach((key) => (map[key] = false));
321
332
  const declarations = func.node.params
322
333
  .map((param, i) => {
323
334
  const paramName = variableDeclarationName(param);
@@ -342,6 +353,8 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
342
353
  return result;
343
354
  };
344
355
  state.post = (node) => {
356
+ if (failed)
357
+ return null;
345
358
  let replacement = null;
346
359
  switch (node.type) {
347
360
  case "Identifier": {
@@ -356,22 +369,28 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
356
369
  }
357
370
  replacement = fixNodeScope(state, node, func.stack);
358
371
  if (!replacement) {
359
- throw new Error(`Inliner: Couldn't fix the scope of '${node.name}`);
372
+ failed = true;
373
+ inlineDiagnostic(state, func, call, `Failed to resolve '${node.name}'`);
374
+ return null;
360
375
  }
361
376
  break;
362
377
  }
363
378
  }
364
379
  return post(replacement || node, state) || replacement;
365
380
  };
366
- return state.traverse(root) || null;
367
- }
368
- catch (ex) {
369
- if (ex instanceof Error) {
370
- if (ex.message.startsWith("Inliner: ")) {
371
- return null;
372
- }
381
+ let ret = state.traverse(root);
382
+ if (failed) {
383
+ return null;
384
+ }
385
+ if (ret === null) {
386
+ ret = root;
387
+ }
388
+ if (!ret) {
389
+ inlineDiagnostic(state, func, call, `Internal error`);
390
+ return null;
373
391
  }
374
- throw ex;
392
+ inlineDiagnostic(state, func, call, null);
393
+ return ret;
375
394
  }
376
395
  finally {
377
396
  state.pre = pre;
@@ -394,7 +413,16 @@ function inliner_unused(expression, top) {
394
413
  case "UnaryExpression":
395
414
  return inliner_unused(expression.argument);
396
415
  case "MemberExpression":
397
- return inliner_unused(expression.object).concat(inliner_unused(expression.property));
416
+ if (expression.computed) {
417
+ return inliner_unused(expression.object).concat(inliner_unused(expression.property));
418
+ }
419
+ return inliner_unused(expression.object);
420
+ case "ArrayExpression":
421
+ return expression.elements.map((e) => inliner_unused(e)).flat(1);
422
+ case "ObjectExpression":
423
+ return expression.properties
424
+ .map((p) => inliner_unused(p.key).concat(inliner_unused(p.value)))
425
+ .flat(1);
398
426
  }
399
427
  return top
400
428
  ? null
@@ -402,30 +430,98 @@ function inliner_unused(expression, top) {
402
430
  {
403
431
  type: "ExpressionStatement",
404
432
  expression,
433
+ start: expression.start,
434
+ end: expression.end,
435
+ loc: expression.loc,
405
436
  },
406
437
  ];
407
438
  }
408
- function inlineWithArgs(state, func, call) {
439
+ function diagnostic(state, loc, message) {
440
+ if (!loc || !loc.source)
441
+ return;
442
+ const source = loc.source;
443
+ if (!state.diagnostics)
444
+ state.diagnostics = {};
445
+ if (!hasProperty(state.diagnostics, source)) {
446
+ if (!message)
447
+ return;
448
+ state.diagnostics[source] = [];
449
+ }
450
+ const diags = state.diagnostics[source];
451
+ let index = diags.findIndex((item) => item.loc === loc);
452
+ if (message) {
453
+ if (index < 0)
454
+ index = diags.length;
455
+ diags[index] = { type: "INFO", loc, message };
456
+ }
457
+ else if (index >= 0) {
458
+ diags.splice(index, 1);
459
+ }
460
+ }
461
+ function inlineDiagnostic(state, func, call, message) {
462
+ if (inlineRequested(state, func)) {
463
+ diagnostic(state, call.loc, message && `While inlining ${func.node.id.name}: ${message}`);
464
+ }
465
+ }
466
+ function inlineWithArgs(state, func, call, context) {
409
467
  if (!func.node || !func.node.body) {
410
468
  return null;
411
469
  }
412
470
  let retStmtCount = 0;
413
- traverseAst(func.node.body, (node) => {
414
- node.type === "ReturnStatement" && retStmtCount++;
415
- });
416
- if (retStmtCount > 1 ||
417
- (retStmtCount === 1 &&
418
- func.node.body.body.slice(-1)[0].type !== "ReturnStatement")) {
419
- return null;
471
+ if (context.type === "ReturnStatement") {
472
+ const last = func.node.body.body.slice(-1)[0];
473
+ if (!last || last.type !== "ReturnStatement") {
474
+ inlineDiagnostic(state, func, call, "Function didn't end with a return statement");
475
+ return null;
476
+ }
477
+ }
478
+ else {
479
+ traverseAst(func.node.body, (node) => {
480
+ node.type === "ReturnStatement" && retStmtCount++;
481
+ });
482
+ if (retStmtCount > 1) {
483
+ inlineDiagnostic(state, func, call, "Function had more than one return statement");
484
+ }
485
+ else if (context.type === "AssignmentExpression" && retStmtCount !== 1) {
486
+ inlineDiagnostic(state, func, call, "Function did not have a return statement");
487
+ return null;
488
+ }
489
+ if (retStmtCount === 1) {
490
+ const last = func.node.body.body.slice(-1)[0];
491
+ if (!last ||
492
+ last.type !== "ReturnStatement" ||
493
+ (context.type === "AssignmentExpression" && !last.argument)) {
494
+ inlineDiagnostic(state, func, call, "There was a return statement, but not at the end of the function");
495
+ return null;
496
+ }
497
+ }
420
498
  }
421
499
  const body = JSON.parse(JSON.stringify(func.node.body));
422
- processInlineBody(state, func, call, body, func.node.params.length ? false : true);
423
- if (retStmtCount) {
500
+ const safeArgs = getArgSafety(state, func, call.arguments, false);
501
+ const params = Object.fromEntries(func.node.params.map((param, i) => {
502
+ const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
503
+ ? i
504
+ : -1;
505
+ const name = variableDeclarationName(param);
506
+ return [name, argnum];
507
+ }));
508
+ if (!processInlineBody(state, func, call, body, func.node.params.length ? false : true, params)) {
509
+ return null;
510
+ }
511
+ diagnostic(state, call.loc, null);
512
+ if (context.type !== "ReturnStatement" && retStmtCount) {
424
513
  const last = body.body[body.body.length - 1];
425
514
  if (last.type != "ReturnStatement") {
426
515
  throw new Error("ReturnStatement got lost!");
427
516
  }
428
- if (last.argument) {
517
+ if (context.type === "AssignmentExpression") {
518
+ context.right = last.argument;
519
+ body.body[body.body.length - 1] = {
520
+ type: "ExpressionStatement",
521
+ expression: context,
522
+ };
523
+ }
524
+ else if (last.argument) {
429
525
  const side_exprs = inliner_unused(last.argument);
430
526
  body.body.splice(body.body.length - 1, 1, ...side_exprs);
431
527
  }
@@ -435,13 +531,13 @@ function inlineWithArgs(state, func, call) {
435
531
  }
436
532
  return body;
437
533
  }
438
- function inliner_inlineFunction(state, func, call, inlineStatus) {
439
- if (inlineStatus == inliner_InlineStatus.AsStatement) {
440
- return inlineWithArgs(state, func, call);
534
+ function inliner_inlineFunction(state, func, call, context) {
535
+ if (context) {
536
+ return inlineWithArgs(state, func, call, context);
441
537
  }
442
538
  const retArg = JSON.parse(JSON.stringify(func.node.body.body[0].argument));
443
539
  const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
444
- return processInlineBody(state, func, call, retArg, true, params) || retArg;
540
+ return processInlineBody(state, func, call, retArg, true, params);
445
541
  }
446
542
  function applyTypeIfNeeded(node) {
447
543
  if ("enumType" in node && node.enumType) {
@@ -456,13 +552,14 @@ function applyTypeIfNeeded(node) {
456
552
  }
457
553
  function fixNodeScope(state, lookupNode, nodeStack) {
458
554
  if (lookupNode.type === "Identifier") {
459
- for (let i = state.stack.length; --i > nodeStack.length;) {
460
- const si = state.stack[i];
461
- if (hasProperty(si.decls, lookupNode.name)) {
462
- // its a local from the inlined function.
463
- // Nothing to do.
464
- return lookupNode;
465
- }
555
+ const locals = state.localsStack[state.localsStack.length - 1];
556
+ const { map } = locals;
557
+ if (!map)
558
+ throw new Error("No local variable map!");
559
+ if (hasProperty(map, lookupNode.name) && map[lookupNode.name] !== false) {
560
+ // map[name] !== false means its an entry that was created during inlining
561
+ // so its definitely one of our locals.
562
+ return lookupNode;
466
563
  }
467
564
  }
468
565
  const [, original] = state.lookup(lookupNode, null, nodeStack);
@@ -938,13 +1035,15 @@ function optimizeNode(node) {
938
1035
  return null;
939
1036
  }
940
1037
  function evaluateFunction(func, args) {
941
- if (args && args.length != func.params.length) {
1038
+ if (!func.body || (args && args.length != func.params.length)) {
942
1039
  return false;
943
1040
  }
944
1041
  const paramValues = args &&
945
1042
  Object.fromEntries(func.params.map((p, i) => [variableDeclarationName(p), args[i]]));
946
1043
  let ret = null;
947
- const body = args ? JSON.parse(JSON.stringify(func.body)) : func.body;
1044
+ const body = args
1045
+ ? JSON.parse(JSON.stringify(func.body))
1046
+ : func.body;
948
1047
  try {
949
1048
  traverseAst(body, (node) => {
950
1049
  switch (node.type) {
@@ -1145,7 +1244,7 @@ async function optimizeMonkeyC(fnMap) {
1145
1244
  if (map) {
1146
1245
  if (hasProperty(map, node.name)) {
1147
1246
  const name = map[node.name];
1148
- if (name !== true) {
1247
+ if (typeof name === "string") {
1149
1248
  node.name = name;
1150
1249
  }
1151
1250
  }
@@ -1236,21 +1335,49 @@ async function optimizeMonkeyC(fnMap) {
1236
1335
  return node.body;
1237
1336
  }
1238
1337
  break;
1338
+ case "ReturnStatement":
1339
+ if (node.argument && node.argument.type === "CallExpression") {
1340
+ return optimizeCall(state, node.argument, node);
1341
+ }
1342
+ break;
1239
1343
  case "CallExpression": {
1240
- const ret = optimizeCall(state, node, false);
1344
+ const ret = optimizeCall(state, node, null);
1241
1345
  if (ret) {
1242
1346
  replace(node, ret);
1243
1347
  }
1244
1348
  break;
1245
1349
  }
1350
+ case "AssignmentExpression":
1351
+ if (node.operator === "=" &&
1352
+ node.left.type === "Identifier" &&
1353
+ node.right.type === "Identifier" &&
1354
+ node.left.name === node.right.name) {
1355
+ return { type: "Literal", value: null, raw: "null" };
1356
+ }
1357
+ break;
1246
1358
  case "ExpressionStatement":
1247
1359
  if (node.expression.type === "CallExpression") {
1248
- const ret = optimizeCall(state, node.expression, true);
1249
- if (ret) {
1250
- if (ret.type === "BlockStatement") {
1251
- return ret;
1360
+ return optimizeCall(state, node.expression, node);
1361
+ }
1362
+ else if (node.expression.type === "AssignmentExpression") {
1363
+ if (node.expression.right.type === "CallExpression") {
1364
+ let ok = false;
1365
+ if (node.expression.left.type === "Identifier") {
1366
+ if (hasProperty(topLocals().map, node.expression.left.type)) {
1367
+ ok = true;
1368
+ }
1369
+ }
1370
+ if (!ok && node.expression.operator == "=") {
1371
+ const [, result] = state.lookup(node.expression.left);
1372
+ ok = result != null;
1373
+ }
1374
+ if (ok) {
1375
+ const ret = optimizeCall(state, node.expression.right, node.expression);
1376
+ if (ret && ret.type === "BlockStatement") {
1377
+ const r2 = state.traverse(ret);
1378
+ return r2 === false || r2 ? r2 : ret;
1379
+ }
1252
1380
  }
1253
- node.expression = ret;
1254
1381
  }
1255
1382
  }
1256
1383
  else {
@@ -1341,8 +1468,9 @@ async function optimizeMonkeyC(fnMap) {
1341
1468
  return ret;
1342
1469
  });
1343
1470
  });
1471
+ return state.diagnostics;
1344
1472
  }
1345
- function optimizeCall(state, node, asStatement) {
1473
+ function optimizeCall(state, node, context) {
1346
1474
  const [name, callees] = state.lookup(node.callee);
1347
1475
  if (!callees || !callees.length) {
1348
1476
  const n = name ||
@@ -1362,7 +1490,8 @@ function optimizeCall(state, node, asStatement) {
1362
1490
  }
1363
1491
  if (callees.length == 1 && callees[0].type === "FunctionDeclaration") {
1364
1492
  const callee = callees[0].node;
1365
- if (callee.optimizable &&
1493
+ if (!context &&
1494
+ callee.optimizable &&
1366
1495
  !callee.hasOverride &&
1367
1496
  node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
1368
1497
  const ret = evaluateFunction(callee, node.arguments);
@@ -1370,10 +1499,8 @@ function optimizeCall(state, node, asStatement) {
1370
1499
  return ret;
1371
1500
  }
1372
1501
  }
1373
- const inlineStatus = shouldInline(state, callees[0], node.arguments);
1374
- if (inlineStatus === InlineStatus.AsExpression ||
1375
- (asStatement && inlineStatus === InlineStatus.AsStatement)) {
1376
- const ret = inlineFunction(state, callees[0], node, inlineStatus);
1502
+ if (shouldInline(state, callees[0], node, context)) {
1503
+ const ret = inlineFunction(state, callees[0], node, context);
1377
1504
  if (ret) {
1378
1505
  return ret;
1379
1506
  }
@@ -1382,7 +1509,7 @@ function optimizeCall(state, node, asStatement) {
1382
1509
  if (!hasProperty(state.calledFunctions, name)) {
1383
1510
  state.calledFunctions[name] = [];
1384
1511
  }
1385
- callees.forEach((c) => isStateNode(c) && state.calledFunctions[name].push(c.node));
1512
+ callees.forEach((c) => isStateNode(c) && pushUnique(state.calledFunctions[name], c.node));
1386
1513
  return null;
1387
1514
  }
1388
1515
 
@@ -1927,10 +2054,10 @@ function formatAst(node, monkeyCSource = null) {
1927
2054
  * The estree printer sometimes looks at the parent node without
1928
2055
  * checking that there *is* a parent node (eg it assumes all
1929
2056
  * BinaryExpressions have a parent node, and crashes if they don't).
1930
- * To avoid issues, wrap nodes in an ExpressionStatement, and
1931
- * then remove the added ";" from the end.
2057
+ * To avoid issues, wrap nodes in an ParenthesizedExpression.
2058
+ * The printer knows that parentless ParenthesizedExpressions
2059
+ * should be ignored.
1932
2060
  */
1933
- let unwrap = false;
1934
2061
  switch (node.type) {
1935
2062
  case "Program":
1936
2063
  case "BlockStatement":
@@ -1938,11 +2065,10 @@ function formatAst(node, monkeyCSource = null) {
1938
2065
  break;
1939
2066
  default: {
1940
2067
  const e = {
1941
- type: "ExpressionStatement",
2068
+ type: "ParenthesizedExpression",
1942
2069
  expression: node,
1943
2070
  };
1944
2071
  node = e;
1945
- unwrap = true;
1946
2072
  }
1947
2073
  }
1948
2074
  // If we *do* have the original source, pass that in ahead of the
@@ -1950,15 +2076,11 @@ function formatAst(node, monkeyCSource = null) {
1950
2076
  // as the ast itself, and the printers will find what they're
1951
2077
  // looking for in the source.
1952
2078
  const source = (monkeyCSource || "") + "\n" + JSON.stringify(node);
1953
- const result = external_prettier_namespaceObject.format(source, {
2079
+ return external_prettier_namespaceObject.format(source, {
1954
2080
  parser: "monkeyc-json",
1955
2081
  plugins: [(prettier_plugin_monkeyc_default())],
1956
2082
  endOfLine: "lf",
1957
2083
  });
1958
- if (unwrap) {
1959
- return result.replace(/;$/, "");
1960
- }
1961
- return result;
1962
2084
  }
1963
2085
  function handleException(state, node, exception) {
1964
2086
  try {
@@ -10983,9 +10983,24 @@ var InlineStatus;
10983
10983
  InlineStatus[InlineStatus["AsExpression"] = 1] = "AsExpression";
10984
10984
  InlineStatus[InlineStatus["AsStatement"] = 2] = "AsStatement";
10985
10985
  })(InlineStatus || (InlineStatus = {}));
10986
- function shouldInline(state, func, args) {
10986
+ function inlineRequested(state, func) {
10987
+ const excludeAnnotations = (func.node.loc?.source &&
10988
+ state.fnMap[func.node.loc?.source]?.excludeAnnotations) ||
10989
+ {};
10990
+ if (func.node.attrs &&
10991
+ func.node.attrs.attrs &&
10992
+ func.node.attrs.attrs.some((attr) => attr.type === "UnaryExpression" &&
10993
+ (attr.argument.name === "inline" ||
10994
+ (attr.argument.name.startsWith("inline_") &&
10995
+ (0,external_api_cjs_namespaceObject.hasProperty)(excludeAnnotations, attr.argument.name.substring(7)))))) {
10996
+ return true;
10997
+ }
10998
+ return false;
10999
+ }
11000
+ function shouldInline(state, func, call, context) {
10987
11001
  let autoInline = false;
10988
11002
  let inlineAsExpression = false;
11003
+ const args = call.arguments;
10989
11004
  if (func.node.body &&
10990
11005
  func.node.body.body.length === 1 &&
10991
11006
  func.node.body.body[0].type === "ReturnStatement" &&
@@ -10995,39 +11010,30 @@ function shouldInline(state, func, args) {
10995
11010
  autoInline = inliningLooksUseful(func.node, func.node.body.body[0].argument);
10996
11011
  }
10997
11012
  if (autoInline === 1) {
10998
- return InlineStatus.AsExpression;
11013
+ return true;
10999
11014
  }
11000
- const excludeAnnotations = (func.node.loc?.source &&
11001
- state.fnMap[func.node.loc?.source]?.excludeAnnotations) ||
11002
- {};
11003
- const inlineRequested = func.node.attrs &&
11004
- func.node.attrs.attrs &&
11005
- func.node.attrs.attrs.some((attr) => attr.type === "UnaryExpression" &&
11006
- (attr.argument.name === "inline" ||
11007
- (attr.argument.name.startsWith("inline_") &&
11008
- (0,external_api_cjs_namespaceObject.hasProperty)(excludeAnnotations, attr.argument.name.substring(7)))));
11009
- if (autoInline || inlineRequested) {
11010
- return inlineAsExpression && canInline(state, func, args)
11011
- ? InlineStatus.AsExpression
11012
- : InlineStatus.AsStatement;
11015
+ const requested = inlineRequested(state, func);
11016
+ if (autoInline || requested) {
11017
+ if (inlineAsExpression) {
11018
+ if (canInline(state, func, args)) {
11019
+ return true;
11020
+ }
11021
+ }
11022
+ if (!context && requested) {
11023
+ inlineDiagnostic(state, func, call, "This function can only be inlined in statement, assignment, or return contexts");
11024
+ }
11025
+ return context != null;
11013
11026
  }
11014
- return InlineStatus.Never;
11027
+ return false;
11015
11028
  }
11016
11029
  function processInlineBody(state, func, call, root, insertedVariableDecls, params) {
11017
- if (!params) {
11018
- const safeArgs = getArgSafety(state, func, call.arguments, false);
11019
- params = Object.fromEntries(func.node.params.map((param, i) => {
11020
- const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
11021
- ? i
11022
- : -1;
11023
- const name = (0,external_api_cjs_namespaceObject.variableDeclarationName)(param);
11024
- return [name, argnum];
11025
- }));
11026
- }
11030
+ let failed = false;
11027
11031
  const pre = state.pre;
11028
11032
  const post = state.post;
11029
11033
  try {
11030
11034
  state.pre = (node) => {
11035
+ if (failed)
11036
+ return [];
11031
11037
  node.start = call.start;
11032
11038
  node.end = call.end;
11033
11039
  node.loc = call.loc;
@@ -11039,6 +11045,11 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
11039
11045
  const { map } = locals;
11040
11046
  if (!map)
11041
11047
  throw new Error("No local variable map!");
11048
+ // We still need to keep track of every local name that was
11049
+ // already in use, but we don't want to use any of its renames.
11050
+ // We also want to know whether a local is from the function being
11051
+ // inlined, or the calling function, so set every element to false.
11052
+ Object.keys(map).forEach((key) => (map[key] = false));
11042
11053
  const declarations = func.node.params
11043
11054
  .map((param, i) => {
11044
11055
  const paramName = (0,external_api_cjs_namespaceObject.variableDeclarationName)(param);
@@ -11063,6 +11074,8 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
11063
11074
  return result;
11064
11075
  };
11065
11076
  state.post = (node) => {
11077
+ if (failed)
11078
+ return null;
11066
11079
  let replacement = null;
11067
11080
  switch (node.type) {
11068
11081
  case "Identifier": {
@@ -11077,22 +11090,28 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
11077
11090
  }
11078
11091
  replacement = fixNodeScope(state, node, func.stack);
11079
11092
  if (!replacement) {
11080
- throw new Error(`Inliner: Couldn't fix the scope of '${node.name}`);
11093
+ failed = true;
11094
+ inlineDiagnostic(state, func, call, `Failed to resolve '${node.name}'`);
11095
+ return null;
11081
11096
  }
11082
11097
  break;
11083
11098
  }
11084
11099
  }
11085
11100
  return post(replacement || node, state) || replacement;
11086
11101
  };
11087
- return state.traverse(root) || null;
11088
- }
11089
- catch (ex) {
11090
- if (ex instanceof Error) {
11091
- if (ex.message.startsWith("Inliner: ")) {
11092
- return null;
11093
- }
11102
+ let ret = state.traverse(root);
11103
+ if (failed) {
11104
+ return null;
11105
+ }
11106
+ if (ret === null) {
11107
+ ret = root;
11094
11108
  }
11095
- throw ex;
11109
+ if (!ret) {
11110
+ inlineDiagnostic(state, func, call, `Internal error`);
11111
+ return null;
11112
+ }
11113
+ inlineDiagnostic(state, func, call, null);
11114
+ return ret;
11096
11115
  }
11097
11116
  finally {
11098
11117
  state.pre = pre;
@@ -11115,7 +11134,16 @@ function unused(expression, top) {
11115
11134
  case "UnaryExpression":
11116
11135
  return unused(expression.argument);
11117
11136
  case "MemberExpression":
11118
- return unused(expression.object).concat(unused(expression.property));
11137
+ if (expression.computed) {
11138
+ return unused(expression.object).concat(unused(expression.property));
11139
+ }
11140
+ return unused(expression.object);
11141
+ case "ArrayExpression":
11142
+ return expression.elements.map((e) => unused(e)).flat(1);
11143
+ case "ObjectExpression":
11144
+ return expression.properties
11145
+ .map((p) => unused(p.key).concat(unused(p.value)))
11146
+ .flat(1);
11119
11147
  }
11120
11148
  return top
11121
11149
  ? null
@@ -11123,30 +11151,98 @@ function unused(expression, top) {
11123
11151
  {
11124
11152
  type: "ExpressionStatement",
11125
11153
  expression,
11154
+ start: expression.start,
11155
+ end: expression.end,
11156
+ loc: expression.loc,
11126
11157
  },
11127
11158
  ];
11128
11159
  }
11129
- function inlineWithArgs(state, func, call) {
11160
+ function diagnostic(state, loc, message) {
11161
+ if (!loc || !loc.source)
11162
+ return;
11163
+ const source = loc.source;
11164
+ if (!state.diagnostics)
11165
+ state.diagnostics = {};
11166
+ if (!(0,external_api_cjs_namespaceObject.hasProperty)(state.diagnostics, source)) {
11167
+ if (!message)
11168
+ return;
11169
+ state.diagnostics[source] = [];
11170
+ }
11171
+ const diags = state.diagnostics[source];
11172
+ let index = diags.findIndex((item) => item.loc === loc);
11173
+ if (message) {
11174
+ if (index < 0)
11175
+ index = diags.length;
11176
+ diags[index] = { type: "INFO", loc, message };
11177
+ }
11178
+ else if (index >= 0) {
11179
+ diags.splice(index, 1);
11180
+ }
11181
+ }
11182
+ function inlineDiagnostic(state, func, call, message) {
11183
+ if (inlineRequested(state, func)) {
11184
+ diagnostic(state, call.loc, message && `While inlining ${func.node.id.name}: ${message}`);
11185
+ }
11186
+ }
11187
+ function inlineWithArgs(state, func, call, context) {
11130
11188
  if (!func.node || !func.node.body) {
11131
11189
  return null;
11132
11190
  }
11133
11191
  let retStmtCount = 0;
11134
- (0,external_api_cjs_namespaceObject.traverseAst)(func.node.body, (node) => {
11135
- node.type === "ReturnStatement" && retStmtCount++;
11136
- });
11137
- if (retStmtCount > 1 ||
11138
- (retStmtCount === 1 &&
11139
- func.node.body.body.slice(-1)[0].type !== "ReturnStatement")) {
11140
- return null;
11192
+ if (context.type === "ReturnStatement") {
11193
+ const last = func.node.body.body.slice(-1)[0];
11194
+ if (!last || last.type !== "ReturnStatement") {
11195
+ inlineDiagnostic(state, func, call, "Function didn't end with a return statement");
11196
+ return null;
11197
+ }
11198
+ }
11199
+ else {
11200
+ (0,external_api_cjs_namespaceObject.traverseAst)(func.node.body, (node) => {
11201
+ node.type === "ReturnStatement" && retStmtCount++;
11202
+ });
11203
+ if (retStmtCount > 1) {
11204
+ inlineDiagnostic(state, func, call, "Function had more than one return statement");
11205
+ }
11206
+ else if (context.type === "AssignmentExpression" && retStmtCount !== 1) {
11207
+ inlineDiagnostic(state, func, call, "Function did not have a return statement");
11208
+ return null;
11209
+ }
11210
+ if (retStmtCount === 1) {
11211
+ const last = func.node.body.body.slice(-1)[0];
11212
+ if (!last ||
11213
+ last.type !== "ReturnStatement" ||
11214
+ (context.type === "AssignmentExpression" && !last.argument)) {
11215
+ inlineDiagnostic(state, func, call, "There was a return statement, but not at the end of the function");
11216
+ return null;
11217
+ }
11218
+ }
11141
11219
  }
11142
11220
  const body = JSON.parse(JSON.stringify(func.node.body));
11143
- processInlineBody(state, func, call, body, func.node.params.length ? false : true);
11144
- if (retStmtCount) {
11221
+ const safeArgs = getArgSafety(state, func, call.arguments, false);
11222
+ const params = Object.fromEntries(func.node.params.map((param, i) => {
11223
+ const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
11224
+ ? i
11225
+ : -1;
11226
+ const name = (0,external_api_cjs_namespaceObject.variableDeclarationName)(param);
11227
+ return [name, argnum];
11228
+ }));
11229
+ if (!processInlineBody(state, func, call, body, func.node.params.length ? false : true, params)) {
11230
+ return null;
11231
+ }
11232
+ diagnostic(state, call.loc, null);
11233
+ if (context.type !== "ReturnStatement" && retStmtCount) {
11145
11234
  const last = body.body[body.body.length - 1];
11146
11235
  if (last.type != "ReturnStatement") {
11147
11236
  throw new Error("ReturnStatement got lost!");
11148
11237
  }
11149
- if (last.argument) {
11238
+ if (context.type === "AssignmentExpression") {
11239
+ context.right = last.argument;
11240
+ body.body[body.body.length - 1] = {
11241
+ type: "ExpressionStatement",
11242
+ expression: context,
11243
+ };
11244
+ }
11245
+ else if (last.argument) {
11150
11246
  const side_exprs = unused(last.argument);
11151
11247
  body.body.splice(body.body.length - 1, 1, ...side_exprs);
11152
11248
  }
@@ -11156,13 +11252,13 @@ function inlineWithArgs(state, func, call) {
11156
11252
  }
11157
11253
  return body;
11158
11254
  }
11159
- function inlineFunction(state, func, call, inlineStatus) {
11160
- if (inlineStatus == InlineStatus.AsStatement) {
11161
- return inlineWithArgs(state, func, call);
11255
+ function inlineFunction(state, func, call, context) {
11256
+ if (context) {
11257
+ return inlineWithArgs(state, func, call, context);
11162
11258
  }
11163
11259
  const retArg = JSON.parse(JSON.stringify(func.node.body.body[0].argument));
11164
11260
  const params = Object.fromEntries(func.node.params.map((param, i) => [(0,external_api_cjs_namespaceObject.variableDeclarationName)(param), i]));
11165
- return processInlineBody(state, func, call, retArg, true, params) || retArg;
11261
+ return processInlineBody(state, func, call, retArg, true, params);
11166
11262
  }
11167
11263
  function applyTypeIfNeeded(node) {
11168
11264
  if ("enumType" in node && node.enumType) {
@@ -11177,13 +11273,14 @@ function applyTypeIfNeeded(node) {
11177
11273
  }
11178
11274
  function fixNodeScope(state, lookupNode, nodeStack) {
11179
11275
  if (lookupNode.type === "Identifier") {
11180
- for (let i = state.stack.length; --i > nodeStack.length;) {
11181
- const si = state.stack[i];
11182
- if ((0,external_api_cjs_namespaceObject.hasProperty)(si.decls, lookupNode.name)) {
11183
- // its a local from the inlined function.
11184
- // Nothing to do.
11185
- return lookupNode;
11186
- }
11276
+ const locals = state.localsStack[state.localsStack.length - 1];
11277
+ const { map } = locals;
11278
+ if (!map)
11279
+ throw new Error("No local variable map!");
11280
+ if ((0,external_api_cjs_namespaceObject.hasProperty)(map, lookupNode.name) && map[lookupNode.name] !== false) {
11281
+ // map[name] !== false means its an entry that was created during inlining
11282
+ // so its definitely one of our locals.
11283
+ return lookupNode;
11187
11284
  }
11188
11285
  }
11189
11286
  const [, original] = state.lookup(lookupNode, null, nodeStack);
@@ -11656,13 +11753,15 @@ function optimizeNode(node) {
11656
11753
  return null;
11657
11754
  }
11658
11755
  function evaluateFunction(func, args) {
11659
- if (args && args.length != func.params.length) {
11756
+ if (!func.body || (args && args.length != func.params.length)) {
11660
11757
  return false;
11661
11758
  }
11662
11759
  const paramValues = args &&
11663
11760
  Object.fromEntries(func.params.map((p, i) => [(0,external_api_cjs_namespaceObject.variableDeclarationName)(p), args[i]]));
11664
11761
  let ret = null;
11665
- const body = args ? JSON.parse(JSON.stringify(func.body)) : func.body;
11762
+ const body = args
11763
+ ? JSON.parse(JSON.stringify(func.body))
11764
+ : func.body;
11666
11765
  try {
11667
11766
  (0,external_api_cjs_namespaceObject.traverseAst)(body, (node) => {
11668
11767
  switch (node.type) {
@@ -11863,7 +11962,7 @@ async function optimizeMonkeyC(fnMap) {
11863
11962
  if (map) {
11864
11963
  if ((0,external_api_cjs_namespaceObject.hasProperty)(map, node.name)) {
11865
11964
  const name = map[node.name];
11866
- if (name !== true) {
11965
+ if (typeof name === "string") {
11867
11966
  node.name = name;
11868
11967
  }
11869
11968
  }
@@ -11954,21 +12053,49 @@ async function optimizeMonkeyC(fnMap) {
11954
12053
  return node.body;
11955
12054
  }
11956
12055
  break;
12056
+ case "ReturnStatement":
12057
+ if (node.argument && node.argument.type === "CallExpression") {
12058
+ return optimizeCall(state, node.argument, node);
12059
+ }
12060
+ break;
11957
12061
  case "CallExpression": {
11958
- const ret = optimizeCall(state, node, false);
12062
+ const ret = optimizeCall(state, node, null);
11959
12063
  if (ret) {
11960
12064
  replace(node, ret);
11961
12065
  }
11962
12066
  break;
11963
12067
  }
12068
+ case "AssignmentExpression":
12069
+ if (node.operator === "=" &&
12070
+ node.left.type === "Identifier" &&
12071
+ node.right.type === "Identifier" &&
12072
+ node.left.name === node.right.name) {
12073
+ return { type: "Literal", value: null, raw: "null" };
12074
+ }
12075
+ break;
11964
12076
  case "ExpressionStatement":
11965
12077
  if (node.expression.type === "CallExpression") {
11966
- const ret = optimizeCall(state, node.expression, true);
11967
- if (ret) {
11968
- if (ret.type === "BlockStatement") {
11969
- return ret;
12078
+ return optimizeCall(state, node.expression, node);
12079
+ }
12080
+ else if (node.expression.type === "AssignmentExpression") {
12081
+ if (node.expression.right.type === "CallExpression") {
12082
+ let ok = false;
12083
+ if (node.expression.left.type === "Identifier") {
12084
+ if ((0,external_api_cjs_namespaceObject.hasProperty)(topLocals().map, node.expression.left.type)) {
12085
+ ok = true;
12086
+ }
12087
+ }
12088
+ if (!ok && node.expression.operator == "=") {
12089
+ const [, result] = state.lookup(node.expression.left);
12090
+ ok = result != null;
12091
+ }
12092
+ if (ok) {
12093
+ const ret = optimizeCall(state, node.expression.right, node.expression);
12094
+ if (ret && ret.type === "BlockStatement") {
12095
+ const r2 = state.traverse(ret);
12096
+ return r2 === false || r2 ? r2 : ret;
12097
+ }
11970
12098
  }
11971
- node.expression = ret;
11972
12099
  }
11973
12100
  }
11974
12101
  else {
@@ -12059,8 +12186,9 @@ async function optimizeMonkeyC(fnMap) {
12059
12186
  return ret;
12060
12187
  });
12061
12188
  });
12189
+ return state.diagnostics;
12062
12190
  }
12063
- function optimizeCall(state, node, asStatement) {
12191
+ function optimizeCall(state, node, context) {
12064
12192
  const [name, callees] = state.lookup(node.callee);
12065
12193
  if (!callees || !callees.length) {
12066
12194
  const n = name ||
@@ -12080,7 +12208,8 @@ function optimizeCall(state, node, asStatement) {
12080
12208
  }
12081
12209
  if (callees.length == 1 && callees[0].type === "FunctionDeclaration") {
12082
12210
  const callee = callees[0].node;
12083
- if (callee.optimizable &&
12211
+ if (!context &&
12212
+ callee.optimizable &&
12084
12213
  !callee.hasOverride &&
12085
12214
  node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
12086
12215
  const ret = evaluateFunction(callee, node.arguments);
@@ -12088,10 +12217,8 @@ function optimizeCall(state, node, asStatement) {
12088
12217
  return ret;
12089
12218
  }
12090
12219
  }
12091
- const inlineStatus = shouldInline(state, callees[0], node.arguments);
12092
- if (inlineStatus === InlineStatus.AsExpression ||
12093
- (asStatement && inlineStatus === InlineStatus.AsStatement)) {
12094
- const ret = inlineFunction(state, callees[0], node, inlineStatus);
12220
+ if (shouldInline(state, callees[0], node, context)) {
12221
+ const ret = inlineFunction(state, callees[0], node, context);
12095
12222
  if (ret) {
12096
12223
  return ret;
12097
12224
  }
@@ -12100,7 +12227,7 @@ function optimizeCall(state, node, asStatement) {
12100
12227
  if (!(0,external_api_cjs_namespaceObject.hasProperty)(state.calledFunctions, name)) {
12101
12228
  state.calledFunctions[name] = [];
12102
12229
  }
12103
- callees.forEach((c) => (0,external_api_cjs_namespaceObject.isStateNode)(c) && state.calledFunctions[name].push(c.node));
12230
+ callees.forEach((c) => (0,external_api_cjs_namespaceObject.isStateNode)(c) && (0,external_util_cjs_namespaceObject.pushUnique)(state.calledFunctions[name], c.node));
12104
12231
  return null;
12105
12232
  }
12106
12233
 
@@ -12112,12 +12239,31 @@ function pragmaChecker(ast) {
12112
12239
  return;
12113
12240
  let index = -1;
12114
12241
  let comment;
12242
+ let matchers;
12115
12243
  const next = () => {
12116
12244
  while (++index < comments.length) {
12117
12245
  comment = comments[index];
12118
- if (comment.value.match(/^\s*@match\s+/)) {
12246
+ let match = comment.value.match(/^\s*@match\s+(.+)/);
12247
+ if (!match)
12248
+ continue;
12249
+ let str = match[1];
12250
+ matchers = [];
12251
+ while ((match = str.match(/^([/%&#@"])(.+?(?<!\\)(?:\\{2})*)\1(\s+|$)/))) {
12252
+ matchers.push({ quote: match[1], needle: match[2] });
12253
+ str = str.substring(match[0].length);
12254
+ if (!str.length)
12255
+ break;
12256
+ }
12257
+ if (!str.length)
12119
12258
  break;
12259
+ if (!matchers.length) {
12260
+ match = str.match(/^(\S+)\s+$/);
12261
+ if (match) {
12262
+ matchers.push({ quote: '"', needle: match[1] });
12263
+ break;
12264
+ }
12120
12265
  }
12266
+ throw new Error(`Build pragma '${comment.value}' is invalid. In ${comment.loc.source}:${comment.loc.start.line}`);
12121
12267
  }
12122
12268
  };
12123
12269
  next();
@@ -12125,29 +12271,23 @@ function pragmaChecker(ast) {
12125
12271
  if (index >= comments.length)
12126
12272
  return false;
12127
12273
  if (node.start && node.start >= (comment.end || Infinity)) {
12128
- let match = comment.value.match(/^\s*@match\s+([/%&#@"])(.+(?<!\\)(?:\\{2})*)\1\s+$/) || comment.value.match(/^\s*@match\s+(\S+)\s+$/);
12129
- if (!match) {
12130
- throw new Error(`Build pragma '${comment.value}' is invalid. In ${comment.loc.source}:${comment.loc.start.line}`);
12131
- }
12274
+ const { quote, needle } = matchers.shift();
12132
12275
  const haystack = (0,external_api_cjs_namespaceObject.formatAst)(node).replace(/[\r\n]/g, " ");
12133
12276
  let found = false;
12134
- let needle = match[1];
12135
- if (match.length == 2) {
12277
+ if (quote == '"') {
12136
12278
  found = haystack.includes(needle);
12137
12279
  }
12138
12280
  else {
12139
- if (needle == '"') {
12140
- found = haystack.includes((needle = match[2]));
12141
- }
12142
- else {
12143
- const re = new RegExp((needle = match[2]));
12144
- found = re.test(haystack);
12145
- }
12281
+ const re = new RegExp(needle);
12282
+ found = re.test(haystack);
12146
12283
  }
12147
12284
  if (!found) {
12148
- throw new Error(`Didn't find '${needle}' in ${comment.loc.source}:${comment.loc.start.line}`);
12285
+ throw new Error(`Didn't find '${needle}' at ${comment.loc.source}:${comment.loc.start.line}`);
12286
+ }
12287
+ if (!matchers.length) {
12288
+ next();
12149
12289
  }
12150
- next();
12290
+ return false;
12151
12291
  }
12152
12292
  return null;
12153
12293
  });
@@ -12236,7 +12376,7 @@ async function buildOptimizedProject(product, options) {
12236
12376
  config.releaseBuild = true;
12237
12377
  }
12238
12378
  }
12239
- const { jungleFiles, program, hasTests } = await generateOptimizedProject(config);
12379
+ const { jungleFiles, program, hasTests, diagnostics } = await generateOptimizedProject(config);
12240
12380
  config.jungleFiles = jungleFiles;
12241
12381
  let bin = config.buildDir || "bin";
12242
12382
  let name = `optimized-${program}.prg`;
@@ -12255,6 +12395,7 @@ async function buildOptimizedProject(product, options) {
12255
12395
  delete config.testBuild;
12256
12396
  return build_project(product, config).then((result) => ({
12257
12397
  hasTests,
12398
+ diagnostics,
12258
12399
  ...result,
12259
12400
  }));
12260
12401
  }
@@ -12393,6 +12534,7 @@ async function generateOptimizedProject(options) {
12393
12534
  (await checkManifest(xml, targets.map((t) => t.product)))) &&
12394
12535
  !dropBarrels;
12395
12536
  let hasTests = false;
12537
+ let diagnostics = {};
12396
12538
  const promises = Object.keys(buildConfigs)
12397
12539
  .sort()
12398
12540
  .map((key) => {
@@ -12410,7 +12552,13 @@ async function generateOptimizedProject(options) {
12410
12552
  e.products = products[key];
12411
12553
  throw e;
12412
12554
  })
12413
- .then((t) => t && (hasTests = true))
12555
+ .then((t) => {
12556
+ if (t.hasTests)
12557
+ hasTests = true;
12558
+ if (t.diagnostics) {
12559
+ diagnostics = { ...diagnostics, ...t.diagnostics };
12560
+ }
12561
+ })
12414
12562
  : promises_namespaceObject.rm(external_path_.resolve(workspace, outputPath), {
12415
12563
  recursive: true,
12416
12564
  force: true,
@@ -12481,6 +12629,7 @@ async function generateOptimizedProject(options) {
12481
12629
  xml,
12482
12630
  program: external_path_.basename(external_path_.dirname(manifest)),
12483
12631
  hasTests,
12632
+ diagnostics,
12484
12633
  };
12485
12634
  }
12486
12635
  async function fileInfoFromConfig(workspace, output, buildConfig, extraExcludes) {
@@ -12559,7 +12708,7 @@ async function generateOneConfig(buildConfig, dependencyFiles, config) {
12559
12708
  const actualOptimizedFiles = (await (0,external_util_cjs_namespaceObject.globa)(external_path_.join(output, "**", "*.mc"), { mark: true }))
12560
12709
  .filter((file) => !file.endsWith("/"))
12561
12710
  .sort();
12562
- const { hasTests, ...prevOptions } = JSON.parse(await promises_namespaceObject.readFile(external_path_.join(output, "build-info.json"), "utf-8")
12711
+ const { hasTests, diagnostics: prevDiagnostics, ...prevOptions } = JSON.parse(await promises_namespaceObject.readFile(external_path_.join(output, "build-info.json"), "utf-8")
12563
12712
  .catch(() => "{}"));
12564
12713
  // check that the set of files thats actually there is the same as the
12565
12714
  // set of files we're going to generate (in case eg a jungle file change
@@ -12577,13 +12726,13 @@ async function generateOneConfig(buildConfig, dependencyFiles, config) {
12577
12726
  // the oldest optimized file, we don't need to regenerate
12578
12727
  const source_time = await (0,external_util_cjs_namespaceObject.last_modified)(Object.keys(fnMap).concat(dependencyFiles));
12579
12728
  const opt_time = await (0,external_util_cjs_namespaceObject.first_modified)(Object.values(fnMap).map((v) => v.output));
12580
- if (source_time < opt_time && 1654389404778 < opt_time) {
12581
- return hasTests;
12729
+ if (source_time < opt_time && 1654554780017 < opt_time) {
12730
+ return { hasTests, diagnostics: prevDiagnostics };
12582
12731
  }
12583
12732
  }
12584
12733
  await promises_namespaceObject.rm(output, { recursive: true, force: true });
12585
12734
  await promises_namespaceObject.mkdir(output, { recursive: true });
12586
- await optimizeMonkeyC(fnMap);
12735
+ const diagnostics = await optimizeMonkeyC(fnMap);
12587
12736
  return Promise.all(Object.values(fnMap).map(async (info) => {
12588
12737
  const name = info.output;
12589
12738
  const dir = external_path_.dirname(name);
@@ -12598,9 +12747,10 @@ async function generateOneConfig(buildConfig, dependencyFiles, config) {
12598
12747
  const hasTests = results.some((v) => v);
12599
12748
  return promises_namespaceObject.writeFile(external_path_.join(output, "build-info.json"), JSON.stringify({
12600
12749
  hasTests,
12750
+ diagnostics,
12601
12751
  ...Object.fromEntries(configOptionsToCheck.map((option) => [option, config[option]])),
12602
12752
  }))
12603
- .then(() => hasTests);
12753
+ .then(() => ({ hasTests, diagnostics }));
12604
12754
  });
12605
12755
  }
12606
12756
  async function getProjectAnalysis(targets, analysis, options) {
@@ -4,10 +4,11 @@ export declare enum InlineStatus {
4
4
  AsExpression = 1,
5
5
  AsStatement = 2
6
6
  }
7
- export declare function shouldInline(state: ProgramStateAnalysis, func: FunctionStateNode, args: mctree.Node[]): InlineStatus;
7
+ export declare function shouldInline(state: ProgramStateAnalysis, func: FunctionStateNode, call: mctree.CallExpression, context: InlineContext | null): boolean;
8
8
  declare type InlineBody = mctree.BlockStatement | mctree.ExpressionStatement["expression"];
9
9
  export declare function unused(expression: mctree.ExpressionStatement["expression"]): mctree.ExpressionStatement[];
10
10
  export declare function unused(expression: mctree.ExpressionStatement["expression"], top: true): mctree.ExpressionStatement[] | null;
11
- export declare function inlineFunction(state: ProgramStateAnalysis, func: FunctionStateNode, call: mctree.CallExpression, inlineStatus: InlineStatus): InlineBody | null;
11
+ export declare type InlineContext = mctree.ReturnStatement | mctree.AssignmentExpression | mctree.ExpressionStatement;
12
+ export declare function inlineFunction(state: ProgramStateAnalysis, func: FunctionStateNode, call: mctree.CallExpression, context: InlineContext | null): InlineBody | null;
12
13
  export declare function applyTypeIfNeeded(node: mctree.Node): mctree.Node;
13
14
  export {};
@@ -4,4 +4,11 @@ export declare function getFileASTs(fnMap: FilesToOptimizeMap): Promise<boolean>
4
4
  export declare function analyze(fnMap: FilesToOptimizeMap): Promise<ProgramStateAnalysis>;
5
5
  export declare function getLiteralFromDecls(decls: StateNodeDecl[]): null;
6
6
  export declare function getLiteralNode(node: mctree.Node | null | undefined): null | mctree.Literal | mctree.AsExpression;
7
- export declare function optimizeMonkeyC(fnMap: FilesToOptimizeMap): Promise<void>;
7
+ export declare function optimizeMonkeyC(fnMap: FilesToOptimizeMap): Promise<Record<string, {
8
+ type: "ERROR" | "WARNING" | "INFO";
9
+ loc: {
10
+ start: mctree.Position;
11
+ end: mctree.Position;
12
+ };
13
+ message: string;
14
+ }[]> | undefined>;
@@ -126,7 +126,7 @@ declare global {
126
126
  localsStack?: {
127
127
  node?: mctree.Node;
128
128
  map?: {
129
- [key: string]: true | string;
129
+ [key: string]: boolean | string;
130
130
  };
131
131
  inners?: {
132
132
  [key: string]: true;
@@ -138,6 +138,14 @@ declare global {
138
138
  constants?: {
139
139
  [key: string]: mctree.Literal;
140
140
  };
141
+ diagnostics?: Record<string, {
142
+ type: "ERROR" | "WARNING" | "INFO";
143
+ loc: {
144
+ start: mctree.Position;
145
+ end: mctree.Position;
146
+ };
147
+ message: string;
148
+ }[]>;
141
149
  };
142
150
  type Finalized<T, Keys extends keyof T> = T & {
143
151
  [key in Keys]-?: NonNullable<T[key]>;
@@ -172,17 +180,34 @@ export declare function buildOptimizedProject(product: string | null, options: B
172
180
  program: string;
173
181
  product: string | null;
174
182
  hasTests: boolean | undefined;
183
+ diagnostics: Record<string, {
184
+ type: "ERROR" | "WARNING" | "INFO";
185
+ loc: {
186
+ start: mctree.Position;
187
+ end: mctree.Position;
188
+ };
189
+ message: string;
190
+ }[]> | undefined;
175
191
  }>;
176
192
  export declare function generateOptimizedProject(options: BuildConfig): Promise<{
177
193
  jungleFiles: string | undefined;
178
194
  program: string;
179
195
  xml?: undefined;
180
196
  hasTests?: undefined;
197
+ diagnostics?: undefined;
181
198
  } | {
182
199
  jungleFiles: string;
183
200
  xml: import("./manifest").ManifestXML;
184
201
  program: string;
185
202
  hasTests: boolean;
203
+ diagnostics: Record<string, {
204
+ type: "ERROR" | "WARNING" | "INFO";
205
+ loc: {
206
+ start: mctree.Position;
207
+ end: mctree.Position;
208
+ };
209
+ message: string;
210
+ }[]>;
186
211
  }>;
187
212
  export declare type PreAnalysis = {
188
213
  fnMap: FilesToOptimizeMap;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@markw65/monkeyc-optimizer",
3
3
  "type": "module",
4
- "version": "1.0.18",
4
+ "version": "1.0.19",
5
5
  "description": "Source to source optimizer for Garmin Monkey C code",
6
6
  "main": "build/optimizer.cjs",
7
7
  "types": "build/src/optimizer.d.ts",
@@ -34,7 +34,7 @@
34
34
  "author": "markw65",
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
- "@markw65/prettier-plugin-monkeyc": "^1.0.21"
37
+ "@markw65/prettier-plugin-monkeyc": "^1.0.22"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/glob": "^7.2.0",