Ejemplo n.º 1
0
    def __init__(self, ast, name=None):

        if name is None:
            # if no name is supplied then use the module name to
            # determine the type name. The assumed convention is that
            # the module is called <name/>_mod and the type is called
            # <name/>_type
            found = False
            for statement, _ in fpapi.walk(ast):
                if isinstance(statement, fparser1.block_statements.Module):
                    module_name = statement.name
                    found = True
                    break
            if not found:
                raise ParseError(
                    "Error KernelType, the file does not contain a module. "
                    "Is it a Kernel file?")

            mn_len = len(module_name)
            if mn_len < 5:
                raise ParseError(
                    "Error, module name '{0}' is too short to have '_mod' as "
                    "an extension. This convention is assumed.".format(
                        module_name))
            base_name = module_name.lower()[:mn_len - 4]
            extension_name = module_name.lower()[mn_len - 4:mn_len]
            if extension_name != "_mod":
                raise ParseError(
                    "Error, module name '{0}' does not have '_mod' as an "
                    "extension. This convention is assumed.".format(
                        module_name))
            name = base_name + "_type"

        self._name = name
        self._ast = ast
        self._ktype = get_kernel_metadata(name, ast)
        operates_on = self.get_integer_variable("operates_on")
        # The GOcean API still uses the 'iterates_over' metadata entry
        # although this is deprecated in the LFRic API.
        # Validation is left to the API-specific code in either dynamo0p3.py
        # or gocean1p0.py.
        iterates_over = self.get_integer_variable("iterates_over")
        if operates_on:
            self._iterates_over = operates_on
        elif iterates_over:
            self._iterates_over = iterates_over
        else:
            # We don't raise an error here - we leave it to the API-specific
            # validation code.
            self._iterates_over = None
        # Although validation of the value given to operates_on or
        # iterates_over is API-specifc, we can check that the metadata doesn't
        # specify both of them because that doesn't make sense.
        if operates_on and iterates_over:
            raise ParseError("The metadata for kernel '{0}' contains both "
                             "'operates_on' and 'iterates_over'. Only one of "
                             "these is permitted.".format(name))
        self._procedure = KernelProcedure(self._ktype, name, ast)
        self._inits = getkerneldescriptors(name, self._ktype)
        self._arg_descriptors = []  # this is set up by the subclasses
Ejemplo n.º 2
0
def get_kernel_metadata(name, ast):
    '''Takes the kernel module parse tree and returns the metadata part
    of the parse tree (a Fortran type) with the name 'name'.

    :param str name: the metadata name (of a Fortran type). Also \
    the name referencing the kernel in the algorithm layer. The name \
    provided and the name of the kernel in the parse tree are case \
    insensitive in this function.
    :param ast: parse tree of the kernel module code
    :type ast: :py:class:`fparser.one.block_statements.BeginSource`

    :returns: Parse tree of the metadata (a Fortran type with name \
    'name')
    :rtype: :py:class:`fparser.one.block_statements.Type`

    :raises ParseError: if the metadata type name is not found in \
    the kernel code parse tree

    '''
    ktype = None
    for statement, _ in fpapi.walk(ast, -1):
        if isinstance(statement, fparser1.block_statements.Type) \
           and statement.name.lower() == name.lower():
            ktype = statement
            break
    if ktype is None:
        raise ParseError("Kernel type {0} does not exist".format(name))
    return ktype
Ejemplo n.º 3
0
    def get_integer_variable(self, name):
        ''' Parse the kernel meta-data and find the value of the
        integer variable with the supplied name. Return None if no
        matching variable is found.

        :param str name: the name of the integer variable to find.

        :returns: value of the specified integer variable or None.
        :rtype: str

        :raises ParseError: if the RHS of the assignment is not a Name.

        '''
        # Ensure the Fortran2003 parser is initialised
        _ = ParserFactory().create()

        for statement, _ in fpapi.walk(self._ktype, -1):
            if isinstance(statement, fparser1.typedecl_statements.Integer):
                # fparser only goes down to the statement level. We use
                # fparser2 to parse the statement itself (eventually we'll
                # use fparser2 to parse the whole thing).
                assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0])
                if str(assign.items[0]) == name:
                    if not isinstance(assign.items[2], Fortran2003.Name):
                        raise ParseError(
                            "get_integer_variable: RHS of assignment is not "
                            "a variable name: '{0}'".format(str(assign)))
                    return str(assign.items[2])
        return None
Ejemplo n.º 4
0
    def gen(self):
        '''
        Generate modified algorithm code

        :rtype: ast

        '''
        from fparser import api
        from psyclone.f2pygen import adduse
        psy_name = self._psy.name
        # run through all statements looking for procedure calls
        idx = 0
        for stmt, _ in api.walk(self._ast, -1):

            if isinstance(stmt, fparser.statements.Call):
                if stmt.designator == "invoke":
                    invoke_info = self._psy.invokes.invoke_list[idx]
                    stmt.designator = invoke_info.name
                    stmt.items = invoke_info.alg_unique_args
                    adduse(psy_name,
                           stmt.parent,
                           only=True,
                           funcnames=[invoke_info.name])
                    idx += 1

        if idx == 0:
            raise NoInvokesError(
                "Algorithm file contains no invoke() calls: refusing to "
                "generate empty PSy code")

        return self._ast
Ejemplo n.º 5
0
 def getKernelMetadata(self, name, ast):
     ktype = None
     for statement, depth in fpapi.walk(ast, -1):
         if isinstance(statement, fparser1.block_statements.Type) \
            and statement.name == name:
             ktype = statement
     if ktype is None:
         raise RuntimeError("Kernel type %s does not exist" % name)
     return ktype
Ejemplo n.º 6
0
def test_walk():
    '''
    Test the walk() method of the api module.
    '''
    tree = api.parse(SOURCE_STR,
                     isfree=True,
                     isstrict=False,
                     ignore_comments=False)
    for stmt, depth in api.walk(tree, 1):
        print(depth, stmt.item)
Ejemplo n.º 7
0
def test_walk():
    source_str = '''\
    ! before foo 
    subroutine foo
    integer i, r
    do i = 1,100
      r = r + 1
    end do
    ! after end do
    end subroutine foo
    '''
    tree = api.parse(source_str, isfree=True, isstrict=False, ignore_comments=False)
    for stmt, depth in api.walk(tree, 1):
        print depth, stmt.item
Ejemplo n.º 8
0
 def get_integer_variable(self, name):
     ''' Parse the kernel meta-data and find the value of the
     integer variable with the supplied name. Return None if no
     matching variable is found.'''
     for statement, _ in fpapi.walk(self._ktype, -1):
         if isinstance(statement, fparser1.typedecl_statements.Integer):
             # fparser only goes down to the statement level. We use
             # the expression parser (expression.py) to parse the
             # statement itself.
             assign = expr.FORT_EXPRESSION.parseString(
                 statement.entity_decls[0])
             if assign[0].name == name:
                 return assign[0].value
     return None
Ejemplo n.º 9
0
def test_empty_kernel_name(monkeypatch):
    ''' Check that we raise the correct error when we get a blank string for
    the name of the Kernel subroutine. '''
    import fparser
    mdata = MDATA.replace("procedure, nopass :: code => testkern_eval_code",
                          "procedure, nopass :: testkern_eval_code")
    ast = fpapi.parse(mdata)
    # Break the AST
    for statement, _ in fpapi.walk(ast, -1):
        if isinstance(statement, fparser.one.statements.SpecificBinding):
            monkeypatch.setattr(statement, "name", "")
            break
    with pytest.raises(InternalError) as err:
        _ = KernelType(ast)
    assert ("Empty Kernel name returned for Kernel type testkern_eval_type"
            in str(err))
Ejemplo n.º 10
0
    def get_integer_array(self, name):
        ''' Parse the kernel meta-data and find the values of the
        integer array variable with the supplied name. Returns an empty list
        if no matching variable is found.

        :param str name: the name of the integer array to find.
        :returns: list of values.
        :rtype: list of str.

        :raises InternalError: if we fail to parse the LHS of the array \
                               declaration or the array constructor.
        :raises ParseError: if the RHS of the declaration is not an array \
                            constructor.

        '''
        # Ensure the classes are setup for the Fortran2003 parser
        _ = ParserFactory().create()

        for statement, _ in fpapi.walk(self._ktype, -1):
            if not isinstance(statement, fparser1.typedecl_statements.Integer):
                # This isn't an integer declaration so skip it
                continue
            # fparser only goes down to the statement level. We use fparser2 to
            # parse the statement itself.
            assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0])
            names = walk_ast(assign.items, [Fortran2003.Name])
            if not names:
                raise InternalError(
                    "Unsupported assignment statement: '{0}'".format(
                        str(assign)))
            if str(names[0]) == name:
                # This is the variable declaration we're looking for
                if not isinstance(assign.items[2],
                                  Fortran2003.Array_Constructor):
                    raise ParseError(
                        "get_integer_array: RHS of assignment is not "
                        "an array constructor: '{0}'".format(str(assign)))
                # fparser2 AST for Array_Constructor is:
                # Array_Constructor('[', Ac_Value_List(',', (Name('w0'),
                #                                      Name('w1'))), ']')
                # Construct a list of the names in the array constructor
                names = walk_ast(assign.items[2].items, [Fortran2003.Name])
                if not names:
                    raise InternalError("Failed to parse array constructor: "
                                        "'{0}'".format(str(assign.items[2])))
                return [str(name) for name in names]
        return []
Ejemplo n.º 11
0
 def get_procedure(ast, name, modast):
     bname = None
     for statement in ast.content:
         if isinstance(statement, fparser1.statements.SpecificBinding):
             if statement.name == "code" and statement.bname != "":
                 # prototype gungho style
                 bname = statement.bname
             elif statement.name.lower() != "code" \
                     and statement.bname != "":
                 raise ParseError(
                     "Kernel type %s binds to a specific procedure but "
                     "does not use 'code' as the generic name." % name)
             else:
                 # psyclone style
                 bname = statement.name
     if bname is None:
         raise RuntimeError(
             "Kernel type %s does not bind a specific procedure" % name)
     if bname == '':
         raise ParseError(
             "Internal error: empty kernel name returned for Kernel type "
             "%s." % name)
     code = None
     default_public = True
     declared_private = False
     declared_public = False
     for statement, depth in fpapi.walk(modast, -1):
         if isinstance(statement, fparser1.statements.Private):
             if len(statement.items) == 0:
                 default_public = False
             elif bname in statement.items:
                 declared_private = True
         if isinstance(statement, fparser1.statements.Public):
             if len(statement.items) == 0:
                 default_public = True
             elif bname in statement.items:
                 declared_public = True
         if isinstance(statement, fparser1.block_statements.Subroutine) \
            and statement.name == bname:
             if statement.is_public():
                 declared_public = True
             code = statement
     if code is None:
         raise RuntimeError("Kernel subroutine %s not implemented" % bname)
     if declared_private or (not default_public and not declared_public):
         raise ParseError("Kernel subroutine '%s' is not public" % bname)
     return code, bname
Ejemplo n.º 12
0
def test_walk():
    source_str = '''\
    ! before foo 
    subroutine foo
    integer i, r
    do i = 1,100
      r = r + 1
    end do
    ! after end do
    end subroutine foo
    '''
    tree = api.parse(source_str,
                     isfree=True,
                     isstrict=False,
                     ignore_comments=False)
    for stmt, depth in api.walk(tree, 1):
        print(depth, stmt.item)
Ejemplo n.º 13
0
 def checkMetadataPublic(self, name, ast):
     default_public = True
     declared_private = False
     declared_public = False
     for statement, depth in fpapi.walk(ast, -1):
         if isinstance(statement, fparser1.statements.Private):
             if len(statement.items) == 0:
                 default_public = False
             elif name in statement.items:
                 declared_private = True
         if isinstance(statement, fparser1.statements.Public):
             if len(statement.items) == 0:
                 default_public = True
             elif name in statement.items:
                 declared_public = True
         if isinstance(statement, fparser1.block_statements.Type) \
            and statement.name == name and statement.is_public():
             declared_public = True
     if declared_private or (not default_public and not declared_public):
         raise ParseError("Kernel type '%s' is not public" % name)
Ejemplo n.º 14
0
    def __init__(self, ast, name=None):

        if name is None:
            # if no name is supplied then use the module name to
            # determine the type name. The assumed convention is that
            # the module is called <name/>_mod and the type is called
            # <name/>_type
            found = False
            for statement, depth in fpapi.walk(ast, -1):
                if isinstance(statement, fparser1.block_statements.Module):
                    module_name = statement.name
                    found = True
                    break
            if not found:
                raise ParseError(
                    "Error KernelType, the file does not contain a module. "
                    "Is it a Kernel file?")

            mn_len = len(module_name)
            if mn_len < 5:
                raise ParseError(
                    "Error, module name '{0}' is too short to have '_mod' as "
                    "an extension. This convention is assumed.".format(
                        module_name))
            base_name = module_name.lower()[:mn_len - 4]
            extension_name = module_name.lower()[mn_len - 4:mn_len]
            if extension_name != "_mod":
                raise ParseError(
                    "Error, module name '{0}' does not have '_mod' as an "
                    "extension. This convention is assumed.".format(
                        module_name))
            name = base_name + "_type"

        self._name = name
        self._ast = ast
        self.checkMetadataPublic(name, ast)
        self._ktype = self.getKernelMetadata(name, ast)
        self._iterates_over = self.get_integer_variable("iterates_over")
        self._procedure = KernelProcedure(self._ktype, name, ast)
        self._inits = self.getkerneldescriptors(self._ktype)
        self._arg_descriptors = None  # this is set up by the subclasses
Ejemplo n.º 15
0
def get_kernel_interface(name, ast):
    '''Takes the kernel module parse tree and returns the interface part
    of the parse tree.

    :param str name: The kernel name
    :param ast: parse tree of the kernel module code
    :type ast: :py:class:`fparser.one.block_statements.BeginSource`

    :returns: Name of the interface block and the names of the module \
              procedures (lower case). Or None, None if there is no \
              interface or the interface has no nodule procedures.
    :rtype: : `str`, list of str`.

    :raises ParseError: if more than one interface is found.
    '''

    iname = None
    sname = None
    count = 0
    for statement, _ in fpapi.walk(ast):
        if isinstance(statement, fparser1.block_statements.Interface):
            # count the interfaces, then can be only one!
            count = count + 1
            if count >= 2:
                raise ParseError("Module containing kernel {0} has more than "
                                 "one interface, this is forbidden in the "
                                 "LFRic API.".format(name))
            # Check the interfaces assigns one or more module procedures.
            if statement.a.module_procedures:
                iname = statement.name.lower()
                # If implicit interface (no name) set to none as there is no
                # procedure name for PSyclone to use.
                if iname == '':
                    iname = None
                else:
                    sname = [
                        str(sname).lower()
                        for sname in statement.a.module_procedures
                    ]
    return iname, sname
Ejemplo n.º 16
0
def parse(alg_filename,
          api="",
          invoke_name="invoke",
          inf_name="inf",
          kernel_path="",
          line_length=False,
          distributed_memory=config.DISTRIBUTED_MEMORY):
    '''Takes a GungHo algorithm specification as input and outputs an AST of
    this specification and an object containing information about the
    invocation calls in the algorithm specification and any associated kernel
    implementations.

    :param str alg_filename: The file containing the algorithm specification.
    :param str invoke_name: The expected name of the invocation calls in the
                            algorithm specification
    :param str inf_name: The expected module name of any required
                         infrastructure routines.
    :param str kernel_path: The path to search for kernel source files (if
                            different from the location of the algorithm
                            source).
    :param bool line_length: A logical flag specifying whether we
                             care about line lengths being longer
                             than 132 characters. If so, the input
                             (algorithm and kernel) code is checked
                             to make sure that it conforms and an
                             error raised if not. The default is
                             False.
    :rtype: ast,invoke_info
    :raises IOError: if the filename or search path does not exist
    :raises ParseError: if there is an error in the parsing
    :raises RuntimeError: if there is an error in the parsing

    For example:

    >>> from parse import parse
    >>> ast,info=parse("argspec.F90")

    '''

    if distributed_memory not in [True, False]:
        raise ParseError(
            "The distributed_memory flag in parse() must be set to"
            " 'True' or 'False'")
    config.DISTRIBUTED_MEMORY = distributed_memory
    if api == "":
        from psyclone.config import DEFAULTAPI
        api = DEFAULTAPI
    else:
        check_api(api)

    # Get the names of the supported Built-in operations for this API
    builtin_names, builtin_defs_file = get_builtin_defs(api)

    # drop cache
    fparser1.parsefortran.FortranParser.cache.clear()
    fparser.logging.disable('CRITICAL')
    if not os.path.isfile(alg_filename):
        raise IOError("File %s not found" % alg_filename)
    try:
        ast = fpapi.parse(alg_filename, ignore_comments=False, analyze=False)
        # ast includes an extra comment line which contains file
        # details. This line can be long which can cause line length
        # issues. Therefore set the information (name) to be empty.
        ast.name = ""
    except:
        import traceback
        traceback.print_exc()
        raise ParseError("Fatal error in external fparser tool")
    if line_length:
        fll = FortLineLength()
        with open(alg_filename, "r") as myfile:
            code_str = myfile.read()
        if fll.long_lines(code_str):
            raise ParseError(
                "parse: the algorithm file does not conform to the specified"
                " {0} line length limit".format(str(fll.length)))

    name_to_module = {}
    try:
        from collections import OrderedDict
    except:
        try:
            from ordereddict import OrderedDict
        except:
            import sys
            python_version = sys.version_info
            if python_version[0] <= 2 and python_version[1] < 7:
                raise ParseError(
                    "OrderedDict not provided natively pre python 2.7 "
                    "(you are running {0}. Try installing with 'sudo "
                    "pip install ordereddict'".format(python_version))
            else:
                raise ParseError(
                    "OrderedDict not found which is unexpected as it is "
                    "meant to be part of the Python library from 2.7 onwards")
    invokecalls = OrderedDict()
    # Keep a list of the named invokes so that we can check that the same
    # name isn't used more than once
    unique_invoke_labels = []
    container_name = None
    for child in ast.content:
        if isinstance(child, fparser1.block_statements.Program) or \
           isinstance(child, fparser1.block_statements.Module) or \
           isinstance(child, fparser1.block_statements.Subroutine):
            container_name = child.name
            break
    if container_name is None:
        raise ParseError(
            "Error, program, module or subroutine not found in ast")

    for statement, depth in fpapi.walk(ast, -1):
        if isinstance(statement, fparser1.statements.Use):
            for name in statement.items:
                name_to_module[name] = statement.name
        if isinstance(statement, fparser1.statements.Call) \
           and statement.designator == invoke_name:
            statement_kcalls = []
            invoke_label = None
            for arg in statement.items:
                # We expect each item in an invoke call to be either a
                # call to a kernel or the name of the invoke (specifed
                # as name="my_name")
                try:
                    parsed = expr.FORT_EXPRESSION.parseString(arg)[0]
                except ParseException:
                    raise ParseError("Failed to parse string: {0}".format(arg))

                if isinstance(parsed, expr.NamedArg):
                    if parsed.name.lower() != "name":
                        raise ParseError(
                            "The arguments to an invoke() must be either "
                            "kernel calls or an (optional) name='invoke-name' "
                            "but got '{0}' in file {1}".format(
                                str(parsed), alg_filename))
                    if invoke_label:
                        raise ParseError(
                            "An invoke must contain one or zero 'name=xxx' "
                            "arguments but found more than one in: {0} in "
                            "file {1}".format(str(statement), alg_filename))
                    if not parsed.is_string:
                        raise ParseError(
                            "The (optional) name of an invoke must be "
                            "specified as a string but got {0} in file {1}.".
                            format(str(parsed), alg_filename))
                    # Store the supplied label. Use lower-case (as Fortran
                    # is case insensitive)
                    invoke_label = parsed.value.lower()
                    # Remove any spaces.
                    # TODO check with LFRic team - should we raise an error if
                    # it contains spaces?
                    invoke_label = invoke_label.replace(" ", "_")
                    # Check that the resulting label is a valid Fortran Name
                    from fparser.two import pattern_tools
                    if not pattern_tools.abs_name.match(invoke_label):
                        raise ParseError(
                            "The (optional) name of an invoke must be a "
                            "string containing a valid Fortran name (with "
                            "any spaces replaced by underscores) but "
                            "got '{0}' in file {1}".format(
                                invoke_label, alg_filename))
                    # Check that it's not already been used in this Algorithm
                    if invoke_label in unique_invoke_labels:
                        raise ParseError(
                            "Found multiple named invoke()'s with the same "
                            "name ('{0}') when parsing {1}".format(
                                invoke_label, alg_filename))
                    unique_invoke_labels.append(invoke_label)
                    # Continue parsing the other arguments to this invoke call
                    continue

                argname = parsed.name
                argargs = []
                for a in parsed.args:
                    if type(a) is str:  # a literal is being passed by argument
                        argargs.append(Arg('literal', a))
                    else:  # assume argument parsed as a FunctionVar
                        variableName = a.name
                        if a.args is not None:
                            # argument is an indexed array so extract the
                            # full text
                            fullText = ""
                            for tok in a.walk_skipping_name():
                                fullText += str(tok)
                            argargs.append(
                                Arg('indexed_variable', fullText,
                                    variableName))
                        else:
                            # argument is a standard variable
                            argargs.append(
                                Arg('variable', variableName, variableName))
                if argname in builtin_names:
                    if argname in name_to_module:
                        raise ParseError("A built-in cannot be named in a use "
                                         "statement but '{0}' is used from "
                                         "module '{1}' in file {2}".format(
                                             argname, name_to_module[argname],
                                             alg_filename))
                    # this is a call to a built-in operation. The
                    # KernelTypeFactory will generate appropriate meta-data
                    statement_kcalls.append(
                        BuiltInCall(
                            BuiltInKernelTypeFactory(api=api).create(
                                builtin_names, builtin_defs_file,
                                name=argname), argargs))
                else:
                    try:
                        modulename = name_to_module[argname]
                    except KeyError:
                        raise ParseError(
                            "kernel call '{0}' must either be named in a use "
                            "statement or be a recognised built-in "
                            "(one of '{1}' for this API)".format(
                                argname, builtin_names))

                    # Search for the file containing the kernel source
                    import fnmatch

                    # We only consider files with the suffixes .f90 and .F90
                    # when searching for the kernel source.
                    search_string = "{0}.[fF]90".format(modulename)

                    # Our list of matching files (should have length == 1)
                    matches = []

                    # If a search path has been specified then we look there.
                    # Otherwise we look in the directory containing the
                    # algorithm definition file
                    if len(kernel_path) > 0:
                        cdir = os.path.abspath(kernel_path)

                        if not os.access(cdir, os.R_OK):
                            raise IOError(
                                "Supplied kernel search path does not exist "
                                "or cannot be read: {0}".format(cdir))

                        # We recursively search down through the directory
                        # tree starting at the specified path
                        if os.path.exists(cdir):
                            for root, dirnames, filenames in os.walk(cdir):
                                for filename in fnmatch.filter(
                                        filenames, search_string):
                                    matches.append(os.path.join(
                                        root, filename))

                    else:
                        # We look *only* in the directory that contained the
                        # algorithm file
                        cdir = os.path.abspath(os.path.dirname(alg_filename))
                        filenames = os.listdir(cdir)
                        for filename in fnmatch.filter(filenames,
                                                       search_string):
                            matches.append(os.path.join(cdir, filename))

                    # Check that we only found one match
                    if len(matches) != 1:
                        if len(matches) == 0:
                            raise IOError(
                                "Kernel file '{0}.[fF]90' not found in {1}".
                                format(modulename, cdir))
                        else:
                            raise IOError(
                                "More than one match for kernel file "
                                "'{0}.[fF]90' found!".format(modulename))
                    else:
                        try:
                            modast = fpapi.parse(matches[0])
                            # ast includes an extra comment line which
                            # contains file details. This line can be
                            # long which can cause line length
                            # issues. Therefore set the information
                            # (name) to be empty.
                            modast.name = ""
                        except:
                            raise ParseError(
                                "Failed to parse kernel code "
                                "'{0}'. Is the Fortran correct?".format(
                                    matches[0]))
                        if line_length:
                            fll = FortLineLength()
                            with open(matches[0], "r") as myfile:
                                code_str = myfile.read()
                            if fll.long_lines(code_str):
                                raise ParseError(
                                    "parse: the kernel file '{0}' does not"
                                    " conform to the specified {1} line length"
                                    " limit".format(modulename,
                                                    str(fll.length)))

                    statement_kcalls.append(
                        KernelCall(
                            modulename,
                            KernelTypeFactory(api=api).create(modast,
                                                              name=argname),
                            argargs))
            invokecalls[statement] = InvokeCall(statement_kcalls,
                                                name=invoke_label)
    return ast, FileInfo(container_name, invokecalls)
Ejemplo n.º 17
0
#! /usr/bin/env python

import fparser
from fparser import api

source_str = '''
        subroutine foo
          integer i, r
          do i=1,100
            r = r + i
          end do
        end
        '''
tree = api.parse(source_str)
for stmt, depth in api.walk(tree):
	print depth, stmt.item

Ejemplo n.º 18
0
    def get_procedure(ast, name, modast):
        '''
        Get the name of the subroutine associated with the Kernel. This is
        a type-bound procedure in the meta-data which may take one of three
        forms:
                PROCEDURE, nopass :: code => <proc_name>
        or
                PROCEDURE, nopass :: <proc_name>
        or if there is no type-bound procedure, an interface may be used:
                INTERFACE <proc_name>

        :param ast: the fparser1 parse tree for the Kernel meta-data.
        :type ast: :py:class:`fparser.one.block_statements.Type`
        :param str name: the name of the Fortran type holding the Kernel \
                         meta-data.
        :param modast: the fparser1 parse tree for the module containing the \
                       Kernel routine.
        :type modast: :py:class:`fparser.one.block_statements.BeginSource`

        :returns: 2-tuple of the fparser1 parse tree of the Subroutine \
                  statement and the name of that Subroutine.
        :rtype: (:py:class:`fparser1.block_statements.Subroutine`, str)

        :raises ParseError: if the supplied Kernel meta-data does not \
                            have a type-bound procedure or interface.
        :raises ParseError: if no implementation is found for the \
                             type-bound procedure or interface module \
                             procedures.
        :raises ParseError: if the type-bound procedure specifies a binding \
                            name but the generic name is not "code".
        :raises InternalError: if we get an empty string for the name of the \
                               type-bound procedure.
        '''
        bname = None
        # Search the the meta-data for a SpecificBinding
        for statement in ast.content:
            if isinstance(statement, fparser1.statements.SpecificBinding):
                # We support either:
                # PROCEDURE, nopass :: code => <proc_name> or
                # PROCEDURE, nopass :: <proc_name>
                if statement.bname:
                    if statement.name.lower() != "code":
                        raise ParseError(
                            "Kernel type {0} binds to a specific procedure but"
                            " does not use 'code' as the generic name.".
                            format(name))
                    bname = statement.bname.lower()
                else:
                    bname = statement.name.lower()
                break
        if bname is None:
            # If no type-bound procedure found, search for an explicit
            # interface that has module procedures.
            bname, subnames = get_kernel_interface(name, modast)
            if bname is None:
                # no interface found either
                raise ParseError(
                    "Kernel type {0} does not bind a specific procedure or \
                    provide an explicit interface".format(name))
        elif bname == '':
            raise InternalError(
                "Empty Kernel name returned for Kernel type {0}.".format(name))
        else:
            # add the name of the tbp to the list of strings to search for.
            subnames = [bname]
        # walk the AST to check the subroutine names exist.
        procedure_count = 0
        for subname in subnames:
            for statement, _ in fpapi.walk(modast):
                if isinstance(statement,
                              fparser1.block_statements.Subroutine) \
                              and statement.name.lower() \
                              == subname:
                    procedure_count = procedure_count + 1
                    if procedure_count == 1:
                        # set code to statement if there is one procedure.
                        code = statement
                    else:
                        code = None  # set to None if there is more than one.
                    break
            else:
                raise ParseError(
                    "kernel.py:KernelProcedure:get_procedure: Kernel "
                    "subroutine '{0}' not found.".format(subname))
        return code, bname
Ejemplo n.º 19
0
    def get_integer_array(self, name):
        ''' Parse the kernel meta-data and find the values of the
        integer array variable with the supplied name. Returns an empty list
        if no matching variable is found. The search is not case sensitive.

        :param str name: the name of the integer array to find.

        :return: list of values (lower-case).
        :rtype: list of str.

        :raises InternalError: if we fail to parse the LHS of the array \
                               declaration or the array constructor.
        :raises ParseError: if the array is not of rank 1.
        :raises ParseError: if the array extent is not specified using an \
                            integer literal.
        :raises ParseError: if the RHS of the declaration is not an array \
                            constructor.
        :raises InternalError: if the parse tree for the array constructor \
                               does not have the expected structure.
        :raises ParseError: if the number of items in the array constructor \
                            does not match the extent of the array.

        '''
        # Ensure the classes are setup for the Fortran2008 parser
        _ = ParserFactory().create(std="f2008")
        # Fortran is not case sensitive so nor is our matching
        lower_name = name.lower()

        for statement, _ in fpapi.walk(self._ktype):
            if not isinstance(statement, fparser1.typedecl_statements.Integer):
                # This isn't an integer declaration so skip it
                continue
            # fparser only goes down to the statement level. We use fparser2 to
            # parse the statement itself.
            assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0])
            names = walk(assign.children, Fortran2003.Name)
            if not names:
                raise InternalError("Unsupported assignment statement: '{0}'".
                                    format(str(assign)))

            if str(names[0]).lower() != lower_name:
                # This is not the variable declaration we're looking for
                continue

            if not isinstance(assign.children[0], Fortran2003.Part_Ref):
                # Not an array declaration
                return []

            if not isinstance(assign.children[0].children[1],
                              Fortran2003.Section_Subscript_List):
                raise InternalError(
                    "get_integer_array: expected array declaration to have a "
                    "Section_Subscript_List but found '{0}' for: '{1}'".format(
                        type(assign.children[0].children[1]).__name__,
                        str(assign)))

            dim_stmt = assign.children[0].children[1]
            if len(dim_stmt.children) != 1:
                raise ParseError(
                    "get_integer_array: array must be 1D but found an array "
                    "with {0} dimensions for name '{1}'".format(
                        len(dim_stmt.children), name))
            if not isinstance(dim_stmt.children[0],
                              Fortran2003.Int_Literal_Constant):
                raise ParseError(
                    "get_integer_array: array extent must be specified using "
                    "an integer literal but found '{0}' for array '{1}'".
                    format(str(dim_stmt.children[0]), name))
            # Get the declared size of the array
            array_extent = int(str(dim_stmt.children[0]))

            if not isinstance(assign.children[2],
                              Fortran2003.Array_Constructor):
                raise ParseError(
                    "get_integer_array: RHS of assignment is not "
                    "an array constructor: '{0}'".format(str(assign)))
            # fparser2 AST for Array_Constructor is:
            # Array_Constructor('[', Ac_Value_List(',', (Name('w0'),
            #                                      Name('w1'))), ']')
            # Construct a list of the names in the array constructor
            names = walk(assign.children[2].children, Fortran2003.Name)
            if not names:
                raise InternalError("Failed to parse array constructor: "
                                    "'{0}'".format(str(assign.items[2])))
            if len(names) != array_extent:
                # Ideally fparser would catch this but it isn't yet mature
                # enough.
                raise ParseError(
                    "get_integer_array: declared length of array '{0}' is {1} "
                    "but constructor only contains {2} names: '{3}'".format(
                        name, array_extent, len(names), str(assign)))
            return [str(name).lower() for name in names]
        # No matching declaration for the provided name was found
        return []
Ejemplo n.º 20
0
    def get_procedure(ast, name, modast):
        '''
        Get the name of the subroutine associated with the Kernel. This is
        a type-bound procedure in the meta-data which may take one of two
        forms:
                PROCEDURE, nopass :: code => <proc_name>
        or
                PROCEDURE, nopass :: <proc_name>

        :param ast: the fparser1 parse tree for the Kernel meta-data.
        :type ast: :py:class:`fparser.one.block_statements.Type`
        :param str name: the name of the Fortran type holding the Kernel \
                         meta-data.
        :param modast: the fparser1 parse tree for the module containing the \
                       Kernel routine.
        :type modast: :py:class:`fparser.one.block_statements.BeginSource`

        :returns: 2-tuple of the fparser1 parse tree of the Subroutine \
                  statement and the name of that Subroutine.
        :rtype: (:py:class:`fparser1.block_statements.Subroutine`, str)

        :raises ParseError: if the supplied Kernel meta-data does not \
                            have a type-bound procedure.
        :raises ParseError: if no implementation is found for the \
                             type-bound procedure.
        :raises ParseError: if the type-bound procedure specifies a binding \
                            name but the generic name is not "code".
        :raises InternalError: if we get an empty string for the name of the \
                               type-bound procedure.
        '''
        bname = None
        # Search the the meta-data for a SpecificBinding
        for statement in ast.content:
            if isinstance(statement, fparser1.statements.SpecificBinding):
                # We support either:
                # PROCEDURE, nopass :: code => <proc_name> or
                # PROCEDURE, nopass :: <proc_name>
                if statement.bname:
                    if statement.name.lower() != "code":
                        raise ParseError(
                            "Kernel type {0} binds to a specific procedure but"
                            " does not use 'code' as the generic name.".format(
                                name))
                    bname = statement.bname
                else:
                    bname = statement.name
                break
        if bname is None:
            raise ParseError(
                "Kernel type {0} does not bind a specific procedure".format(
                    name))
        if bname == '':
            raise InternalError(
                "Empty Kernel name returned for Kernel type {0}.".format(name))
        code = None
        for statement, _ in fpapi.walk(modast, -1):
            if isinstance(statement, fparser1.block_statements.Subroutine) \
               and statement.name == bname:
                code = statement
                break
        if not code:
            raise ParseError(
                "kernel.py:KernelProcedure:get_procedure: Kernel subroutine "
                "'{0}' not found.".format(bname))
        return code, bname