Пример #1
0
def test_long_lines_false():
    ''' Tests that the long_lines method returns false with fortran
    input which has all lines shorter than the specified maximum'''
    input_string = ("! line1\n" "! " + "line2" * 5 + "\n" + "! line3\n")
    fll = FortLineLength(line_length=30)
    assert not fll.long_lines(input_string),\
        "long_lines_false test should return False"
Пример #2
0
def test_long_lines_true():
    ''' Tests that the long_lines method returns true with fortran
    input which has at least one line longer than the specified
    maximum'''
    input_string = ("! line1\n" "! " + "line2" * 6 + "\n" "! line3\n")
    fll = FortLineLength(line_length=30)
    assert fll.long_lines(input_string),\
        "long_lines_true test should return True"
Пример #3
0
def check_line_length(filename):
    '''Check that the code contained within the filename file
    conforms to the 132 line length limit.

    :param str filename: The file containing the code.

    :raises InternalError: if the specified file can not be opened or read.
    :raises ParseError: if one of more lines are longer than the 132 \
                        line length limit.
    '''
    fll = FortLineLength()
    try:
        with io.open(filename, "r", encoding='utf8') as myfile:
            code_str = myfile.read()
    except IOError as excinfo:
        raise InternalError("In utils.py:check_line_length: {0}".format(
            str(excinfo)))

    if fll.long_lines(code_str):
        raise ParseError("the file does not conform to the specified {0} line "
                         "length limit".format(str(fll.length)))
Пример #4
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)