Repository: stfc/PSyclone

Issue:
[nemo] fparser2 frontend does not handle REAL intrinsic with two arguments
Currently, processing code such as `REAL(jk, wp)` results in a `CodeBlock` whereas it should give a `BinaryOperation`.

Discussion:

Patch:
diff --git a/changelog b/changelog
index 84a7c5ca1..49d33d6b9 100644
--- a/changelog
+++ b/changelog
@@ -428,6 +428,8 @@
         loop fusion transformation. Moves transformations into separate
         files in the appropriate directories.
 
+	139) PR #1155 for #529. Adds PSyIR support for REAL and INT BinaryOperations.
+
 release 1.9.0 20th May 2020
 
 	1) #602 for #597. Modify Node.ancestor() to optionally include
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index 6a182367e..65dfddae1 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -723,6 +723,8 @@ class Fparser2Reader(object):
         ('.gt.', BinaryOperation.Operator.GT),
         ('.and.', BinaryOperation.Operator.AND),
         ('.or.', BinaryOperation.Operator.OR),
+        ('int', BinaryOperation.Operator.INT),
+        ('real', BinaryOperation.Operator.REAL),
         ('sign', BinaryOperation.Operator.SIGN),
         ('size', BinaryOperation.Operator.SIZE),
         ('sum', BinaryOperation.Operator.SUM),
@@ -3043,6 +3045,7 @@ class Fparser2Reader(object):
             raise NotImplementedError(operator_str)
 
         binary_op = BinaryOperation(operator, parent=parent)
+
         self.process_nodes(parent=binary_op, nodes=[arg_nodes[0]])
         self.process_nodes(parent=binary_op, nodes=[arg_nodes[1]])
 
diff --git a/src/psyclone/psyir/nodes/operation.py b/src/psyclone/psyir/nodes/operation.py
index dd98b47b1..ee6c684fb 100644
--- a/src/psyclone/psyir/nodes/operation.py
+++ b/src/psyclone/psyir/nodes/operation.py
@@ -183,6 +183,7 @@ class BinaryOperation(Operation):
     '''
     Node representing a BinaryOperation expression. As such it has two operands
     as children 0 and 1, and an attribute with the operator type.
+
     '''
     Operator = Enum('Operator', [
         # Arithmetic Operators. ('REM' is remainder AKA 'MOD' in Fortran.)
@@ -193,6 +194,8 @@ class BinaryOperation(Operation):
         'AND', 'OR',
         # Other Maths Operators
         'SIGN', 'MIN', 'MAX',
+        # Casting operators with precise type specified by second argument
+        'REAL', 'INT',
         # Query Operators
         'SIZE', 'LBOUND', 'UBOUND',
         # Matrix and Vector Operators
@@ -220,6 +223,18 @@ class BinaryOperation(Operation):
        :returns: the value of the upper bound of the `index` dimension of \
                  `array`.
 
+    Casting Operators:
+
+    .. function:: REAL(arg0, precision)
+
+       :returns: `arg0` converted to a floating point number of the \
+                 specified precision.
+
+    .. function:: INT(arg0, precision)
+
+       :returns: `arg0` converted to an integer number of the specified \
+                  precision.
+
     Matrix and Vector Operators:
 
     .. function:: MATMUL(array1, array2) -> array


Test patch:
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py
index 32309a358..52d9ab08b 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py
@@ -1681,58 +1681,77 @@ def test_handling_part_ref():
     assert len(new_node.children) == 3  # Array dimensions
 
 
-@pytest.mark.usefixtures("disable_declaration_check", "f2008_parser")
-def test_handling_intrinsics():
+@pytest.fixture(scope="module", name="symbol_table")
+def make_symbol_table():
+    '''
+    pytest fixture to create and populate a symbol table for the
+    'test_handling_intrinsics' test below.
+
+    '''
+    symbol_table = SymbolTable()
+    symbol_table.new_symbol("x", symbol_type=DataSymbol,
+                            datatype=REAL_TYPE)
+    symbol_table.new_symbol("a", symbol_type=DataSymbol,
+                            datatype=REAL_TYPE)
+    symbol_table.new_symbol("b", symbol_type=DataSymbol,
+                            datatype=REAL_TYPE)
+    symbol_table.new_symbol("c", symbol_type=DataSymbol,
+                            datatype=REAL_TYPE)
+    symbol_table.new_symbol("idx", symbol_type=DataSymbol,
+                            datatype=INTEGER_TYPE)
+    symbol_table.new_symbol("mask", symbol_type=DataSymbol,
+                            datatype=ArrayType(INTEGER_TYPE, [10]))
+    return symbol_table
+
+
+@pytest.mark.parametrize(
+    "code, expected_type, expected_op",
+    [('x = exp(a)', UnaryOperation, UnaryOperation.Operator.EXP),
+     ('x = sin(a)', UnaryOperation, UnaryOperation.Operator.SIN),
+     ('x = asin(a)', UnaryOperation, UnaryOperation.Operator.ASIN),
+     ('idx = ceiling(a)', UnaryOperation, UnaryOperation.Operator.CEIL),
+     ('x = abs(a)', UnaryOperation, UnaryOperation.Operator.ABS),
+     ('x = cos(a)', UnaryOperation, UnaryOperation.Operator.COS),
+     ('x = acos(a)', UnaryOperation, UnaryOperation.Operator.ACOS),
+     ('x = tan(a)', UnaryOperation, UnaryOperation.Operator.TAN),
+     ('x = atan(a)', UnaryOperation, UnaryOperation.Operator.ATAN),
+     ('x = real(a)', UnaryOperation, UnaryOperation.Operator.REAL),
+     ('x = real(a, 8)', BinaryOperation, BinaryOperation.Operator.REAL),
+     ('x = int(a)', UnaryOperation, UnaryOperation.Operator.INT),
+     ('x = int(a, 8)', BinaryOperation, BinaryOperation.Operator.INT),
+     ('x = log(a)', UnaryOperation, UnaryOperation.Operator.LOG),
+     ('x = log10(a)', UnaryOperation, UnaryOperation.Operator.LOG10),
+     ('x = mod(a, b)', BinaryOperation, BinaryOperation.Operator.REM),
+     ('x = matmul(a, b)', BinaryOperation,
+      BinaryOperation.Operator.MATMUL),
+     ('x = max(a, b)', BinaryOperation, BinaryOperation.Operator.MAX),
+     ('x = mAx(a, b, c)', NaryOperation, NaryOperation.Operator.MAX),
+     ('x = min(a, b)', BinaryOperation, BinaryOperation.Operator.MIN),
+     ('x = min(a, b, c)', NaryOperation, NaryOperation.Operator.MIN),
+     ('x = sign(a, b)', BinaryOperation, BinaryOperation.Operator.SIGN),
+     ('x = sqrt(a)', UnaryOperation, UnaryOperation.Operator.SQRT),
+     ('x = sum(a, idx)', BinaryOperation, BinaryOperation.Operator.SUM),
+     ('x = suM(a, idx, mask)', NaryOperation, NaryOperation.Operator.SUM),
+     # Check that we get a CodeBlock for an unsupported N-ary operation
+     ('x = reshape(a, b, c)', CodeBlock, None)])
+@pytest.mark.usefixtures("f2008_parser")
+def test_handling_intrinsics(code, expected_type, expected_op, symbol_table):
     ''' Test that fparser2 Intrinsic_Function_Reference nodes are
     handled appropriately.
 
-    TODO #754 fix test so that 'disable_declaration_check' fixture is not
-    required.
     '''
     processor = Fparser2Reader()
-
-    # Test parsing all supported binary operators.
-    testlist = (
-        ('x = exp(a)', UnaryOperation, UnaryOperation.Operator.EXP),
-        ('x = sin(a)', UnaryOperation, UnaryOperation.Operator.SIN),
-        ('x = asin(a)', UnaryOperation, UnaryOperation.Operator.ASIN),
-        ('ix = ceiling(a)', UnaryOperation, UnaryOperation.Operator.CEIL),
-        ('x = abs(a)', UnaryOperation, UnaryOperation.Operator.ABS),
-        ('x = cos(a)', UnaryOperation, UnaryOperation.Operator.COS),
-        ('x = acos(a)', UnaryOperation, UnaryOperation.Operator.ACOS),
-        ('x = tan(a)', UnaryOperation, UnaryOperation.Operator.TAN),
-        ('x = atan(a)', UnaryOperation, UnaryOperation.Operator.ATAN),
-        ('x = real(a)', UnaryOperation, UnaryOperation.Operator.REAL),
-        ('x = real(a, 8)', CodeBlock, None),
-        ('x = int(a)', UnaryOperation, UnaryOperation.Operator.INT),
-        ('x = int(a, 8)', CodeBlock, None),
-        ('x = log(a)', UnaryOperation, UnaryOperation.Operator.LOG),
-        ('x = log10(a)', UnaryOperation, UnaryOperation.Operator.LOG10),
-        ('x = mod(a, b)', BinaryOperation, BinaryOperation.Operator.REM),
-        ('x = matmul(a, b)', BinaryOperation,
-         BinaryOperation.Operator.MATMUL),
-        ('x = mAx(a, b, c)', NaryOperation, NaryOperation.Operator.MAX),
-        ('x = min(a, b)', BinaryOperation, BinaryOperation.Operator.MIN),
-        ('x = min(a, b, c)', NaryOperation, NaryOperation.Operator.MIN),
-        ('x = sign(a, b)', BinaryOperation, BinaryOperation.Operator.SIGN),
-        ('x = sqrt(a)', UnaryOperation, UnaryOperation.Operator.SQRT),
-        ('x = sum(a, idim)', BinaryOperation, BinaryOperation.Operator.SUM),
-        ('x = suM(a, idim, mask)', NaryOperation, NaryOperation.Operator.SUM),
-        # Check that we get a CodeBlock for an unsupported N-ary operation
-        ('x = reshape(a, b, c)', CodeBlock, None),
-    )
-
-    for code, expected_type, expected_op in testlist:
-        fake_parent = Schedule()
-        reader = FortranStringReader(code)
-        fp2node = Execution_Part.match(reader)[0][0]
-        processor.process_nodes(fake_parent, [fp2node])
-        assert len(fake_parent.children) == 1
-        assert isinstance(fake_parent[0].rhs, expected_type), \
+    fake_parent = Schedule(symbol_table=symbol_table)
+    reader = FortranStringReader(code)
+    fp2node = Execution_Part.match(reader)[0][0]
+    processor.process_nodes(fake_parent, [fp2node])
+    assign = fake_parent.children[0]
+    assert isinstance(assign, Assignment)
+    assert isinstance(assign.rhs, expected_type), \
+        "Fails when parsing '" + code + "'"
+    if expected_type is not CodeBlock:
+        assert assign.rhs._operator == expected_op, \
             "Fails when parsing '" + code + "'"
-        if expected_type is not CodeBlock:
-            assert fake_parent[0].rhs._operator == expected_op, \
-                "Fails when parsing '" + code + "'"
 
 
 @pytest.mark.usefixtures("f2008_parser")
diff --git a/src/psyclone/tests/psyir/nodes/type_convert_operation_test.py b/src/psyclone/tests/psyir/nodes/type_convert_operation_test.py
new file mode 100644
index 000000000..e2939c60f
--- /dev/null
+++ b/src/psyclone/tests/psyir/nodes/type_convert_operation_test.py
@@ -0,0 +1,104 @@
+# -----------------------------------------------------------------------------
+# BSD 3-Clause License
+#
+# Copyright (c) 2021, Science and Technology Facilities Council.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+#
+# * Neither the name of the copyright holder nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------------------------
+# Author: A. R. Porter, STFC Daresbury Lab
+# -----------------------------------------------------------------------------
+
+''' Performs pytest tests specific to the REAL/INT type-conversion
+    BinaryOperations. '''
+
+from __future__ import absolute_import
+import pytest
+from psyclone.psyir.nodes import BinaryOperation, Literal, Reference
+from psyclone.psyir.symbols import DataSymbol, INTEGER_SINGLE_TYPE, \
+    REAL_SINGLE_TYPE
+from psyclone.psyir.backend.fortran import FortranWriter
+from psyclone.tests.utilities import check_links
+
+
+@pytest.mark.parametrize("operation, op_str",
+                         [(BinaryOperation.Operator.REAL, "real"),
+                          (BinaryOperation.Operator.INT, "int")])
+def test_type_convert_binaryop_create(operation, op_str):
+    '''Test that the create method in the BinaryOperation class correctly
+    creates a BinaryOperation instance for the REAL and INT type-conversion
+    operations..
+
+    '''
+    sym = DataSymbol("tmp1", REAL_SINGLE_TYPE)
+    lhs = Reference(sym)
+    wp_sym = DataSymbol("wp", INTEGER_SINGLE_TYPE)
+    # Reference to a kind parameter
+    rhs = Reference(wp_sym)
+    binaryoperation = BinaryOperation.create(operation, lhs, rhs)
+    assert binaryoperation._operator is operation
+    check_links(binaryoperation, [lhs, rhs])
+    result = FortranWriter().binaryoperation_node(binaryoperation)
+    assert op_str + "(tmp1, wp)" in result.lower()
+    lhs = Reference(sym)
+    # Kind specified with an integer literal
+    rhs = Literal("4", INTEGER_SINGLE_TYPE)
+    binaryoperation = BinaryOperation.create(operation, lhs, rhs)
+    check_links(binaryoperation, [lhs, rhs])
+    result = FortranWriter().binaryoperation_node(binaryoperation)
+    assert op_str + "(tmp1, 4)" in result.lower()
+    # Kind specified as an arithmetic expression
+    rhs = BinaryOperation.create(BinaryOperation.Operator.ADD,
+                                 Reference(wp_sym),
+                                 Literal("2", INTEGER_SINGLE_TYPE))
+    binaryoperation = BinaryOperation.create(operation, lhs, rhs)
+    check_links(binaryoperation, [lhs, rhs])
+    result = FortranWriter().binaryoperation_node(binaryoperation)
+    assert op_str + "(tmp1, wp + 2)" in result.lower()
+
+
+@pytest.mark.xfail(reason="Only limited checking is performed on the "
+                   "arguments supplied to the BinaryOperation.create() "
+                   "method - TODO #658.")
+def test_real_binaryop_invalid():
+    ''' Test that the create method rejects invalid precisions. '''
+    sym = DataSymbol("tmp1", REAL_SINGLE_TYPE)
+    oper = BinaryOperation.Operator.REAL
+    with pytest.raises(TypeError) as err:
+        _ = BinaryOperation.create(oper, Reference(sym),
+                                   Literal("1.0", REAL_SINGLE_TYPE))
+    assert ("Precision argument to REAL operation must be specified using a "
+            "DataSymbol, ScalarType.PRECISION or integer Literal but got "
+            "xxxx" in str(err.value))
+    # A Symbol of REAL type cannot be used to specify a precision
+    wrong_kind = DataSymbol("not_wp", REAL_SINGLE_TYPE)
+    with pytest.raises(TypeError) as err:
+        _ = BinaryOperation.create(oper, Reference(sym), Reference(wrong_kind))
+    assert ("If the precision argument to a REAL operation is a Reference "
+            "then it must be to a symbol of integer type but got: 'yyyy'" in
+            str(err.value))