@markw65/monkeyc-optimizer 1.0.17 → 1.0.20

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
@@ -193,3 +193,45 @@ More fixes found via open source projects.
193
193
 
194
194
  - Bug Fixes
195
195
  - Fix a bug affecting lookup of types, which could cause definitions, references and links to the api docs to be missed in the vscode extension
196
+
197
+ ### 1.0.18
198
+
199
+ - Bug Fixes
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)
223
+
224
+ ### 1.0.20
225
+
226
+ - Bug fixes
227
+
228
+ - Fix a bug marking unknown callees
229
+ - Fix a bug in test.js that didn't notice when tests failed, and fix a failing test
230
+
231
+ - Optimizer enhancements
232
+
233
+ - Re-run the main optimization step, to properly account for functions that are unused after optimization
234
+ - Call the optimizer on 'unused' nodes before returning them
235
+
236
+ - Code cleanup
237
+ - Called function cleanup
package/build/api.cjs CHANGED
@@ -181,7 +181,7 @@ function getArgSafety(state, func, args, requireAll) {
181
181
  })) {
182
182
  return false;
183
183
  }
184
- if (allSafe)
184
+ if (allSafe && requireAll)
185
185
  return true;
186
186
  let callSeen = false;
187
187
  let ok = true;
@@ -204,10 +204,16 @@ function getArgSafety(state, func, args, requireAll) {
204
204
  .map(([key]) => key);
205
205
  }, (node) => {
206
206
  switch (node.type) {
207
+ case "AssignmentExpression":
208
+ case "UpdateExpression": {
209
+ const v = node.type == "UpdateExpression" ? node.argument : node.left;
210
+ if (v.type === "Identifier" && hasProperty(params, v.name)) {
211
+ safeArgs[params[v.name]] = null;
212
+ }
213
+ }
214
+ // fall through
207
215
  case "CallExpression":
208
216
  case "NewExpression":
209
- case "AssignmentExpression":
210
- case "UpdateExpression":
211
217
  callSeen = true;
212
218
  break;
213
219
  case "Identifier":
@@ -250,15 +256,30 @@ function inliningLooksUseful(func, node) {
250
256
  }
251
257
  return false;
252
258
  }
253
- var inliner_InlineStatus;
259
+ var InlineStatus;
254
260
  (function (InlineStatus) {
255
261
  InlineStatus[InlineStatus["Never"] = 0] = "Never";
256
262
  InlineStatus[InlineStatus["AsExpression"] = 1] = "AsExpression";
257
263
  InlineStatus[InlineStatus["AsStatement"] = 2] = "AsStatement";
258
- })(inliner_InlineStatus || (inliner_InlineStatus = {}));
259
- 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) {
260
280
  let autoInline = false;
261
281
  let inlineAsExpression = false;
282
+ const args = call.arguments;
262
283
  if (func.node.body &&
263
284
  func.node.body.body.length === 1 &&
264
285
  func.node.body.body[0].type === "ReturnStatement" &&
@@ -268,39 +289,30 @@ function inliner_shouldInline(state, func, args) {
268
289
  autoInline = inliningLooksUseful(func.node, func.node.body.body[0].argument);
269
290
  }
270
291
  if (autoInline === 1) {
271
- return inliner_InlineStatus.AsExpression;
292
+ return true;
272
293
  }
273
- const excludeAnnotations = (func.node.loc?.source &&
274
- state.fnMap[func.node.loc?.source]?.excludeAnnotations) ||
275
- {};
276
- const inlineRequested = func.node.attrs &&
277
- func.node.attrs.attrs &&
278
- func.node.attrs.attrs.some((attr) => attr.type === "UnaryExpression" &&
279
- (attr.argument.name === "inline" ||
280
- (attr.argument.name.startsWith("inline_") &&
281
- hasProperty(excludeAnnotations, attr.argument.name.substring(7)))));
282
- if (autoInline || inlineRequested) {
283
- return inlineAsExpression && canInline(state, func, args)
284
- ? inliner_InlineStatus.AsExpression
285
- : 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;
286
305
  }
287
- return inliner_InlineStatus.Never;
306
+ return false;
288
307
  }
289
308
  function processInlineBody(state, func, call, root, insertedVariableDecls, params) {
290
- if (!params) {
291
- const safeArgs = getArgSafety(state, func, call.arguments, false);
292
- params = Object.fromEntries(func.node.params.map((param, i) => {
293
- const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
294
- ? i
295
- : -1;
296
- const name = variableDeclarationName(param);
297
- return [name, argnum];
298
- }));
299
- }
309
+ let failed = false;
300
310
  const pre = state.pre;
301
311
  const post = state.post;
302
312
  try {
303
313
  state.pre = (node) => {
314
+ if (failed)
315
+ return [];
304
316
  node.start = call.start;
305
317
  node.end = call.end;
306
318
  node.loc = call.loc;
@@ -312,6 +324,11 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
312
324
  const { map } = locals;
313
325
  if (!map)
314
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));
315
332
  const declarations = func.node.params
316
333
  .map((param, i) => {
317
334
  const paramName = variableDeclarationName(param);
@@ -336,6 +353,8 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
336
353
  return result;
337
354
  };
338
355
  state.post = (node) => {
356
+ if (failed)
357
+ return null;
339
358
  let replacement = null;
340
359
  switch (node.type) {
341
360
  case "Identifier": {
@@ -350,22 +369,28 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
350
369
  }
351
370
  replacement = fixNodeScope(state, node, func.stack);
352
371
  if (!replacement) {
353
- 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;
354
375
  }
355
376
  break;
356
377
  }
357
378
  }
358
379
  return post(replacement || node, state) || replacement;
359
380
  };
360
- return state.traverse(root) || null;
361
- }
362
- catch (ex) {
363
- if (ex instanceof Error) {
364
- if (ex.message.startsWith("Inliner: ")) {
365
- return null;
366
- }
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;
367
391
  }
368
- throw ex;
392
+ inlineDiagnostic(state, func, call, null);
393
+ return ret;
369
394
  }
370
395
  finally {
371
396
  state.pre = pre;
@@ -388,7 +413,16 @@ function inliner_unused(expression, top) {
388
413
  case "UnaryExpression":
389
414
  return inliner_unused(expression.argument);
390
415
  case "MemberExpression":
391
- 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);
392
426
  }
393
427
  return top
394
428
  ? null
@@ -396,30 +430,98 @@ function inliner_unused(expression, top) {
396
430
  {
397
431
  type: "ExpressionStatement",
398
432
  expression,
433
+ start: expression.start,
434
+ end: expression.end,
435
+ loc: expression.loc,
399
436
  },
400
437
  ];
401
438
  }
402
- 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) {
403
467
  if (!func.node || !func.node.body) {
404
468
  return null;
405
469
  }
406
470
  let retStmtCount = 0;
407
- traverseAst(func.node.body, (node) => {
408
- node.type === "ReturnStatement" && retStmtCount++;
409
- });
410
- if (retStmtCount > 1 ||
411
- (retStmtCount === 1 &&
412
- func.node.body.body.slice(-1)[0].type !== "ReturnStatement")) {
413
- 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
+ }
414
498
  }
415
499
  const body = JSON.parse(JSON.stringify(func.node.body));
416
- processInlineBody(state, func, call, body, func.node.params.length ? false : true);
417
- 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) {
418
513
  const last = body.body[body.body.length - 1];
419
514
  if (last.type != "ReturnStatement") {
420
515
  throw new Error("ReturnStatement got lost!");
421
516
  }
422
- 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) {
423
525
  const side_exprs = inliner_unused(last.argument);
424
526
  body.body.splice(body.body.length - 1, 1, ...side_exprs);
425
527
  }
@@ -429,13 +531,13 @@ function inlineWithArgs(state, func, call) {
429
531
  }
430
532
  return body;
431
533
  }
432
- function inliner_inlineFunction(state, func, call, inlineStatus) {
433
- if (inlineStatus == inliner_InlineStatus.AsStatement) {
434
- return inlineWithArgs(state, func, call);
534
+ function inliner_inlineFunction(state, func, call, context) {
535
+ if (context) {
536
+ return inlineWithArgs(state, func, call, context);
435
537
  }
436
538
  const retArg = JSON.parse(JSON.stringify(func.node.body.body[0].argument));
437
539
  const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
438
- return processInlineBody(state, func, call, retArg, true, params) || retArg;
540
+ return processInlineBody(state, func, call, retArg, true, params);
439
541
  }
440
542
  function applyTypeIfNeeded(node) {
441
543
  if ("enumType" in node && node.enumType) {
@@ -450,13 +552,14 @@ function applyTypeIfNeeded(node) {
450
552
  }
451
553
  function fixNodeScope(state, lookupNode, nodeStack) {
452
554
  if (lookupNode.type === "Identifier") {
453
- for (let i = state.stack.length; --i > nodeStack.length;) {
454
- const si = state.stack[i];
455
- if (hasProperty(si.decls, lookupNode.name)) {
456
- // its a local from the inlined function.
457
- // Nothing to do.
458
- return lookupNode;
459
- }
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;
460
563
  }
461
564
  }
462
565
  const [, original] = state.lookup(lookupNode, null, nodeStack);
@@ -932,13 +1035,15 @@ function optimizeNode(node) {
932
1035
  return null;
933
1036
  }
934
1037
  function evaluateFunction(func, args) {
935
- if (args && args.length != func.params.length) {
1038
+ if (!func.body || (args && args.length != func.params.length)) {
936
1039
  return false;
937
1040
  }
938
1041
  const paramValues = args &&
939
1042
  Object.fromEntries(func.params.map((p, i) => [variableDeclarationName(p), args[i]]));
940
1043
  let ret = null;
941
- 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;
942
1047
  try {
943
1048
  traverseAst(body, (node) => {
944
1049
  switch (node.type) {
@@ -981,6 +1086,13 @@ function evaluateFunction(func, args) {
981
1086
  return false;
982
1087
  }
983
1088
  }
1089
+ function markFunctionCalled(state, func) {
1090
+ if (!hasProperty(state.calledFunctions, func.id.name)) {
1091
+ state.calledFunctions[func.id.name] = [func];
1092
+ return;
1093
+ }
1094
+ pushUnique(state.calledFunctions[func.id.name], func);
1095
+ }
984
1096
  async function optimizeMonkeyC(fnMap) {
985
1097
  const state = {
986
1098
  ...(await analyze(fnMap)),
@@ -1139,7 +1251,7 @@ async function optimizeMonkeyC(fnMap) {
1139
1251
  if (map) {
1140
1252
  if (hasProperty(map, node.name)) {
1141
1253
  const name = map[node.name];
1142
- if (name !== true) {
1254
+ if (typeof name === "string") {
1143
1255
  node.name = name;
1144
1256
  }
1145
1257
  }
@@ -1190,10 +1302,7 @@ async function optimizeMonkeyC(fnMap) {
1190
1302
  used = checkInherited(parent, node.id.name);
1191
1303
  }
1192
1304
  if (used) {
1193
- if (!hasProperty(state.calledFunctions, node.id.name)) {
1194
- state.calledFunctions[node.id.name] = [];
1195
- }
1196
- state.calledFunctions[node.id.name].push(node);
1305
+ markFunctionCalled(state, node);
1197
1306
  }
1198
1307
  }
1199
1308
  }
@@ -1230,27 +1339,61 @@ async function optimizeMonkeyC(fnMap) {
1230
1339
  return node.body;
1231
1340
  }
1232
1341
  break;
1342
+ case "ReturnStatement":
1343
+ if (node.argument && node.argument.type === "CallExpression") {
1344
+ return optimizeCall(state, node.argument, node);
1345
+ }
1346
+ break;
1233
1347
  case "CallExpression": {
1234
- const ret = optimizeCall(state, node, false);
1348
+ const ret = optimizeCall(state, node, null);
1235
1349
  if (ret) {
1236
1350
  replace(node, ret);
1237
1351
  }
1238
1352
  break;
1239
1353
  }
1354
+ case "AssignmentExpression":
1355
+ if (node.operator === "=" &&
1356
+ node.left.type === "Identifier" &&
1357
+ node.right.type === "Identifier" &&
1358
+ node.left.name === node.right.name) {
1359
+ return { type: "Literal", value: null, raw: "null" };
1360
+ }
1361
+ break;
1240
1362
  case "ExpressionStatement":
1241
1363
  if (node.expression.type === "CallExpression") {
1242
- const ret = optimizeCall(state, node.expression, true);
1243
- if (ret) {
1244
- if (ret.type === "BlockStatement") {
1245
- return ret;
1364
+ return optimizeCall(state, node.expression, node);
1365
+ }
1366
+ else if (node.expression.type === "AssignmentExpression") {
1367
+ if (node.expression.right.type === "CallExpression") {
1368
+ let ok = false;
1369
+ if (node.expression.left.type === "Identifier") {
1370
+ if (hasProperty(topLocals().map, node.expression.left.type)) {
1371
+ ok = true;
1372
+ }
1373
+ }
1374
+ if (!ok && node.expression.operator == "=") {
1375
+ const [, result] = state.lookup(node.expression.left);
1376
+ ok = result != null;
1377
+ }
1378
+ if (ok) {
1379
+ const ret = optimizeCall(state, node.expression.right, node.expression);
1380
+ if (ret && ret.type === "BlockStatement") {
1381
+ const r2 = state.traverse(ret);
1382
+ return r2 === false || r2 ? r2 : ret;
1383
+ }
1246
1384
  }
1247
- node.expression = ret;
1248
1385
  }
1249
1386
  }
1250
1387
  else {
1251
1388
  const ret = unused(node.expression, true);
1252
1389
  if (ret) {
1253
- return ret;
1390
+ return ret
1391
+ .map((s) => {
1392
+ const r2 = state.traverse(s);
1393
+ return r2 === false || r2 ? r2 : s;
1394
+ })
1395
+ .flat(1)
1396
+ .filter((s) => s !== false);
1254
1397
  }
1255
1398
  }
1256
1399
  break;
@@ -1260,6 +1403,11 @@ async function optimizeMonkeyC(fnMap) {
1260
1403
  Object.values(fnMap).forEach((f) => {
1261
1404
  collectNamespaces(f.ast, state);
1262
1405
  });
1406
+ state.calledFunctions = {};
1407
+ state.exposed = {};
1408
+ Object.values(fnMap).forEach((f) => {
1409
+ collectNamespaces(f.ast, state);
1410
+ });
1263
1411
  delete state.pre;
1264
1412
  delete state.post;
1265
1413
  const cleanup = (node) => {
@@ -1335,9 +1483,12 @@ async function optimizeMonkeyC(fnMap) {
1335
1483
  return ret;
1336
1484
  });
1337
1485
  });
1486
+ return state.diagnostics;
1338
1487
  }
1339
- function optimizeCall(state, node, asStatement) {
1340
- const [name, callees] = state.lookup(node.callee);
1488
+ function optimizeCall(state, node, context) {
1489
+ const [name, results] = state.lookup(node.callee);
1490
+ const callees = results &&
1491
+ results.filter((c) => c.type === "FunctionDeclaration");
1341
1492
  if (!callees || !callees.length) {
1342
1493
  const n = name ||
1343
1494
  ("name" in node.callee && node.callee.name) ||
@@ -1356,7 +1507,8 @@ function optimizeCall(state, node, asStatement) {
1356
1507
  }
1357
1508
  if (callees.length == 1 && callees[0].type === "FunctionDeclaration") {
1358
1509
  const callee = callees[0].node;
1359
- if (callee.optimizable &&
1510
+ if (!context &&
1511
+ callee.optimizable &&
1360
1512
  !callee.hasOverride &&
1361
1513
  node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
1362
1514
  const ret = evaluateFunction(callee, node.arguments);
@@ -1364,19 +1516,14 @@ function optimizeCall(state, node, asStatement) {
1364
1516
  return ret;
1365
1517
  }
1366
1518
  }
1367
- const inlineStatus = shouldInline(state, callees[0], node.arguments);
1368
- if (inlineStatus === InlineStatus.AsExpression ||
1369
- (asStatement && inlineStatus === InlineStatus.AsStatement)) {
1370
- const ret = inlineFunction(state, callees[0], node, inlineStatus);
1519
+ if (shouldInline(state, callees[0], node, context)) {
1520
+ const ret = inlineFunction(state, callees[0], node, context);
1371
1521
  if (ret) {
1372
1522
  return ret;
1373
1523
  }
1374
1524
  }
1375
1525
  }
1376
- if (!hasProperty(state.calledFunctions, name)) {
1377
- state.calledFunctions[name] = [];
1378
- }
1379
- callees.forEach((c) => isStateNode(c) && state.calledFunctions[name].push(c.node));
1526
+ callees.forEach((c) => markFunctionCalled(state, c.node));
1380
1527
  return null;
1381
1528
  }
1382
1529
 
@@ -1921,10 +2068,10 @@ function formatAst(node, monkeyCSource = null) {
1921
2068
  * The estree printer sometimes looks at the parent node without
1922
2069
  * checking that there *is* a parent node (eg it assumes all
1923
2070
  * BinaryExpressions have a parent node, and crashes if they don't).
1924
- * To avoid issues, wrap nodes in an ExpressionStatement, and
1925
- * then remove the added ";" from the end.
2071
+ * To avoid issues, wrap nodes in an ParenthesizedExpression.
2072
+ * The printer knows that parentless ParenthesizedExpressions
2073
+ * should be ignored.
1926
2074
  */
1927
- let unwrap = false;
1928
2075
  switch (node.type) {
1929
2076
  case "Program":
1930
2077
  case "BlockStatement":
@@ -1932,11 +2079,10 @@ function formatAst(node, monkeyCSource = null) {
1932
2079
  break;
1933
2080
  default: {
1934
2081
  const e = {
1935
- type: "ExpressionStatement",
2082
+ type: "ParenthesizedExpression",
1936
2083
  expression: node,
1937
2084
  };
1938
2085
  node = e;
1939
- unwrap = true;
1940
2086
  }
1941
2087
  }
1942
2088
  // If we *do* have the original source, pass that in ahead of the
@@ -1944,15 +2090,11 @@ function formatAst(node, monkeyCSource = null) {
1944
2090
  // as the ast itself, and the printers will find what they're
1945
2091
  // looking for in the source.
1946
2092
  const source = (monkeyCSource || "") + "\n" + JSON.stringify(node);
1947
- const result = external_prettier_namespaceObject.format(source, {
2093
+ return external_prettier_namespaceObject.format(source, {
1948
2094
  parser: "monkeyc-json",
1949
2095
  plugins: [(prettier_plugin_monkeyc_default())],
1950
2096
  endOfLine: "lf",
1951
2097
  });
1952
- if (unwrap) {
1953
- return result.replace(/;$/, "");
1954
- }
1955
- return result;
1956
2098
  }
1957
2099
  function handleException(state, node, exception) {
1958
2100
  try {