Repository: stfc/fparser

Issue:
Allocate with mold= parameter
This is valid Fortran, but not accepted by fparser:
```
subroutine a(b1)
   integer, dimension(:,:), allocatable :: b1
   integer, dimension(:,:), allocatable :: a1
   allocate(a1, mold=b1)
end subroutine a
```
Supporting the `mold` argument makes it easier to create the kernel-extraction driver (since we can then allocate variables without having to analyse the number of dimensions). It's not urgent, since I have a work around.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2a372b..6b6df97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,8 @@ Modifications by (in alphabetical order):
 * P. Vitt, University of Siegen, Germany
 * A. Voysey, UK Met Office
 
+13/10/2022 PR #381 for #298. Fix F2008 allocate statement with arguments.
+
 20/09/2022 PR #376 for #349. Add support for use association to the
            symbol table.
 
@@ -30,6 +32,8 @@ Modifications by (in alphabetical order):
 05/09/2022 PR #372 fix for whitespace being lost when Format_Item_List is
            contained within parentheses.
 
+02/09/2022 PR #356 - add support for the mold allocate parameter.
+
 11/08/2022 PR #368 for #367. Add support for visiting tuples in walk()
            utility.
 
@@ -47,8 +51,6 @@ Modifications by (in alphabetical order):
 
 20/06/2022 PR #345 - add fparser2 performance benchmark in the scripts folder.
 
-02/09/2022 PR #356 - add support for the mold allocate parameter.
-
 ## Release 0.0.16 (16/06/2022) ##
 
 14/06/2022 PR #337 towards #312 (performance improvements). Removes some
diff --git a/src/fparser/two/Fortran2003.py b/src/fparser/two/Fortran2003.py
index bc7b7f9..4623784 100644
--- a/src/fparser/two/Fortran2003.py
+++ b/src/fparser/two/Fortran2003.py
@@ -4853,28 +4853,53 @@ class Allocate_Stmt(StmtBase):  # R623
     subclass_names = []
     use_names = ["Type_Spec", "Allocation_List", "Alloc_Opt_List"]
 
-    @staticmethod
-    def match(string):
+    @classmethod
+    def match(cls, string):
+        """
+        Attempts to match the supplied string as an Allocate_Stmt.
+
+        :param str string: the string to attempt to match.
+
+        :returns: A 2-tuple giving the Type_Spec and Allocation_List if the \
+            match is successful, None otherwise.
+        :rtype: Optional[ \
+            Tuple[Optional[:py:class:`fparser.two.Fortran2003.Type_Spec`], \
+                  :py:class:`fparser.two.Fortran2003.Allocation_List`]]
+        """
         if string[:8].upper() != "ALLOCATE":
-            return
+            return None
         line = string[8:].lstrip()
         if not line or line[0] != "(" or line[-1] != ")":
-            return
+            return None
         line, repmap = string_replace_map(line[1:-1].strip())
-        i = line.find("::")
+        idx = line.find("::")
         spec = None
-        if i != -1:
-            spec = Type_Spec(repmap(line[:i].rstrip()))
-            line = line[i + 2 :].lstrip()
-        i = line.find("=")
+        if idx != -1:
+            spec = Type_Spec(repmap(line[:idx].rstrip()))
+            line = line[idx + 2 :].lstrip()
+        idx = line.find("=")
         opts = None
-        if i != -1:
-            j = line[:i].rfind(",")
-            assert j != -1, repr((i, j, line))
-            opts = Alloc_Opt_List(repmap(line[j + 1 :].lstrip()))
-            line = line[:j].rstrip()
+        if idx != -1:
+            jdx = line[:idx].rfind(",")
+            if jdx == -1:
+                # There must be at least one positional argument before any
+                # named arguments.
+                return None
+            # Use the class 'alloc_opt_list' property to ensure we use the
+            # correct class depending on whether 'cls' is associated with
+            # Fortran2003 or Fortran2008.
+            opts = cls.alloc_opt_list()(repmap(line[jdx + 1 :].lstrip()))
+            line = line[:jdx].rstrip()
         return spec, Allocation_List(repmap(line)), opts
 
+    @classmethod
+    def alloc_opt_list(cls):
+        """
+        :returns: the Fortran2003 flavour of Alloc_Opt_List.
+        :rtype: type
+        """
+        return Alloc_Opt_List
+
     def tostr(self):
         spec, lst, opts = self.items
         if spec is not None:
diff --git a/src/fparser/two/Fortran2008.py b/src/fparser/two/Fortran2008.py
index 31a9646..6d5f8ea 100644
--- a/src/fparser/two/Fortran2008.py
+++ b/src/fparser/two/Fortran2008.py
@@ -760,6 +760,14 @@ class Allocate_Stmt(Allocate_Stmt_2003):  # R626
     subclass_names = []
     use_names = ["Type_Spec", "Allocation_List", "Alloc_Opt_List"]
 
+    @classmethod
+    def alloc_opt_list(cls):
+        """
+        :returns: the Fortran2008 flavour of Alloc_Opt_List.
+        :rtype: type
+        """
+        return Alloc_Opt_List
+
 
 class If_Stmt(If_Stmt_2003):  # R837
     """


Test patch:
diff --git a/src/fparser/two/tests/fortran2003/test_allocate_stmt_r623.py b/src/fparser/two/tests/fortran2003/test_allocate_stmt_r623.py
index 1290b23..263fe4c 100644
--- a/src/fparser/two/tests/fortran2003/test_allocate_stmt_r623.py
+++ b/src/fparser/two/tests/fortran2003/test_allocate_stmt_r623.py
@@ -37,13 +37,15 @@
 
 import pytest
 from fparser.two.utils import NoMatchError
-from fparser.two.Fortran2003 import Allocate_Stmt, Alloc_Opt
+from fparser.two.Fortran2003 import Allocate_Stmt, Alloc_Opt, Alloc_Opt_List
 
 
 @pytest.mark.usefixtures("f2003_create")
 def test_allocate_stmt():
     """Tests for the allocate statement: R623."""
     tcls = Allocate_Stmt
+    assert tcls.alloc_opt_list() == Alloc_Opt_List
+
     obj = tcls("allocate(a,b)")
     assert isinstance(obj, tcls), repr(obj)
     assert str(obj) == "ALLOCATE(a, b)"
@@ -55,6 +57,31 @@ def test_allocate_stmt():
     assert str(obj) == "ALLOCATE(REAL(KIND = 8)::a, STAT = b, SOURCE = c // d)"
 
 
+@pytest.mark.usefixtures("f2003_create")
+def test_allocate_no_match():
+    """Tests that the expected NoMatchError is raised if there are problems."""
+    tcls = Allocate_Stmt
+    # Missing parenthesis.
+    with pytest.raises(NoMatchError) as err:
+        tcls("allocate(var(3)")
+    assert "allocate(var(3)" in str(err.value)
+    with pytest.raises(NoMatchError) as err:
+        tcls("allocate var(3))")
+    assert "allocate var(3))" in str(err.value)
+    # Misspelt key word.
+    with pytest.raises(NoMatchError) as err:
+        tcls("allocte(var(3))")
+    assert "allocte(var(3))" in str(err.value)
+    # No arguments.
+    with pytest.raises(NoMatchError) as err:
+        tcls("allocate()")
+    assert "allocate()" in str(err.value)
+    # Missing positional argument.
+    with pytest.raises(NoMatchError) as err:
+        tcls("allocate(stat=ierr)")
+    assert "allocate(stat=ierr)" in str(err.value)
+
+
 @pytest.mark.usefixtures("f2003_create")
 def test_alloc_opt():
     """Tests for the various forms of alloc-opt: R624."""
diff --git a/src/fparser/two/tests/fortran2008/test_alloc_opt_r627.py b/src/fparser/two/tests/fortran2008/test_alloc_opt_r627.py
index 914656c..fd8ec2a 100644
--- a/src/fparser/two/tests/fortran2008/test_alloc_opt_r627.py
+++ b/src/fparser/two/tests/fortran2008/test_alloc_opt_r627.py
@@ -45,7 +45,7 @@ test for.
 """
 
 import pytest
-from fparser.two.Fortran2008 import Allocate_Stmt, Alloc_Opt
+from fparser.two.Fortran2008 import Allocate_Stmt, Alloc_Opt, Alloc_Opt_List
 
 
 @pytest.mark.usefixtures("f2008_create")
@@ -61,5 +61,9 @@ def test_allocate_stmt():
     """Check that the Fortran2008 version of allocate has picked up the
     version of Alloc_Opt that supports MOLD."""
     obj = Allocate_Stmt("allocate(b, mold=c)")
+    assert obj.alloc_opt_list() == Alloc_Opt_List
     assert isinstance(obj, Allocate_Stmt), repr(obj)
     assert str(obj) == "ALLOCATE(b, MOLD = c)"
+    obj = Allocate_Stmt("allocate(b, mold=c, stat=ierr)")
+    assert isinstance(obj, Allocate_Stmt), repr(obj)
+    assert str(obj) == "ALLOCATE(b, MOLD = c, STAT = ierr)"