Repository: Aunsiels/pyformlang

Issue:
pyformlang.regular_expression.regex_objects.MisformedRegexError: Wrong parenthesis regex Regex
Hi, @Aunsiels!

Thank you for the awesome library! :clap: 

Unfortunately, while using your library, I got the error :bug:  mentioned in the title. :disappointed:

### Environment

* OS: Ubuntu 20.04
* Python: 3.8.10
* Pyformlang: 0.1.26

### How to reproduce

```python
from pyformlang.cfg import CFG
from pyformlang.rsa import RecursiveAutomaton as RSA

rsa = RSA.from_cfg(CFG.from_text("S -> ((a|((b|c))))"))
```

### Possible reason

I think this is due to the fact that instead of a regular expression on the right side, `CFG` breaks it down into products(using `|`) of the form like this:

```python
S -> c))))
S -> ((a
S -> ((b
```

Which will be converted to text like this 

```python
S -> c))))|((a|((b
```

### Possible solution

I can suggest replacing the [`from_cfg()`](https://github.com/Aunsiels/pyformlang/blob/6594ec8fa2efe9dbbc487285b483f146c1c92ee8/pyformlang/rsa/recursive_automaton.py#L162) function with the `from_text()` function, since regex in production body is not supported in `CFG` in the expected way.

But these will be **backward-incompatible changes** and I want to know what you think about this?

Discussion:

Patch:
diff --git a/pyformlang/rsa/recursive_automaton.py b/pyformlang/rsa/recursive_automaton.py
index 747472d..e534cb0 100644
--- a/pyformlang/rsa/recursive_automaton.py
+++ b/pyformlang/rsa/recursive_automaton.py
@@ -12,30 +12,6 @@ from pyformlang.cfg import CFG, Epsilon
 from pyformlang.rsa.box import Box
 
 
-def remove_repetition_of_nonterminals_from_productions(grammar_in_text: str):
-    """ Remove nonterminal repeats on the left side of the rule
-    For example:
-    grammar: S -> a S b
-             S -> a b
-    grammar after function execution: S -> a S b | a b
-    """
-    productions = dict()
-    for production in grammar_in_text.splitlines():
-        if "->" not in production:
-            continue
-
-        head, body = production.split(" -> ")
-        if head in productions:
-            productions[head] += " | " + body
-        else:
-            productions[head] = body
-
-    grammar_new = str()
-    for nonterminal in productions:
-        grammar_new += f'{nonterminal} -> {productions[nonterminal]}\n'
-    return grammar_new[:-1]
-
-
 class RecursiveAutomaton:
     """ Represents a recursive automaton
 
@@ -159,13 +135,15 @@ class RecursiveAutomaton:
         return RecursiveAutomaton({initial_label}, initial_label, {box})
 
     @classmethod
-    def from_cfg(cls, cfg: CFG):
-        """ Create a recursive automaton from context-free grammar
+    def from_text(cls, text, start_symbol: Symbol = Symbol("S")):
+        """ Create a recursive automaton from text
 
         Parameters
         -----------
-        cfg : :class:`~pyformlang.cfg.CFG`
-            The context-free grammar
+        text : str
+            The text of transform
+        start_symbol : str, optional
+            The start symbol, S by default
 
         Returns
         -----------
@@ -173,22 +151,28 @@ class RecursiveAutomaton:
             The new recursive automaton built from context-free grammar
         """
 
-        initial_label = to_symbol(cfg.start_symbol)
-        grammar_in_true_format = remove_repetition_of_nonterminals_from_productions(cfg.to_text())
-
+        productions = dict()
         boxes = set()
         labels = set()
-        notation_for_epsilon = Epsilon().to_text()
-        for production in grammar_in_true_format.splitlines():
+        for production in text.splitlines():
+            if " -> " not in production:
+                continue
+
             head, body = production.split(" -> ")
             labels.add(to_symbol(head))
 
             if body == "":
-                body = notation_for_epsilon
+                body = Epsilon().to_text()
+
+            if head in productions:
+                productions[head] += " | " + body
+            else:
+                productions[head] = body
 
+        for head, body in productions.items():
             boxes.add(Box(Regex(body).to_epsilon_nfa().minimize(), to_symbol(head)))
 
-        return RecursiveAutomaton(labels, initial_label, boxes)
+        return RecursiveAutomaton(labels, start_symbol, boxes)
 
     def is_equivalent_to(self, other):
         """ Check whether two recursive automata are equivalent


Test patch:
diff --git a/pyformlang/rsa/tests/test_rsa.py b/pyformlang/rsa/tests/test_rsa.py
index 308c38c..c80f4a2 100644
--- a/pyformlang/rsa/tests/test_rsa.py
+++ b/pyformlang/rsa/tests/test_rsa.py
@@ -63,16 +63,16 @@ class TestRSA(unittest.TestCase):
         self.assertEqual(new_box.dfa, rsa_1.get_box(Symbol("S")).dfa)
         self.assertEqual(rsa_1.labels, {Symbol("S")})
 
-    def test_from_cfg(self):
+    def test_from_text(self):
         # g1: S -> a S b | a b
-        rsa1_g1 = RecursiveAutomaton.from_cfg(CFG.from_text("S -> a S b | a b"))
+        rsa1_g1 = RecursiveAutomaton.from_text("S -> a S b | a b")
         rsa2_g1 = RecursiveAutomaton.from_regex(Regex("a S b | a b"), Symbol("S"))
 
         self.assertEqual(rsa1_g1, rsa2_g1)
 
         # g2: S -> a V b
         #     V -> c S d | c d
-        rsa1_g2 = RecursiveAutomaton.from_cfg(CFG.from_text("S -> a V b\nV -> c S d | c d"))
+        rsa1_g2 = RecursiveAutomaton.from_text("S -> a V b\nV -> c S d | c d")
         self.assertEqual(rsa1_g2.get_number_of_boxes(), 2)
         self.assertEqual(rsa1_g2.labels, {Symbol("S"), Symbol("V")})