treeMatching.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. //StretchyTreeMatcher.prototype = new NodeVisitor();
  2. //ASTMap.prototype = new Object();
  3. //CT_Map.prototype = new Object();
  4. function CT_Map(){
  5. this.keys = [];
  6. this.values = [];
  7. this.cacheKey = null;
  8. this.cacheIndex = -1;
  9. }
  10. CT_Map.prototype.updateCache = function(key){
  11. if(this.cacheIndex == -1 || !(this.cacheKey === key)){
  12. this.cacheKey = key;
  13. this.cacheIndex = this.keys.indexOf(key);
  14. }
  15. }
  16. CT_Map.prototype.clearCache = function(){
  17. this.cacheKey = null;
  18. this.cacheIndex = -1;
  19. }
  20. CT_Map.prototype.clear = function(){
  21. this.keys = [];
  22. this.values = [];
  23. this.clearCache();
  24. }
  25. CT_Map.prototype.delete = function(key){
  26. this.updateCache(key);
  27. this.keys.splice(this.cacheIndex, 1);
  28. this.clearCache();
  29. }
  30. CT_Map.prototype.get = function(key){
  31. this.updateCache(key);
  32. return this.values[this.cacheIndex];
  33. }
  34. CT_Map.prototype.has = function(key){
  35. this.updateCache(key);
  36. return this.cacheIndex >= 0;
  37. }
  38. CT_Map.prototype.keys = function(){
  39. return this.keys;
  40. }
  41. CT_Map.prototype.values = function(){
  42. return this.values;
  43. }
  44. CT_Map.prototype.set = function(key, value){
  45. this.updateCache(key);
  46. if(this.cacheIndex === -1){
  47. this.keys.push(key);
  48. this.values.push(value);
  49. }else{
  50. this.values[this.cacheIndex] = value;
  51. }
  52. }
  53. CT_Map.prototype.size = function(){
  54. return this.keys.length;
  55. }
  56. function ASTMap(){
  57. this.mappings = new CT_Map();
  58. this.symbolTable = new CT_Map();
  59. this.expTable = new CT_Map();
  60. this.conflictKeys = [];
  61. }
  62. /**
  63. Adds insNode.id to the symbol table if it doesn't already exist,
  64. mapping it to a set of insNode.
  65. Updates a second dictionary that maps insNode to an stdNode, and overwrites
  66. the current stdNode since there should only be one mapping.
  67. */
  68. ASTMap.prototype.addVarToSymbolTable = function(insNode, stdNode){
  69. var key = null;
  70. if(typeof insNode == "string"){
  71. key = insNode;
  72. }else{
  73. key = Sk.ffi.remapToJs(insNode.astNode.id);
  74. }
  75. var value = new Object();
  76. value.id = Sk.ffi.remapToJs(stdNode.astNode.id);
  77. value.node = stdNode.astNode;
  78. var newList = null;
  79. if(this.symbolTable.has(key)){
  80. newList = this.symbolTable.get(key);
  81. newList.push(value);
  82. if(this.conflictKeys.indexOf(key) === -1){
  83. for(var i = 0; i < newList.length; i += 1){
  84. var other = newList[i];
  85. if(value.id != other.id){
  86. this.conflictKeys.push(key);
  87. break;
  88. }
  89. }
  90. }
  91. }else{
  92. newList = [value];
  93. }
  94. this.symbolTable.set(key, newList);
  95. return this.conflictKeys.length;
  96. }
  97. ASTMap.prototype.addExpToSymbolTable = function(insNode, stdNode){
  98. var key = Sk.ffi.remapToJs(insNode.astNode.id);
  99. this.expTable.set(key, stdNode.astNode);
  100. }
  101. ASTMap.prototype.addNodePairing = function(insNode, stdNode){
  102. this.mappings.set(insNode.astNode, stdNode.astNode);
  103. }
  104. ASTMap.prototype.hasConflicts = function(){
  105. return this.conflictKeys.length > 0;
  106. }
  107. /**
  108. Returns a newly merged map consisting of this and other
  109. without modifying this.
  110. @param {ASTMap} other - the other ASTMap to be merged with
  111. @return this modified by adding the contents of other
  112. **/
  113. ASTMap.prototype.newMergedMap = function(other){
  114. var newMap = new ASTMap();
  115. newMap.mergeMapWith(this);
  116. newMap.mergeMapWith(other);
  117. return newMap;
  118. }
  119. /**
  120. Returns a newly merged map consisting of this and other
  121. by modifying this
  122. @param {ASTMap} other - the other ASTMap to be merged with
  123. @return this modified by adding the contents of other
  124. **/
  125. ASTMap.prototype.mergeMapWith = function(other){
  126. //TODO: check if other is also an ASTMap.
  127. //merge all mappings
  128. var otherMap = other.mappings;
  129. for(var i = 0; i < otherMap.keys.length; i += 1){
  130. this.mappings.set(otherMap.keys[i], otherMap.values[i]);
  131. }
  132. //merge all expressions
  133. var otherExp = other.expTable;
  134. for(var i = 0; i < otherExp.keys.length; i += 1){
  135. this.expTable.set(otherExp.keys[i], otherExp.values[i]);
  136. }
  137. //merge all symbols
  138. var otherSym = other.symbolTable;
  139. for(var i = 0; i < otherSym.keys.length; i += 1){
  140. var value = otherSym.values[i];
  141. for(var j = 0; j < value.length; j += 1){
  142. this.addVarToSymbolTable(otherSym.keys[i], new EasyNode(value[j].node));
  143. }
  144. }
  145. }
  146. function EasyNode(astNode, myField){
  147. this.children = [];
  148. this.astNode = astNode;
  149. this.field = myField;
  150. var myFieldList = iter_fields(this.astNode);
  151. for (var i = 0; i < myFieldList.length; i += 1) {
  152. var field = myFieldList[i][0];
  153. var value = myFieldList[i][1];
  154. //if the field doesn't have a value, no child exists
  155. if (value === null) {
  156. continue;
  157. }
  158. //If the children are not in an array, wrap it in an array for consistency in the code the follows
  159. if(!(Array === value.constructor)){
  160. value = [value];
  161. }
  162. //Reference ast_node_visitor.js for the original behavior and keep note of it for the purposes of handling the children noting the special case when the nodes of the array are actually parameters of the node (e.g. a load function) instead of a child node
  163. for (var j = 0; j < value.length; j += 1) {
  164. //if the item in the array is actually a child astNode
  165. var subvalue = value[j];
  166. if (isAstNode(subvalue)) {
  167. this.children.push(new EasyNode(subvalue, field));
  168. }
  169. }
  170. }
  171. }
  172. function StretchyTreeMatcher(code){
  173. //TODO: check that both are ast nodes at the module level
  174. var astNode = null;
  175. if(typeof code == "string"){
  176. var filename = "__main__"
  177. var parse = Sk.parse(filename, code);
  178. astNode = Sk.astFromParse(parse.cst, filename, parse.flags);
  179. }else{
  180. astNode = code;
  181. }
  182. if(astNode == null){
  183. this.rootNode = null;
  184. }else{
  185. this.rootNode = new EasyNode(astNode, "none");
  186. }
  187. }
  188. StretchyTreeMatcher.prototype.findMatches = function(other){
  189. //TODO: check that both are ast nodes at the module level
  190. var otherTree = null;
  191. if(typeof other == "string"){
  192. var filename = "__main__";
  193. var parse = Sk.parse(filename, other);
  194. otherTree = Sk.astFromParse(parse.cst, filename, parse.flags);
  195. }else{
  196. otherTree = other;
  197. }
  198. var easyOther = new EasyNode(otherTree, "none");
  199. return this.anyNodeMatch(this.rootNode, easyOther);
  200. }
  201. /**
  202. Finds whether insNode can be matched to some node in the tree stdNode
  203. @return a mapping of nodes and a symbol table mapping insNode to some node in the tree stdNode or false if such a matching does not exist
  204. **/
  205. StretchyTreeMatcher.prototype.anyNodeMatch = function(insNode, stdNode){
  206. //@TODO: create a more public function that converts insNode and stdNode into EasyNodes
  207. //matching: an object representing the mapping and the symbol table
  208. var matching = this.deep_findMatch(insNode, stdNode, true);
  209. //if a direct matching is found
  210. if(matching){
  211. return matching;//return it
  212. }else{//otherwise
  213. var foundMatch = false;
  214. //try to matching insNode to each child of stdNode, recursively
  215. for(var i = 0; i < stdNode.children.length; i += 1){
  216. var stdChild = stdNode.children[i];
  217. matching = this.anyNodeMatch(insNode, stdChild);
  218. if (matching){
  219. return matching;
  220. }
  221. }
  222. }
  223. return false;
  224. }
  225. /**
  226. Finds whether insNode and matches stdNode and whether insNode's children flexibly match stdNode's children in order
  227. @return a mapping of nodes and a symbol table mapping insNode to stdNode
  228. **/
  229. StretchyTreeMatcher.prototype.deep_findMatch = function(insNode, stdNode, checkMeta){
  230. var method_name = "deep_findMatch_" + insNode.astNode._astname;
  231. if(method_name in this){
  232. return this[method_name](insNode, stdNode, checkMeta);
  233. }else{
  234. return this.deep_findMatch_generic(insNode, stdNode, checkMeta);
  235. }
  236. }
  237. StretchyTreeMatcher.prototype.deep_findMatch_BinOp = function(insNode, stdNode, checkMeta){
  238. var op = insNode.astNode.op;
  239. op = op.toString();
  240. op = op.substring(("function").length + 1, op.length - 4);
  241. var isGeneric = !(op === "Mult" || op === "Add");
  242. if(isGeneric){
  243. return this.deep_findMatch_generic(insNode, stdNode, checkMeta);
  244. }else{
  245. return this.deep_findMatch_BinFlex(insNode, stdNode, checkMeta);
  246. }
  247. }
  248. StretchyTreeMatcher.prototype.deep_findMatch_BinFlex = function(insNode, stdNode, checkMeta){
  249. var baseMappings = this.shallowMatch(insNode, stdNode, checkMeta);
  250. if(baseMappings){
  251. var insLeft = insNode.children[0];
  252. var insRight = insNode.children[1];
  253. var stdLeft = stdNode.children[0];
  254. var stdRight = stdNode.children[1];
  255. var newMappings = [];
  256. //case 1: insLeft->stdLeft and insRight->stdRight
  257. var caseLeft = this.deep_findMatch(insLeft, stdLeft, false);
  258. var caseRight = this.deep_findMatch(insRight, stdRight, false);
  259. if(caseLeft && caseRight){
  260. for(var i = 0; i < caseLeft.length; i += 1){
  261. var newMap = baseMappings[0].newMergedMap(caseLeft[i]);
  262. for(var j = 0; j < caseRight.length; j += 1){
  263. var both = newMap.newMergedMap(caseRight[j]);
  264. newMappings.push(both);
  265. }
  266. }
  267. }
  268. //case 2: insLeft->stdRight and insRight->stdLeft
  269. caseLeft = this.deep_findMatch(insLeft, stdRight, false);
  270. caseRight = this.deep_findMatch(insRight, stdLeft, false);
  271. if(caseLeft && caseRight){
  272. for(var i = 0; i < caseLeft.length; i += 1){
  273. var newMap = baseMappings[0].newMergedMap(caseLeft[i]);
  274. for(var j = 0; j < caseRight.length; j += 1){
  275. var both = newMap.newMergedMap(caseRight[j]);
  276. newMappings.push(both);
  277. }
  278. }
  279. }
  280. if(newMappings.length == 0){
  281. return false;
  282. }
  283. return newMappings;
  284. }
  285. return false;
  286. }
  287. StretchyTreeMatcher.prototype.deep_findMatch_generic = function(insNode, stdNode, checkMeta){
  288. var baseMappings = this.shallowMatch(insNode, stdNode, checkMeta);
  289. if (baseMappings){
  290. //base case this runs 0 times because no children
  291. //find each child of insNode that matches IN ORDER
  292. var runningMaps = baseMappings;
  293. var baseSibs = [-1];
  294. var runningSibs = [-1];
  295. var youngestSib = 0;
  296. //for each child
  297. for(var i = 0; i < insNode.children.length; i += 1){
  298. //make a new set of maps
  299. runningMaps = [];
  300. runningSibs = [];
  301. var insChild = insNode.children[i];
  302. //accumulate all potential matches for current child
  303. for(var j = youngestSib; j < stdNode.children.length; j += 1){
  304. var stdChild = stdNode.children[j];
  305. var newMapping = this.deep_findMatch(insChild, stdChild, true);
  306. if (newMapping){
  307. runningMaps.push(newMapping);
  308. runningSibs.push(j);
  309. }
  310. }
  311. var mapUpdate = this.mapMerge(baseMappings, baseSibs, runningMaps, runningSibs);
  312. if (mapUpdate == null){
  313. return false;
  314. }
  315. baseMappings = mapUpdate.newMaps;
  316. baseSibs = mapUpdate.newSibs;
  317. youngestSib = mapUpdate.youngestSib + 1;
  318. }
  319. return baseMappings;
  320. }
  321. return false;
  322. }
  323. StretchyTreeMatcher.prototype.mapMerge = function(baseMaps, baseSibs, runMaps, runSibs){
  324. //no matching nodes were found
  325. if(runMaps.length == 0){
  326. return null;
  327. }
  328. var newMaps = [];
  329. var newSibs = [];
  330. var youngestSib = runSibs[0];
  331. for(var i = 0; i < baseMaps.length; i += 1){
  332. var baseMap = baseMaps[i];
  333. var baseSib = baseSibs[i];
  334. for(var j = 0; j < runMaps.length; j += 1){
  335. var runMap = runMaps[j];
  336. var runSib = runSibs[j];
  337. if(runSib > baseSib){
  338. for(var k = 0; k < runMap.length; k += 1){
  339. var runMapSub = runMap[k];
  340. var newMap = baseMap.newMergedMap(runMapSub);
  341. if(!newMap.hasConflicts()){
  342. newMaps.push(newMap);
  343. newSibs.push(runSib);
  344. }
  345. }
  346. }
  347. }
  348. }
  349. var mapUpdate = null;
  350. if(newMaps.length != 0){
  351. mapUpdate = new Object();
  352. mapUpdate.newMaps = newMaps;
  353. mapUpdate.newSibs = newSibs;
  354. mapUpdate.youngestSib = youngestSib;
  355. }
  356. return mapUpdate;
  357. }
  358. /**
  359. Flexibly matches a module node to a module or a body
  360. @return a mapping of insNode to stdNode, or false if doesn't match
  361. **/
  362. StretchyTreeMatcher.prototype.shallowMatch_Module = function(insNode, stdNode, checkMeta){
  363. if(stdNode.astNode._astname == "Module" || stdNode.field == "body"){
  364. var mapping = new ASTMap();
  365. mapping.addNodePairing(insNode, stdNode);
  366. return [mapping];
  367. }
  368. return false;
  369. }
  370. /**
  371. Matches insNode to stdNode for different cases of encountering a name node in insNode
  372. case 1: _var_ matches if stdNode is a name node and automatically returns a mapping and symbol table
  373. case 2: __exp__ matches to any subtree and automatically returns a mapping and symbol table
  374. case 3: ___ matches to any subtree and automatically returns a mapping
  375. case 4: matches only if the exact names are the same (falls through to shallowMatch_generic)
  376. @return a mapping of insNode to stdNode and possibly a symbolTable, or false if it doesn't match
  377. **/
  378. StretchyTreeMatcher.prototype.shallowMatch_Name = function(insNode, stdNode, checkMeta){
  379. var id = Sk.ffi.remapToJs(insNode.astNode.id);
  380. var varMatch = /^_[^_].*_$/;//regex
  381. var expMatch = /^__.*__$/;//regex
  382. var wildCard =/^___$/;//regex
  383. var mapping = new ASTMap();
  384. var matched = false;
  385. var metaMatched = (checkMeta && insNode.field === stdNode.field) || !checkMeta;
  386. if(varMatch.test(id) && metaMatched){//variable
  387. if(stdNode.astNode._astname == "Name"){
  388. var result = mapping.addVarToSymbolTable(insNode, stdNode);
  389. matched = true;
  390. }//could else return false, but shallowMatch_generic should do this as well
  391. }else if(expMatch.test(id) && metaMatched){//expression
  392. mapping.addExpToSymbolTable(insNode, stdNode);
  393. matched = true;
  394. }else if(wildCard.test(id) && metaMatched){//don't care
  395. matched = true;
  396. }
  397. if(matched){
  398. mapping.addNodePairing(insNode, stdNode);
  399. return [mapping];
  400. }//else
  401. return this.shallowMatch_generic(insNode, stdNode, checkMeta);
  402. }
  403. /**
  404. An empty loop body should match to anything
  405. @return a mappping of insNode to stdNode
  406. **/
  407. StretchyTreeMatcher.prototype.shallowMatch_Pass = function(insNode, stdNode){
  408. var mapping = new ASTMap();
  409. mapping.addNodePairing(insNode, stdNode)
  410. return [mapping];
  411. }
  412. /**
  413. An Expression node should match to anything
  414. @return a mappping of insNode to stdNode
  415. **/
  416. StretchyTreeMatcher.prototype.shallowMatch_Expr = function(insNode, stdNode){
  417. var mapping = new ASTMap();
  418. mapping.addNodePairing(insNode, stdNode)
  419. return [mapping];
  420. }
  421. /**
  422. Checks that all non astNode attributes are equal between insNode and stdNode
  423. @return a mappin gof insNode to stdNode, or false, if the attributes aren't equal
  424. **/
  425. StretchyTreeMatcher.prototype.shallowMatch_generic = function(insNode, stdNode, checkMeta){
  426. var ins = insNode.astNode;
  427. var std = stdNode.astNode;
  428. var insFieldList = iter_fields(ins);
  429. var stdFieldList = iter_fields(std);
  430. var metaMatched = (checkMeta && insNode.field === stdNode.field) || !checkMeta;
  431. var isMatch = insFieldList.length === stdFieldList.length && ins._astname === std._astname && metaMatched;
  432. for (var i = 0; i < insFieldList.length && isMatch; i += 1){
  433. var insField = insFieldList[i][0];
  434. var insValue = insFieldList[i][1];
  435. var stdField = stdFieldList[i][0];
  436. var stdValue = stdFieldList[i][1];
  437. if(insValue === null){
  438. continue;
  439. }
  440. if(!(Array === insValue.constructor)){
  441. insValue = [insValue];
  442. }
  443. if(!(Array === stdValue.constructor)){
  444. stdValue = [stdValue];
  445. }
  446. //isMatch = insValue.length === stdValue.length;//for stretchy matching this isn't true
  447. //Reference ast_node_visitor.js for the original behavior and keep note of it for the purposes of handling the children noting the special case when the nodes of the array are actually parameters of the node (e.g. a load function) instead of a child node
  448. for (var j = 0; j < insValue.length && isMatch; j += 1) {
  449. var insSubvalue = insValue[j];
  450. var stdSubvalue = stdValue[j];
  451. //TODO: make this a smarter comparison
  452. if(isSkBuiltin(insSubvalue) && isSkBuiltin(stdSubvalue)){
  453. //TODO: make this work for actual objects/dictionaries?
  454. isMatch = Sk.ffi.remapToJs(insSubvalue) === Sk.ffi.remapToJs(stdSubvalue);
  455. }
  456. }
  457. }
  458. var mapping = false;
  459. if(isMatch){
  460. mapping = new ASTMap();//return MAPPING
  461. mapping.addNodePairing(insNode, stdNode);
  462. mapping = [mapping];
  463. }
  464. return mapping;
  465. }
  466. //filter function for various types of nodes
  467. StretchyTreeMatcher.prototype.shallowMatch = function(insNode, stdNode, checkMeta){
  468. var method_name = 'shallowMatch_' + insNode.astNode._astname;
  469. if (method_name in this){
  470. return this[method_name](insNode, stdNode, checkMeta);
  471. }//else
  472. return this.shallowMatch_generic(insNode, stdNode, checkMeta);
  473. }