Repository: DmitrySoshnikov/regexp-tree
Pull request #72 (2017-05-05 23:21:11)

better disjunction processor
Sorry, just forgot about that simple case: `/a|b|c/` -> `/[abc]/`.

Also here is another feature - group merge: `/[12]|3|4/` -> `/[1234]/`

And also chars are now sorted, so `/[43]|a|b|[53]|c|[9]/` will be optimized to `/[3459abc]/`

DmitrySoshnikov: @silentroach, would you like to proceed with this PR? Or I can merge it, and do a follow up with fixes.
silentroach: Yep, I'll fix it today, sorry for delay

Patch:
diff --git a/package.json b/package.json
index 57fde43..3276630 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "regexp-tree",
-  "version": "0.0.48",
+  "version": "0.0.47",
   "license": "MIT",
   "description": "Regular Expressions parser in JavaScript",
   "repository": "DmitrySoshnikov/regexp-tree",
diff --git a/src/optimizer/transforms/group-single-chars-to-char-class.js b/src/optimizer/transforms/group-single-chars-to-char-class.js
index 13adb97..8169b8f 100644
--- a/src/optimizer/transforms/group-single-chars-to-char-class.js
+++ b/src/optimizer/transforms/group-single-chars-to-char-class.js
@@ -8,17 +8,23 @@
 /**
  * A regexp-tree plugin to replace single char group disjunction to char group
  *
+ * a|b|c -> [abc]
+ * [12]|3|4 -> [1234]
  * (a|b|c) -> ([abc])
  * (?:a|b|c) -> [abc]
  */
 module.exports = {
-  Group(path) {
-    const {node} = path;
+  Disjunction(path) {
+    const {node, parent} = path;
+
+    if (!handlers[parent.type]) {
+      return;
+    }
 
     const charset = new Map();
 
     if (
-      !shouldProcessExpressionCharset(node.expression, charset) ||
+      !shouldProcess(node, charset) ||
       !charset.size
     ) {
       return;
@@ -26,31 +32,50 @@ module.exports = {
 
     const characterClass = {
       type: 'CharacterClass',
-      expressions: Array.from(charset.values())
+      expressions: Array.from(charset.keys())
+        .sort()
+        .map(key => charset.get(key))
     };
 
+    handlers[parent.type](path.getParent(), characterClass);
+  }
+};
+
+const handlers = {
+  RegExp(path, characterClass) {
+    const {node} = path;
+
+    node.body = characterClass;
+  },
+
+  Group(path, characterClass) {
+    const {node} = path;
+
     if (node.capturing) {
       node.expression = characterClass;
     } else {
       path.replace(characterClass);
     }
-  }
-}
+  },
+};
 
-function shouldProcessExpressionCharset(expression, charset) {
+function shouldProcess(expression, charset) {
   const {type} = expression;
 
   if (type === 'Disjunction') {
     const {left, right} = expression;
 
-    return shouldProcessExpressionCharset(left, charset)
-      && shouldProcessExpressionCharset(right, charset);
+    return shouldProcess(left, charset)
+      && shouldProcess(right, charset);
   } else if (type === 'Char') {
     const {value} = expression;
 
     charset.set(value, expression);
 
     return true;
+  } else if (type === 'CharacterClass') {
+    return expression.expressions
+      .every(expression => shouldProcess(expression, charset));
   }
 
   return false;


Test patch:
diff --git a/src/optimizer/transforms/__tests__/group-single-chars-to-char-class-test.js b/src/optimizer/transforms/__tests__/group-single-chars-to-char-class-test.js
index d692be7..2752a69 100644
--- a/src/optimizer/transforms/__tests__/group-single-chars-to-char-class-test.js
+++ b/src/optimizer/transforms/__tests__/group-single-chars-to-char-class-test.js
@@ -10,6 +10,20 @@ const singleCharsGroupToCharClass = require('../group-single-chars-to-char-class
 
 describe('(a|b|c) -> ([abc])', () => {
 
+  it('replaces single chars disjunction to char class', () => {
+    const re = transform(/a|b|c/, [
+      singleCharsGroupToCharClass,
+    ]);
+    expect(re.toString()).toBe('/[abc]/');
+  });
+
+  it('merges single chars disjunction with character class', () => {
+    const re = transform(/[43]|a|b|[53]|c|[9]/, [
+      singleCharsGroupToCharClass,
+    ]);
+    expect(re.toString()).toBe('/[3459abc]/');
+  });
+
   it('replaces single chars group to char class', () => {
     const re = transform(/(a|b|c)/, [
       singleCharsGroupToCharClass,