@markw65/monkeyc-optimizer 1.0.21 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/api.cjs CHANGED
@@ -1,4 +1,4 @@
1
- 0 && (module.exports = {collectNamespaces,formatAst,getApiMapping,hasProperty,isStateNode,traverseAst,variableDeclarationName});
1
+ 0 && (module.exports = {collectNamespaces,findUsingForNode,formatAst,getApiMapping,hasProperty,isStateNode,sameLookupResult,traverseAst,variableDeclarationName,visitReferences});
2
2
  /******/ (() => { // webpackBootstrap
3
3
  /******/ "use strict";
4
4
  /******/ // The require scope
@@ -53,12 +53,15 @@ __webpack_require__.r(__webpack_exports__);
53
53
  // EXPORTS
54
54
  __webpack_require__.d(__webpack_exports__, {
55
55
  "collectNamespaces": () => (/* binding */ api_collectNamespaces),
56
- "formatAst": () => (/* binding */ formatAst),
56
+ "findUsingForNode": () => (/* binding */ findUsingForNode),
57
+ "formatAst": () => (/* binding */ api_formatAst),
57
58
  "getApiMapping": () => (/* binding */ api_getApiMapping),
58
59
  "hasProperty": () => (/* binding */ api_hasProperty),
59
60
  "isStateNode": () => (/* binding */ api_isStateNode),
61
+ "sameLookupResult": () => (/* binding */ api_sameLookupResult),
60
62
  "traverseAst": () => (/* binding */ api_traverseAst),
61
- "variableDeclarationName": () => (/* binding */ api_variableDeclarationName)
63
+ "variableDeclarationName": () => (/* binding */ api_variableDeclarationName),
64
+ "visitReferences": () => (/* reexport */ visitor_visitReferences)
62
65
  });
63
66
 
64
67
  ;// CONCATENATED MODULE: external "@markw65/prettier-plugin-monkeyc"
@@ -169,11 +172,13 @@ function getArgSafety(state, func, args, requireAll) {
169
172
  case "Identifier":
170
173
  case "MemberExpression": {
171
174
  const [, results] = state.lookup(arg);
172
- if (!results || results.length !== 1) {
175
+ if (!results ||
176
+ results.length !== 1 ||
177
+ results[0].results.length !== 1) {
173
178
  safeArgs.push(null);
174
179
  return !requireAll;
175
180
  }
176
- const safety = getSafety(results[0]);
181
+ const safety = getSafety(results[0].results[0]);
177
182
  safeArgs.push(safety);
178
183
  if (!safety) {
179
184
  allSafe = false;
@@ -265,12 +270,6 @@ function inliningLooksUseful(func, node) {
265
270
  }
266
271
  return false;
267
272
  }
268
- var InlineStatus;
269
- (function (InlineStatus) {
270
- InlineStatus[InlineStatus["Never"] = 0] = "Never";
271
- InlineStatus[InlineStatus["AsExpression"] = 1] = "AsExpression";
272
- InlineStatus[InlineStatus["AsStatement"] = 2] = "AsStatement";
273
- })(InlineStatus || (InlineStatus = {}));
274
273
  function inlineRequested(state, func) {
275
274
  const excludeAnnotations = (func.node.loc?.source &&
276
275
  state.fnMap[func.node.loc?.source]?.excludeAnnotations) ||
@@ -280,7 +279,7 @@ function inlineRequested(state, func) {
280
279
  func.node.attrs.attributes.elements.some((attr) => attr.type === "UnaryExpression" &&
281
280
  (attr.argument.name === "inline" ||
282
281
  (attr.argument.name.startsWith("inline_") &&
283
- hasProperty(excludeAnnotations, attr.argument.name.substring(7)))))) {
282
+ !hasProperty(excludeAnnotations, attr.argument.name.substring(7)))))) {
284
283
  return true;
285
284
  }
286
285
  return false;
@@ -450,13 +449,13 @@ function inliner_unused(expression, top) {
450
449
  },
451
450
  ];
452
451
  }
453
- function diagnostic(state, loc, message) {
452
+ function inliner_diagnostic(state, loc, message, type = "INFO") {
454
453
  if (!loc || !loc.source)
455
454
  return;
456
455
  const source = loc.source;
457
456
  if (!state.diagnostics)
458
457
  state.diagnostics = {};
459
- if (!hasProperty(state.diagnostics, source)) {
458
+ if (!(0,external_api_cjs_namespaceObject.hasProperty)(state.diagnostics, source)) {
460
459
  if (!message)
461
460
  return;
462
461
  state.diagnostics[source] = [];
@@ -466,7 +465,7 @@ function diagnostic(state, loc, message) {
466
465
  if (message) {
467
466
  if (index < 0)
468
467
  index = diags.length;
469
- diags[index] = { type: "INFO", loc, message };
468
+ diags[index] = { type, loc, message };
470
469
  }
471
470
  else if (index >= 0) {
472
471
  diags.splice(index, 1);
@@ -474,7 +473,7 @@ function diagnostic(state, loc, message) {
474
473
  }
475
474
  function inlineDiagnostic(state, func, call, message) {
476
475
  if (inlineRequested(state, func)) {
477
- diagnostic(state, call.loc, message && `While inlining ${func.node.id.name}: ${message}`);
476
+ inliner_diagnostic(state, call.loc, message && `While inlining ${func.node.id.name}: ${message}`);
478
477
  }
479
478
  }
480
479
  function inlineWithArgs(state, func, call, context) {
@@ -522,7 +521,7 @@ function inlineWithArgs(state, func, call, context) {
522
521
  if (!processInlineBody(state, func, call, body, func.node.params.length ? false : true, params)) {
523
522
  return null;
524
523
  }
525
- diagnostic(state, call.loc, null);
524
+ inliner_diagnostic(state, call.loc, null);
526
525
  if (context.type !== "ReturnStatement" && retStmtCount) {
527
526
  const last = body.body[body.body.length - 1];
528
527
  if (last.type != "ReturnStatement") {
@@ -585,23 +584,25 @@ function fixNodeScope(state, lookupNode, nodeStack) {
585
584
  // With a bit more work, we could find the guaranteed shortest
586
585
  // reference, and then use this to optimize *all* symbols, not
587
586
  // just fix inlined ones.
588
- if (current &&
589
- current.length === original.length &&
590
- current.every((item, index) => item == original[index])) {
587
+ if (current && sameLookupResult(original, current)) {
591
588
  return lookupNode;
592
589
  }
593
590
  const node = lookupNode.type === "Identifier"
594
591
  ? lookupNode
595
592
  : lookupNode.property;
596
- if (original.length === 1 && original[0].type === "EnumStringMember") {
597
- return applyTypeIfNeeded(original[0].init);
593
+ if (original.length === 1 &&
594
+ original[0].results.length === 1 &&
595
+ original[0].results[0].type === "EnumStringMember") {
596
+ return applyTypeIfNeeded(original[0].results[0].init);
598
597
  }
599
- const prefixes = original.map((sn) => {
598
+ const prefixes = original
599
+ .map((lookupDef) => lookupDef.results.map((sn) => {
600
600
  if (isStateNode(sn) && sn.fullName) {
601
601
  return sn.fullName;
602
602
  }
603
603
  return "";
604
- });
604
+ }))
605
+ .flat();
605
606
  if (prefixes.length &&
606
607
  prefixes[0].startsWith("$.") &&
607
608
  prefixes.every((prefix, i) => !i || prefix === prefixes[i - 1])) {
@@ -611,9 +612,7 @@ function fixNodeScope(state, lookupNode, nodeStack) {
611
612
  if (found)
612
613
  return current;
613
614
  const [, results] = state.lookup(current);
614
- if (results &&
615
- results.length === original.length &&
616
- results.every((result, i) => result === original[i])) {
615
+ if (results && sameLookupResult(original, results)) {
617
616
  found = true;
618
617
  return current;
619
618
  }
@@ -655,6 +654,85 @@ function fixNodeScope(state, lookupNode, nodeStack) {
655
654
 
656
655
  ;// CONCATENATED MODULE: external "./util.cjs"
657
656
  const external_util_cjs_namespaceObject = require("./util.cjs");
657
+ ;// CONCATENATED MODULE: ./src/visitor.ts
658
+
659
+ function visitor_visitReferences(state, ast, name, defn, callback) {
660
+ const checkResults = ([name, results], node) => {
661
+ if (name && results) {
662
+ if (!defn || (0,external_api_cjs_namespaceObject.sameLookupResult)(results, defn)) {
663
+ if (callback(node, results, false) === false) {
664
+ return [];
665
+ }
666
+ }
667
+ }
668
+ else if (defn === false) {
669
+ if (callback(node, [], results === null) === false) {
670
+ return [];
671
+ }
672
+ }
673
+ return null;
674
+ };
675
+ state.pre = (node) => {
676
+ switch (node.type) {
677
+ case "AttributeList":
678
+ return [];
679
+ case "UnaryExpression":
680
+ // a bare symbol isn't a reference
681
+ if (node.operator === ":")
682
+ return [];
683
+ break;
684
+ case "BinaryExpression":
685
+ /*
686
+ * `expr has :symbol` can be treated as a reference
687
+ * to expr.symbol.
688
+ */
689
+ if (node.operator === "has") {
690
+ if (node.right.type === "UnaryExpression" &&
691
+ node.right.operator === ":") {
692
+ if (!name || node.right.argument.name === name) {
693
+ return checkResults(state.lookup({
694
+ type: "MemberExpression",
695
+ object: node.left,
696
+ property: node.right.argument,
697
+ computed: false,
698
+ }), node.right.argument);
699
+ }
700
+ }
701
+ }
702
+ break;
703
+ case "CallExpression":
704
+ // A call expression whose callee is an identifier is looked
705
+ // up as a non-local. ie even if there's a same named local,
706
+ // it will be ignored, and the lookup will start as if the
707
+ // call had been written self.foo() rather than foo().
708
+ if (node.callee.type === "Identifier") {
709
+ if (!name || node.callee.name === name) {
710
+ /* ignore return value */
711
+ checkResults(state.lookupNonlocal(node.callee), node.callee);
712
+ }
713
+ return ["arguments"];
714
+ }
715
+ break;
716
+ case "Identifier":
717
+ if (!name || node.name === name) {
718
+ return checkResults(state.lookup(node), node);
719
+ }
720
+ break;
721
+ case "MemberExpression":
722
+ if (!node.computed && node.property.type === "Identifier") {
723
+ if (!name || node.property.name === name) {
724
+ return checkResults(state.lookup(node), node) || ["object"];
725
+ }
726
+ return ["object"];
727
+ }
728
+ break;
729
+ }
730
+ return null;
731
+ };
732
+ (0,external_api_cjs_namespaceObject.collectNamespaces)(ast, state);
733
+ delete state.pre;
734
+ }
735
+
658
736
  ;// CONCATENATED MODULE: ./src/mc-rewrite.ts
659
737
 
660
738
 
@@ -662,43 +740,24 @@ const external_util_cjs_namespaceObject = require("./util.cjs");
662
740
 
663
741
 
664
742
 
665
- function processImports(allImports, lookup) {
666
- allImports.forEach(({ node, stack }) => {
667
- const [name, module] = lookup(node.id, ("as" in node && node.as && node.as.name) || null, stack);
668
- if (name && module) {
669
- const [parent] = stack.slice(-1);
670
- if (!parent.decls)
671
- parent.decls = {};
672
- const decls = parent.decls;
673
- if (!hasProperty(decls, name))
674
- decls[name] = [];
675
- module.forEach((m) => {
676
- if (isStateNode(m) && m.type == "ModuleDeclaration") {
677
- pushUnique(decls[name], m);
678
- if (!parent.type_decls)
679
- parent.type_decls = {};
680
- const tdecls = parent.type_decls;
681
- if (!hasProperty(tdecls, name))
682
- tdecls[name] = [];
683
- pushUnique(tdecls[name], m);
684
- if (node.type == "ImportModule" && m.type_decls) {
685
- Object.entries(m.type_decls).forEach(([name, decls]) => {
686
- if (!hasProperty(tdecls, name))
687
- tdecls[name] = [];
688
- decls.forEach((decl) => pushUnique(tdecls[name], decl));
689
- });
690
- }
691
- }
692
- });
693
- }
694
- });
695
- }
743
+
696
744
  function collectClassInfo(state) {
745
+ const toybox = state.stack[0].decls["Toybox"][0];
746
+ const lang = toybox.decls["Lang"][0];
747
+ const object = lang.decls["Object"];
697
748
  state.allClasses.forEach((elm) => {
749
+ if (elm.stack[elm.stack.length - 1].type === "ClassDeclaration") {
750
+ // nested classes don't get access to their contained
751
+ // context. Put them in the global scope instead.
752
+ elm.stack = elm.stack.slice(0, 1);
753
+ }
698
754
  if (elm.node.superClass) {
699
- const [name, classes] = state.lookup(elm.node.superClass, null, elm.stack);
700
- const superClass = classes &&
701
- classes.filter((c) => isStateNode(c) && c.type === "ClassDeclaration");
755
+ const [name, lookupDefns] = state.lookup(elm.node.superClass, null, elm.stack);
756
+ const superClass = lookupDefns &&
757
+ lookupDefns
758
+ .map((lookupDefn) => lookupDefn.results)
759
+ .flat()
760
+ .filter((c) => isStateNode(c) && c.type === "ClassDeclaration");
702
761
  // set it "true" if there is a superClass, but we can't find it.
703
762
  elm.superClass = superClass && superClass.length ? superClass : true;
704
763
  if (name && elm.superClass !== true) {
@@ -730,6 +789,9 @@ function collectClassInfo(state) {
730
789
  elm.decls[name] = elm.superClass;
731
790
  }
732
791
  }
792
+ else if (elm !== object[0]) {
793
+ elm.superClass = object;
794
+ }
733
795
  });
734
796
  const markOverrides = (cls, scls) => {
735
797
  if (scls === true)
@@ -783,11 +845,12 @@ function getFileASTs(fnMap) {
783
845
  return ok;
784
846
  }, true));
785
847
  }
786
- async function analyze(fnMap) {
848
+ async function analyze(fnMap, barrelList, config) {
787
849
  let hasTests = false;
788
- const allImports = [];
850
+ let markApi = true;
789
851
  const preState = {
790
852
  fnMap,
853
+ config,
791
854
  allFunctions: [],
792
855
  allClasses: [],
793
856
  shouldExclude(node) {
@@ -815,45 +878,37 @@ async function analyze(fnMap) {
815
878
  }
816
879
  return false;
817
880
  },
818
- post(node, state) {
881
+ pre(node, state) {
819
882
  switch (node.type) {
820
883
  case "FunctionDeclaration":
884
+ if (markApi) {
885
+ node.body = null;
886
+ break;
887
+ }
888
+ case "ModuleDeclaration":
821
889
  case "ClassDeclaration": {
822
890
  const [scope] = state.stack.slice(-1);
823
- const stack = state.stack.slice(0, -1);
824
- scope.stack = stack;
891
+ scope.stack = state.stackClone().slice(0, -1);
825
892
  if (scope.type == "FunctionDeclaration") {
893
+ scope.isStatic =
894
+ scope.stack.slice(-1)[0].type !== "ClassDeclaration" ||
895
+ (scope.node.attrs &&
896
+ scope.node.attrs.access &&
897
+ scope.node.attrs.access.includes("static"));
826
898
  state.allFunctions.push(scope);
827
899
  }
828
- else {
900
+ else if (scope.type === "ClassDeclaration") {
829
901
  state.allClasses.push(scope);
830
902
  }
831
- return null;
903
+ break;
832
904
  }
833
- case "Using":
834
- case "ImportModule":
835
- allImports.push({ node, stack: state.stack.slice() });
836
- return null;
837
- default:
838
- return null;
839
905
  }
906
+ return null;
840
907
  },
841
908
  };
842
- await getApiMapping(preState);
909
+ await getApiMapping(preState, barrelList);
910
+ markApi = false;
843
911
  const state = preState;
844
- // Mark all functions from api.mir as "special" by
845
- // setting their bodies to null. In api.mir, they're
846
- // all empty, which makes it look like they're
847
- // do-nothing functions.
848
- const markApi = (node) => {
849
- if (node.type == "FunctionDeclaration") {
850
- node.node.body = null;
851
- }
852
- if (isStateNode(node) && node.decls) {
853
- Object.values(node.decls).forEach((v) => v.forEach(markApi));
854
- }
855
- };
856
- markApi(state.stack[0]);
857
912
  await getFileASTs(fnMap);
858
913
  Object.entries(fnMap).forEach(([name, value]) => {
859
914
  const { ast, parserError } = value;
@@ -866,8 +921,28 @@ async function analyze(fnMap) {
866
921
  });
867
922
  delete state.shouldExclude;
868
923
  delete state.post;
869
- processImports(allImports, state.lookup);
870
924
  collectClassInfo(state);
925
+ const diagnosticType = config?.checkInvalidSymbols !== "OFF"
926
+ ? config?.checkInvalidSymbols || "WARNING"
927
+ : null;
928
+ if (diagnosticType &&
929
+ !config?.compilerOptions?.includes("--Eno-invalid-symbol")) {
930
+ const checkTypes = config?.typeCheckLevel && config.typeCheckLevel !== "Off";
931
+ Object.entries(fnMap).forEach(([k, v]) => {
932
+ visitReferences(state, v.ast, null, false, (node, results, error) => {
933
+ if (!error)
934
+ return undefined;
935
+ const nodeStr = formatAst(node);
936
+ if (state.inType) {
937
+ if (!checkTypes || nodeStr.match(/^Void|Null$/)) {
938
+ return undefined;
939
+ }
940
+ }
941
+ diagnostic(state, node.loc, `Undefined symbol ${nodeStr}`, diagnosticType);
942
+ return false;
943
+ });
944
+ });
945
+ }
871
946
  return state;
872
947
  }
873
948
  function compareLiteralLike(a, b) {
@@ -877,11 +952,11 @@ function compareLiteralLike(a, b) {
877
952
  b = b.left;
878
953
  return a.type === "Literal" && b.type === "Literal" && a.value === b.value;
879
954
  }
880
- function getLiteralFromDecls(decls) {
881
- if (!decls.length)
955
+ function getLiteralFromDecls(lookupDefns) {
956
+ if (!lookupDefns.length)
882
957
  return null;
883
958
  let result = null;
884
- if (decls.every((d) => {
959
+ if (lookupDefns.every((lookupDefn) => lookupDefn.results.every((d) => {
885
960
  if (d.type === "EnumStringMember" ||
886
961
  (d.type === "VariableDeclarator" && d.node.kind === "const")) {
887
962
  const init = getLiteralNode(d.type === "EnumStringMember" ? d.init : d.node.init);
@@ -896,7 +971,7 @@ function getLiteralFromDecls(decls) {
896
971
  }
897
972
  }
898
973
  return false;
899
- })) {
974
+ }))) {
900
975
  return result;
901
976
  }
902
977
  return null;
@@ -1107,20 +1182,23 @@ function markFunctionCalled(state, func) {
1107
1182
  }
1108
1183
  pushUnique(state.calledFunctions[func.id.name], func);
1109
1184
  }
1110
- async function optimizeMonkeyC(fnMap) {
1185
+ async function optimizeMonkeyC(fnMap, barrelList, config) {
1111
1186
  const state = {
1112
- ...(await analyze(fnMap)),
1187
+ ...(await analyze(fnMap, barrelList, config)),
1113
1188
  localsStack: [{}],
1114
1189
  exposed: {},
1115
1190
  calledFunctions: {},
1116
1191
  };
1117
- const replace = (node) => {
1192
+ const replace = (node, old) => {
1118
1193
  if (node === false || node === null)
1119
1194
  return node;
1120
1195
  const rep = state.traverse(node);
1121
- return rep === false || rep ? rep : node;
1196
+ if (rep === false || Array.isArray(rep))
1197
+ return rep;
1198
+ return { ...(rep || node), loc: old.loc, start: old.start, end: old.end };
1122
1199
  };
1123
1200
  const inPlaceReplacement = (node, obj) => {
1201
+ const { start, end, loc } = node;
1124
1202
  for (const k of Object.keys(node)) {
1125
1203
  delete node[k];
1126
1204
  }
@@ -1128,13 +1206,16 @@ async function optimizeMonkeyC(fnMap) {
1128
1206
  obj = {
1129
1207
  type: "BinaryExpression",
1130
1208
  operator: "as",
1131
- left: obj,
1209
+ left: { ...obj, start, end, loc },
1132
1210
  right: { type: "TypeSpecList", ts: [obj.enumType] },
1133
1211
  };
1134
1212
  }
1135
1213
  for (const [k, v] of Object.entries(obj)) {
1136
1214
  node[k] = v;
1137
1215
  }
1216
+ node.loc = loc;
1217
+ node.start = start;
1218
+ node.end = end;
1138
1219
  };
1139
1220
  const lookupAndReplace = (node) => {
1140
1221
  const [, objects] = state.lookup(node);
@@ -1253,6 +1334,37 @@ async function optimizeMonkeyC(fnMap) {
1253
1334
  }
1254
1335
  return ["init"];
1255
1336
  }
1337
+ case "CatchClause":
1338
+ if (node.param) {
1339
+ state.localsStack.push({ node, map: { ...(topLocals().map || {}) } });
1340
+ const locals = topLocals();
1341
+ const map = locals.map;
1342
+ const declName = variableDeclarationName(node.param);
1343
+ const name = renameVariable(state, locals, declName);
1344
+ if (name) {
1345
+ if (node.param.type === "Identifier") {
1346
+ node.param.name = name;
1347
+ }
1348
+ else {
1349
+ node.param.left.name = name;
1350
+ }
1351
+ }
1352
+ else {
1353
+ map[declName] = true;
1354
+ }
1355
+ return ["body"];
1356
+ }
1357
+ break;
1358
+ case "BinaryExpression":
1359
+ if (node.operator === "has") {
1360
+ if (node.right.type === "UnaryExpression" &&
1361
+ node.right.operator === ":") {
1362
+ // Using `expr has :symbol` doesn't "expose"
1363
+ // symbol. So skip the right operand.
1364
+ return ["left"];
1365
+ }
1366
+ }
1367
+ break;
1256
1368
  case "UnaryExpression":
1257
1369
  if (node.operator == ":") {
1258
1370
  // If we produce a Symbol, for a given name,
@@ -1335,7 +1447,7 @@ async function optimizeMonkeyC(fnMap) {
1335
1447
  }
1336
1448
  const opt = optimizeNode(node);
1337
1449
  if (opt) {
1338
- return replace(opt);
1450
+ return replace(opt, node);
1339
1451
  }
1340
1452
  switch (node.type) {
1341
1453
  case "ConditionalExpression":
@@ -1345,7 +1457,7 @@ async function optimizeMonkeyC(fnMap) {
1345
1457
  const rep = node.test.value ? node.consequent : node.alternate;
1346
1458
  if (!rep)
1347
1459
  return false;
1348
- return replace(rep);
1460
+ return replace(rep, rep);
1349
1461
  }
1350
1462
  break;
1351
1463
  case "WhileStatement":
@@ -1360,11 +1472,11 @@ async function optimizeMonkeyC(fnMap) {
1360
1472
  break;
1361
1473
  case "ReturnStatement":
1362
1474
  if (node.argument && node.argument.type === "CallExpression") {
1363
- return replace(optimizeCall(state, node.argument, node));
1475
+ return replace(optimizeCall(state, node.argument, node), node.argument);
1364
1476
  }
1365
1477
  break;
1366
1478
  case "CallExpression": {
1367
- return replace(optimizeCall(state, node, null));
1479
+ return replace(optimizeCall(state, node, null), node);
1368
1480
  }
1369
1481
  case "AssignmentExpression":
1370
1482
  if (node.operator === "=" &&
@@ -1376,7 +1488,7 @@ async function optimizeMonkeyC(fnMap) {
1376
1488
  break;
1377
1489
  case "ExpressionStatement":
1378
1490
  if (node.expression.type === "CallExpression") {
1379
- return replace(optimizeCall(state, node.expression, node));
1491
+ return replace(optimizeCall(state, node.expression, node), node.expression);
1380
1492
  }
1381
1493
  else if (node.expression.type === "AssignmentExpression") {
1382
1494
  if (node.expression.right.type === "CallExpression") {
@@ -1388,10 +1500,10 @@ async function optimizeMonkeyC(fnMap) {
1388
1500
  }
1389
1501
  if (!ok && node.expression.operator == "=") {
1390
1502
  const [, result] = state.lookup(node.expression.left);
1391
- ok = result != null;
1503
+ ok = !!result;
1392
1504
  }
1393
1505
  if (ok) {
1394
- return replace(optimizeCall(state, node.expression.right, node.expression));
1506
+ return replace(optimizeCall(state, node.expression.right, node.expression), node.expression.right);
1395
1507
  }
1396
1508
  }
1397
1509
  }
@@ -1399,7 +1511,7 @@ async function optimizeMonkeyC(fnMap) {
1399
1511
  const ret = unused(node.expression, true);
1400
1512
  if (ret) {
1401
1513
  return ret
1402
- .map(replace)
1514
+ .map((r) => replace(r, r))
1403
1515
  .flat(1)
1404
1516
  .filter((s) => !!s);
1405
1517
  }
@@ -1420,6 +1532,9 @@ async function optimizeMonkeyC(fnMap) {
1420
1532
  delete state.post;
1421
1533
  const cleanup = (node) => {
1422
1534
  switch (node.type) {
1535
+ case "ThisExpression":
1536
+ node.text = "self";
1537
+ break;
1423
1538
  case "EnumStringBody":
1424
1539
  if (node.members.every((m) => {
1425
1540
  const name = "name" in m ? m.name : m.id.name;
@@ -1484,6 +1599,19 @@ async function optimizeMonkeyC(fnMap) {
1484
1599
  return false;
1485
1600
  }
1486
1601
  break;
1602
+ case "ClassDeclaration":
1603
+ case "ModuleDeclaration":
1604
+ // none of the attributes means anything on classes and
1605
+ // modules, and the new compiler complains about some
1606
+ // of them. Just drop them all.
1607
+ if (node.attrs && node.attrs.access) {
1608
+ if (node.attrs.attributes) {
1609
+ delete node.attrs.access;
1610
+ }
1611
+ else {
1612
+ delete node.attrs;
1613
+ }
1614
+ }
1487
1615
  }
1488
1616
  return null;
1489
1617
  };
@@ -1499,9 +1627,12 @@ async function optimizeMonkeyC(fnMap) {
1499
1627
  return state.diagnostics;
1500
1628
  }
1501
1629
  function optimizeCall(state, node, context) {
1502
- const [name, results] = state.lookup(node.callee);
1630
+ const [name, results] = state.lookupNonlocal(node.callee);
1503
1631
  const callees = results &&
1504
- results.filter((c) => c.type === "FunctionDeclaration");
1632
+ results
1633
+ .map((r) => r.results)
1634
+ .flat()
1635
+ .filter((c) => c.type === "FunctionDeclaration");
1505
1636
  if (!callees || !callees.length) {
1506
1637
  const n = name ||
1507
1638
  ("name" in node.callee && node.callee.name) ||
@@ -1606,6 +1737,8 @@ const external_sdk_util_cjs_namespaceObject = require("./sdk-util.cjs");
1606
1737
 
1607
1738
 
1608
1739
 
1740
+
1741
+
1609
1742
  /*
1610
1743
  * This is an unfortunate hack. I want to be able to extract things
1611
1744
  * like the types of all of a Class's variables (in particular the type
@@ -1616,16 +1749,30 @@ const external_sdk_util_cjs_namespaceObject = require("./sdk-util.cjs");
1616
1749
  * but those are at least in a standard format.
1617
1750
  */
1618
1751
  // Extract all enum values from api.mir
1619
- async function api_getApiMapping(state) {
1752
+ async function api_getApiMapping(state, barrelList) {
1620
1753
  // get the path to the currently active sdk
1621
1754
  const parser = (prettier_plugin_monkeyc_default()).parsers.monkeyc;
1622
1755
  const sdk = await (0,external_sdk_util_cjs_namespaceObject.getSdkPath)();
1756
+ const rezDecl = `module Rez { ${[
1757
+ "Drawables",
1758
+ "Fonts",
1759
+ "JsonData",
1760
+ "Layouts",
1761
+ "Menus",
1762
+ "Strings",
1763
+ ]
1764
+ .map((s) => ` module ${s} {}\n`)
1765
+ .join("")}}`;
1623
1766
  const api = (await promises_namespaceObject.readFile(`${sdk}bin/api.mir`))
1624
1767
  .toString()
1625
1768
  .replace(/\r\n/g, "\n")
1626
1769
  .replace(/^\s*\[.*?\]\s*$/gm, "")
1627
1770
  //.replace(/(COLOR_TRANSPARENT|LAYOUT_[HV]ALIGN_\w+) = (\d+)/gm, "$1 = -$2")
1628
- .replace(/^(\s*type)\s/gm, "$1def ");
1771
+ .replace(/^(\s*type)\s/gm, "$1def ") +
1772
+ (barrelList || [])
1773
+ .map((name) => `module ${name} { ${rezDecl} }`)
1774
+ .concat(rezDecl)
1775
+ .join("");
1629
1776
  try {
1630
1777
  const result = api_collectNamespaces(parser.parse(api, null, {
1631
1778
  filepath: "api.mir",
@@ -1643,19 +1790,19 @@ async function api_getApiMapping(state) {
1643
1790
  (value.type !== "VariableDeclarator" || value.kind != "const")) {
1644
1791
  throw `Negative constant ${fixup} did not refer to a constant`;
1645
1792
  }
1646
- const init = value.init;
1793
+ const init = getLiteralNode(value.init);
1647
1794
  if (!init || init.type !== "Literal") {
1648
1795
  throw `Negative constant ${fixup} was not a Literal`;
1649
1796
  }
1650
1797
  if (typeof init.value !== "number") {
1651
- console.log(`Negative fixup ${fixup} was already not a number!`);
1798
+ console.log(`Negative fixup ${fixup} was not a number!`);
1652
1799
  }
1653
1800
  else if (init.value > 0) {
1654
1801
  init.value = -init.value;
1655
1802
  init.raw = "-" + init.raw;
1656
1803
  }
1657
1804
  else {
1658
- console.log(`Negative fixup ${fixup} was already negative!`);
1805
+ // console.log(`Negative fixup ${fixup} was already negative!`);
1659
1806
  }
1660
1807
  });
1661
1808
  return result;
@@ -1674,67 +1821,195 @@ function api_isStateNode(node) {
1674
1821
  function api_variableDeclarationName(node) {
1675
1822
  return ("left" in node ? node.left : node).name;
1676
1823
  }
1677
- function checkOne(ns, decls, name) {
1824
+ function lookupToStateNodeDecls(results) {
1825
+ return results.reduce((result, current) => current.results.length
1826
+ ? result
1827
+ ? result.concat(current.results)
1828
+ : current.results
1829
+ : result, null);
1830
+ }
1831
+ function checkOne(state, ns, decls, node, isStatic) {
1832
+ // follow the superchain, looking up node in each class
1833
+ const superChain = (cls) => {
1834
+ if (!cls.superClass || cls.superClass === true) {
1835
+ return null;
1836
+ }
1837
+ return cls.superClass.reduce((result, sup) => {
1838
+ const next = api_hasProperty(sup[decls], node.name)
1839
+ ? sup[decls][node.name]
1840
+ : superChain(sup);
1841
+ return next ? (result ? result.concat(next) : next) : result;
1842
+ }, null);
1843
+ };
1844
+ const lookupInContext = (ns) => {
1845
+ const [, lkup] = lookup(state, decls, node, null, ns.stack);
1846
+ return lkup && lookupToStateNodeDecls(lkup);
1847
+ };
1848
+ // follow the superchain, looking up node in each class's scope
1849
+ const superChainScopes = (ns) => {
1850
+ const result = lookupInContext(ns);
1851
+ if (result)
1852
+ return result;
1853
+ if (!ns.superClass || ns.superClass === true) {
1854
+ return null;
1855
+ }
1856
+ return ns.superClass.reduce((result, sup) => {
1857
+ const next = superChainScopes(sup);
1858
+ return next ? (result ? result.concat(next) : next) : result;
1859
+ }, null);
1860
+ };
1678
1861
  if (api_isStateNode(ns)) {
1679
- if (api_hasProperty(ns[decls], name)) {
1680
- return ns[decls][name];
1681
- }
1682
- if (ns.type == "ClassDeclaration" &&
1683
- ns.superClass &&
1684
- ns.superClass !== true) {
1685
- const found = ns.superClass
1686
- .map((cls) => checkOne(cls, decls, name))
1687
- .filter((n) => n != null)
1688
- .flat(1);
1689
- return found.length ? found : null;
1862
+ if (api_hasProperty(ns[decls], node.name)) {
1863
+ return ns[decls][node.name];
1864
+ }
1865
+ switch (ns.type) {
1866
+ case "ClassDeclaration":
1867
+ if (!isStatic) {
1868
+ return superChain(ns) || superChainScopes(ns) || false;
1869
+ }
1870
+ // fall through
1871
+ case "ModuleDeclaration":
1872
+ return lookupInContext(ns) || false;
1690
1873
  }
1691
1874
  }
1692
1875
  return null;
1693
1876
  }
1694
- function lookup(state, decls, node, name, stack) {
1695
- stack || (stack = state.stack);
1877
+ function sameStateNodeDecl(a, b) {
1878
+ if (a === b)
1879
+ return true;
1880
+ if (!a || !b)
1881
+ return false;
1882
+ if (!api_isStateNode(a) || a.type !== b.type)
1883
+ return false;
1884
+ return (a.node === b.node ||
1885
+ a.type === "Program" ||
1886
+ (a.type === "ModuleDeclaration" && a.fullName === b.fullName));
1887
+ }
1888
+ function sameLookupDefinition(a, b) {
1889
+ return (
1890
+ // sameStateNodeDecl(a.parent, b.parent) &&
1891
+ (0,external_util_cjs_namespaceObject.sameArrays)(a.results, b.results, (ar, br) => sameStateNodeDecl(ar, br)));
1892
+ }
1893
+ function api_sameLookupResult(a, b) {
1894
+ return (0,external_util_cjs_namespaceObject.sameArrays)(a, b, sameLookupDefinition);
1895
+ }
1896
+ /**
1897
+ *
1898
+ * @param state - The ProgramState
1899
+ * @param decls - The field to use to look things up. either "decls" or "type_decls"
1900
+ * @param node - The node to lookup
1901
+ * @param name - Overrides the name of the node.
1902
+ * @param stack - if provided, use this stack, rather than the current
1903
+ * state.stack for the lookup
1904
+ * @param nonlocal - when true, a plain identifier will be looked up as a
1905
+ * non-local. This is needed when looking up a callee.
1906
+ * If the callee is a MemberExpression, the flag is ignored.
1907
+ * @returns
1908
+ * - [string, LookupDefinition[]] - if the lookup succeeds
1909
+ * - [false, false] - if the lookup fails, but its not an error because its not the kind of expression we can lookup
1910
+ * - [null, null] - if the lookup fails unexpectedly.
1911
+ */
1912
+ function lookup(state, decls, node, name, maybeStack, nonlocal) {
1913
+ const stack = maybeStack || state.stack;
1696
1914
  switch (node.type) {
1697
1915
  case "MemberExpression": {
1698
1916
  if (node.property.type != "Identifier" || node.computed)
1699
1917
  break;
1700
- const [, module, where] = lookup(state, decls, node.object, name, stack);
1701
- if (module && module.length === 1) {
1702
- const result = checkOne(module[0], decls, node.property.name);
1703
- if (result) {
1704
- return [
1705
- name || node.property.name,
1706
- result,
1707
- where.concat(module[0]),
1708
- ];
1918
+ const property = node.property;
1919
+ let result;
1920
+ if (node.object.type === "ThisExpression") {
1921
+ [, result] = lookup(state, decls, node.property, name, stack, true);
1922
+ }
1923
+ else {
1924
+ const [, results] = lookup(state, decls, node.object, name, stack, false);
1925
+ if (results === false)
1926
+ break;
1927
+ if (!results)
1928
+ return [null, null];
1929
+ result = results.reduce((current, lookupDef) => {
1930
+ const items = lookupDef.results
1931
+ .map((module) => {
1932
+ const res = checkOne(state, module, decls, property, false);
1933
+ return res ? { parent: module, results: res } : null;
1934
+ })
1935
+ .filter((r) => r != null);
1936
+ if (!items.length)
1937
+ return current;
1938
+ return current ? current.concat(items) : items;
1939
+ }, null);
1940
+ if (!result &&
1941
+ results.some((ld) => ld.results.some((sn) => (api_isStateNode(sn) && sn.fullName?.match(/^\$\.(\w+\.)?Rez\./)) ||
1942
+ sn.type === "VariableDeclarator" ||
1943
+ sn.type === "Identifier" ||
1944
+ sn.type === "BinaryExpression" ||
1945
+ (sn.type === "ClassDeclaration" &&
1946
+ property.name === "initialize")))) {
1947
+ // - The Rez module can contain lots of things from the resource
1948
+ // compiler which we don't track.
1949
+ // - Variables, and formal parameters would require type tracking
1950
+ // which we don't yet do
1951
+ // - Its ok to call an undeclared initialize method.
1952
+ // Report them all as "expected failures".
1953
+ return [false, false];
1709
1954
  }
1710
1955
  }
1711
- break;
1956
+ if (!result)
1957
+ return [null, null];
1958
+ return [name || property.name, result];
1712
1959
  }
1713
1960
  case "ThisExpression": {
1714
- for (let i = stack.length; i--;) {
1961
+ for (let i = stack.length;;) {
1715
1962
  const si = stack[i];
1716
1963
  if (si.type == "ModuleDeclaration" ||
1717
1964
  si.type == "ClassDeclaration" ||
1718
1965
  !i) {
1719
- return [name || si.name, [si], stack.slice(0, i)];
1966
+ return [
1967
+ name || si.name,
1968
+ [{ parent: i ? stack[i - 1] : null, results: [si] }],
1969
+ ];
1720
1970
  }
1721
1971
  }
1722
- break;
1723
1972
  }
1724
1973
  case "Identifier": {
1725
1974
  if (node.name == "$") {
1726
- return [name || node.name, [stack[0]], []];
1975
+ return [name || node.name, [{ parent: null, results: [stack[0]] }]];
1727
1976
  }
1977
+ let inStatic = false;
1978
+ let checkedImports = false;
1728
1979
  for (let i = stack.length; i--;) {
1729
- const result = checkOne(stack[i], decls, node.name);
1730
- if (result) {
1731
- return [name || node.name, result, stack.slice(0, i + 1)];
1980
+ const si = stack[i];
1981
+ switch (si.type) {
1982
+ case "ClassDeclaration":
1983
+ case "ModuleDeclaration":
1984
+ case "Program":
1985
+ if (!checkedImports) {
1986
+ checkedImports = true;
1987
+ const results = findUsingForNode(state, stack, i, node, decls === "type_decls");
1988
+ if (results) {
1989
+ return [name || node.name, [{ parent: si, results }]];
1990
+ }
1991
+ }
1992
+ break;
1993
+ case "FunctionDeclaration":
1994
+ inStatic = si.isStatic === true;
1995
+ // fall through
1996
+ default:
1997
+ if (nonlocal)
1998
+ continue;
1999
+ break;
2000
+ }
2001
+ const results = checkOne(state, si, decls, node, inStatic);
2002
+ if (results) {
2003
+ return [name || node.name, [{ parent: si, results }]];
2004
+ }
2005
+ else if (results === false) {
2006
+ break;
1732
2007
  }
1733
2008
  }
1734
- break;
2009
+ return [null, null];
1735
2010
  }
1736
2011
  }
1737
- return [null, null, null];
2012
+ return [false, false];
1738
2013
  }
1739
2014
  function api_collectNamespaces(ast, stateIn) {
1740
2015
  const state = (stateIn || {});
@@ -1766,8 +2041,12 @@ function api_collectNamespaces(ast, stateIn) {
1766
2041
  }
1767
2042
  };
1768
2043
  state.lookup = (node, name, stack) => lookup(state, state.inType ? "type_decls" : "decls", node, name, stack);
2044
+ state.lookupNonlocal = (node, name, stack) => lookup(state, "decls", node, name, stack, true);
1769
2045
  state.lookupValue = (node, name, stack) => lookup(state, "decls", node, name, stack);
1770
2046
  state.lookupType = (node, name, stack) => lookup(state, "type_decls", node, name, stack);
2047
+ state.stackClone = () => state.stack.map((elm) => elm.type === "ModuleDeclaration" || elm.type === "Program"
2048
+ ? { ...elm }
2049
+ : elm);
1771
2050
  state.inType = false;
1772
2051
  state.traverse = (root) => api_traverseAst(root, (node) => {
1773
2052
  try {
@@ -1780,14 +2059,60 @@ function api_collectNamespaces(ast, stateIn) {
1780
2059
  if (state.stack.length != 1) {
1781
2060
  throw new Error("Unexpected stack length for Program node");
1782
2061
  }
2062
+ state.stack[0].node = node;
1783
2063
  break;
1784
2064
  case "TypeSpecList":
1785
2065
  state.inType = true;
1786
2066
  break;
2067
+ case "ImportModule":
2068
+ case "Using": {
2069
+ const [parent] = state.stack.slice(-1);
2070
+ if (!parent.usings) {
2071
+ parent.usings = {};
2072
+ }
2073
+ const name = (node.type === "Using" && node.as && node.as.name) ||
2074
+ (node.id.type === "Identifier"
2075
+ ? node.id.name
2076
+ : node.id.property.name);
2077
+ const using = { node };
2078
+ parent.usings[name] = using;
2079
+ if (node.type == "ImportModule") {
2080
+ if (!parent.imports) {
2081
+ parent.imports = [using];
2082
+ }
2083
+ else {
2084
+ const index = parent.imports.findIndex((using) => (using.node.id.type === "Identifier"
2085
+ ? using.node.id.name
2086
+ : using.node.id.property.name) === name);
2087
+ if (index >= 0)
2088
+ parent.imports.splice(index, 1);
2089
+ parent.imports.push(using);
2090
+ }
2091
+ }
2092
+ break;
2093
+ }
2094
+ case "CatchClause":
2095
+ if (node.param) {
2096
+ const [parent] = state.stack.slice(-1);
2097
+ if (!parent.decls)
2098
+ parent.decls = {};
2099
+ const id = node.param.type === "Identifier"
2100
+ ? node.param
2101
+ : node.param.left;
2102
+ state.stack.push({
2103
+ type: "BlockStatement",
2104
+ fullName: undefined,
2105
+ name: undefined,
2106
+ node: node.body,
2107
+ decls: { [id.name]: [id] },
2108
+ });
2109
+ }
2110
+ break;
1787
2111
  case "BlockStatement": {
1788
2112
  const [parent] = state.stack.slice(-1);
1789
- if (parent.type != "FunctionDeclaration" &&
1790
- parent.type != "BlockStatement") {
2113
+ if (parent.node === node ||
2114
+ (parent.type != "FunctionDeclaration" &&
2115
+ parent.type != "BlockStatement")) {
1791
2116
  break;
1792
2117
  }
1793
2118
  // fall through
@@ -1833,6 +2158,9 @@ function api_collectNamespaces(ast, stateIn) {
1833
2158
  parent.decls[name].push(elm);
1834
2159
  if (node.type == "ModuleDeclaration" ||
1835
2160
  node.type == "ClassDeclaration") {
2161
+ // Inject the class/module name into itself,
2162
+ // so you can say Graphics.Graphics.Graphics.COLOR_RED
2163
+ elm.decls = { [name]: [elm] };
1836
2164
  if (!parent.type_decls)
1837
2165
  parent.type_decls = {};
1838
2166
  if (!api_hasProperty(parent.type_decls, name)) {
@@ -1880,7 +2208,7 @@ function api_collectNamespaces(ast, stateIn) {
1880
2208
  if (!parent.decls)
1881
2209
  parent.decls = {};
1882
2210
  const decls = parent.decls;
1883
- const stack = state.stack.slice();
2211
+ const stack = state.stackClone();
1884
2212
  node.declarations.forEach((decl) => {
1885
2213
  const name = api_variableDeclarationName(decl.id);
1886
2214
  if (!api_hasProperty(decls, name)) {
@@ -1984,8 +2312,14 @@ function api_collectNamespaces(ast, stateIn) {
1984
2312
  state.inType = true;
1985
2313
  break;
1986
2314
  }
1987
- if (state.stack.slice(-1).pop()?.node === node) {
1988
- state.stack.pop();
2315
+ const [parent] = state.stack.slice(-1);
2316
+ if (parent.node === node ||
2317
+ (node.type === "CatchClause" && parent.node === node.body)) {
2318
+ delete parent.usings;
2319
+ delete parent.imports;
2320
+ if (node.type != "Program") {
2321
+ state.stack.pop();
2322
+ }
1989
2323
  }
1990
2324
  }
1991
2325
  if (ret === false) {
@@ -2067,7 +2401,7 @@ function api_traverseAst(node, pre, post) {
2067
2401
  }
2068
2402
  return post && post(node);
2069
2403
  }
2070
- function formatAst(node, monkeyCSource = null) {
2404
+ function api_formatAst(node, monkeyCSource = null) {
2071
2405
  /*
2072
2406
  * The estree printer sometimes looks at the parent node without
2073
2407
  * checking that there *is* a parent node (eg it assumes all
@@ -2108,7 +2442,7 @@ function handleException(state, node, exception) {
2108
2442
  .filter((e) => e != null)
2109
2443
  .join(".");
2110
2444
  const location = node.loc && node.loc.source
2111
- ? `${node.loc.source}:${node.start || 0}:${node.end || 0}`
2445
+ ? `${node.loc.source}:${node.loc.start.line || 0}:${node.loc.end.line || 0}`
2112
2446
  : "<unknown>";
2113
2447
  const message = `Got exception \`${exception instanceof Error
2114
2448
  ? exception.message
@@ -2124,6 +2458,61 @@ function handleException(state, node, exception) {
2124
2458
  throw exception;
2125
2459
  }
2126
2460
  }
2461
+ function findUsing(state, stack, using) {
2462
+ if (using.module)
2463
+ return using.module;
2464
+ let module = stack[0];
2465
+ const find = (node) => {
2466
+ let name;
2467
+ if (node.type == "Identifier") {
2468
+ name = node.name;
2469
+ }
2470
+ else {
2471
+ find(node.object);
2472
+ name = node.property.name;
2473
+ }
2474
+ if (api_hasProperty(module.decls, name)) {
2475
+ const decls = module.decls[name];
2476
+ if (decls &&
2477
+ decls.length === 1 &&
2478
+ decls[0].type === "ModuleDeclaration") {
2479
+ module = decls[0];
2480
+ return true;
2481
+ }
2482
+ }
2483
+ return false;
2484
+ };
2485
+ if (find(using.node.id)) {
2486
+ using.module = module;
2487
+ return using.module;
2488
+ }
2489
+ if (state.config?.checkInvalidSymbols !== "OFF") {
2490
+ inliner_diagnostic(state, using.node.id.loc, `Unable to resolve import of ${api_formatAst(using.node.id)}`, state.config?.checkInvalidSymbols || "WARNING");
2491
+ }
2492
+ return null;
2493
+ }
2494
+ function findUsingForNode(state, stack, i, node, isType) {
2495
+ while (i >= 0) {
2496
+ const si = stack[i--];
2497
+ if (api_hasProperty(si.usings, node.name)) {
2498
+ const using = si.usings[node.name];
2499
+ const module = findUsing(state, stack, using);
2500
+ return module && [module];
2501
+ }
2502
+ if (si.imports && isType) {
2503
+ for (let j = si.imports.length; j--;) {
2504
+ const using = si.imports[j];
2505
+ const module = findUsing(state, stack, using);
2506
+ if (using.module) {
2507
+ if (api_hasProperty(using.module.type_decls, node.name)) {
2508
+ return using.module.type_decls[node.name];
2509
+ }
2510
+ }
2511
+ }
2512
+ }
2513
+ }
2514
+ return null;
2515
+ }
2127
2516
 
2128
2517
  var __webpack_export_target__ = exports;
2129
2518
  for(var i in __webpack_exports__) __webpack_export_target__[i] = __webpack_exports__[i];