Пример #1
0
    def parse(self, alg_filename):
        '''Takes a PSyclone conformant algorithm file as input and outputs a
        parse tree of the code contained therein and an object
        containing information about the 'invoke' calls in the
        algorithm file and any associated kernels within the invoke
        calls. If the NEMO API is being used then the parsed code is
        returned without any additional information about the code.

        :param str alg_filename: The file containing the algorithm code.

        :returns: 2-tuple consisting of the fparser2 parse tree of the \
            algorithm code and an object holding details of the \
            algorithm code and the invokes found within it, unless it \
            is the NEMO API, where the first entry of the tuple is \
            None and the second is the fparser2 parse tree of the \
            code.
        :rtype: (:py:class:`fparser.two.Fortran2003.Program`, \
            :py:class:`psyclone.parse.FileInfo`) or (NoneType, \
            :py:class:`fparser.two.Fortran2003.Program`)

        '''
        self._alg_filename = alg_filename
        if self._line_length:
            # Make sure the code conforms to the line length limit.
            check_line_length(alg_filename)
        alg_parse_tree = parse_fp2(alg_filename)

        if self._api == "nemo":
            # For this API we just parse the NEMO code and return the resulting
            # fparser2 AST with None for the Algorithm AST.
            return None, alg_parse_tree

        return alg_parse_tree, self.invoke_info(alg_parse_tree)
Пример #2
0
def test_parsefp2_invalid_file(tmpdir):
    '''Test that if there is an error finding the input file specified in
    filename argument then an exception is raised in the expected way.

    '''
    with pytest.raises(ParseError) as excinfo:
        _ = parse_fp2(str(tmpdir.join("does_not_exist.f90")))
    assert "Failed to parse file" in str(excinfo.value)
    assert "Error returned was ' [Errno 2] No such file or directory" \
        in str(excinfo.value)
Пример #3
0
def test_parser_invokeinfo_containers(tmpdir, code, name):
    '''Check that the invoke_info() method in the Parser() class works
    with program, module, subroutine and function.

    '''
    parser = Parser()
    alg_filename = str(tmpdir.join("container.f90"))
    with open(alg_filename, "w") as ffile:
        ffile.write(code)
    alg_parse_tree = parse_fp2(alg_filename)
    res = parser.invoke_info(alg_parse_tree)
    assert isinstance(res, FileInfo)
    assert res.name == name
Пример #4
0
def test_parser_invokeinfo_nocode(tmpdir):
    '''Check that the invoke_info() method in the Parser() class raises
    the expected exception if no relevant code (subroutine, module
    etc.) is found in the supplied fparser2 tree.

    '''
    parser = Parser()
    alg_filename = str(tmpdir.join("empty.f90"))
    with open(alg_filename, "w") as ffile:
        ffile.write("")
    alg_parse_tree = parse_fp2(alg_filename)
    with pytest.raises(ParseError) as info:
        parser.invoke_info(alg_parse_tree)
    assert ("Program, module, function or subroutine not found in fparser2 "
            "parse tree.") in str(info.value)
Пример #5
0
def test_parsefp2_invalid_fortran(tmpdir):
    '''Test that if the Fortran contained in the file specified in the
    filename argument then an exception is raised in the expected
    way. *** Create the parse_tree in-place rather than running
    PSyclone. Once created make the parse_tree content invalid using
    monkeypatch.

    '''
    my_file = str(tmpdir.join("invalid.f90"))
    ffile = open(my_file, "w")
    ffile.write("invalid Fortran code")
    ffile.close()
    with pytest.raises(ParseError) as excinfo:
        _ = parse_fp2(my_file)
    assert "Syntax error in file" in str(excinfo.value)
Пример #6
0
def test_parser_invokeinfo_datatypes_self():
    '''Test that the invoke_info method in the Parser class captures the
    required datatype information when the argument is part of a class
    and is referenced via self.

    '''
    alg_filename = os.path.join(
        LFRIC_TEST_PATH, "26.2_mixed_precision_self.f90")
    parser = Parser(kernel_path=LFRIC_TEST_PATH)
    alg_parse_tree = parse_fp2(alg_filename)
    info = parser.invoke_info(alg_parse_tree)
    args = info.calls[0].kcalls[0].args
    assert args[0]._datatype == ("r_solver_operator_type", None)
    assert args[1]._datatype == ("r_solver_field_type", None)
    assert args[2]._datatype == ("real", "r_solver")
    assert args[3]._datatype == ("quadrature_xyoz_type", None)
Пример #7
0
def test_parser_invokeinfo_internalerror():
    '''Test that the invoke_info method in the Parser class raises the
    expected exception if an unexpected child of Type_Declaration_Stmt
    or Data_Component_Def_Stmt is found.

    '''
    alg_filename = os.path.join(
        LFRIC_TEST_PATH, "26.1_mixed_precision.f90")
    parser = Parser(kernel_path=LFRIC_TEST_PATH)
    alg_parse_tree = parse_fp2(alg_filename)
    # Modify parse tree to make it invalid
    alg_parse_tree.children[0].children[1].children[9].items = ["hello"]
    with pytest.raises(InternalError) as info:
        parser.invoke_info(alg_parse_tree)
    assert (
        "Expected first child of Type_Declaration_Stmt or "
        "Data_Component_Def_Stmt to be Declaration_Type_Spec or "
        "Intrinsic_Type_Spec but found 'str'" in str(info.value))
Пример #8
0
def test_parser_invokeinfo_datatypes():
    '''Test that the invoke_info method in the Parser class
    captures the required datatype information for "standard" fields,
    operators and scalars i.e. defined as field_type, operator_type
    and r_def respectively. We also capture the datatype of quadrature
    but don't care. field_type is actually a vector which shows that
    the code works with arrays as well as individual types.

    '''
    alg_filename = os.path.join(LFRIC_TEST_PATH, "10_operator.f90")
    parser = Parser(kernel_path=LFRIC_TEST_PATH)
    alg_parse_tree = parse_fp2(alg_filename)
    info = parser.invoke_info(alg_parse_tree)
    args = info.calls[0].kcalls[0].args
    assert args[0]._datatype == ("operator_type", None)
    assert args[1]._datatype == ("field_type", None)
    assert args[2]._datatype == ("real", "r_def")
    assert args[3]._datatype == ("quadrature_xyoz_type", None)
Пример #9
0
def test_parser_invokeinfo_first(tmpdir):
    '''Check that the invoke_info() method in the Parser() class evaluates
    the first subroutine, module, etc if more than one exist in the
    supplied fparser2 tree.

    '''
    parser = Parser()
    alg_filename = str(tmpdir.join("two_routines.f90"))
    with open(alg_filename, "w") as ffile:
        ffile.write(
            "subroutine first()\n"
            "end subroutine first\n"
            "subroutine second()\n"
            "end subroutine second\n")
    alg_parse_tree = parse_fp2(alg_filename)
    res = parser.invoke_info(alg_parse_tree)
    assert isinstance(res, FileInfo)
    assert res.name == "first"
Пример #10
0
def test_parser_invokeinfo_datatypes_clash():
    '''Test that the invoke_info method in the Parser class
    allows multiple symbols with the same name and type but raises an
    exception if a symbol has the same name but a different type. This
    is simply a limitation of the current implementation as we do not
    capture the context of a symbol so do not deal with variable
    scope. This limitation will disapear when the PSyIR is used to
    determine datatypes, see issue #753.

    '''
    alg_filename = os.path.join(
        LFRIC_TEST_PATH, "26.3_mixed_precision_error.f90")
    parser = Parser(kernel_path=LFRIC_TEST_PATH)
    alg_parse_tree = parse_fp2(alg_filename)
    with pytest.raises(NotImplementedError) as info:
        parser.invoke_info(alg_parse_tree)
    assert ("The same symbol 'a' is used for different datatypes, 'real, "
            "r_solver' and 'real, None'. This is not currently supported."
            in str(info.value))
Пример #11
0
def test_parser_invokeinfo_structure_error():
    '''Test that the invoke_info method in the Parser class
    provides None as the datatype to the associated Arg class if an
    argument to an invoke is a structure that comes from a use
    statement (as we then do not know its datatype), but that the
    datatype for a structure is found if the structure is declared
    within the code.

    '''
    alg_filename = os.path.join(
        LFRIC_TEST_PATH, "26.5_mixed_precision_structure.f90")
    parser = Parser(kernel_path=LFRIC_TEST_PATH)
    alg_parse_tree = parse_fp2(alg_filename)
    info = parser.invoke_info(alg_parse_tree)
    args = info.calls[0].kcalls[0].args
    assert args[0]._datatype is None
    assert args[1]._datatype == ("r_solver_field_type", None)
    assert args[2]._datatype == ("real", "r_def")
    assert args[3]._datatype == ("quadrature_xyoz_type", None)
Пример #12
0
def test_parser_invokeinfo_use_error():
    '''Test that the invoke_info method in the Parser class
    provides None as the datatype to the associated Arg class if an
    argument to an invoke comes from a use statement (as we then do
    not know its datatype). Also check for the same behaviour if the
    variable is not declared at all i.e. is included via a wildcard
    use statement, or implicit none is not specified.

    '''
    alg_filename = os.path.join(
        LFRIC_TEST_PATH, "26.4_mixed_precision_use.f90")
    parser = Parser(kernel_path=LFRIC_TEST_PATH)
    alg_parse_tree = parse_fp2(alg_filename)
    info = parser.invoke_info(alg_parse_tree)
    args = info.calls[0].kcalls[0].args
    assert args[0]._datatype is None
    assert args[1]._datatype == ("r_solver_field_type", None)
    assert args[2]._datatype is None
    assert args[3]._datatype == ("quadrature_xyoz_type", None)
Пример #13
0
def test_parser_invokeinfo_datatypes_mixed():
    '''Test that the invoke_info method in the Parser class captures the
    required datatype information with mixed-precision fields,
    operators and scalars e.g. defined as r_solver_field_type,
    r_solver_operator_type and r_solver respectively.

    Also tests that the datatype information is always lower case
    irrespective of the case of the declaration and argument. This
    covers the situation where the variable is declared and used with
    different case e.g. real a\n call invoke(kern(A))

    '''
    alg_filename = os.path.join(
        LFRIC_TEST_PATH, "26.1_mixed_precision.f90")
    parser = Parser(kernel_path=LFRIC_TEST_PATH)
    alg_parse_tree = parse_fp2(alg_filename)
    info = parser.invoke_info(alg_parse_tree)
    args = info.calls[0].kcalls[0].args
    assert args[0]._datatype == ("r_solver_operator_type", None)
    assert args[1]._datatype == ("r_solver_field_type", None)
    assert args[2]._datatype == ("real", "r_solver")
    assert args[3]._datatype == ("quadrature_xyoz_type", None)
Пример #14
0
    def parse(self, alg_filename):
        '''Takes a PSyclone conformant algorithm file as input and outputs a
        parse tree of the code contained therein and an object containing
        information about the 'invoke' calls in the algorithm file and any
        associated kernels within the invoke calls.

        :param str alg_filename: The file containing the algorithm code.
        :returns: 2-tuple consisting of the fparser2 parse tree of the \
        algorithm code and an object holding details of the algorithm \
        code and the invokes found within it.
        :rtype: (:py:class:`fparser.two.Fortran2003.Program`, \
                 :py:class:`psyclone.parse.FileInfo`)
        :raises ParseError: if a program, module, subroutine or \
        function is not found in the input file.

        '''
        self._alg_filename = alg_filename

        if self._line_length:
            # Make sure the code conforms to the line length limit.
            check_line_length(alg_filename)

        alg_parse_tree = parse_fp2(alg_filename)

        if self._api == "nemo":
            # For this API we just parse the NEMO code and return the resulting
            # fparser2 AST with None for the Algorithm AST.
            return None, alg_parse_tree

        # Find the first program, module, subroutine or function in the
        # parse tree. The assumption here is that the first is the one
        # that is required. See issue #307.
        container_name = None
        for child in alg_parse_tree.content:
            if isinstance(child, (Main_Program, Module, Subroutine_Subprogram,
                                  Function_Subprogram)):
                container_name = str(child.content[0].items[1])
                break

        if not container_name:
            # Nothing relevant found.
            raise ParseError(
                "algorithm.py:parser:parse: Program, module, function or "
                "subroutine not found in parse tree for file "
                "'{0}'".format(alg_filename))

        self._unique_invoke_labels = []
        self._arg_name_to_module_name = OrderedDict()
        invoke_calls = []

        for statement in walk(alg_parse_tree.content):

            if isinstance(statement, Use_Stmt):
                # found a Fortran use statement
                self.update_arg_to_module_map(statement)

            if isinstance(statement, Call_Stmt):
                # found a Fortran call statement
                call_name = str(statement.items[0])
                if call_name.lower() == self._invoke_name.lower():
                    # The call statement is an invoke
                    invoke_call = self.create_invoke_call(statement)
                    invoke_calls.append(invoke_call)

        return alg_parse_tree, FileInfo(container_name, invoke_calls)