combine-repeating-patterns-transform.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /**
  2. * The MIT License (MIT)
  3. * Copyright (c) 2017-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
  4. */
  5. 'use strict';
  6. function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
  7. var NodePath = require('../../traverse/node-path');
  8. var _require = require('../../transform/utils'),
  9. increaseQuantifierByOne = _require.increaseQuantifierByOne;
  10. /**
  11. * A regexp-tree plugin to combine repeating patterns.
  12. *
  13. * /^abcabcabc/ -> /^abc{3}/
  14. * /^(?:abc){2}abc/ -> /^(?:abc){3}/
  15. * /^abc(?:abc){2}/ -> /^(?:abc){3}/
  16. */
  17. module.exports = {
  18. Alternative: function Alternative(path) {
  19. var node = path.node;
  20. // We can skip the first child
  21. var index = 1;
  22. while (index < node.expressions.length) {
  23. var child = path.getChild(index);
  24. index = Math.max(1, combineRepeatingPatternLeft(path, child, index));
  25. if (index >= node.expressions.length) {
  26. break;
  27. }
  28. child = path.getChild(index);
  29. index = Math.max(1, combineWithPreviousRepetition(path, child, index));
  30. if (index >= node.expressions.length) {
  31. break;
  32. }
  33. child = path.getChild(index);
  34. index = Math.max(1, combineRepetitionWithPrevious(path, child, index));
  35. index++;
  36. }
  37. }
  38. };
  39. // abcabc -> (?:abc){2}
  40. function combineRepeatingPatternLeft(alternative, child, index) {
  41. var node = alternative.node;
  42. var nbPossibleLengths = Math.ceil(index / 2);
  43. var i = 0;
  44. while (i < nbPossibleLengths) {
  45. var startIndex = index - 2 * i - 1;
  46. var right = void 0,
  47. left = void 0;
  48. if (i === 0) {
  49. right = child;
  50. left = alternative.getChild(startIndex);
  51. } else {
  52. right = NodePath.getForNode({
  53. type: 'Alternative',
  54. expressions: [].concat(_toConsumableArray(node.expressions.slice(index - i, index)), [child.node])
  55. });
  56. left = NodePath.getForNode({
  57. type: 'Alternative',
  58. expressions: [].concat(_toConsumableArray(node.expressions.slice(startIndex, index - i)))
  59. });
  60. }
  61. if (right.hasEqualSource(left)) {
  62. for (var j = 0; j < 2 * i + 1; j++) {
  63. alternative.getChild(startIndex).remove();
  64. }
  65. child.replace({
  66. type: 'Repetition',
  67. expression: i === 0 && right.node.type !== 'Repetition' ? right.node : {
  68. type: 'Group',
  69. capturing: false,
  70. expression: right.node
  71. },
  72. quantifier: {
  73. type: 'Quantifier',
  74. kind: 'Range',
  75. from: 2,
  76. to: 2,
  77. greedy: true
  78. }
  79. });
  80. return startIndex;
  81. }
  82. i++;
  83. }
  84. return index;
  85. }
  86. // (?:abc){2}abc -> (?:abc){3}
  87. function combineWithPreviousRepetition(alternative, child, index) {
  88. var node = alternative.node;
  89. var i = 0;
  90. while (i < index) {
  91. var previousChild = alternative.getChild(i);
  92. if (previousChild.node.type === 'Repetition' && previousChild.node.quantifier.greedy) {
  93. var left = previousChild.getChild();
  94. var right = void 0;
  95. if (left.node.type === 'Group' && !left.node.capturing) {
  96. left = left.getChild();
  97. }
  98. if (i + 1 === index) {
  99. right = child;
  100. if (right.node.type === 'Group' && !right.node.capturing) {
  101. right = right.getChild();
  102. }
  103. } else {
  104. right = NodePath.getForNode({
  105. type: 'Alternative',
  106. expressions: [].concat(_toConsumableArray(node.expressions.slice(i + 1, index + 1)))
  107. });
  108. }
  109. if (left.hasEqualSource(right)) {
  110. for (var j = i; j < index; j++) {
  111. alternative.getChild(i + 1).remove();
  112. }
  113. increaseQuantifierByOne(previousChild.node.quantifier);
  114. return i;
  115. }
  116. }
  117. i++;
  118. }
  119. return index;
  120. }
  121. // abc(?:abc){2} -> (?:abc){3}
  122. function combineRepetitionWithPrevious(alternative, child, index) {
  123. var node = alternative.node;
  124. if (child.node.type === 'Repetition' && child.node.quantifier.greedy) {
  125. var right = child.getChild();
  126. var left = void 0;
  127. if (right.node.type === 'Group' && !right.node.capturing) {
  128. right = right.getChild();
  129. }
  130. var rightLength = void 0;
  131. if (right.node.type === 'Alternative') {
  132. rightLength = right.node.expressions.length;
  133. left = NodePath.getForNode({
  134. type: 'Alternative',
  135. expressions: [].concat(_toConsumableArray(node.expressions.slice(index - rightLength, index)))
  136. });
  137. } else {
  138. rightLength = 1;
  139. left = alternative.getChild(index - 1);
  140. if (left.node.type === 'Group' && !left.node.capturing) {
  141. left = left.getChild();
  142. }
  143. }
  144. if (left.hasEqualSource(right)) {
  145. for (var j = index - rightLength; j < index; j++) {
  146. alternative.getChild(index - rightLength).remove();
  147. }
  148. increaseQuantifierByOne(child.node.quantifier);
  149. return index - rightLength;
  150. }
  151. }
  152. return index;
  153. }