Repository: stfc/PSyclone (python)

Issue:
[fparser2-FE] Add support for Fortran PARAMETER statement.
A Fortran parameter statement (R538) allows the specification of the parameter attribute and initial value, independently of the declaration of a type of a symbol, e.g.

    PARAMETER (MODULUS = MOD (28, 3), NUMBER_OF_SENATORS = 100)

Currently Simon reports that we (silently :-() lose any such statements when processing MOCSY code in MEDUSA.

Pull request description:
(Closes #1558) support out-of-order parameter statements
This PR simply moves the existing functionality for processing PARAMETER statements into a separate routine which can then be called once all the variable declarations have been processed.

Patch:
diff --git a/changelog b/changelog
index f52c414c2..61d166cab 100644
--- a/changelog
+++ b/changelog
@@ -592,6 +592,8 @@
 
 	199) PR #2293 for #2301. Add 'is_independent' property/query to Loop.
 
+	200) PR #2309 for #1558. Add support for out-of-order parameter statements.
+
 release 2.3.1 17th of June 2022
 
 	1) PR #1747 for #1720. Adds support for If blocks to PSyAD.
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index 6b3287ab6..191c3d398 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -2248,6 +2248,63 @@ class Fparser2Reader():
 
         return datatype, init_expr
 
+    def _process_parameter_stmts(self, nodes, parent):
+        '''
+        Examine the supplied list of fparser2 nodes and handle any
+        PARAMETER statements. This is done separately so that it can be
+        performed after all the declarations have been processed (since
+        a PARAMETER statement can come *before* a symbol's declaration.)
+
+        :param nodes: fparser2 AST nodes containing declaration statements.
+        :type nodes: list of :py:class:`fparser.two.utils.Base`
+        :param parent: PSyIR node in which to insert the symbols found.
+        :type parent: :py:class:`psyclone.psyir.nodes.KernelSchedule`
+
+        :raises NotImplementedError: if there are any issues parsing a
+            parameter statement.
+
+        '''
+        for node in nodes:
+            if not isinstance(node, Fortran2003.Implicit_Part):
+                continue
+            for stmt in node.children:
+                if not isinstance(stmt, Fortran2003.Parameter_Stmt):
+                    continue
+                for parameter_def in stmt.children[1].items:
+                    name, expr = parameter_def.items
+                    try:
+                        symbol = parent.symbol_table.lookup(str(name))
+                    except Exception as err:
+                        # If there is any problem put the whole thing
+                        # in a codeblock (as we presume the original
+                        # code is correct).
+                        raise NotImplementedError(
+                            f"Could not parse '{stmt}' because: "
+                            f"{err}.") from err
+
+                    if not isinstance(symbol, DataSymbol):
+                        raise NotImplementedError(
+                            f"Could not parse '{stmt}' because "
+                            f"'{symbol.name}' is not a DataSymbol.")
+                    if isinstance(symbol.datatype, UnknownType):
+                        raise NotImplementedError(
+                            f"Could not parse '{stmt}' because "
+                            f"'{symbol.name}' has an UnknownType.")
+
+                    # Parse its initialization into a dummy Assignment
+                    # (but connected to the parent scope since symbols
+                    # must be resolved)
+                    dummynode = Assignment(parent=parent)
+                    self.process_nodes(parent=dummynode, nodes=[expr])
+
+                    # Add the initialization expression in the symbol
+                    # constant_value attribute
+                    ct_expr = dummynode.children[0].detach()
+                    symbol.initial_value = ct_expr
+                    symbol.is_constant = True
+                    # Ensure the interface to this Symbol is static
+                    symbol.interface = StaticInterface()
+
     def process_declarations(self, parent, nodes, arg_list,
                              visibility_map=None):
         '''
@@ -2433,51 +2490,21 @@ class Fparser2Reader():
                 # These node types are handled separately
                 pass
             elif isinstance(node, Fortran2003.Implicit_Part):
-                for stmt in node.children:
-                    if isinstance(stmt, Fortran2003.Parameter_Stmt):
-                        for parameter_def in stmt.children[1].items:
-                            name, expr = parameter_def.items
-                            try:
-                                symbol = parent.symbol_table.lookup(str(name))
-                            except Exception as err:
-                                # If there is any problem put the whole thing
-                                # in a codeblock (as we presume the original
-                                # code is correct).
-                                raise NotImplementedError(
-                                    f"Could not parse '{stmt}' because: "
-                                    f"{err}.") from err
-
-                            if not isinstance(symbol, DataSymbol):
-                                raise NotImplementedError(
-                                    f"Could not parse '{stmt}' because "
-                                    f"'{symbol.name}' is not a DataSymbol.")
-                            if isinstance(symbol.datatype, UnknownType):
-                                raise NotImplementedError(
-                                    f"Could not parse '{stmt}' because "
-                                    f"'{symbol.name}' has an UnknownType.")
-
-                            # Parse its initialization into a dummy Assignment
-                            # (but connected to the parent scope since symbols
-                            # must be resolved)
-                            dummynode = Assignment(parent=parent)
-                            self.process_nodes(parent=dummynode, nodes=[expr])
-
-                            # Add the initialization expression in the symbol
-                            # constant_value attribute
-                            ct_expr = dummynode.children[0].detach()
-                            symbol.initial_value = ct_expr
-                            symbol.is_constant = True
-                            # Ensure the interface to this Symbol is static
-                            symbol.interface = StaticInterface()
-                    else:
-                        # TODO #1254: We currently silently ignore the rest of
-                        # the Implicit_Part statements
-                        pass
+                # Any PARAMETER statements are handled separately by the
+                # call to _process_parameter_stmts below.
+                # TODO #1254: We currently silently ignore the rest of
+                # the Implicit_Part statements
+                pass
             else:
                 raise NotImplementedError(
                     f"Error processing declarations: fparser2 node of type "
                     f"'{type(node).__name__}' not supported")
 
+        # Process the nodes again, looking for PARAMETER statements. This is
+        # done after the main declarations loop because they modify existing
+        # symbols and can appear in any order.
+        self._process_parameter_stmts(nodes, parent)
+
         # We process the nodes again looking for common blocks. We do this
         # here, after the main declarations loop, because they modify the
         # interface of existing symbols and can appear in any order.


Test patch:
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py b/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py
index a1cd5f7a2..4685639b0 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py
@@ -41,9 +41,10 @@ import pytest
 from fparser.common.readfortran import FortranStringReader
 from fparser.two.Fortran2003 import Specification_Part
 from psyclone.psyir.frontend.fparser2 import Fparser2Reader
-from psyclone.psyir.nodes import Routine, Literal, BinaryOperation, \
-    Container, CodeBlock, Reference
-from psyclone.psyir.symbols import Symbol, StaticInterface
+from psyclone.psyir.nodes import (
+    Routine, Literal, BinaryOperation, Container, CodeBlock, Reference,
+    UnaryOperation)
+from psyclone.psyir.symbols import Symbol, StaticInterface, ScalarType
 
 
 @pytest.mark.usefixtures("f2008_parser")
@@ -230,3 +231,32 @@ MODULE my_mod
   END SUBROUTINE my_sub
 END MODULE my_mod
 '''
+
+
+def test_parameter_before_decln(fortran_reader):
+    '''
+    Test when a PARAMETER statement occurs *before* the named symbol is
+    actually declared.
+    '''
+    psyir = fortran_reader.psyir_from_source('''\
+module test_mod
+  implicit none
+  PARAMETER(MPI_DISPLACEMENT_CURRENT = - 54278278)
+  INTEGER*8 :: MPI_DISPLACEMENT_CURRENT
+  PARAMETER(MPI_TROUBLE = atan(-1.0))
+  real :: mpi_trouble
+contains
+
+  subroutine some_sub()
+  end subroutine some_sub
+end module test_mod
+''')
+    # We should have succeeded in parsing the code and creating a Container.
+    assert isinstance(psyir.children[0], Container)
+    sym = psyir.children[0].symbol_table.lookup("MPI_DISPLACEMENT_CURRENT")
+    # The Symbol should be a runtime constant with an initial value.
+    assert sym.is_constant
+    assert isinstance(sym.initial_value, UnaryOperation)
+    sym2 = psyir.children[0].symbol_table.lookup("MPI_TROUBLE")
+    assert sym2.is_constant
+    assert sym2.datatype.intrinsic == ScalarType.Intrinsic.REAL