Repository: sybila/eBCSgen

Issue:
raise error when an undefined complex alias is used in a rule
Raise `ComplexParsingError` when the alias is not defined.

Comments:

Patch:
diff --git a/eBCSgen/Parsing/ParseBCSL.py b/eBCSgen/Parsing/ParseBCSL.py
index 1fc448f..4bb2843 100644
--- a/eBCSgen/Parsing/ParseBCSL.py
+++ b/eBCSgen/Parsing/ParseBCSL.py
@@ -362,6 +362,10 @@ class TransformAbstractSyntax(Transformer):
         self.complex_defns = complex_defns
 
     def cmplx_name(self, matches):
+        if str(matches[0]) not in self.complex_defns:
+            raise ComplexParsingError(
+                f"Complex alias {matches[0]} not found in defined complexes: {list(self.complex_defns.keys())}", matches
+            )
         return deepcopy(self.complex_defns[str(matches[0])])
 
     def abstract_sequence(self, matches):
@@ -544,11 +548,19 @@ class TreeToComplex(Transformer):
 
     def structure(self, matches):
         name = str(matches[0].children[0])
-        if len(matches) > 1:
-            composition = set(matches[1].children)
-            return StructureAgent(name, composition)
-        else:
+        if len(matches) <= 1:
             return StructureAgent(name, set())
+        atomic_names = set()
+        composition = set()
+        for atomic in matches[1].children:
+            if atomic.name in atomic_names:
+                raise ComplexParsingError(
+                    f"Duplicate atomic agent in structure: {atomic.name}", matches
+                )
+            atomic_names.add(atomic.name)
+            composition.add(atomic)
+
+        return StructureAgent(name, composition)
 
     def rate_complex(self, matches):
         sequence = []


Test patch:
diff --git a/Testing/objects_testing.py b/Testing/objects_testing.py
index 71742b5..a324d26 100644
--- a/Testing/objects_testing.py
+++ b/Testing/objects_testing.py
@@ -58,7 +58,7 @@ u2_c1_u = AtomicAgent("U", "u")
 # structure
 s1 = StructureAgent("B", {a1})
 s2 = StructureAgent("D", set())
-s3 = StructureAgent("K", {a1, a3, a5})
+s3 = StructureAgent("K", {a1, a3, a11})
 s4 = StructureAgent("B", {a4})
 s5 = StructureAgent("D", {a5, a6})
 s6 = StructureAgent("K", set())
diff --git a/Testing/parsing/test_complex.py b/Testing/parsing/test_complex.py
index 77d5d35..e16dfb3 100644
--- a/Testing/parsing/test_complex.py
+++ b/Testing/parsing/test_complex.py
@@ -6,12 +6,12 @@ def test_parser():
     assert ret.success
     assert ret.data.children[0] == objects.c1
 
-    ret = objects.rate_complex_parser.parse("B(T{s}).D().K(T{s},S{s},S{_})::cell")
+    ret = objects.rate_complex_parser.parse("B(T{s}).D().K(T{s},S{s},U{a})::cell")
     assert ret.success
     assert ret.data.children[0] == objects.c2
 
     ret = objects.rate_complex_parser.parse(
-        "B(T{s}).K(T{s}, S{s}, S{_}).D(S{_},T{p})::cyt"
+        "B(T{s}).K(T{s}, S{s}, U{a}).D(S{_},T{p})::cyt"
     )
     assert ret.success
     assert ret.data.children[0] == objects.c3
@@ -58,3 +58,12 @@ def test_parser():
 
     ret = objects.rate_complex_parser.parse("B(T{s})::")
     assert not ret.success
+
+    ret = objects.rate_complex_parser.parse("B(T{s}, T{_})::cell")
+    assert not ret.success
+
+    ret = objects.rate_complex_parser.parse("B(T{s}, T{s})::cell")
+    assert not ret.success
+
+    ret = objects.rate_complex_parser.parse("B(T{s}, T{a})::cell")
+    assert not ret.success
diff --git a/Testing/parsing/test_side.py b/Testing/parsing/test_side.py
index 6034958..908aa9b 100644
--- a/Testing/parsing/test_side.py
+++ b/Testing/parsing/test_side.py
@@ -13,13 +13,13 @@ def test_parser():
     assert ret.data.to_side() == objects.side2
 
     ret = objects.side_parser.parse(
-        "B(T{s})::cell + B(T{s}).D().K(T{s},S{s},S{_})::cell + B(T{s}).D().K(T{s},S{s},S{_})::cell"
+        "B(T{s})::cell + B(T{s}).D().K(T{s},S{s},U{a})::cell + B(T{s}).D().K(T{s},S{s},U{a})::cell"
     )
     assert ret.success
     assert ret.data.to_side() == objects.side3
 
     ret = objects.side_parser.parse(
-        "B(T{s})::cell + 2 B(T{s}).D().K(T{s},S{s},S{_})::cell"
+        "B(T{s})::cell + 2 B(T{s}).D().K(T{s},S{s},U{a})::cell"
     )
     assert ret.success
     assert ret.data.to_side() == objects.side3
@@ -48,3 +48,9 @@ def test_parser():
 
     ret = objects.side_parser.parse("B(T{s}")
     assert not ret.success
+
+    # not unique atomics in structure
+    ret = objects.side_parser.parse(
+        "B(T{s})::cell + B(T{s}).D().K(T{s},S{s},S{_})::cell + B(T{s}).D().K(T{s},S{s},S{_})::cell"
+    )
+    assert not ret.success
diff --git a/Testing/parsing/test_structure.py b/Testing/parsing/test_structure.py
index bed18af..a7fbfd4 100644
--- a/Testing/parsing/test_structure.py
+++ b/Testing/parsing/test_structure.py
@@ -4,7 +4,7 @@ import Testing.objects_testing as objects
 def test_parser():
     assert objects.structure_parser.parse("B(T{s})").data == objects.s1
     assert objects.structure_parser.parse("D()").data == objects.s2
-    assert objects.structure_parser.parse("K(T{s}, S{s}, S{_})").data == objects.s3
+    assert objects.structure_parser.parse("K(T{s}, S{s}, U{a})").data == objects.s3
     assert objects.structure_parser.parse("B(T{_})").data == objects.s4
     assert objects.structure_parser.parse("D(S{_},T{p})").data == objects.s5
     assert objects.structure_parser.parse("K()").data == objects.s6
@@ -18,3 +18,6 @@ def test_parser():
     assert not objects.structure_parser.parse("[B(T{s})]").success
     assert not objects.structure_parser.parse("").success
     assert not objects.structure_parser.parse("B({s})").success
+    assert not objects.structure_parser.parse("B(S{s}, S{a})").success
+    assert not objects.structure_parser.parse("B(S{a}, S{a})").success
+    assert not objects.structure_parser.parse("B(S{_}, S{a})").success