Repository: Aunsiels/pyformlang

Issue:
Terminal(X) == Variable(X)
The design of Variables and Terminals as CFGObjects enables comparing them - resulting in "True" if they have the same value. This is highly undesirable (I think?). Terminals and Variables should never be equal...

https://github.com/Aunsiels/pyformlang/blob/04d769ab7da44a7ae80eec49661c0b03fdcd0bac/pyformlang/cfg/variable.py#L22-L24

This breaks i.e. the following (after resolving #32 )
```python
first_lang = rf"""S -> "TER:S" """
second_lang = rf"""S -> a"""
concat = CFG.from_text(first_lang).concatenate(CFG.from_text(second_lang))
assert "Sa" in concat
```

Proposed fix: `CFGObject` -> `Variable`

Hints from issue comments:

Solution patch:
diff --git a/pyformlang/cfg/cfg.py b/pyformlang/cfg/cfg.py
index 4a07efd..867e4f0 100644
--- a/pyformlang/cfg/cfg.py
+++ b/pyformlang/cfg/cfg.py
@@ -1081,16 +1081,18 @@ class CFG:
                     body_component = body_component[5:-1]
                 else:
                     type_component = ""
-                if body_component[0] in string.ascii_uppercase or \
-                        type_component == "VAR":
-                    body_var = Variable(body_component)
-                    variables.add(body_var)
-                    body.append(body_var)
-                elif body_component not in EPSILON_SYMBOLS or type_component \
-                        == "TER":
+                if type_component != "VAR" and (not body_component[0].isupper() or type_component == "TER" or body_component in EPSILON_SYMBOLS):
+                    if body_component in EPSILON_SYMBOLS:
+                        continue
                     body_ter = Terminal(body_component)
                     terminals.add(body_ter)
                     body.append(body_ter)
+                elif type_component != "TER" and (body_component[0].isupper() or type_component == "VAR"):
+                    body_var = Variable(body_component)
+                    variables.add(body_var)
+                    body.append(body_var)
+                else:
+                    raise ValueError(f"Invalid rule definition: {body_component}")
             productions.add(Production(head, body))
 
     def is_normal_form(self):
diff --git a/pyformlang/cfg/variable.py b/pyformlang/cfg/variable.py
index 808ebbd..89cd250 100644
--- a/pyformlang/cfg/variable.py
+++ b/pyformlang/cfg/variable.py
@@ -19,7 +19,7 @@ class Variable(CFGObject):  # pylint: disable=too-few-public-methods
         self.index_cfg_converter = None
 
     def __eq__(self, other):
-        if isinstance(other, CFGObject):
+        if isinstance(other, Variable):
             return self._value == other.value
         return self._value == other
 


Test patch:
diff --git a/pyformlang/cfg/tests/test_cfg.py b/pyformlang/cfg/tests/test_cfg.py
index cb6afde..4220905 100644
--- a/pyformlang/cfg/tests/test_cfg.py
+++ b/pyformlang/cfg/tests/test_cfg.py
@@ -280,6 +280,12 @@ class TestCFG:
         assert not new_cfg.is_empty()
         assert new_cfg.contains([ter_a, ter_a, ter_b, ter_b, ter_a, ter_b])
 
+    def test_concatenation_variable_names(self):
+        first_lang = CFG({Variable("S")}, {Terminal("S")}, Variable("S"), {Production(Variable("S"), [Terminal("S")])})
+        second_lang = CFG({Variable("S")}, {Terminal("a")}, Variable("S"), {Production(Variable("S"), [Terminal("a")])})
+        concat = first_lang.concatenate(second_lang)
+        assert "Sa" in concat
+
     def test_closure(self):
         """ Tests the closure of a cfg """
         var_s = Variable("S")
@@ -769,6 +775,16 @@ class TestCFG:
         assert cfg.contains(["a", "b"])
         assert ["a", "b"] in cfg
 
+    def test_from_text3(self):
+        text = """
+        S  -> "TER:A" "TER:B" "VAR:a"
+        "VAR:a" -> "TER:A" | $
+        """
+        cfg = CFG.from_text(text)
+        assert cfg.contains(["A", "B"])
+        assert cfg.contains(["A", "B", "A"])
+
+
     def test_from_text_union(self):
         text = """
         "VAR:S" -> TER:a | b