Repository: sybila/eBCSgen

Issue:
Use  complexes directly in `abstract_sequence`
Allow usage of complexes directly in abstract_sequence - currently only aliases in place of complex are allowed

Discussion:

Patch:
diff --git a/eBCSgen/Parsing/ParseBCSL.py b/eBCSgen/Parsing/ParseBCSL.py
index a9ac674..e70646c 100644
--- a/eBCSgen/Parsing/ParseBCSL.py
+++ b/eBCSgen/Parsing/ParseBCSL.py
@@ -156,9 +156,9 @@ GRAMMAR = r"""
 
 EXTENDED_GRAMMAR = """
     abstract_sequence: atomic_complex | atomic_structure_complex | structure_complex
-    atomic_complex: atomic ":" (cmplx_name|VAR)
-    atomic_structure_complex: atomic ":" structure ":" (cmplx_name|VAR)
-    structure_complex: structure ":" (cmplx_name|VAR)
+    atomic_complex: atomic ":" (VAR | value)
+    atomic_structure_complex: atomic ":" structure ":" (VAR | value)
+    structure_complex: structure ":" (VAR | value)
 
     variable: VAR "=" "{" cmplx_name ("," cmplx_name)+ "}"
     VAR: "?"
@@ -401,18 +401,23 @@ class TransformAbstractSyntax(Transformer):
         Raises:
             ComplexParsingError: If no matching struct is found in the complex.
         """
-        for i in range(len(complex.children)):
-            if self.get_name(struct) == self.get_name(complex.children[i].children[0]):
+        if isinstance(complex.children[0].children[0].children[0].children[0], Tree):
+            search = complex.children[0]
+        else:
+            search = complex
+
+        for i in range(len(search.children)):
+            if self.get_name(struct) == self.get_name(search.children[i].children[0]):
                 struct_found = True
                 # search same name structs - if they contain atomics with matching names, they are considered incompatible
                 for j in range(len(struct.children[1].children)):
                     for k in range(
-                        len(complex.children[i].children[0].children[1].children)
+                        len(search.children[i].children[0].children[1].children)
                     ):
                         if self.get_name(
                             struct.children[1].children[j]
                         ) == self.get_name(
-                            complex.children[i].children[0].children[1].children[k]
+                            search.children[i].children[0].children[1].children[k]
                         ):
                             struct_found = False
                             break
@@ -422,13 +427,11 @@ class TransformAbstractSyntax(Transformer):
 
                 if struct_found:
                     # if the complex's struct is empty, replace it with the struct
-                    if self.is_empty(complex.children[i]):
-                        complex.children[i] = Tree("agent", [struct])
+                    if self.is_empty(search.children[i]):
+                        search.children[i] = Tree("agent", [struct])
                     else:
                         # if the complex's struct is not empty merge the struct's children into the complex's struct
-                        complex.children[i].children[0].children[
-                            1
-                        ].children += struct.children[1].children
+                        search.children[i].children[0].children[1].children += struct.children[1].children
                     return complex
 
         raise ComplexParsingError(
@@ -450,10 +453,15 @@ class TransformAbstractSyntax(Transformer):
         Raises:
             ComplexParsingError: If an atomic with the same name is already present in the complex.
         """
-        for i in range(len(complex.children)):
-            if self.get_name(atomic) == self.get_name(complex.children[i].children[0]):
-                if self.is_empty(complex.children[i].children[0]):
-                    complex.children[i] = Tree("agent", [atomic])
+        if isinstance(complex.children[0].children[0].children[0].children[0], Tree):
+            search = complex.children[0]
+        else:
+            search = complex
+
+        for i in range(len(search.children)):
+            if self.get_name(atomic) == self.get_name(search.children[i].children[0]):
+                if self.is_empty(search.children[i].children[0]):
+                    search.children[i] = Tree("agent", [atomic])
                     return complex
         raise ComplexParsingError(
             f"Illegal atomic nesting or duplication: {atomic}:{complex}", complex


Test patch:
diff --git a/Testing/models/model_cmplx_in_abstr_seq1.txt b/Testing/models/model_cmplx_in_abstr_seq1.txt
new file mode 100644
index 0000000..f03c61d
--- /dev/null
+++ b/Testing/models/model_cmplx_in_abstr_seq1.txt
@@ -0,0 +1,5 @@
+#! rules
+S{i}:A():A2::cell  => A()::cell
+
+#! complexes
+A2 = A().A()
diff --git a/Testing/models/model_cmplx_in_abstr_seq2.txt b/Testing/models/model_cmplx_in_abstr_seq2.txt
new file mode 100644
index 0000000..801026b
--- /dev/null
+++ b/Testing/models/model_cmplx_in_abstr_seq2.txt
@@ -0,0 +1,2 @@
+#! rules
+S{i}:A():A().A()::cell => A()::cell
diff --git a/Testing/parsing/test_cmplx_in_abstr_seq.py b/Testing/parsing/test_cmplx_in_abstr_seq.py
new file mode 100644
index 0000000..068f47d
--- /dev/null
+++ b/Testing/parsing/test_cmplx_in_abstr_seq.py
@@ -0,0 +1,17 @@
+import pytest
+
+from Testing.models.get_model_str import get_model_str
+import Testing.objects_testing as objects
+
+
+def test_complexes_in_abstract_sequence():
+    # is allowed
+    model = get_model_str("model_cmplx_in_abstr_seq1")
+    ret1 = objects.model_parser.parse(model)
+    assert ret1.success
+
+    # should be allowed
+    model = get_model_str("model_cmplx_in_abstr_seq2")
+    ret2 = objects.model_parser.parse(model)
+    assert ret2.success
+    assert ret1.data == ret2.data