Source: diff/match/HashMatcher.js

  1. import {persistBestMatches} from './BestMatchPersister.js';
  2. import {DiffConfig} from '../../config/DiffConfig.js';
  3. /**
  4. * A matching module that employs hashing to find robust matches efficiently.
  5. * @implements {MatcherInterface}
  6. */
  7. export class HashMatcher {
  8. /**
  9. * Extend the matching with matches between subtrees with an identical
  10. * hash value. If multiple subtrees have the same value, the first pair with
  11. * the lowest positional comparison value is matched.
  12. * @param {Node} oldTree The root of the old (original) process tree
  13. * @param {Node} newTree The root of the new (changed) process tree
  14. * @param {Matching} matching The existing matching to be extended
  15. * @param {Comparator} comparator The comparator used for comparisons.
  16. */
  17. match(oldTree, newTree, matching, comparator) {
  18. const oldNodes =
  19. oldTree
  20. .nonPropertyNodes()
  21. .filter((node) => !matching.isMatched(node));
  22. const newNodes =
  23. newTree
  24. .nonPropertyNodes()
  25. .filter((node) => !matching.isMatched(node))
  26. // Match subtrees in a greedy fashion (starting with the "heaviest")
  27. // to improve performance
  28. .sort((a, b) => comparator.compareSize(b, a));
  29. const hashExtractor = comparator.hashExtractor;
  30. const keyFunction = (node) => hashExtractor.get(node);
  31. const compareFunction =
  32. (oldNode, newNode) => comparator.comparePosition(oldNode, newNode);
  33. /**
  34. * Match all nodes of two subtrees.
  35. * @param {Node} oldRoot
  36. * @param {Node} newRoot
  37. */
  38. const matchFunction = (oldRoot, newRoot) => {
  39. // Don't match positionally dissimilar interrupt nodes
  40. if (oldRoot.isInnterruptLeafNode() &&
  41. compareFunction(oldRoot, newRoot) > DiffConfig.COMPARISON_THRESHOLD) {
  42. return;
  43. }
  44. // found a perfect match, match entire subtrees
  45. const newPreOrder = newRoot.toPreOrderArray();
  46. const oldPreOrder = oldRoot.toPreOrderArray();
  47. // stable sort both arrays because hash may ignore child order of
  48. // certain nodes
  49. newPreOrder.sort((a, b) =>
  50. hashExtractor.get(a) - hashExtractor.get(b));
  51. oldPreOrder.sort((a, b) =>
  52. hashExtractor.get(a) - hashExtractor.get(b));
  53. for (let i = 0; i < newPreOrder.length; i++) {
  54. if (!matching.isMatched(newPreOrder[i]) &&
  55. !matching.isMatched(oldPreOrder[i])) {
  56. matching.matchNew(newPreOrder[i], oldPreOrder[i]);
  57. }
  58. }
  59. };
  60. // every match is accepted when the hash values equal
  61. const threshOldFunction = (CV) => true;
  62. persistBestMatches(
  63. oldNodes,
  64. newNodes,
  65. matching,
  66. keyFunction,
  67. compareFunction,
  68. matchFunction,
  69. threshOldFunction,
  70. );
  71. }
  72. }