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"
def test_comment():
    ''' Tests that a long comment line wrapped as expected '''
    input_file = " ! this is a comment"
    expected_output = " ! this is a \n!& comment"
    fll = FortLineLength(line_length=len(input_file) - 5)
    output_file = fll.process(input_file)
    assert output_file == expected_output
def test_multiple_lines_comment():
    ''' test that multiple lines works as expected for comments '''
    input_file = ("! blahdeblah, blahdeblah, blahdeblah, blahdeblah\n")
    expected_output = (
        "! blahdeblah, \n!& blahdeblah, \n!& blahdeblah, \n!& blahdeblah\n")
    fll = FortLineLength(line_length=18)
    output_file = fll.process(input_file)
    assert output_file == expected_output
def test_fail_to_wrap():
    ''' Tests that we raise an error if we can't find anywhere to wrap
    the line'''
    input_file = "!$OMPPARALLELDO"
    with pytest.raises(Exception) as excinfo:
        fll = FortLineLength(line_length=len(input_file) - 1)
        _ = fll.process(input_file)
    assert 'No suitable break point found' in str(excinfo.value)
def test_acc_directive():
    ''' Tests that we deal with an OpenACC directive appropriately
    when its needs to be line wrapped '''
    input_file = "  !$ACC kernels loop gang(32), vector(16)\n"
    expected_output = "  !$ACC kernels loop gang(32),  &\n!$acc& vector(16)\n"
    fll = FortLineLength(line_length=len(input_file) - 5)
    output_file = fll.process(input_file)
    assert output_file == expected_output
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"
def test_wrapped():
    ''' Tests that a file whose lines are longer than the specified
    line length is wrapped appropriately by the FortLineLength class '''
    fll = FortLineLength(line_length=30)
    output_file = fll.process(INPUT_FILE)
    print "(" + EXPECTED_OUTPUT + ")"
    print "(" + output_file + ")"
    assert output_file == EXPECTED_OUTPUT, "output and expected output differ "
def test_openmp_directive():
    ''' Tests that we successfully break an OpenMP directive line
    on a space '''
    input_file = "  !$OMP PARALLEL LOOP\n"
    expected_output = "  !$OMP PARALLEL  &\n!$omp& LOOP\n"
    fll = FortLineLength(line_length=len(input_file) - 3)
    output_file = fll.process(input_file)
    assert output_file == expected_output
Exemple #9
0
    def _code_compiles(self, psy_ast):
        '''Attempts to build the Fortran code supplied as an AST of
        f2pygen objects. Returns True for success, False otherwise.
        It is meant for internal test uses only, and must only be
        called when compilation is actually enabled (use code_compiles
        otherwse). All files produced are deleted.

        :param psy_ast: The AST of the generated PSy layer
        :type psy_ast: Instance of :py:class:`psyclone.psyGen.PSy`
        :return: True if generated code compiles, False otherwise
        :rtype: bool
        '''

        kernel_modules = set()
        # Get the names of the modules associated with the kernels.
        # By definition, built-ins do not have associated Fortran modules.
        for invoke in psy_ast.invokes.invoke_list:
            for call in invoke.schedule.coded_kernels():
                kernel_modules.add(call.module_name)

        # Change to the temporary directory passed in to us from
        # pytest. (This is a LocalPath object.)
        old_pwd = self._tmpdir.chdir()

        # Create a file containing our generated PSy layer.
        psy_filename = "psy.f90"
        with open(psy_filename, 'w') as psy_file:
            # We limit the line lengths of the generated code so that
            # we don't trip over compiler limits.
            from psyclone.line_length import FortLineLength
            fll = FortLineLength()
            psy_file.write(fll.process(str(psy_ast.gen)))

        success = True

        try:
            # Build the kernels. We allow kernels to also be located in
            # the temporary directory that we have been passed.
            for fort_file in kernel_modules:

                # Skip file if it is not Fortran. TODO #372: Add support
                # for C/OpenCL compiling as part of the test suite.
                if fort_file.endswith(".cl"):
                    continue

                name = self.find_fortran_file(
                    [self.base_path, str(self._tmpdir)], fort_file)
                self.compile_file(name)

            # Finally, we can build the psy file we have generated
            self.compile_file(psy_filename)
        except CompileError:
            # Failed to compile one of the files
            success = False
        finally:
            old_pwd.chdir()

        return success
def test_multiple_lines_acc():
    ''' test that multiple lines works as expected for OpenACC directives '''
    input_file = ("!$acc blahdeblah, blahdeblah, blahdeblah, blahdeblah\n")
    expected_output = (
        "!$acc blahdeblah,  &\n!$acc& blahdeblah,  &\n!$acc& blahdeblah,"
        "  &\n!$acc& blahdeblah\n")
    fll = FortLineLength(line_length=24)
    output_file = fll.process(input_file)
    assert output_file == expected_output
def test_multiple_lines_statements():
    ''' test that multiple lines works as expected for statements
    (INTEGER, REAL, TYPE, CALL, SUBROUTINE and USE) '''
    input_file = ("INTEGER blahdeblah, blahdeblah, blahdeblah, blahdeblah\n")
    expected_output = (
        "INTEGER &\n&blahdeblah, &\n&blahdeblah, &\n&blahdeblah,"
        " &\n&blahdeblah\n")
    fll = FortLineLength(line_length=18)
    output_file = fll.process(input_file)
    assert output_file == expected_output
Exemple #12
0
def test_wrapped_lower():
    ''' Tests that a lower case file whose lines are longer than the
    specified line length is wrapped appropriately by the
    FortLineLength class'''
    fll = FortLineLength(line_length=30)
    output_file = fll.process(INPUT_FILE.lower())
    print("(" + EXPECTED_OUTPUT.lower() + ")")
    print("(" + output_file + ")")
    assert output_file == EXPECTED_OUTPUT.lower(), \
        "output and expected output differ "
def test_exception_line_too_long():
    ''' Test that output lines are not longer than the maximum
    specified'''
    input_file = ("INTEGER stuffynostrils, blahdeblah,blahdeblah blahdeblah\n"
                  "!$omp stuffynostrils,(blahdeblah)blahdeblah=(blahdeblah)\n"
                  "!$acc stuffynostrils,(blahdeblah)blahdeblah=(blahdeblah)\n"
                  "!     stuffynostrils,blahdeblah.blahdeblah blahdeblah).\n")
    fll = FortLineLength(line_length=24)
    output_file = fll.process(input_file)
    for line in output_file.split('\n'):
        assert len(line) <= 24, \
            "Error, output line is longer than the maximum allowed"
def test_long_line_continuator():
    '''Tests that an input algorithm file with long lines of a type not
       recognised by FortLineLength (assignments in this case), which
       already have continuators to make the code conform to the line
       length limit, does not cause an error.
    '''
    alg, _ = generate(os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                   "test_files", "dynamo0p3",
                                   "13.2_alg_long_line_continuator.f90"),
                      api="dynamo0.3")
    input_string = str(alg)
    fll = FortLineLength()
    _ = fll.process(input_string)
Exemple #15
0
def run():
    ''' Top-level driver for the kernel-stub generator. Handles command-line
    flags, calls generate() and applies line-length limiting to the output (if
    requested). '''
    import argparse
    parser = argparse.ArgumentParser(description="Create Kernel stub code from"
                                     " Kernel metadata")
    parser.add_argument("-o", "--outfile", help="filename of output")
    parser.add_argument(
        "-api",
        default=Config.get().default_stub_api,
        help="choose a particular api from {0}, default {1}".format(
            str(Config.get().supported_stub_apis),
            Config.get().default_stub_api))
    parser.add_argument('filename', help='Kernel metadata')
    parser.add_argument('-l',
                        '--limit',
                        dest='limit',
                        action='store_true',
                        default=False,
                        help='limit the fortran line length to 132 characters')

    args = parser.parse_args()

    try:
        stub = generate(args.filename, api=args.api)
    except (IOError, ParseError, GenerationError, RuntimeError) as error:
        print("Error:", error)
        exit(1)
    except Exception as error:  # pylint: disable=broad-except
        print("Error, unexpected exception:\n")
        exc_type, exc_value, exc_traceback = sys.exc_info()
        print(exc_type)
        print(exc_value)
        traceback.print_tb(exc_traceback)
        exit(1)

    if args.limit:
        fll = FortLineLength()
        stub_str = fll.process(str(stub))
    else:
        stub_str = str(stub)
    if args.outfile is not None:
        my_file = open(args.outfile, "w")
        my_file.write(stub_str)
        my_file.close()
    else:
        print("Kernel stub code:\n", stub_str)
def test_length():
    ''' Tests that the length method returns the expected value '''
    input_length = 20
    fll = FortLineLength(line_length=input_length)
    output_length = fll.length
    assert output_length == input_length,\
        "test_length expecting length method to be the same as the length" +\
        "provided on input"
def test_unchanged():
    ''' Tests that a file whose lines are shorter than the specified
    line length is unchanged by the FortLineLength class '''
    input_file = ("    INTEGER stuff\n"
                  "    REAL stuff\n"
                  "    TYPE stuff\n"
                  "    CALL stuff\n"
                  "    SUBROUTINE stuff\n"
                  "    USE stuff\n"
                  "    !$OMP stuff\n"
                  "    !$ACC stuff\n"
                  "    ! stuff\n"
                  "    stuff\n")
    fll = FortLineLength(line_length=25)
    output_file = fll.process(input_file)
    print "(" + input_file + ")"
    print "(" + output_file + ")"
    assert input_file == output_file, "input should remain unchanged"
def test_edge_conditions_comments():
    '''Test that we get correct behaviour with comments when the input
    line equals the max line length, or multiples thereof and lengths
    one larger and one smaller. This is to make sure we don't have
    issues like ending up with a continuation but no following line.'''
    input_string = ("!  COMMENT COMME\n"
                    "!  COMMENT COMMEN\n"
                    "!  COMMENT COMMENT\n"
                    "!  COMMENT COMMENT COMME\n"
                    "!  COMMENT COMMENT COMMEN\n"
                    "!  COMMENT COMMENT COMMENT\n")
    expected_output = ("!  COMMENT COMME\n"
                       "!  COMMENT COMMEN\n"
                       "!  COMMENT \n!& COMMENT\n"
                       "!  COMMENT \n!& COMMENT COMME\n"
                       "!  COMMENT \n!& COMMENT COMMEN\n"
                       "!  COMMENT \n!& COMMENT \n!& COMMENT\n")
    fll = FortLineLength(line_length=len("!  COMMENT COMMEN"))
    output_string = fll.process(input_string)
    assert output_string == expected_output
Exemple #19
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)))
def test_edge_conditions_omp():
    '''Test that we get correct behaviour using OpenMP directives when
    the input line equals the max line length, or multiples thereof
    and lengths one larger and one smaller. This is to make sure we
    don't have issues like ending up with a continuation but no
    following line. '''
    input_string = ("!$OMP  OPENMP OPEN\n"
                    "!$OMP  OPENMP OPENM\n"
                    "!$OMP  OPENMP OPENMP\n"
                    "!$OMP  OPENMP OPENMP OPEN\n"
                    "!$OMP  OPENMP OPENMP OPENM\n"
                    "!$OMP  OPENMP OPENMP OPENMP\n")
    expected_output = ("!$OMP  OPENMP OPEN\n"
                       "!$OMP  OPENMP OPENM\n"
                       "!$OMP  OPENMP  &\n!$omp& OPENMP\n"
                       "!$OMP  OPENMP  &\n!$omp& OPENMP OPEN\n"
                       "!$OMP  OPENMP  &\n!$omp& OPENMP OPENM\n"
                       "!$OMP  OPENMP  &\n!$omp& OPENMP  &\n!$omp& OPENMP\n")
    fll = FortLineLength(line_length=len("!$OMP  OPENMP OPENM"))
    output_string = fll.process(input_string)
    assert output_string == expected_output
def test_break_types_multi_line():
    ''' Test the different supported line breaks.'''
    input_file = ("INTEGER stuffynostrils, blahdeblah,blahdeblah blahdeblah\n"
                  "!$omp stuffynostrils,(blahdeblah)blahdeblah=(blahdeblah)\n"
                  "!$acc stuffynostrils,(blahdeblah)blahdeblah=(blahdeblah)\n"
                  "!     stuffynostrils,blahdeblah.blahdeblah blahdeblah).\n")
    expected_output = (
        "INTEGER stuffynostrils,&\n& blahdeblah,&\n&blahdeblah"
        " blahdeblah\n"
        "!$omp  &\n!$omp& stuffynostrils, &\n!$omp& (blahdeblah) &\n!$omp&"
        " blahdeblah= &\n!$omp& (blahdeblah)\n"
        "!$acc  &\n!$acc& stuffynostrils, &\n!$acc& (blahdeblah) &\n!$acc&"
        " blahdeblah= &\n!$acc& (blahdeblah)\n"
        "!     \n!& stuffynostrils,\n!& blahdeblah.\n!& blahdeblah \n!&"
        " blahdeblah).\n")

    fll = FortLineLength(line_length=24)
    output_file = fll.process(input_file)
    print "(" + output_file + ")"
    print expected_output
    assert output_file == expected_output
def test_edge_conditions_statements():
    ''' Test that we get correct behaviour using statements (INTEGER,
    REAL, TYPE, CALL, SUBROUTINE and USE) when the input line equals
    the max line length, or multiples thereof and lengths one larger
    and one smaller. This is to make sure we don't have issues like
    ending up with a continuation but no following line.'''
    input_string = ("INTEGER INTEG\n" "INTEGER INTEGE\n" "INTEGER INTEGER\n")
    expected_output = ("INTEGER INTEG\n"
                       "INTEGER INTEGE\n"
                       "INTEGER &\n&INTEGER\n")
    fll = FortLineLength(line_length=len("INTEGER INTEGE"))
    output_string = fll.process(input_string)
    print output_string
    print expected_output
    assert output_string == expected_output

    input_string = ("INTEGER INTEGER INTEG\n"
                    "INTEGER INTEGER INTEGE\n"
                    "INTEGER INTEGER INTEGER\n")
    expected_output = ("INTEGER &\n&INTEGER INTEG\n"
                       "INTEGER &\n&INTEGER INTEGE\n"
                       "INTEGER &\n&INTEGER &\n&INTEGER\n")
    fll = FortLineLength(line_length=len("INTEGER INTEGER"))
    output_string = fll.process(input_string)
    print output_string
    print expected_output
    assert output_string == expected_output
Exemple #23
0
''' Script to demonstrate the use on the line wrapping support in PSyclone'''
from __future__ import print_function
from psyclone.generator import generate
from psyclone.line_length import FortLineLength
# long_lines=True checks whether the input fortran conforms to the 132 line
# length limit
ALG, PSY = generate(
    "longlines.f90",
    kernel_path="../../src/psyclone/tests/test_files/dynamo0p3",
    line_length=True)
LINE_LENGTH = FortLineLength()
ALG_STR = LINE_LENGTH.process(str(ALG))
print(ALG_STR)
PSY_STR = LINE_LENGTH.process(str(PSY))
print(PSY_STR)
def code_compiles(api, psy_ast, tmpdir, f90, f90flags):
    '''Attempts to build the Fortran code supplied as an AST of
    f2pygen objects. Returns True for success, False otherwise.
    If no Fortran compiler is available then returns True. All files
    produced are deleted.

    :param api: Which PSyclone API the supplied code is using
    :type api: string
    :param psy_ast: The AST of the generated PSy layer
    :type psy_ast: Instance of :py:class:`psyGen.PSy`
    :param tmpdir: py.test-supplied temporary directory. Can contain \
                   transformed kernel source.
    :type tmpdir: :py:class:`LocalPath`
    :param f90: The command to invoke the Fortran compiler
    :type f90: string
    :param f90flags: Flags to pass to the Fortran compiler
    :type f90flags: string
    :return: True if generated code compiles, False otherwise
    :rtype: bool
    '''
    if not TEST_COMPILE:
        # Compilation testing is not enabled
        return True

    # API-specific set-up - where to find infrastructure source files
    # and which ones to build
    supported_apis = ["dynamo0.3"]
    if api not in supported_apis:
        raise CompileError("Unsupported API in code_compiles. Got {0} but "
                           "only support {1}".format(api, supported_apis))

    if api == "dynamo0.3":
        base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                 "test_files", "dynamo0p3")
        from dynamo0p3_build import INFRASTRUCTURE_MODULES as module_files

    kernel_modules = set()
    # Get the names of the modules associated with the kernels. By definition,
    # built-ins do not have associated Fortran modules.
    for invoke in psy_ast.invokes.invoke_list:
        for call in invoke.schedule.kern_calls():
            kernel_modules.add(call.module_name)

    # Change to the temporary directory passed in to us from
    # pytest. (This is a LocalPath object.)
    old_pwd = tmpdir.chdir()

    # Create a file containing our generated PSy layer.
    psy_filename = "psy.f90"
    with open(psy_filename, 'w') as psy_file:
        # We limit the line lengths of the generated code so that
        # we don't trip over compiler limits.
        from psyclone.line_length import FortLineLength
        fll = FortLineLength()
        psy_file.write(fll.process(str(psy_ast.gen)))

    # Infrastructure modules are in the 'infrastructure' directory
    module_path = os.path.join(base_path, "infrastructure")
    kernel_path = base_path

    success = False
    try:
        # First build the infrastructure modules
        for fort_file in module_files:
            name = find_fortran_file([module_path], fort_file)
            # We don't have to copy the source file - just compile it in the
            # current working directory.
            success = compile_file(name, f90, f90flags)

        # Next, build the kernels. We allow kernels to also be located in
        # the temporary directory that we have been passed.
        for fort_file in kernel_modules:
            name = find_fortran_file([kernel_path, str(tmpdir)], fort_file)
            success = compile_file(name, f90, f90flags)

        # Finally, we can build the psy file we have generated
        success = compile_file(psy_filename, f90, f90flags)

    except CompileError:
        # Failed to compile one of the files
        success = False

    finally:
        # Clean-up - delete all generated files. This permits this routine
        # to be called multiple times from within the same test.
        os.chdir(str(old_pwd))
        for ofile in tmpdir.listdir():
            ofile.remove()

    return success
Exemple #25
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)
Exemple #26
0
def main(args):
    '''
    Parses and checks the command line arguments, calls the generate
    function if all is well, catches any errors and outputs the
    results.
    :param list args: the list of command-line arguments that PSyclone has \
                      been invoked with.
    '''
    # pylint: disable=too-many-statements,too-many-branches

    # Make sure we have the supported APIs defined in the Config singleton,
    # but postpone loading the config file till the command line was parsed
    # in case that the user specifies a different config file.
    Config.get(do_not_load_file=True)

    parser = argparse.ArgumentParser(
        description='Run the PSyclone code generator on a particular file')
    parser.add_argument('-oalg', help='filename of transformed algorithm code')
    parser.add_argument('-opsy', help='filename of generated PSy code')
    parser.add_argument('-okern',
                        help='directory in which to put transformed kernels, '
                        'default is the current working directory.')
    parser.add_argument('-api',
                        help='choose a particular api from {0}, '
                        'default \'{1}\'.'.format(
                            str(Config.get().supported_apis),
                            Config.get().default_api))
    parser.add_argument('filename', help='algorithm-layer source code')
    parser.add_argument('-s',
                        '--script',
                        help='filename of a PSyclone'
                        ' optimisation script')
    parser.add_argument('-d',
                        '--directory',
                        default="",
                        help='path to root of directory '
                        'structure containing kernel source code')
    # Make the default an empty list so that we can check whether the
    # user has supplied a value(s) later
    parser.add_argument('-I',
                        '--include',
                        default=[],
                        action="append",
                        help='path to Fortran INCLUDE files (nemo API only)')
    parser.add_argument('-l',
                        '--limit',
                        dest='limit',
                        action='store_true',
                        default=False,
                        help='limit the fortran line length to 132 characters')
    parser.add_argument('-dm',
                        '--dist_mem',
                        dest='dist_mem',
                        action='store_true',
                        help='generate distributed memory code')
    parser.add_argument('-nodm',
                        '--no_dist_mem',
                        dest='dist_mem',
                        action='store_false',
                        help='do not generate distributed memory code')
    parser.add_argument(
        '--kernel-renaming',
        default="multiple",
        choices=configuration.VALID_KERNEL_NAMING_SCHEMES,
        help="Naming scheme to use when re-naming transformed kernels")
    parser.add_argument(
        '--profile',
        '-p',
        action="append",
        choices=Profiler.SUPPORTED_OPTIONS,
        help="Add profiling hooks for either 'kernels' or 'invokes'")
    parser.add_argument(
        '--force-profile',
        action="append",
        choices=Profiler.SUPPORTED_OPTIONS,
        help="Add profiling hooks for either 'kernels' or 'invokes' even if a "
        "transformation script is used. Use at your own risk.")
    parser.set_defaults(dist_mem=Config.get().distributed_memory)

    parser.add_argument("--config",
                        help="Config file with "
                        "PSyclone specific options.")
    parser.add_argument(
        '-v',
        '--version',
        dest='version',
        action="store_true",
        help='Display version information ({0})'.format(__VERSION__))

    args = parser.parse_args(args)

    if args.version:
        print("PSyclone version: {0}".format(__VERSION__))

    if args.script is not None and args.profile is not None:
        print(
            "Error: use of automatic profiling in combination with an\n"
            "optimisation script is not recommended since it may not work\n"
            "as expected.\n"
            "You can use --force-profile instead of --profile if you \n"
            "really want to use both options at the same time.",
            file=sys.stderr)
        exit(1)

    if args.profile is not None and args.force_profile is not None:
        print("Specify only one of --profile and --force-profile.",
              file=sys.stderr)
        exit(1)

    if args.profile:
        Profiler.set_options(args.profile)
    elif args.force_profile:
        Profiler.set_options(args.force_profile)

    # If an output directory has been specified for transformed kernels
    # then check that it is valid
    if args.okern:
        if not os.path.exists(args.okern):
            print("Specified kernel output directory ({0}) does not exist.".
                  format(args.okern),
                  file=sys.stderr)
            exit(1)
        if not os.access(args.okern, os.W_OK):
            print("Cannot write to specified kernel output directory ({0}).".
                  format(args.okern),
                  file=sys.stderr)
            exit(1)
        kern_out_path = args.okern
    else:
        # We write any transformed kernels to the current working directory
        kern_out_path = os.getcwd()

    # If no config file name is specified, args.config is none
    # and config will load the default config file.
    Config.get().load(args.config)

    # Check API, if none is specified, take the setting from the config file
    if args.api is None:
        # No command line option, use the one specified in Config - which
        # is either based on a parameter in the config file, or otherwise
        # the default:
        api = Config.get().api
    elif args.api not in Config.get().supported_apis:
        print("Unsupported API '{0}' specified. Supported API's are "
              "{1}.".format(args.api,
                            Config.get().supported_apis),
              file=sys.stderr)
        exit(1)
    else:
        # There is a valid API specified on the command line. Set it
        # as API in the config object as well.
        api = args.api
        Config.get().api = api

    # Store the search path(s) for include files
    if args.include and api != 'nemo':
        # We only support passing include paths to fparser2 and it's
        # only the NEMO API that uses fparser2 currently.
        print(
            "Setting the search path for Fortran include files "
            "(-I/--include) is only supported for the 'nemo' API.",
            file=sys.stderr)
        exit(1)

    # The Configuration manager checks that the supplied path(s) is/are
    # valid so protect with a try
    try:
        if args.include:
            Config.get().include_paths = args.include
        else:
            # Default is to instruct fparser2 to look in the directory
            # containing the file being parsed
            Config.get().include_paths = ["./"]
    except ConfigurationError as err:
        print(str(err), file=sys.stderr)
        exit(1)

    try:
        alg, psy = generate(args.filename,
                            api=api,
                            kernel_path=args.directory,
                            script_name=args.script,
                            line_length=args.limit,
                            distributed_memory=args.dist_mem,
                            kern_out_path=kern_out_path,
                            kern_naming=args.kernel_renaming)
    except NoInvokesError:
        _, exc_value, _ = sys.exc_info()
        print("Warning: {0}".format(exc_value))
        # no invoke calls were found in the algorithm file so we need
        # not need to process it, or generate any psy layer code so
        # output the original algorithm file and set the psy file to
        # be empty
        alg_file = open(args.filename)
        alg = alg_file.read()
        psy = ""
    except (OSError, IOError, ParseError, GenerationError, RuntimeError):
        _, exc_value, _ = sys.exc_info()
        print(exc_value, file=sys.stderr)
        exit(1)
    except Exception:  # pylint: disable=broad-except
        print("Error, unexpected exception, please report to the authors:",
              file=sys.stderr)
        exc_type, exc_value, exc_tb = sys.exc_info()
        print("Description ...", file=sys.stderr)
        print(exc_value, file=sys.stderr)
        print("Type ...", file=sys.stderr)
        print(exc_type, file=sys.stderr)
        print("Stacktrace ...", file=sys.stderr)
        traceback.print_tb(exc_tb, limit=20, file=sys.stderr)
        exit(1)
    if args.limit:
        fll = FortLineLength()
        psy_str = fll.process(str(psy))
        alg_str = fll.process(str(alg))
    else:
        psy_str = str(psy)
        alg_str = str(alg)
    if args.oalg is not None:
        my_file = open(args.oalg, "w")
        my_file.write(alg_str)
        my_file.close()
    else:
        print("Transformed algorithm code:\n%s" % alg_str)

    if not psy_str:
        # empty file so do not output anything
        pass
    elif args.opsy is not None:
        my_file = open(args.opsy, "w")
        my_file.write(psy_str)
        my_file.close()
    else:
        print("Generated psy layer code:\n", psy_str)
Exemple #27
0
def main(args):
    ''' Parses and checks the command line arguments, calls the generate
    function if all is well, catches any errors and outputs the
    results
    '''
    # pylint: disable=too-many-statements
    parser = argparse.ArgumentParser(
        description='Run the PSyclone code generator on a particular file')
    parser.add_argument('-oalg', help='filename of transformed algorithm code')
    parser.add_argument('-opsy', help='filename of generated PSy code')
    parser.add_argument('-api',
                        default=DEFAULTAPI,
                        help='choose a particular api from {0}, '
                        'default {1}'.format(str(SUPPORTEDAPIS), DEFAULTAPI))
    parser.add_argument('filename', help='algorithm-layer source code')
    parser.add_argument('-s',
                        '--script',
                        help='filename of a PSyclone'
                        ' optimisation script')
    parser.add_argument('-d',
                        '--directory',
                        default="",
                        help='path to root of directory '
                        'structure containing kernel source code')
    parser.add_argument('-l',
                        '--limit',
                        dest='limit',
                        action='store_true',
                        default=False,
                        help='limit the fortran line length to 132 characters')
    parser.add_argument('-dm',
                        '--dist_mem',
                        dest='dist_mem',
                        action='store_true',
                        help='generate distributed memory code')
    parser.add_argument('-nodm',
                        '--no_dist_mem',
                        dest='dist_mem',
                        action='store_false',
                        help='do not generate distributed memory code')
    parser.set_defaults(dist_mem=DISTRIBUTED_MEMORY)

    parser.add_argument(
        '-v',
        '--version',
        dest='version',
        action="store_true",
        help='Display version information ({0})'.format(__VERSION__))

    args = parser.parse_args(args)

    if args.api not in SUPPORTEDAPIS:
        print "Unsupported API '{0}' specified. Supported API's are "\
            "{1}.".format(args.api, SUPPORTEDAPIS)
        exit(1)

    if args.version:
        print "PSyclone version: {0}".format(__VERSION__)

    # pylint: disable=broad-except
    try:
        alg, psy = generate(args.filename,
                            api=args.api,
                            kernel_path=args.directory,
                            script_name=args.script,
                            line_length=args.limit,
                            distributed_memory=args.dist_mem)
    except NoInvokesError:
        _, exc_value, _ = sys.exc_info()
        print "Warning: {0}".format(exc_value)
        # no invoke calls were found in the algorithm file so we need
        # not need to process it, or generate any psy layer code so
        # output the original algorithm file and set the psy file to
        # be empty
        alg_file = open(args.filename)
        alg = alg_file.read()
        psy = ""
    except (OSError, IOError, ParseError, GenerationError, RuntimeError):
        _, exc_value, _ = sys.exc_info()
        print exc_value
        exit(1)
    except Exception:
        print "Error, unexpected exception, please report to the authors:"
        exc_type, exc_value, exc_tb = sys.exc_info()
        print "Description ..."
        print exc_value
        print "Type ..."
        print exc_type
        print "Stacktrace ..."
        traceback.print_tb(exc_tb, limit=10, file=sys.stdout)
        exit(1)
    if args.limit:
        fll = FortLineLength()
        psy_str = fll.process(str(psy))
        alg_str = fll.process(str(alg))
    else:
        psy_str = str(psy)
        alg_str = str(alg)
    if args.oalg is not None:
        my_file = open(args.oalg, "w")
        my_file.write(alg_str)
        my_file.close()
    else:
        print "Transformed algorithm code:\n", alg_str

    if not psy_str:
        # empty file so do not output anything
        pass
    elif args.opsy is not None:
        my_file = open(args.opsy, "w")
        my_file.write(psy_str)
        my_file.close()
    else:
        print "Generated psy layer code:\n", psy_str