diff --git a/doc/developer_guide/APIs.rst b/doc/developer_guide/APIs.rst index 7045b2e129..81c745a4a4 100644 --- a/doc/developer_guide/APIs.rst +++ b/doc/developer_guide/APIs.rst @@ -1179,50 +1179,6 @@ the `w0` function space then at least one of the the `meta_arg` arguments must be on the `w0` function space. However, this is not checked in the current implementation. -GOcean1.0 -========= - -TBD - -.. OpenMP Support -.. -------------- -.. -.. Loop directives are treated as first class entities in the psyGen -.. package. Therefore they can be added to psyGen's high level -.. representation of the fortran code structure in the same way as calls -.. and loops. Obviously it is only valid to add a loop directive outside -.. of a loop. -.. -.. When adding a call inside a loop the placement of any additional calls -.. or declarations must be specified correctly to ensure that they are -.. placed at the correct location in the hierarchy. To avoid accidentally -.. splitting the loop directive from its loop the start_parent_loop() -.. method can be used. This is available as a method in all fortran -.. generation calls. *We could have placed it in psyGen instead of -.. f2pygen*. This method returns the location at the top of any loop -.. hierarchy and before any comments immediately before the top level -.. loop. -.. -.. The OpenMPLoopDirective object needs to know which variables are -.. shared and which are private. In the current implementation default -.. shared is used and private variables are listed. To determine the -.. objects private variables the OpenMP implementation uses its internal -.. xxx_get_private_list() method. This method first finds all loops -.. contained within the directive and adds each loops variable name as a -.. private variable. this method then finds all calls contained within -.. the directive and adds each calls list of private variables, returned -.. with the local_vars() method. Therefore the OpenMPLoopDirective object -.. relies on calls specifying which variables they require being local. -.. -.. Next ... -.. -.. Update transformation for colours -.. -.. OpenMPLoop transformation in transformations.py. -.. -.. Create third transformation which goes over all loops in a schedule and -.. applies the OpenMP loop transformation. - NEMO ==== diff --git a/doc/developer_guide/modules.rst b/doc/developer_guide/modules.rst index f404bce9c0..1b17e05af2 100644 --- a/doc/developer_guide/modules.rst +++ b/doc/developer_guide/modules.rst @@ -40,67 +40,6 @@ Modules This section describes the functionality of the various Python modules that make up PSyclone. -Module: f2pygen -=============== - -.. warning:: - The f2pygen functionality has been superseded by the development of - the PSyIR and will be removed entirely in a future release. - -`f2pygen` provides functionality for generating Fortran code from -scratch and supports the addition of a use statement to an existing -parse tree. - -Variable Declarations ---------------------- - -Three different classes are provided to support the creation of -variable declarations (for intrinsic, character and derived-type -variables). An example of their use might be: - ->>> from psyclone.f2pygen import (ModuleGen, SubroutineGen, DeclGen, -... CharDeclGen, TypeDeclGen) ->>> module = ModuleGen(name="testmodule") ->>> sub = SubroutineGen(module, name="testsubroutine") ->>> module.add(sub) ->>> sub.add(DeclGen(sub, datatype="integer", entity_decls=["my_int"])) ->>> sub.add(CharDeclGen(sub, length="10", entity_decls=["my_char"])) ->>> sub.add(TypeDeclGen(sub, datatype="field_type", entity_decls=["ufld"])) ->>> gen = str(module.root) ->>> print(gen) - MODULE testmodule - IMPLICIT NONE - CONTAINS - SUBROUTINE testsubroutine() - TYPE(field_type) ufld - CHARACTER(LEN=10) my_char - INTEGER my_int - END SUBROUTINE testsubroutine - END MODULE testmodule - -The full interface to each of these classes is detailed below: - -.. autoclass:: psyclone.f2pygen.DeclGen - :members: - :noindex: - -.. autoclass:: psyclone.f2pygen.CharDeclGen - :members: - :noindex: - -.. autoclass:: psyclone.f2pygen.TypeDeclGen - :members: - :noindex: - -Adding code ------------ - -`f2pygen` supports the addition of use statements to an existing -`fparser1` parse tree: - -.. autofunction:: psyclone.f2pygen.adduse - - .. _dev_configuration: Module: configuration diff --git a/doc/developer_guide/psyir.rst b/doc/developer_guide/psyir.rst index 51502f5497..5824b14e87 100644 --- a/doc/developer_guide/psyir.rst +++ b/doc/developer_guide/psyir.rst @@ -990,9 +990,7 @@ The Kernel-layer subclasses will be used to: translated into LFRic PSyIR using the expected datatypes as specified by the kernel metadata and associated LFRic rules. -3) replace the existing kernel stub generation implementation so that - the PSyIR back ends can be used and PSyclone will rely less on - ``f2pygen`` and ``fparser1``. At the moment ``kernel_interface`` +3) At the moment ``kernel_interface`` provides the same functionality as ``kern_stub_arg_list``, except that it uses the symbol table (which keeps datatypes and their declarations together). diff --git a/src/psyclone/domain/common/psylayer/psyloop.py b/src/psyclone/domain/common/psylayer/psyloop.py index b419febd39..321f580229 100644 --- a/src/psyclone/domain/common/psylayer/psyloop.py +++ b/src/psyclone/domain/common/psylayer/psyloop.py @@ -330,20 +330,6 @@ def args_filter(self, arg_types=None, arg_accesses=None, unique=False): all_args.extend(call_args) return all_args - def gen_mark_halos_clean_dirty(self, parent): - ''' - Generates the necessary code to mark halo regions as clean or dirty - following execution of this loop. This default implementation does - nothing. - - TODO #1648 - this method should be removed when the corresponding - one in LFRicLoop is removed. - - :param parent: the node in the f2pygen AST to which to add content. - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - - ''' - def _halo_read_access(self, arg): '''Determines whether the supplied argument has (or might have) its halo data read within this loop. Returns True if it does, or if diff --git a/src/psyclone/f2pygen.py b/src/psyclone/f2pygen.py deleted file mode 100644 index d772b88e2f..0000000000 --- a/src/psyclone/f2pygen.py +++ /dev/null @@ -1,1467 +0,0 @@ -# ----------------------------------------------------------------------------- -# BSD 3-Clause License -# -# Copyright (c) 2017-2025 and Technology Facilities Council. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# ----------------------------------------------------------------------------- -# Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab -# Modified: A. B. G. Chalk and N. Nobre, STFC Daresbury Lab - -''' Fortran code-generation library. This wraps the f2py fortran parser to - provide routines which can be used to generate fortran code. ''' - -import abc -from fparser.common.readfortran import FortranStringReader -from fparser.common.sourceinfo import FortranFormat -from fparser.one.statements import Comment, Case -from fparser.one.block_statements import SelectCase, SelectType, EndSelect -from fparser.one.parsefortran import FortranParser -# This alias is useful to refer to parts of fparser.one later but -# cannot be used for imports (as that involves looking for the -# specified name in sys.modules). -from fparser import one as fparser1 -from psyclone.configuration import Config -from psyclone.errors import InternalError - -# Module-wide utility methods - - -def bubble_up_type(obj): - ''' - Checks whether the supplied object must be bubbled-up (e.g. from - within DO loops). - - :returns: True if the supplied object is of a type which must be \ - bubbled-up and False otherwise. - ''' - return isinstance(obj, (UseGen, BaseDeclGen)) - - -def index_of_object(alist, obj): - '''Effectively implements list.index(obj) but returns the index of - the first item in the list that *is* the supplied object (rather than - comparing values) ''' - for idx, body in enumerate(alist): - if body is obj: - return idx - raise Exception(f"Object {obj} not found in list") - - -# This section subclasses the f2py comment class so that we can -# reason about directives - - -class Directive(Comment): - ''' - Base class for directives so we can reason about them when walking - the tree. Sub-classes the fparser1 Comment class. - - :param root: the parent node in the AST to which we are adding the \ - directive - :type root: subclass of :py:class:`fparser.common.base_classes.Statement` - :param line: the fparser object which we will manipulate to create \ - the desired directive. - :type line: :py:class:`fparser.common.readfortran.Comment` - :param str position: e.g. 'begin' or 'end' (language specific) - :param str dir_type: the type of directive that this is (e.g. \ - 'parallel do') - ''' - def __init__(self, root, line, position, dir_type): - if dir_type not in self._types: - raise RuntimeError(f"Error, unrecognised directive type " - f"'{dir_type}'. Should be one of {self._types}") - if position not in self._positions: - raise RuntimeError(f"Error, unrecognised position '{position}'. " - f"Should be one of {self._positions}") - self._my_type = dir_type - self._position = position - Comment.__init__(self, root, line) - - @property - def type(self): - ''' - :returns: the type of this Directive. - :rtype: str - ''' - return self._my_type - - @property - def position(self): - ''' - :returns: the position of this Directive ('begin' or 'end'). - :rtype: str - ''' - return self._position - - -class OMPDirective(Directive): - ''' - Subclass Directive for OpenMP directives so we can reason about - them when walking the tree. - - :param root: the parent node in the AST to which we are adding the \ - directive. - :type root: subclass of :py:class:`fparser.common.base_classes.Statement` - :param line: the fparser object which we will manipulate to create \ - the desired directive. - :type line: :py:class:`fparser.common.readfortran.Comment` - :param str position: e.g. 'begin' or 'end' (language specific). - :param str dir_type: the type of directive that this is (e.g. \ - 'parallel do'). - ''' - def __init__(self, root, line, position, dir_type): - self._types = ["parallel do", "parallel", "do", "master", "single", - "taskloop", "taskwait", "declare", "target", "teams", - "teams distribute parallel do"] - self._positions = ["begin", "end"] - - super(OMPDirective, self).__init__(root, line, position, dir_type) - - -class ACCDirective(Directive): - ''' - Subclass Directive for OpenACC directives so we can reason about them - when walking the tree. - - :param root: the parent node in the AST to which we are adding the \ - directive. - :type root: subclass of :py:class:`fparser.common.base_classes.Statement` - :param line: the fparser object which we will manipulate to create \ - the desired directive. - :type line: :py:class:`fparser.common.readfortran.Comment` - :param str position: e.g. 'begin' or 'end' (language specific). - :param str dir_type: the type of directive that this is (e.g. \ - 'loop'). - ''' - def __init__(self, root, line, position, dir_type): - self._types = ["parallel", "kernels", "enter data", "loop", "routine"] - self._positions = ["begin", "end"] - - super(ACCDirective, self).__init__(root, line, position, dir_type) - - -# This section provides new classes which provide a relatively high -# level interface to creating code and adding code to an existing ast - - -class BaseGen(): - ''' The base class for all classes that are responsible for generating - distinct code elements (modules, subroutines, do loops etc.) ''' - def __init__(self, parent, root): - self._parent = parent - self._root = root - self._children = [] - - @property - def parent(self): - ''' Returns the parent of this object ''' - return self._parent - - @property - def children(self): - ''' Returns the list of children of this object ''' - return self._children - - @property - def root(self): - ''' Returns the root of the tree containing this object ''' - return self._root - - def add(self, new_object, position=None): - '''Adds a new object to the tree. The actual position is determined by - the position argument. Note, there are two trees, the first is - the f2pygen object tree, the other is the f2py generated code - tree. These are similar but different. At the moment we - specify where to add things in terms of the f2pygen tree - (which is a higher level api) but we also insert into the f2py - tree at exactly the same location which needs to be sorted out - at some point. - - ''' - - # By default the position is 'append'. We set it up this way for - # safety because in python, default arguments are instantiated - # as objects at the time of definition. If this object is - # subsequently modified then the value of the default argument - # is modified for subsequent calls of this routine. - if position is None: - position = ["append"] - - if position[0] == "auto": - raise Exception("Error: BaseGen:add: auto option must be " - "implemented by the sub class!") - options = ["append", "first", "after", "before", "insert", - "before_index", "after_index"] - if position[0] not in options: - raise Exception(f"Error: BaseGen:add: supported positions are " - f"{options} but found {position[0]}") - if position[0] == "append": - self.root.content.append(new_object.root) - elif position[0] == "first": - self.root.content.insert(0, new_object.root) - elif position[0] == "insert": - index = position[1] - self.root.content.insert(index, new_object.root) - elif position[0] == "after": - idx = index_of_object(self.root.content, position[1]) - self.root.content.insert(idx+1, new_object.root) - elif position[0] == "after_index": - self.root.content.insert(position[1]+1, new_object.root) - elif position[0] == "before_index": - self.root.content.insert(position[1], new_object.root) - elif position[0] == "before": - try: - idx = index_of_object(self.root.content, position[1]) - except Exception as err: - print(str(err)) - raise RuntimeError( - "Failed to find supplied object in existing content - " - "is it a child of the parent?") - self.root.content.insert(idx, new_object.root) - else: - raise Exception("Error: BaseGen:add: internal error, should " - "not get to here") - self.children.append(new_object) - - def previous_loop(self): - ''' Returns the *last* occurrence of a loop in the list of - siblings of this node ''' - from fparser.one.block_statements import Do - for sibling in reversed(self.root.content): - if isinstance(sibling, Do): - return sibling - raise RuntimeError("Error, no loop found - there is no previous loop") - - def last_declaration(self): - '''Returns the *last* occurrence of a Declaration in the list of - siblings of this node - - ''' - from fparser.one.typedecl_statements import TypeDeclarationStatement - for sibling in reversed(self.root.content): - if isinstance(sibling, TypeDeclarationStatement): - return sibling - - raise RuntimeError("Error, no variable declarations found") - - def start_parent_loop(self, debug=False): - ''' Searches for the outer-most loop containing this object. Returns - the index of that line in the content of the parent. ''' - from fparser.one.block_statements import Do - if debug: - print("Entered before_parent_loop") - print(f"The type of the current node is {type(self.root)}") - print(("If the current node is a Do loop then move up to the " - "top of the do loop nest")) - - # First off, check that we do actually have an enclosing Do loop - current = self.root - while not isinstance(current, Do) and getattr(current, 'parent', None): - current = current.parent - if not isinstance(current, Do): - raise RuntimeError("This node has no enclosing Do loop") - - current = self.root - local_current = self - while isinstance(current.parent, Do): - if debug: - print("Parent is a do loop so moving to the parent") - current = current.parent - local_current = local_current.parent - if debug: - print("The type of the current node is now " + str(type(current))) - print("The type of parent is " + str(type(current.parent))) - print("Finding the loops position in its parent ...") - index = current.parent.content.index(current) - if debug: - print("The loop's index is ", index) - parent = current.parent - local_current = local_current.parent - if debug: - print("The type of the object at the index is " + - str(type(parent.content[index]))) - print("If preceding node is a directive then move back one") - if index == 0: - if debug: - print("current index is 0 so finish") - elif isinstance(parent.content[index-1], Directive): - if debug: - print( - f"preceding node is a directive so find out what type ..." - f"\n type is {parent.content[index-1].position}" - f"\n diretive is {parent.content[index-1]}") - if parent.content[index-1].position == "begin": - if debug: - print("type of directive is begin so move back one") - index -= 1 - else: - if debug: - print("directive type is not begin so finish") - else: - if debug: - print("preceding node is not a directive so finish") - if debug: - print("type of final location ", type(parent.content[index])) - print("code for final location ", str(parent.content[index])) - return local_current, parent.content[index] - - -class ProgUnitGen(BaseGen): - ''' Functionality relevant to program units (currently modules, - subroutines)''' - def __init__(self, parent, sub): - BaseGen.__init__(self, parent, sub) - - def add(self, content, position=None, bubble_up=False): - ''' - Specialise the add method to provide module- and subroutine- - -specific intelligent adding of use statements, implicit - none statements and declarations if the position argument - is set to auto (which is the default). - - :param content: the Node (or sub-tree of Nodes) to add in to \ - the AST. - :type content: :py:class:`psyclone.f2pygen.BaseGen` - :param list position: where to insert the node. One of "append", \ - "first", "insert", "after", "after_index", \ - "before_index", "before" or "auto". For the \ - *_index options, the second element of the \ - list holds the integer index. - :param bool bubble_up: whether or not object (content) is in the \ - process of being bubbled-up. - ''' - # By default the position is 'auto'. We set it up this way for - # safety because in python, default arguments are instantiated - # as objects at the time of definition. If this object is - # subsequently modified then the value of the default argument - # is modified for subsequent calls of this routine. - if position is None: - position = ["auto"] - - # For an object to be added to another we require that they - # share a common ancestor. This means that the added object must - # have the current object or one of its ancestors as an ancestor. - # Loop over the ancestors of this object (starting with itself) - self_ancestor = self.root - while self_ancestor: - # Loop over the ancestors of the object being added - obj_parent = content.root.parent - while (obj_parent != self_ancestor and - getattr(obj_parent, 'parent', None)): - obj_parent = obj_parent.parent - if obj_parent == self_ancestor: - break - # Object being added is not an ancestor of the current - # self_ancestor so move one level back up the tree and - # try again - if getattr(self_ancestor, 'parent', None): - self_ancestor = self_ancestor.parent - else: - break - - if obj_parent != self_ancestor: - raise RuntimeError( - f"Cannot add '{content}' to '{self}' because it is not a " - f"descendant of it or of any of its ancestors.") - - if bubble_up: - # If content has been passed on (is being bubbled up) then change - # its parent to be this object - content.root.parent = self.root - - if position[0] != "auto": - # position[0] is not 'auto' so the baseclass can deal with it - BaseGen.add(self, content, position) - else: - # position[0] == "auto" so insert in a context sensitive way - if isinstance(content, BaseDeclGen): - - if isinstance(content, (DeclGen, CharDeclGen)): - # have I already been declared? - for child in self._children: - if isinstance(child, (DeclGen, CharDeclGen)): - # is this declaration the same type as me? - if child.root.name == content.root.name: - # we are modifying the list so we need - # to iterate over a copy - for var_name in content.root.entity_decls[:]: - for child_name in child.root.entity_decls: - if var_name.lower() == \ - child_name.lower(): - content.root.entity_decls.\ - remove(var_name) - if not content.root.entity_decls: - # return as all variables in - # this declaration already - # exist - return - if isinstance(content, TypeDeclGen): - # have I already been declared? - for child in self._children: - if isinstance(child, TypeDeclGen): - # is this declaration the same type as me? - if child.root.selector[1] == \ - content.root.selector[1]: - # we are modifying the list so we need - # to iterate over a copy - for var_name in content.root.entity_decls[:]: - for child_name in child.root.entity_decls: - if var_name.lower() == \ - child_name.lower(): - content.root.entity_decls.\ - remove(var_name) - if not content.root.entity_decls: - # return as all variables in - # this declaration already - # exist - return - - index = 0 - # skip over any use statements - index = self._skip_use_and_comments(index) - # skip over implicit none if it exists - index = self._skip_imp_none_and_comments(index) - # skip over any declarations which have an intent - try: - intent = True - while intent: - intent = False - for attr in self.root.content[index].attrspec: - if attr.find("intent") == 0: - intent = True - index += 1 - break - except AttributeError: - pass - elif isinstance(content.root, fparser1.statements.Use): - # have I already been declared? - for child in self._children: - if isinstance(child, UseGen): - if child.root.name == content.root.name: - # found an existing use with the same name - if not child.root.isonly and not \ - content.root.isonly: - # both are generic use statements so - # skip this declaration - return - if child.root.isonly and not content.root.isonly: - # new use is generic and existing use - # is specific so we can safely add - pass - if not child.root.isonly and content.root.isonly: - # existing use is generic and new use - # is specific so we can skip this - # declaration - return - if child.root.isonly and content.root.isonly: - # we are modifying the list so we need - # to iterate over a copy - for new_name in content.root.items[:]: - for existing_name in child.root.items: - if existing_name.lower() == \ - new_name.lower(): - content.root.items.remove(new_name) - if not content.root.items: - return - index = 0 - elif isinstance(content, ImplicitNoneGen): - # does implicit none already exist? - for child in self._children: - if isinstance(child, ImplicitNoneGen): - return - # skip over any use statements - index = 0 - index = self._skip_use_and_comments(index) - else: - index = len(self.root.content) - 1 - self.root.content.insert(index, content.root) - self._children.append(content) - - def _skip_use_and_comments(self, index): - ''' skip over any use statements and comments in the ast ''' - while isinstance(self.root.content[index], - fparser1.statements.Use) or\ - isinstance(self.root.content[index], - fparser1.statements.Comment): - index += 1 - # now roll back to previous Use - while isinstance(self.root.content[index-1], - fparser1.statements.Comment): - index -= 1 - return index - - def _skip_imp_none_and_comments(self, index): - ''' skip over an implicit none statement if it exists and any - comments before it ''' - end_index = index - while isinstance(self.root.content[index], - fparser1.typedecl_statements.Implicit) or\ - isinstance(self.root.content[index], - fparser1.statements.Comment): - if isinstance(self.root.content[index], - fparser1.typedecl_statements.Implicit): - end_index = index + 1 - break - else: - index = index + 1 - return end_index - - -class PSyIRGen(BaseGen): - ''' Create a Fortran block of code that comes from a given PSyIR tree. - - :param parent: node in AST to which we are adding the PSyIR block. - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param content: the PSyIR tree we are adding. - :type content: :py:class:`psyclone.psyir.nodes.Node` - - ''' - - def __init__(self, parent, content): - # Import FortranWriter here to avoid circular-dependency - # pylint: disable=import-outside-toplevel - from psyclone.psyir.backend.fortran import FortranWriter - # We need the Config object in order to see whether or not to disable - # the validation performed in the PSyIR backend. - config = Config.get() - - # Use the PSyIR Fortran backend to generate Fortran code of the - # supplied PSyIR tree and pass the resulting code to the fparser1 - # Fortran parser. - fortran_writer = FortranWriter( - check_global_constraints=config.backend_checks_enabled) - reader = FortranStringReader(fortran_writer(content), - ignore_comments=False) - # Set reader as free form, strict - reader.set_format(FortranFormat(True, True)) - fparser1_parser = FortranParser(reader, ignore_comments=False) - fparser1_parser.parse() - - # If the fparser content is larger than 1, add all the nodes but - # the last one as siblings of self. This is done because self - # can only represent one node. - for fparser_node in fparser1_parser.block.content[:-1]: - f2pygen_node = BaseGen(parent, fparser_node) - f2pygen_node.root.parent = parent.root - parent.add(f2pygen_node) - - # Update this f2pygen node to be equivalent to the last of the - # fparser nodes that represent the provided content. - BaseGen.__init__(self, parent, fparser1_parser.block.content[-1]) - self.root.parent = parent.root - - -class ModuleGen(ProgUnitGen): - ''' create a fortran module ''' - def __init__(self, name="", contains=True, implicitnone=True): - from fparser import api - - code = '''\ -module vanilla -''' - if contains: - code += '''\ -contains -''' - code += '''\ -end module vanilla -''' - tree = api.parse(code, ignore_comments=False) - module = tree.content[0] - module.name = name - endmod = module.content[len(module.content)-1] - endmod.name = name - ProgUnitGen.__init__(self, None, module) - if implicitnone: - self.add(ImplicitNoneGen(self)) - - def add_raw_subroutine(self, content): - ''' adds a subroutine to the module that is a raw f2py parse object. - This is used for inlining kernel subroutines into a module. - ''' - from psyclone.parse.kernel import KernelProcedure - if not isinstance(content, KernelProcedure): - raise Exception( - "Expecting a KernelProcedure type but received " + - str(type(content))) - content.ast.parent = self.root - # add content after any existing subroutines - index = len(self.root.content) - 1 - self.root.content.insert(index, content.ast) - - -class CommentGen(BaseGen): - ''' Create a Fortran Comment ''' - def __init__(self, parent, content): - ''' - :param parent: node in AST to which to add the Comment as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str content: the content of the comment - ''' - reader = FortranStringReader("! content\n", ignore_comments=False) - reader.set_format(FortranFormat(True, True)) # free form, strict - subline = reader.next() - - my_comment = Comment(parent.root, subline) - my_comment.content = content - - BaseGen.__init__(self, parent, my_comment) - - -class DirectiveGen(BaseGen): - ''' - Class for creating a Fortran directive, e.g. OpenMP or OpenACC. - - :param parent: node in AST to which to add directive as a child. - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str language: the type of directive (e.g. OMP or ACC). - :param str position: "end" if this is the end of a directive block. - :param str directive_type: the directive itself (e.g. "PARALLEL DO"). - :param str content: any additional arguments to add to the directive \ - (e.g. "PRIVATE(ji)"). - - :raises RuntimeError: if an unrecognised directive language is specified. - ''' - def __init__(self, parent, language, position, directive_type, content=""): - self._supported_languages = ["omp", "acc"] - self._language = language - self._directive_type = directive_type - - reader = FortranStringReader("! content\n", ignore_comments=False) - reader.set_format(FortranFormat(True, True)) # free form, strict - subline = reader.next() - - if language == "omp": - my_comment = OMPDirective(parent.root, subline, position, - directive_type) - my_comment.content = "$omp" - elif language == "acc": - my_comment = ACCDirective(parent.root, subline, position, - directive_type) - my_comment.content = "$acc" - else: - raise RuntimeError( - f"Error, unsupported directive language. Expecting one of " - f"{self._supported_languages} but found '{language}'") - if position == "end": - my_comment.content += " end" - my_comment.content += " " + directive_type - if content != "": - my_comment.content += " " + content - - BaseGen.__init__(self, parent, my_comment) - - -class ImplicitNoneGen(BaseGen): - ''' Generate a Fortran 'implicit none' statement ''' - def __init__(self, parent): - ''' - :param parent: node in AST to which to add 'implicit none' as a child - :type parent: :py:class:`psyclone.f2pygen.ModuleGen` or - :py:class:`psyclone.f2pygen.SubroutineGen` - - :raises Exception: if `parent` is not a ModuleGen or SubroutineGen - ''' - if not isinstance(parent, ModuleGen) and not isinstance(parent, - SubroutineGen): - raise Exception( - f"The parent of ImplicitNoneGen must be a module or a " - f"subroutine, but found {type(parent)}") - reader = FortranStringReader("IMPLICIT NONE\n") - reader.set_format(FortranFormat(True, True)) # free form, strict - subline = reader.next() - - from fparser.one.typedecl_statements import Implicit - my_imp_none = Implicit(parent.root, subline) - - BaseGen.__init__(self, parent, my_imp_none) - - -class SubroutineGen(ProgUnitGen): - ''' Generate a Fortran subroutine ''' - def __init__(self, parent, name="", args=None, implicitnone=False): - ''' - :param parent: node in AST to which to add Subroutine as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str name: name of the Fortran subroutine - :param list args: list of arguments accepted by the subroutine - :param bool implicitnone: whether or not we should specify - "implicit none" for the body of this - subroutine - ''' - reader = FortranStringReader( - "subroutine vanilla(vanilla_arg)\nend subroutine") - reader.set_format(FortranFormat(True, True)) # free form, strict - subline = reader.next() - endsubline = reader.next() - - from fparser.one.block_statements import Subroutine, EndSubroutine - self._sub = Subroutine(parent.root, subline) - self._sub.name = name - if args is None: - args = [] - self._sub.args = args - endsub = EndSubroutine(self._sub, endsubline) - self._sub.content.append(endsub) - ProgUnitGen.__init__(self, parent, self._sub) - if implicitnone: - self.add(ImplicitNoneGen(self)) - - @property - def args(self): - ''' Returns the list of arguments of this subroutine ''' - return self._sub.args - - @args.setter - def args(self, namelist): - ''' sets the subroutine arguments to the values in the list provide.''' - self._sub.args = namelist - - -class CallGen(BaseGen): - ''' Generates a Fortran call of a subroutine ''' - def __init__(self, parent, name="", args=None): - ''' - :param parent: node in AST to which to add CallGen as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str name: the name of the routine to call - :param list args: list of arguments to pass to the call - ''' - reader = FortranStringReader("call vanilla(vanilla_arg)") - reader.set_format(FortranFormat(True, True)) # free form, strict - myline = reader.next() - - from fparser.one.block_statements import Call - self._call = Call(parent.root, myline) - self._call.designator = name - if args is None: - args = [] - self._call.items = args - - BaseGen.__init__(self, parent, self._call) - - -class UseGen(BaseGen): - ''' Generate a Fortran use statement ''' - def __init__(self, parent, name="", only=False, funcnames=None): - ''' - :param parent: node in AST to which to add UseGen as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str name: name of the module to USE - :param bool only: whether this USE has an ONLY clause - :param list funcnames: list of names to follow ONLY clause - ''' - reader = FortranStringReader("use kern,only : func1_kern=>func1") - reader.set_format(FortranFormat(True, True)) # free form, strict - myline = reader.next() - root = parent.root - from fparser.one.block_statements import Use - use = Use(root, myline) - use.name = name - use.isonly = only - if funcnames is None: - funcnames = [] - use.isonly = False - local_funcnames = funcnames[:] - use.items = local_funcnames - BaseGen.__init__(self, parent, use) - - -def adduse(name, parent, only=False, funcnames=None): - ''' - Adds a use statement with the specified name to the supplied object. - This routine is required when modifying an existing AST (e.g. when - modifying a kernel). The classes are used when creating an AST from - scratch (for the PSy layer). - - :param str name: name of module to USE - :param parent: node in fparser1 AST to which to add this USE as a child - :type parent: :py:class:`fparser.one.block_statements.*` - :param bool only: whether this USE has an "ONLY" clause - :param list funcnames: list of quantities to follow the "ONLY" clause - - :returns: an fparser1 Use object - :rtype: :py:class:`fparser.one.block_statements.Use` - ''' - reader = FortranStringReader("use kern,only : func1_kern=>func1") - reader.set_format(FortranFormat(True, True)) # free form, strict - myline = reader.next() - - # find an appropriate place to add in our use statement - while not isinstance(parent, (fparser1.block_statements.Program, - fparser1.block_statements.Module, - fparser1.block_statements.Subroutine)): - parent = parent.parent - use = fparser1.block_statements.Use(parent, myline) - use.name = name - use.isonly = only - if funcnames is None: - funcnames = [] - use.isonly = False - use.items = funcnames - - parent.content.insert(0, use) - return use - - -class AllocateGen(BaseGen): - ''' Generates a Fortran allocate statement ''' - def __init__(self, parent, content, mold=None): - ''' - :param parent: node to which to add this ALLOCATE as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param content: string or list of variables to allocate - :type content: list of strings or a single string - :param mold: A string to be used as the 'mold' parameter of ALLOCATE. - :type mold: str or None. - - :raises RuntimeError: if `content` is not of correct type - ''' - reader = FortranStringReader("allocate(dummy)") - reader.set_format(FortranFormat(True, False)) # free form, strict - myline = reader.next() - self._decl = fparser1.statements.Allocate(parent.root, myline) - if isinstance(content, str): - self._decl.items = [content] - elif isinstance(content, list): - self._decl.items = content - else: - raise RuntimeError( - f"AllocateGen expected the content argument to be a str or" - f" a list, but found {type(content)}") - if mold: - self._decl.items.append(f"mold={mold}") - BaseGen.__init__(self, parent, self._decl) - - -class DeallocateGen(BaseGen): - ''' Generates a Fortran deallocate statement ''' - def __init__(self, parent, content): - ''' - :param parent: node to which to add this DEALLOCATE as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param content: string or list of variables to deallocate - :type content: list of strings or a single string - - :raises RuntimeError: if `content` is not of correct type - ''' - reader = FortranStringReader("deallocate(dummy)") - reader.set_format(FortranFormat(True, False)) # free form, strict - myline = reader.next() - self._decl = fparser1.statements.Deallocate(parent.root, myline) - if isinstance(content, str): - self._decl.items = [content] - elif isinstance(content, list): - self._decl.items = content - else: - raise RuntimeError( - f"DeallocateGen expected the content argument to be a str" - f" or a list, but found {type(content)}") - BaseGen.__init__(self, parent, self._decl) - - -class BaseDeclGen(BaseGen, metaclass=abc.ABCMeta): - ''' - Abstract base class for all types of Fortran declaration. Uses the - abc module so it cannot be instantiated. - - :param parent: node to which to add this declaration as a child. - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str datatype: the (intrinsic) type for this declaration. - :param list entity_decls: list of variable names to declare. - :param str intent: the INTENT attribute of this declaration. - :param bool pointer: whether or not this is a pointer declaration. - :param str dimension: the DIMENSION specifier (i.e. the xx in \ - DIMENSION(xx)). - :param bool allocatable: whether this declaration is for an \ - ALLOCATABLE quantity. - :param bool save: whether this declaration has the SAVE attribute. - :param bool target: whether this declaration has the TARGET attribute. - :param initial_values: initial value to give each variable. - :type initial_values: list of str with same no. of elements as entity_decls - :param bool private: whether this declaration has the PRIVATE attribute \ - (default is False). - - :raises RuntimeError: if no variable names are specified. - :raises RuntimeError: if the wrong number or type of initial values are \ - supplied. - :raises RuntimeError: if initial values are supplied for a quantity that \ - is allocatable or has INTENT(in). - :raises NotImplementedError: if initial values are supplied for array \ - variables (dimension != ""). - - ''' - _decl = None # Will hold the declaration object created by sub-class - - def __init__(self, parent, datatype="", entity_decls=None, intent="", - pointer=False, dimension="", allocatable=False, - save=False, target=False, initial_values=None, private=False): - if entity_decls is None: - raise RuntimeError( - "Cannot create a variable declaration without specifying the " - "name(s) of the variable(s)") - - # If initial values have been supplied then check that there - # are the right number of them and that they are consistent - # with the type of the variable(s) being declared. - if initial_values: - if len(initial_values) != len(entity_decls): - raise RuntimeError( - f"f2pygen.DeclGen.init: number of initial values supplied " - f"({len(initial_values)}) does not match the number of " - f"variables to be declared ({len(entity_decls)}: " - f"{entity_decls})") - if allocatable: - raise RuntimeError( - f"Cannot specify initial values for variable(s) " - f"{entity_decls} because they have the 'allocatable' " - f"attribute.") - if dimension: - raise NotImplementedError( - "Specifying initial values for array declarations is not " - "currently supported.") - if intent.lower() == "in": - raise RuntimeError( - f"Cannot assign (initial) values to variable(s) " - f"{entity_decls} as they have INTENT(in).") - # Call sub-class-provided implementation to check actual - # values provided. - self._check_initial_values(datatype, initial_values) - - # Store the list of variable names - self._names = entity_decls[:] - - # Make a copy of entity_decls as we may modify it - local_entity_decls = entity_decls[:] - if initial_values: - # Create a list of 2-tuples - value_pairs = zip(local_entity_decls, initial_values) - # Construct an assignment from each tuple - self._decl.entity_decls = ["=".join(_) for _ in value_pairs] - else: - self._decl.entity_decls = local_entity_decls - - # Construct the list of attributes - my_attrspec = [] - if intent != "": - my_attrspec.append(f"intent({intent})") - if pointer: - my_attrspec.append("pointer") - if target: - my_attrspec.append("target") - if allocatable: - my_attrspec.append("allocatable") - if save: - my_attrspec.append("save") - if private: - my_attrspec.append("private") - if dimension != "": - my_attrspec.append(f"dimension({dimension})") - self._decl.attrspec = my_attrspec - - super(BaseDeclGen, self).__init__(parent, self._decl) - - @property - def names(self): - ''' - :returns: the names of the variables being declared. - :rtype: list of str. - ''' - return self._names - - @property - def root(self): - ''' - :returns: the associated Type object. - :rtype: \ - :py:class:`fparser.one.typedecl_statements.TypeDeclarationStatement`. - ''' - return self._decl - - @abc.abstractmethod - def _check_initial_values(self, dtype, values): - ''' - Check that the supplied values are consistent with the requested - data type. This method must be overridden in any sub-class of - BaseDeclGen and is called by the BaseDeclGen constructor. - - :param str dtype: Fortran type. - :param list values: list of values as strings. - :raises RuntimeError: if the supplied values are not consistent \ - with the specified data type or are not \ - supported. - ''' - - -class DeclGen(BaseDeclGen): - '''Generates a Fortran declaration for variables of various intrinsic - types (integer, real and logical). For character variables - CharDeclGen should be used. - - :param parent: node to which to add this declaration as a child. - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str datatype: the (intrinsic) type for this declaration. - :param list entity_decls: list of variable names to declare. - :param str intent: the INTENT attribute of this declaration. - :param bool pointer: whether or not this is a pointer declaration. - :param str kind: the KIND attribute to use for this declaration. - :param str dimension: the DIMENSION specifier (i.e. the xx in \ - DIMENSION(xx)). - :param bool allocatable: whether this declaration is for an \ - ALLOCATABLE quantity. - :param bool save: whether this declaration has the SAVE attribute. - :param bool target: whether this declaration has the TARGET attribute. - :param initial_values: initial value to give each variable. - :type initial_values: list of str with same no. of elements as \ - entity_decls - :param bool private: whether this declaration has the PRIVATE attribute \ - (default is False). - - :raises RuntimeError: if datatype is not one of DeclGen.SUPPORTED_TYPES. - - ''' - # The Fortran intrinsic types supported by this class - SUPPORTED_TYPES = ["integer", "real", "logical"] - - def __init__(self, parent, datatype="", entity_decls=None, intent="", - pointer=False, kind="", dimension="", allocatable=False, - save=False, target=False, initial_values=None, private=False): - - dtype = datatype.lower() - if dtype not in self.SUPPORTED_TYPES: - raise RuntimeError( - f"f2pygen.DeclGen.init: Only {self.SUPPORTED_TYPES} types are " - f"currently supported and you specified '{datatype}'") - - fort_fmt = FortranFormat(True, False) # free form, strict - if dtype == "integer": - reader = FortranStringReader("integer :: vanilla") - reader.set_format(fort_fmt) - myline = reader.next() - self._decl = fparser1.typedecl_statements.Integer(parent.root, - myline) - elif dtype == "real": - reader = FortranStringReader("real :: vanilla") - reader.set_format(fort_fmt) - myline = reader.next() - self._decl = fparser1.typedecl_statements.Real(parent.root, myline) - elif dtype == "logical": - reader = FortranStringReader("logical :: vanilla") - reader.set_format(fort_fmt) - myline = reader.next() - self._decl = fparser1.typedecl_statements.Logical(parent.root, - myline) - else: - # Defensive programming in case SUPPORTED_TYPES is added to - # but not handled here - raise InternalError( - f"Type '{dtype}' is in DeclGen.SUPPORTED_TYPES " - f"but not handled by constructor.") - - # Add any kind-selector - if kind: - self._decl.selector = ('', kind) - - super(DeclGen, self).__init__(parent=parent, datatype=datatype, - entity_decls=entity_decls, - intent=intent, pointer=pointer, - dimension=dimension, - allocatable=allocatable, save=save, - target=target, - initial_values=initial_values, - private=private) - - def _check_initial_values(self, dtype, values): - ''' - Check that the supplied values are consistent with the requested - data type. Note that this checking is fairly basic and does not - support a number of valid Fortran forms (e.g. arithmetic expressions - involving constants or parameters). - - :param str dtype: Fortran intrinsic type. - :param list values: list of values as strings. - :raises RuntimeError: if the supplied values are not consistent \ - with the specified data type or are not \ - supported. - ''' - from fparser.two.pattern_tools import abs_name, \ - abs_logical_literal_constant, abs_signed_int_literal_constant, \ - abs_signed_real_literal_constant - if dtype == "logical": - # Can be .true., .false. or a valid Fortran variable name - for val in values: - if not abs_logical_literal_constant.match(val) and \ - not abs_name.match(val): - raise RuntimeError( - f"Initial value of '{val}' for a logical variable is " - f"invalid or unsupported") - elif dtype == "integer": - # Can be a an integer expression or a valid Fortran variable name - for val in values: - if not abs_signed_int_literal_constant.match(val) and \ - not abs_name.match(val): - raise RuntimeError( - f"Initial value of '{val}' for an integer variable is " - f"invalid or unsupported") - elif dtype == "real": - # Can be a floating-point expression or a valid Fortran name - for val in values: - if not abs_signed_real_literal_constant.match(val) and \ - not abs_name.match(val): - raise RuntimeError( - f"Initial value of '{val}' for a real variable is " - f"invalid or unsupported") - else: - # We should never get to here because we check that the type - # is supported before calling this routine. - raise InternalError( - f"unsupported type '{dtype}' - should be " - f"one of {DeclGen.SUPPORTED_TYPES}") - - -class CharDeclGen(BaseDeclGen): - ''' - Generates a Fortran declaration for character variables. - - :param parent: node to which to add this declaration as a child. - :type parent: :py:class:`psyclone.f2pygen.BaseGen`. - :param list entity_decls: list of variable names to declare. - :param str intent: the INTENT attribute of this declaration. - :param bool pointer: whether or not this is a pointer declaration. - :param str kind: the KIND attribute to use for this declaration. - :param str dimension: the DIMENSION specifier (i.e. the xx in \ - DIMENSION(xx)). - :param bool allocatable: whether this declaration is for an \ - ALLOCATABLE quantity. - :param bool save: whether this declaration has the SAVE attribute. - :param bool target: whether this declaration has the TARGET attribute. - :param str length: expression to use for the (len=xx) selector. - :param initial_values: list of initial values, one for each variable. \ - Each of these can be either a variable name or a literal, quoted \ - string (e.g. "'hello'"). Default is None. - :type initial_values: list of str with same no. of elements as entity_decls - :param bool private: whether this declaration has the PRIVATE attribute. - - ''' - def __init__(self, parent, entity_decls=None, intent="", - pointer=False, kind="", dimension="", allocatable=False, - save=False, target=False, length="", initial_values=None, - private=False): - - reader = FortranStringReader( - "character(len=vanilla_len) :: vanilla") - reader.set_format(FortranFormat(True, False)) - myline = reader.next() - self._decl = fparser1.typedecl_statements.Character(parent.root, - myline) - # Add character- and kind-selectors - self._decl.selector = (length, kind) - - super(CharDeclGen, self).__init__(parent=parent, - datatype="character", - entity_decls=entity_decls, - intent=intent, pointer=pointer, - dimension=dimension, - allocatable=allocatable, save=save, - target=target, - initial_values=initial_values, - private=private) - - def _check_initial_values(self, _, values): - ''' - Check that initial values provided for a Character declaration are - valid. - :param _: for consistency with base-class interface. - :param list values: list of strings containing initial values. - :raises RuntimeError: if any of the supplied initial values is not \ - valid for a Character declaration. - ''' - from fparser.two.pattern_tools import abs_name - # Can be a quoted string or a valid Fortran name - # TODO it would be nice if fparser.two.pattern_tools provided - # e.g. abs_character_literal_constant - for val in values: - if not abs_name.match(val): - if not ((val.startswith("'") and val.endswith("'")) or - (val.startswith('"') and val.endswith('"'))): - raise RuntimeError( - f"Initial value of '{val}' for a character variable " - f"is invalid or unsupported") - - -class TypeDeclGen(BaseDeclGen): - ''' - Generates a Fortran declaration for variables of a derived type. - - :param parent: node to which to add this declaration as a child. - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str datatype: the type for this declaration. - :param list entity_decls: list of variable names to declare. - :param str intent: the INTENT attribute of this declaration. - :param bool pointer: whether or not this is a pointer declaration. - :param str dimension: the DIMENSION specifier (i.e. the xx in \ - DIMENSION(xx)). - :param bool allocatable: whether this declaration is for an \ - ALLOCATABLE quantity. - :param bool save: whether this declaration has the SAVE attribute. - :param bool target: whether this declaration has the TARGET attribute. - :param bool is_class: whether this is a class rather than type declaration. - :param bool private: whether or not this declaration has the PRIVATE \ - attribute. (Defaults to False.) - ''' - def __init__(self, parent, datatype="", entity_decls=None, intent="", - pointer=False, dimension="", allocatable=False, - save=False, target=False, is_class=False, private=False): - if is_class: - reader = FortranStringReader("class(vanillatype) :: vanilla") - else: - reader = FortranStringReader("type(vanillatype) :: vanilla") - reader.set_format(FortranFormat(True, False)) # free form, strict - myline = reader.next() - if is_class: - self._decl = fparser1.typedecl_statements.Class(parent.root, - myline) - else: - self._decl = fparser1.typedecl_statements.Type(parent.root, myline) - self._decl.selector = ('', datatype) - - super(TypeDeclGen, self).__init__(parent=parent, datatype=datatype, - entity_decls=entity_decls, - intent=intent, pointer=pointer, - dimension=dimension, - allocatable=allocatable, save=save, - target=target, private=private) - - def _check_initial_values(self, _type, _values): - ''' - Simply here to override abstract method in base class. It is an - error if we ever call it because we don't support initial values for - declarations of derived types. - - :param str _type: the type of the Fortran variable to be declared. - :param list _values: list of str containing initialisation \ - values/expressions. - :raises InternalError: because specifying initial values for \ - variables of derived type is not supported. - ''' - raise InternalError( - "This method should not have been called because initial values " - "for derived-type declarations are not supported.") - - -class TypeCase(Case): - ''' Generate a Fortran SELECT CASE statement ''' - # TODO can this whole class be deleted? - def tofortran(self, isfix=None): - tab = self.get_indent_tab(isfix=isfix) - type_str = 'TYPE IS' - if self.items: - item_list = [] - for item in self.items: - item_list.append((' : '.join(item)).strip()) - type_str += f" ( {(', '.join(item_list))} )" - else: - type_str = 'CLASS DEFAULT' - if self.name: - type_str += ' ' + self.name - return tab + type_str - - -class SelectionGen(BaseGen): - ''' Generate a Fortran SELECT block ''' - # TODO can this whole class be deleted? - - def __init__(self, parent, expr="UNSET", typeselect=False): - ''' - Construct a SelectionGen for creating a SELECT block - - :param parent: node to which to add this select block as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str expr: the CASE expression - :param bool typeselect: whether or not this is a SELECT TYPE rather - than a SELECT CASE - ''' - self._typeselect = typeselect - reader = FortranStringReader( - "SELECT CASE (x)\nCASE (1)\nCASE DEFAULT\nEND SELECT") - reader.set_format(FortranFormat(True, True)) # free form, strict - select_line = reader.next() - self._case_line = reader.next() - self._case_default_line = reader.next() - end_select_line = reader.next() - if self._typeselect: - select = SelectType(parent.root, select_line) - else: - select = SelectCase(parent.root, select_line) - endselect = EndSelect(select, end_select_line) - select.expr = expr - select.content.append(endselect) - BaseGen.__init__(self, parent, select) - - def addcase(self, casenames, content=None): - ''' Add a case to this select block ''' - if content is None: - content = [] - if self._typeselect: - case = TypeCase(self.root, self._case_line) - else: - case = Case(self.root, self._case_line) - case.items = [casenames] - self.root.content.insert(0, case) - idx = 0 - for stmt in content: - idx += 1 - self.root.content.insert(idx, stmt.root) - - def adddefault(self): - ''' Add the default case to this select block ''' - if self._typeselect: - case_default = TypeCase(self.root, self._case_default_line) - else: - case_default = Case(self.root, self._case_default_line) - self.root.content.insert(len(self.root.content)-1, case_default) - - -class DoGen(BaseGen): - ''' Create a Fortran Do loop ''' - def __init__(self, parent, variable_name, start, end, step=None): - ''' - :param parent: the node to which to add this do loop as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str variable_name: the name of the loop variable - :param str start: start value for Do loop - :param str end: upper-limit of Do loop - :param str step: increment to use in Do loop - ''' - reader = FortranStringReader("do i=1,n\nend do") - reader.set_format(FortranFormat(True, True)) # free form, strict - doline = reader.next() - enddoline = reader.next() - dogen = fparser1.block_statements.Do(parent.root, doline) - dogen.loopcontrol = variable_name + "=" + start + "," + end - if step is not None: - dogen.loopcontrol = dogen.loopcontrol + "," + step - enddo = fparser1.block_statements.EndDo(dogen, enddoline) - dogen.content.append(enddo) - - BaseGen.__init__(self, parent, dogen) - - def add(self, content, position=None, bubble_up=False): - if position is None: - position = ["auto"] - - if position[0] == "auto" and bubble_up: # pragma: no cover - # There's currently no case where a bubbled-up statement - # will live within a do loop so bubble it up again. - self.parent.add(content, bubble_up=True) - return - - if position[0] == "auto" or position[0] == "append": - if (position[0] == "auto" and - bubble_up_type(content)): # pragma: no cover - # use and declaration statements cannot appear in a do loop - # so pass on to parent - self.parent.add(content, bubble_up=True) - return - else: - # append at the end of the loop. This is not a simple - # append as the last element in the loop is the "end - # do" so we insert at the penultimate location - BaseGen.add(self, content, - position=["insert", len(self.root.content)-1]) - else: - BaseGen.add(self, content, position=position) - - -class IfThenGen(BaseGen): - ''' Generate a fortran if, then, end if statement. ''' - - def __init__(self, parent, clause): - ''' - :param parent: Node to which to add this IfThen as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str clause: the condition, xx, to evaluate in the if(xx)then - ''' - reader = FortranStringReader("if (dummy) then\nend if") - reader.set_format(FortranFormat(True, True)) # free form, strict - ifthenline = reader.next() - endifline = reader.next() - - my_if = fparser1.block_statements.IfThen(parent.root, ifthenline) - my_if.expr = clause - my_endif = fparser1.block_statements.EndIfThen(my_if, endifline) - my_if.content.append(my_endif) - - BaseGen.__init__(self, parent, my_if) - - def add(self, content, position=None): - if position is None: - position = ["auto"] - if position[0] == "auto" or position[0] == "append": - if position[0] == "auto" and bubble_up_type(content): - # use and declaration statements cannot appear in an if - # block so pass on (bubble-up) to parent - self.parent.add(content, bubble_up=True) - else: - # append at the end of the loop. This is not a simple - # append as the last element in the if is the "end if" - # so we insert at the penultimate location - BaseGen.add(self, content, - position=["insert", len(self.root.content)-1]) - else: - BaseGen.add(self, content, position=position) - - -class AssignGen(BaseGen): - ''' Generates a Fortran statement where a value is assigned to a - variable quantity ''' - - def __init__(self, parent, lhs="", rhs="", pointer=False): - ''' - :param parent: the node to which to add this assignment as a child - :type parent: :py:class:`psyclone.f2pygen.BaseGen` - :param str lhs: the LHS of the assignment expression - :param str rhs: the RHS of the assignment expression - :param bool pointer: whether or not this is a pointer assignment - ''' - if pointer: - reader = FortranStringReader("lhs=>rhs") - else: - reader = FortranStringReader("lhs=rhs") - reader.set_format(FortranFormat(True, True)) # free form, strict - myline = reader.next() - if pointer: - self._assign = fparser1.statements.PointerAssignment(parent.root, - myline) - else: - self._assign = fparser1.statements.Assignment(parent.root, myline) - self._assign.expr = rhs - self._assign.variable = lhs - BaseGen.__init__(self, parent, self._assign) diff --git a/src/psyclone/tests/f2pygen_test.py b/src/psyclone/tests/f2pygen_test.py deleted file mode 100644 index ab6a38f52e..0000000000 --- a/src/psyclone/tests/f2pygen_test.py +++ /dev/null @@ -1,1548 +0,0 @@ -# ----------------------------------------------------------------------------- -# BSD 3-Clause License -# -# Copyright (c) 2017-2025, Science and Technology Facilities Council -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# ----------------------------------------------------------------------------- -# Authors: R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab - -''' Tests for the f2pygen module of PSyclone ''' - -import pytest -from psyclone.configuration import Config -from psyclone.f2pygen import ( - adduse, AssignGen, AllocateGen, BaseGen, CallGen, CharDeclGen, CommentGen, - DeallocateGen, DeclGen, DirectiveGen, DoGen, IfThenGen, ImplicitNoneGen, - ModuleGen, PSyIRGen, SelectionGen, SubroutineGen, TypeDeclGen, UseGen) -from psyclone.errors import GenerationError, InternalError -from psyclone.psyir.nodes import Node, Return -from psyclone.tests.utilities import Compile, count_lines, line_number - -# Fortran we have to add to some of the generated code in order to -# perform compilation checks. -TYPEDECL = '''\ -type :: field_type - integer :: halo_dirty -end type field_type -''' - - -def test_decl_no_replication_scalars(): - '''Check that the same scalar variable will only get declared once in - a module and a subroutine. - - ''' - variable_name = "arg_name" - for datatype in DeclGen.SUPPORTED_TYPES: - module = ModuleGen(name="testmodule") - module.add(DeclGen(module, datatype=datatype, - entity_decls=[variable_name])) - module.add(DeclGen(module, datatype=datatype, - entity_decls=[variable_name])) - subroutine = SubroutineGen(module, name="testsubroutine") - module.add(subroutine) - subroutine.add(DeclGen(subroutine, datatype=datatype, - entity_decls=[variable_name])) - subroutine.add(DeclGen(subroutine, datatype=datatype, - entity_decls=[variable_name])) - generated_code = str(module.root) - assert generated_code.count(variable_name) == 2 - - -def test_decl_no_replication_types(): - '''Check that the same derived-type variable will only get declared - once in a module and a subroutine. - - ''' - variable_name = "arg_name" - datatype = "field_type" - module = ModuleGen(name="testmodule") - module.add(TypeDeclGen(module, datatype=datatype, - entity_decls=[variable_name])) - module.add(TypeDeclGen(module, datatype=datatype, - entity_decls=[variable_name])) - subroutine = SubroutineGen(module, name="testsubroutine") - module.add(subroutine) - subroutine.add(TypeDeclGen(subroutine, datatype=datatype, - entity_decls=[variable_name])) - subroutine.add(TypeDeclGen(subroutine, datatype=datatype, - entity_decls=[variable_name])) - generated_code = str(module.root) - assert generated_code.count(variable_name) == 2 - - -def test_decl_no_replication_char(): - '''Check that the character variable will only get declared once in a - module and a subroutine. - - ''' - variable_name = "arg_name" - module = ModuleGen(name="testmodule") - module.add(CharDeclGen(module, entity_decls=[variable_name])) - module.add(CharDeclGen(module, entity_decls=[variable_name])) - subroutine = SubroutineGen(module, name="testsubroutine") - module.add(subroutine) - subroutine.add(CharDeclGen(subroutine, entity_decls=[variable_name])) - subroutine.add(CharDeclGen(subroutine, entity_decls=[variable_name])) - generated_code = str(module.root) - assert generated_code.count(variable_name) == 2 - - -def test_subroutine_var_with_implicit_none(): - ''' test that a variable is added after an implicit none - statement in a subroutine''' - module = ModuleGen(name="testmodule") - subroutine = SubroutineGen(module, name="testsubroutine", - implicitnone=True) - module.add(subroutine) - subroutine.add(DeclGen(subroutine, datatype="integer", - entity_decls=["var1"])) - idx_var = line_number(subroutine.root, "INTEGER var1") - idx_imp_none = line_number(subroutine.root, "IMPLICIT NONE") - print(str(module.root)) - assert idx_var - idx_imp_none == 1, \ - "variable declation must be after implicit none" - - -def test_subroutine_var_intent_in_with_directive(): - ''' test that a variable declared as intent in is added before - a directive in a subroutine''' - module = ModuleGen(name="testmodule") - subroutine = SubroutineGen(module, name="testsubroutine", - implicitnone=False) - module.add(subroutine) - subroutine.add(DirectiveGen(subroutine, "omp", "begin", - "parallel", "")) - subroutine.add(DeclGen(subroutine, datatype="integer", - intent="in", entity_decls=["var1"])) - idx_par = line_number(subroutine.root, "!$omp parallel") - idx_var = line_number(subroutine.root, "INTEGER, intent(in) :: var1") - assert idx_par - idx_var == 1, \ - "variable declaration must be before directive" - - -def test_if(): - ''' Check that an if gets created succesfully. ''' - module = ModuleGen(name="testmodule") - clause = "a < b" - fortran_if = IfThenGen(module, clause) - module.add(fortran_if) - lines = str(module.root).splitlines() - assert "IF (" + clause + ") THEN" in lines[3] - assert "END IF" in lines[4] - - -def test_if_content(): - ''' Check that the content of an if gets created successfully. ''' - module = ModuleGen(name="testmodule") - clause = "a < b" - if_statement = IfThenGen(module, clause) - if_statement.add(CommentGen(if_statement, "HELLO")) - module.add(if_statement) - lines = str(module.root).splitlines() - assert "IF (" + clause + ") THEN" in lines[3] - assert "!HELLO" in lines[4] - assert "END IF" in lines[5] - - -def test_if_with_position_before(): - ''' Check that IfThenGen.add() correctly uses the position - argument if supplied. ''' - module = ModuleGen(name="testmodule") - clause = "a < b" - if_statement = IfThenGen(module, clause) - com1 = CommentGen(if_statement, "HELLO") - if_statement.add(com1) - if_statement.add(CommentGen(if_statement, "GOODBYE"), - position=["before", com1.root]) - module.add(if_statement) - lines = str(module.root).splitlines() - assert "IF (" + clause + ") THEN" in lines[3] - assert "!GOODBYE" in lines[4] - assert "!HELLO" in lines[5] - assert "END IF" in lines[6] - - -def test_if_with_position_append(): - ''' Check that IfThenGen.add() correctly uses the position - argument when *append* is specified. ''' - module = ModuleGen(name="testmodule") - clause = "a < b" - if_statement = IfThenGen(module, clause) - com1 = CommentGen(if_statement, "HELLO") - if_statement.add(com1) - if_statement.add(CommentGen(if_statement, "GOODBYE"), - position=["append"]) - module.add(if_statement) - print(str(module.root)) - lines = str(module.root).splitlines() - assert "IF (" + clause + ") THEN" in lines[3] - assert "!HELLO" in lines[4] - assert "!GOODBYE" in lines[5] - assert "END IF" in lines[6] - - -def test_if_add_use(): - ''' Check that IfThenGen.add() correctly handles the case - when it is passed a UseGen object ''' - module = ModuleGen(name="testmodule") - clause = "a < b" - if_statement = IfThenGen(module, clause) - if_statement.add(CommentGen(if_statement, "GOODBYE")) - if_statement.add(UseGen(if_statement, name="dibna")) - module.add(if_statement) - print(str(module.root)) - use_line = line_number(module.root, "USE dibna") - if_line = line_number(module.root, "IF (" + clause + ") THEN") - # The use statement must come before the if..then block - assert use_line < if_line - - -def test_comment(): - ''' check that a comment gets created succesfully. ''' - module = ModuleGen(name="testmodule") - content = "HELLO" - comment = CommentGen(module, content) - module.add(comment) - lines = str(module.root).splitlines() - assert "!" + content in lines[3] - - -def test_add_before(): - ''' add the new code before a particular object ''' - module = ModuleGen(name="testmodule") - subroutine = SubroutineGen(module, name="testsubroutine") - module.add(subroutine) - loop = DoGen(subroutine, "it", "1", "10") - subroutine.add(loop) - call = CallGen(subroutine, "testcall") - subroutine.add(call, position=["before", loop.root]) - lines = str(module.root).splitlines() - # the call should be inserted before the loop - print(lines) - assert "SUBROUTINE testsubroutine" in lines[3] - assert "CALL testcall" in lines[4] - assert "DO it=1,10" in lines[5] - - -def test_mod_vanilla(): - ''' Check that we can create a basic, vanilla module ''' - module = ModuleGen() - lines = str(module.root).splitlines() - assert "MODULE" in lines[0] - assert "IMPLICIT NONE" in lines[1] - assert "CONTAINS" in lines[2] - assert "END MODULE" in lines[3] - - -def test_mod_name(): - ''' Check that we can create a module with a specified name ''' - name = "test" - module = ModuleGen(name=name) - assert "MODULE " + name in str(module.root) - - -def test_mod_no_contains(): - ''' Check that we can switch-off the generation of a CONTAINS - statement within a module ''' - module = ModuleGen(name="test", contains=False) - assert "CONTAINS" not in str(module.root) - - -def test_mod_no_implicit_none(): - ''' Check that we can switch off the generation of IMPLICIT NONE - within a module ''' - module = ModuleGen(name="test", implicitnone=False) - assert "IMPLICIT NONE" not in str(module.root) - - -def test_invalid_add_raw_subroutine_argument(): - ''' test that an error is thrown if the wrong type of object - is passed to the add_raw_subroutine method ''' - module = ModuleGen(name="test") - invalid_type = "string" - with pytest.raises(Exception): - module.add_raw_subroutine(invalid_type) - - -def test_allocate_arg_str(): - '''check that an allocate gets created succesfully with content being - a string.''' - module = ModuleGen(name="testmodule") - content = "hello" - allocate = AllocateGen(module, content) - module.add(allocate) - lines = str(module.root).splitlines() - assert "ALLOCATE (" + content + ")" in lines[3] - - -def test_allocate_mold(): - '''check that an allocate gets created succesfully with a - mold parameter.''' - module = ModuleGen(name="testmodule") - allocate = AllocateGen(module, "hello", mold="abc") - module.add(allocate) - lines = str(module.root).splitlines() - assert "ALLOCATE (hello, mold=abc)" in lines[3] - - -def test_allocate_arg_list(): - '''check that an allocate gets created succesfully with content being - a list.''' - module = ModuleGen(name="testmodule") - content = ["hello", "how", "are", "you"] - content_str = "" - for idx, name in enumerate(content): - content_str += name - if idx+1 < len(content): - content_str += ", " - allocate = AllocateGen(module, content) - module.add(allocate) - lines = str(module.root).splitlines() - assert "ALLOCATE (" + content_str + ")" in lines[3] - - -def test_allocate_incorrect_arg_type(): - '''check that an allocate raises an error if an unknown type is - passed.''' - module = ModuleGen(name="testmodule") - content = 3 - with pytest.raises(RuntimeError): - _ = AllocateGen(module, content) - - -def test_deallocate_arg_str(): - '''check that a deallocate gets created succesfully with content - being a str.''' - module = ModuleGen(name="testmodule") - content = "goodbye" - deallocate = DeallocateGen(module, content) - module.add(deallocate) - lines = str(module.root).splitlines() - assert "DEALLOCATE (" + content + ")" in lines[3] - - -def test_deallocate_arg_list(): - '''check that a deallocate gets created succesfully with content - being a list.''' - module = ModuleGen(name="testmodule") - content = ["and", "now", "the", "end", "is", "near"] - content_str = "" - for idx, name in enumerate(content): - content_str += name - if idx+1 < len(content): - content_str += ", " - deallocate = DeallocateGen(module, content) - module.add(deallocate) - lines = str(module.root).splitlines() - assert "DEALLOCATE (" + content_str + ")" in lines[3] - - -def test_deallocate_incorrect_arg_type(): - '''check that a deallocate raises an error if an unknown type is - passed.''' - module = ModuleGen(name="testmodule") - content = 3 - with pytest.raises(RuntimeError): - _ = DeallocateGen(module, content) - - -def test_imp_none_in_module(): - ''' test that implicit none can be added to a module in the - correct location''' - module = ModuleGen(name="testmodule", implicitnone=False) - module.add(ImplicitNoneGen(module)) - in_idx = line_number(module.root, "IMPLICIT NONE") - cont_idx = line_number(module.root, "CONTAINS") - assert in_idx > -1, "IMPLICIT NONE not found" - assert cont_idx > -1, "CONTAINS not found" - assert cont_idx - in_idx == 1, "CONTAINS is not on the line after" +\ - " IMPLICIT NONE" - - -def test_imp_none_in_module_with_decs(): - ''' test that implicit none is added before any declaration - statements in a module when auto (the default) is used for - insertion ''' - module = ModuleGen(name="testmodule", implicitnone=False) - module.add(DeclGen(module, datatype="integer", - entity_decls=["var1"])) - module.add(TypeDeclGen(module, datatype="my_type", - entity_decls=["type1"])) - module.add(ImplicitNoneGen(module)) - in_idx = line_number(module.root, "IMPLICIT NONE") - assert in_idx == 1 - - -def test_imp_none_in_module_with_use_and_decs(): - ''' test that implicit none is added after any use statements - and before any declarations in a module when auto (the - default) is used for insertion''' - module = ModuleGen(name="testmodule", implicitnone=False) - module.add(DeclGen(module, datatype="integer", - entity_decls=["var1"])) - module.add(TypeDeclGen(module, datatype="my_type", - entity_decls=["type1"])) - module.add(UseGen(module, "fred")) - module.add(ImplicitNoneGen(module)) - in_idx = line_number(module.root, "IMPLICIT NONE") - assert in_idx == 2 - - -def test_imp_none_in_module_with_use_and_decs_and_comments(): - ''' test that implicit none is added after any use statements - and before any declarations in a module in the presence of - comments when auto (the default) is used for insertion''' - module = ModuleGen(name="testmodule", implicitnone=False) - module.add(DeclGen(module, datatype="integer", - entity_decls=["var1"])) - module.add(TypeDeclGen(module, datatype="my_type", - entity_decls=["type1"])) - module.add(UseGen(module, "fred")) - for idx in [0, 1, 2, 3]: - module.add(CommentGen(module, " hello "+str(idx)), - position=["before_index", 2*idx]) - module.add(ImplicitNoneGen(module)) - in_idx = line_number(module.root, "IMPLICIT NONE") - assert in_idx == 3 - - -def test_imp_none_in_module_already_exists(): - ''' test that implicit none is not added to a module when one - already exists''' - module = ModuleGen(name="testmodule", implicitnone=True) - module.add(ImplicitNoneGen(module)) - count = count_lines(module.root, "IMPLICIT NONE") - print(str(module.root)) - assert count == 1, \ - "There should only be one instance of IMPLICIT NONE" - - -def test_imp_none_in_subroutine(): - ''' test that implicit none can be added to a subroutine ''' - module = ModuleGen(name="testmodule") - subroutine = SubroutineGen(module, name="testsubroutine") - module.add(subroutine) - subroutine.add(ImplicitNoneGen(subroutine)) - assert 'IMPLICIT NONE' in str(subroutine.root) - - -def test_imp_none_in_subroutine_with_decs(): - ''' test that implicit none is added before any declaration - statements in a subroutine when auto (the default) is used for - insertion ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var1"])) - sub.add(TypeDeclGen(sub, datatype="my_type", - entity_decls=["type1"])) - sub.add(ImplicitNoneGen(module)) - in_idx = line_number(sub.root, "IMPLICIT NONE") - assert in_idx == 1 - - -def test_imp_none_in_subroutine_with_use_and_decs(): - ''' test that implicit none is added after any use statements - and before any declarations in a subroutine when auto (the - default) is used for insertion''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var1"])) - sub.add(TypeDeclGen(sub, datatype="my_type", - entity_decls=["type1"])) - sub.add(UseGen(sub, "fred")) - sub.add(ImplicitNoneGen(sub)) - in_idx = line_number(sub.root, "IMPLICIT NONE") - assert in_idx == 2 - - -def test_imp_none_in_subroutine_with_use_and_decs_and_comments(): - ''' test that implicit none is added after any use statements - and before any declarations in a subroutine in the presence of - comments when auto (the default) is used for insertion''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var1"])) - sub.add(TypeDeclGen(sub, datatype="my_type", - entity_decls=["type1"])) - sub.add(UseGen(sub, "fred")) - for idx in [0, 1, 2, 3]: - sub.add(CommentGen(sub, " hello "+str(idx)), - position=["before_index", 2*idx]) - sub.add(ImplicitNoneGen(sub)) - in_idx = line_number(sub.root, "IMPLICIT NONE") - assert in_idx == 3 - - -def test_imp_none_in_subroutine_already_exists(): - ''' test that implicit none is not added to a subroutine when - one already exists''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine", implicitnone=True) - module.add(sub) - sub.add(ImplicitNoneGen(sub)) - count = count_lines(sub.root, "IMPLICIT NONE") - assert count == 1, \ - "There should only be one instance of IMPLICIT NONE" - - -def test_imp_none_exception_if_wrong_parent(): - ''' test that an exception is thrown if implicit none is added - and the parent is not a module or a subroutine ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - dogen = DoGen(sub, "i", "1", "10") - sub.add(dogen) - with pytest.raises(Exception): - dogen.add(ImplicitNoneGen(dogen)) - - -def test_subgen_implicit_none_false(): - ''' test that implicit none is not added to the subroutine if - not requested ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine", implicitnone=False) - module.add(sub) - count = count_lines(sub.root, "IMPLICIT NONE") - assert count == 0, "IMPLICIT NONE SHOULD NOT EXIST" - - -def test_subgen_implicit_none_true(): - ''' test that implicit none is added to the subroutine if - requested ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine", implicitnone=True) - module.add(sub) - count = count_lines(sub.root, "IMPLICIT NONE") - assert count == 1, "IMPLICIT NONE SHOULD EXIST" - - -def test_subgen_implicit_none_default(): - ''' test that implicit none is not added to the subroutine by - default ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - count = count_lines(sub.root, "IMPLICIT NONE") - assert count == 0, "IMPLICIT NONE SHOULD NOT EXIST BY DEFAULT" - - -def test_subgen_args(): - ''' Test that the args property works as expected ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine", - args=["arg1", "arg2"]) - my_args = sub.args - assert len(my_args) == 2 - - -def test_directive_wrong_type(): - ''' Check that we raise an error if we request a Directive of - unrecognised type ''' - parent = Node() - with pytest.raises(RuntimeError) as err: - _ = DirectiveGen(parent, - "some_dir_type", "begin", "do", - "schedule(static)") - assert "unsupported directive language" in str(err.value) - - -def test_ompdirective_wrong(): - ''' Check that we raise an error if we request an OMP Directive of - unrecognised type ''' - parent = Node() - with pytest.raises(RuntimeError) as err: - _ = DirectiveGen(parent, - "omp", "begin", "dosomething", - "schedule(static)") - assert "unrecognised directive type" in str(err.value) - - -def test_ompdirective_wrong_posn(): - ''' Check that we raise an error if we request an OMP Directive with - an invalid position ''' - parent = Node() - with pytest.raises(RuntimeError) as err: - _ = DirectiveGen(parent, - "omp", "start", "do", - "schedule(static)") - assert "unrecognised position 'start'" in str(err.value) - - -def test_ompdirective_type(): - ''' Check that we can query the type of an OMP Directive ''' - parent = Node() - dirgen = DirectiveGen(parent, - "omp", "begin", "do", - "schedule(static)") - ompdir = dirgen.root - assert ompdir.type == "do" - - -def test_basegen_add_auto(): - ''' Check that attempting to call add on BaseGen raises an error if - position is "auto"''' - parent = Node() - bgen = BaseGen(parent, parent) - obj = Node() - with pytest.raises(Exception) as err: - bgen.add(obj, position=['auto']) - assert "auto option must be implemented by the sub" in str(err.value) - - -def test_basegen_add_invalid_posn(): - '''Check that attempting to call add on BaseGen with an invalid - position argument raises an error''' - parent = Node() - bgen = BaseGen(parent, parent) - obj = Node() - with pytest.raises(Exception) as err: - bgen.add(obj, position=['wrong']) - assert "supported positions are ['append', 'first'" in str(err.value) - - -def test_basegen_append(): - '''Check that we can append an object to the tree''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var1"])) - sub.add(CommentGen(sub, " hello"), position=["append"]) - cindex = line_number(sub.root, "hello") - assert cindex == 3 - - -def test_basegen_append_default(): - ''' Check if no position argument is supplied to BaseGen.add() then it - defaults to appending ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - BaseGen.add(sub, DeclGen(sub, datatype="integer", - entity_decls=["var1"])) - BaseGen.add(sub, CommentGen(sub, " hello")) - cindex = line_number(sub.root, "hello") - assert cindex == 3 - - -def test_basegen_first(): - '''Check that we can insert an object as the first child''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var1"])) - sub.add(CommentGen(sub, " hello"), position=["first"]) - cindex = line_number(sub.root, "hello") - assert cindex == 1 - - -def test_basegen_after_index(): - '''Check that we can insert an object using "after_index"''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var1"])) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var2"])) - sub.add(CommentGen(sub, " hello"), position=["after_index", 1]) - # The code checked by line_number() *includes* the SUBROUTINE - # statement (which is obviously not a child of the SubroutineGen - # object) and therefore the index it returns is 1 greater than we - # might expect. - assert line_number(sub.root, "hello") == 3 - - -def test_basegen_before_error(): - '''Check that we raise an error when attempting to insert an object - before another object that is not present in the tree''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var1"])) - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["var2"])) - # Create an object but do not add it as a child of sub - dgen = DeclGen(sub, datatype="real", - entity_decls=["rvar1"]) - # Try to add an object before the orphan dgen - with pytest.raises(RuntimeError) as err: - sub.add(CommentGen(sub, " hello"), position=["before", dgen]) - assert "Failed to find supplied object" in str(err.value) - - -def test_basegen_last_declaration_no_vars(): - '''Check that we raise an error when requesting the position of the - last variable declaration if we don't have any variables''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - # Request the position of the last variable declaration - # even though we haven't got any - with pytest.raises(RuntimeError) as err: - sub.last_declaration() - assert "no variable declarations found" in str(err.value) - - -def test_basegen_start_parent_loop_dbg(capsys): - '''Check the debug option to the start_parent_loop method''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - loop = DoGen(sub, "it", "1", "10") - sub.add(loop) - call = CallGen(loop, "testcall") - loop.add(call) - call.start_parent_loop(debug=True) - out, _ = capsys.readouterr() - print(out) - expected = ("Parent is a do loop so moving to the parent\n" - "The type of the current node is now \n" - "The type of parent is \n" - "Finding the loops position in its parent ...\n" - "The loop's index is 0\n") - assert expected in out - - -def test_basegen_start_parent_loop_not_first_child_dbg(capsys): - '''Check the debug option to the start_parent_loop method when the loop - is not the first child of the subroutine''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - call0 = CallGen(sub, "testcall") - sub.add(call0) - loop = DoGen(sub, "it", "1", "10") - sub.add(loop) - call = CallGen(loop, "testcall") - loop.add(call) - call.start_parent_loop(debug=True) - out, _ = capsys.readouterr() - print(out) - expected = ("Parent is a do loop so moving to the parent\n" - "The type of the current node is now \n" - "The type of parent is \n" - "Finding the loops position in its parent ...\n" - "The loop's index is 1\n") - assert expected in out - - -def test_basegen_start_parent_loop_omp_begin_dbg(capsys): - '''Check the debug option to the start_parent_loop method when we have - an OpenMP begin directive''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - dgen = DirectiveGen(sub, "omp", "begin", "do", "schedule(static)") - sub.add(dgen) - loop = DoGen(sub, "it", "1", "10") - sub.add(loop) - call = CallGen(loop, "testcall") - loop.add(call) - call.start_parent_loop(debug=True) - out, _ = capsys.readouterr() - print(out) - expected = ("Parent is a do loop so moving to the parent\n" - "The type of the current node is now \n" - "The type of parent is \n" - "Finding the loops position in its parent ...\n" - "The loop's index is 1\n" - "The type of the object at the index is \n" - "If preceding node is a directive then move back one\n" - "preceding node is a directive so find out what type ...\n") - assert expected in out - - -def test_basegen_start_parent_loop_omp_end_dbg(capsys): - '''Check the debug option to the start_parent_loop method when we have - an OpenMP end directive''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - dgen = DirectiveGen(sub, "omp", "end", "do", "") - sub.add(dgen) - loop = DoGen(sub, "it", "1", "10") - sub.add(loop) - call = CallGen(loop, "testcall") - loop.add(call) - call.start_parent_loop(debug=True) - out, _ = capsys.readouterr() - print(out) - expected = ("Parent is a do loop so moving to the parent\n" - "The type of the current node is now \n" - "The type of parent is \n" - "Finding the loops position in its parent ...\n" - "The loop's index is 1\n" - "The type of the object at the index is \n" - "If preceding node is a directive then move back one\n" - "preceding node is a directive so find out what type ...\n") - - assert expected in out - - -def test_basegen_start_parent_loop_no_loop_dbg(): - '''Check the debug option to the start_parent_loop method when we have - no loop''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - dgen = DirectiveGen(sub, "omp", "end", "do", "") - sub.add(dgen) - call = CallGen(sub, name="testcall", args=["a", "b"]) - sub.add(call) - with pytest.raises(RuntimeError) as err: - call.start_parent_loop(debug=True) - assert "This node has no enclosing Do loop" in str(err.value) - - -def test_progunitgen_multiple_generic_use(): - '''Check that we correctly handle the case where duplicate use statements - are added''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(UseGen(sub, name="fred")) - sub.add(UseGen(sub, name="fred")) - assert count_lines(sub.root, "USE fred") == 1 - - -def test_progunitgen_multiple_use1(): - '''Check that we correctly handle the case where duplicate use statements - are added but one is specific''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(UseGen(sub, name="fred")) - sub.add(UseGen(sub, name="fred", only=True, funcnames=["astaire"])) - assert count_lines(sub.root, "USE fred") == 1 - - -def test_progunitgen_multiple_use2(): - '''Check that we correctly handle the case where the same module - appears in two use statements but, because the first use is - specific, the second, generic use is included. - - ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(UseGen(sub, name="fred", only=True, funcnames=["astaire"])) - sub.add(UseGen(sub, name="fred")) - assert count_lines(sub.root, "USE fred") == 2 - - -def test_progunit_multiple_use3(): - '''Check that we correctly handle the case where the same module is - specified in two UseGen objects statements both of which are - specific and they have overlapping variable names. - - ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - funcnames = ["a", "b", "c"] - sub.add(UseGen(sub, name="fred", only=True, funcnames=funcnames)) - funcnames = ["c", "d"] - sub.add(UseGen(sub, name="fred", only=True, funcnames=funcnames)) - gen = str(sub.root) - expected = ( - " USE fred, ONLY: d\n" - " USE fred, ONLY: a, b, c") - assert expected in gen - assert count_lines(sub.root, "USE fred") == 2 - # ensure that the input list does not get modified - assert funcnames == ["c", "d"] - - -def test_adduse_empty_only(): - ''' Test that the adduse module method works correctly when we specify - that we want it to be specific but then don't provide a list of - entities for the only qualifier ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - # Add a use statement with only=True but an empty list of entities - adduse("fred", sub.root, only=True, funcnames=[]) - assert count_lines(sub.root, "USE fred") == 1 - assert count_lines(sub.root, "USE fred, only") == 0 - - -def test_adduse(): - ''' Test that the adduse module method works correctly when we use a - call object as our starting point ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - call = CallGen(sub, name="testcall", args=["a", "b"]) - sub.add(call) - adduse("fred", call.root, only=True, funcnames=["astaire"]) - gen = str(sub.root) - expected = (" SUBROUTINE testsubroutine()\n" - " USE fred, ONLY: astaire\n") - assert expected in gen - - -def test_adduse_default_funcnames(): - ''' Test that the adduse module method works correctly when we do - not specify a list of funcnames ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - call = CallGen(sub, name="testcall", args=["a", "b"]) - sub.add(call) - adduse("fred", call.root) - gen = str(sub.root) - expected = (" SUBROUTINE testsubroutine()\n" - " USE fred\n") - assert expected in gen - - -def test_basedecl_errors(): - ''' Check that the BaseDeclGen class raises the correct errors if - invalid combinations are requested. ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - with pytest.raises(RuntimeError) as err: - sub.add(DeclGen(sub, datatype="integer", allocatable=True, - entity_decls=["my_int"], initial_values=["1"])) - assert ("Cannot specify initial values for variable(s) [\'my_int\'] " - "because they have the \'allocatable\' attribute" - in str(err.value)) - with pytest.raises(NotImplementedError) as err: - sub.add(DeclGen(sub, datatype="integer", dimension="10", - entity_decls=["my_int"], initial_values=["1"])) - assert ("Specifying initial values for array declarations is not " - "currently supported" in str(err.value)) - with pytest.raises(RuntimeError) as err: - sub.add(DeclGen(sub, datatype="integer", intent="iN", - entity_decls=["my_int"], initial_values=["1"])) - assert ("Cannot assign (initial) values to variable(s) [\'my_int\'] as " - "they have INTENT(in)" in str(err.value)) - - -def test_decl_logical(tmpdir): - ''' Check that we can create a declaration for a logical variable ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(DeclGen(sub, datatype="logical", entity_decls=["first_time"])) - gen = str(sub.root).lower() - assert "logical first_time" in gen - # Add a second logical variable. Note that "first_time" will be ignored - # since it has already been declared. - sub.add(DeclGen(sub, datatype="logical", entity_decls=["first_time", - "var2"])) - gen = str(sub.root).lower() - assert "logical var2" in gen - assert gen.count("logical first_time") == 1 - # Check that the generated code compiles (if enabled) - assert Compile(tmpdir).string_compiles(gen) - - -def test_decl_char(tmpdir): - ''' Check that we can create a declaration for a character variable ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sub.add(CharDeclGen(sub, entity_decls=["my_string"])) - # This time specifying a length - sub.add(CharDeclGen(sub, length="28", - entity_decls=["my_string2"])) - # This time specifying a length and an initial value - sub.add(CharDeclGen(sub, length="28", - entity_decls=["my_string3"], - initial_values=["\'this is a string\'"])) - gen = str(sub.root).lower() - assert "character my_string" in gen - assert "character(len=28) my_string2" in gen - assert "character(len=28) :: my_string3='this is a string'" in gen - # Check that the generated Fortran compiles (if compilation testing is - # enabled) - assert Compile(tmpdir).string_compiles(gen) - # Finally, check initialisation using a variable name. Since this - # variable isn't declared, we can't include it in the compilation test. - sub.add(CharDeclGen(sub, length="my_len", - entity_decls=["my_string4"], - initial_values=["some_variable"])) - gen = str(sub.root).lower() - assert "character(len=my_len) :: my_string4=some_variable" in gen - - -def test_decl_save(tmpdir): - ''' Check that we can declare variables with the save attribute ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - for idx, dtype in enumerate(DeclGen.SUPPORTED_TYPES): - sub.add(DeclGen(sub, datatype=dtype, save=True, - entity_decls=["var"+str(idx)])) - sub.add(CharDeclGen(sub, save=True, length="10", - entity_decls=["varchar"])) - sub.add(TypeDeclGen(sub, save=True, datatype="field_type", - entity_decls=["ufld"])) - gen = str(module.root).lower() - for dtype in DeclGen.SUPPORTED_TYPES: - assert f"{dtype.lower()}, save :: var" in gen - assert "character(len=10), save :: varchar" in gen - assert "type(field_type), save :: ufld" in gen - # Check that the generated code compiles (if enabled). We have to - # manually add a declaration for "field_type". - parts = gen.split("implicit none") - gen = parts[0] + "implicit none\n" + TYPEDECL + parts[1] - assert Compile(tmpdir).string_compiles(gen) - - -def test_decl_target(tmpdir): - ''' Check that we can declare variables with the target attribute ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - for idx, dtype in enumerate(DeclGen.SUPPORTED_TYPES): - sub.add(DeclGen(sub, datatype=dtype, target=True, - entity_decls=["var"+str(idx)])) - sub.add(CharDeclGen(sub, target=True, length="10", - entity_decls=["varchar"])) - sub.add(TypeDeclGen(sub, target=True, datatype="field_type", - entity_decls=["ufld"])) - gen = str(module.root).lower() - for dtype in DeclGen.SUPPORTED_TYPES: - assert f"{dtype.lower()}, target :: var" in gen - assert "character(len=10), target :: varchar" in gen - assert "type(field_type), target :: ufld" in gen - # Check that the generated code compiles (if enabled). We - # must manually add a definition for the derived type. - parts = gen.split("implicit none") - gen = parts[0] + "implicit none\n" + TYPEDECL + parts[1] - assert Compile(tmpdir).string_compiles(gen) - - -def test_decl_private(tmpdir): - ''' Check that we can declare variables with the 'private' attribute. ''' - module = ModuleGen(name="testmodule") - for idx, dtype in enumerate(DeclGen.SUPPORTED_TYPES): - module.add(DeclGen(module, datatype=dtype, private=True, - entity_decls=["var"+str(idx)])) - module.add(CharDeclGen(module, private=True, length="10", - entity_decls=["varchar"])) - module.add(TypeDeclGen(module, private=True, datatype="field_type", - entity_decls=["ufld"])) - gen = str(module.root).lower() - for dtype in DeclGen.SUPPORTED_TYPES: - assert f"{dtype.lower()}, private :: var" in gen - assert "character(len=10), private :: varchar" in gen - assert "type(field_type), private :: ufld" in gen - # Check that the generated code compiles (if enabled). We - # must manually add a definition for the derived type. - parts = gen.split("implicit none") - gen = parts[0] + "implicit none\n" + TYPEDECL + parts[1] - assert Compile(tmpdir).string_compiles(gen) - - -def test_decl_initial_vals(tmpdir): - ''' Check that we can specify initial values for a declaration ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - # Check that we raise the correct error if the wrong number of - # initial values is supplied - with pytest.raises(RuntimeError) as err: - sub.add(DeclGen(sub, datatype="real", entity_decls=["r1", "r2"], - initial_values=["1.0"])) - assert ("number of initial values supplied (1) does not match the number " - "of variables to be declared (2: ['r1', 'r2'])" in str(err.value)) - - # Single variables - sub.add(DeclGen(sub, datatype="integer", save=True, - entity_decls=["ivar"], initial_values=["1"])) - sub.add(DeclGen(sub, datatype="real", save=True, - entity_decls=["var"], initial_values=["1.0"])) - sub.add(DeclGen(sub, datatype="logical", save=True, - entity_decls=["lvar"], initial_values=[".false."])) - gen = str(sub.root).lower() - assert "logical, save :: lvar=.false." in gen - assert "integer, save :: ivar=1" in gen - assert "real, save :: var=1.0" in gen - # Check that the generated code compiles (if enabled) - _compile = Compile(tmpdir) - assert _compile.string_compiles(gen) - - # Multiple variables - sub.add(DeclGen(sub, datatype="integer", save=True, - entity_decls=["ivar1", "ivar2"], - initial_values=["1", "2"])) - sub.add(DeclGen(sub, datatype="real", save=True, - entity_decls=["var1", "var2"], - initial_values=["1.0", "-1.0"])) - sub.add(DeclGen(sub, datatype="logical", save=True, - entity_decls=["lvar1", "lvar2"], - initial_values=[".false.", ".true."])) - gen = str(sub.root).lower() - assert "logical, save :: lvar1=.false., lvar2=.true." in gen - assert "integer, save :: ivar1=1, ivar2=2" in gen - assert "real, save :: var1=1.0, var2=-1.0" in gen - # Check that the generated code compiles (if enabled) - assert _compile.string_compiles(gen) - - -def test_declgen_invalid_vals(): - ''' Check that we raise the expected error if we attempt to create a - DeclGen with an initial value that is inconsistent with the type of - the variable ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - with pytest.raises(RuntimeError) as err: - _ = DeclGen(sub, datatype="integer", - entity_decls=["ival1", "ival2", "ival3"], - initial_values=["good", "1", "-0.35"]) - assert ("Initial value of '-0.35' for an integer " - "variable is invalid or unsupported" in str(err.value)) - with pytest.raises(RuntimeError) as err: - _ = DeclGen(sub, datatype="real", - entity_decls=["val1", "val2", "val3"], - initial_values=["good", "1.0", "35"]) - assert ("Initial value of '35' for a real " - "variable is invalid or unsupported" in str(err.value)) - with pytest.raises(RuntimeError) as err: - _ = DeclGen(sub, datatype="logical", - entity_decls=["val1", "val2", "val3"], - initial_values=["good", ".fAlse.", "35"]) - assert ("Initial value of '35' for a logical variable is invalid or " - "unsupported" in str(err.value)) - with pytest.raises(RuntimeError) as err: - CharDeclGen(sub, entity_decls=["val1", "val2"], - initial_values=["good", ".fAlse."]) - assert ("Initial value of \'.fAlse.' for a character variable" - in str(err.value)) - with pytest.raises(RuntimeError) as err: - CharDeclGen(sub, entity_decls=["val1"], initial_values=["35"]) - assert "Initial value of \'35\' for a character variable" in str(err.value) - - -def test_declgen_wrong_type(monkeypatch): - ''' Check that we raise an appropriate error if we attempt to create - a DeclGen for an unsupported type ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - with pytest.raises(RuntimeError) as err: - _ = DeclGen(sub, datatype="complex", - entity_decls=["rvar1"]) - assert ("Only ['integer', 'real', 'logical'] types are " - "currently supported" in str(err.value)) - # Check the internal error is raised within the validation routine if - # an unsupported type is specified - dgen = DeclGen(sub, datatype="integer", entity_decls=["my_int"]) - with pytest.raises(InternalError) as err: - dgen._check_initial_values("complex", ["1"]) - assert (f"internal error: unsupported type 'complex' - should be one " - f"of {dgen.SUPPORTED_TYPES}" in str(err.value)) - # Check that we get an internal error if the supplied type is in the - # list of those supported but has not actually been implemented. - # We have to monkeypatch the list of supported types... - monkeypatch.setattr(DeclGen, "SUPPORTED_TYPES", value=["complex"]) - with pytest.raises(InternalError) as err: - _ = DeclGen(sub, datatype="complex", - entity_decls=["rvar1"]) - assert ("internal error: Type 'complex' is in DeclGen.SUPPORTED_TYPES " - "but not handled by constructor" in str(err.value)) - - -def test_declgen_missing_names(): - ''' Check that we raise an error if we attempt to create a DeclGen - without naming the variable(s) ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - with pytest.raises(RuntimeError) as err: - _ = DeclGen(sub, datatype="integer") - assert ("Cannot create a variable declaration without specifying " - "the name" in str(err.value)) - - -def test_typedeclgen_names(): - ''' Check that the names method of TypeDeclGen works as expected ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - dgen = TypeDeclGen(sub, datatype="my_type", - entity_decls=["type1"]) - sub.add(dgen) - names = dgen.names - assert len(names) == 1 - assert names[0] == "type1" - - -def test_typedeclgen_missing_names(): - ''' Check that we raise an error if we attempt to create TypeDeclGen - without naming the variables ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - with pytest.raises(RuntimeError) as err: - _ = TypeDeclGen(sub, datatype="my_type") - assert ("Cannot create a variable declaration without specifying" - in str(err.value)) - - -def test_typedeclgen_values_error(): - ''' Check that we reject attempts to create a TypeDeclGen with - initial values. ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - decl = TypeDeclGen(sub, datatype="my_type", entity_decls=["field1"]) - with pytest.raises(InternalError) as err: - decl._check_initial_values("my_type", ["1.0"]) - assert ("This method should not have been called because initial values " - "for derived-type declarations are not supported" - in str(err.value)) - - -def test_typedeclgen_multiple_use(): - '''Check that we correctly handle the case where data of the same type - has already been declared. ''' - - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - # first declaration - datanames = ["type1"] - sub.add(TypeDeclGen(sub, datatype="my_type", - entity_decls=datanames)) - gen = str(sub.root) - # second declaration - datanames = ["type1", "type2"] - sub.add(TypeDeclGen(sub, datatype="my_type", - entity_decls=datanames)) - gen = str(sub.root) - print(gen) - expected = ( - " TYPE(my_type) type2\n" - " TYPE(my_type) type1") - assert expected in gen - # check input data is not modified - assert datanames == ["type1", "type2"] - - -def test_typedeclgen_multiple_use2(): - '''Check that we do not correctly handle the case where data of a - different type with the same name has already been declared.''' - - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - # first declaration - datanames = ["type1"] - sub.add(TypeDeclGen(sub, datatype="my_type", - entity_decls=datanames)) - gen = str(sub.root) - # second declaration - datanames = ["type1", "type2"] - sub.add(TypeDeclGen(sub, datatype="my_type2", - entity_decls=datanames)) - gen = str(sub.root) - print(gen) - expected = ( - " TYPE(my_type2) type1, type2\n" - " TYPE(my_type) type1") - assert expected in gen - # check input data is not modified - assert datanames == ["type1", "type2"] - - -def test_declgen_multiple_use(): - '''Check that we correctly handle the case where data of the same type - has already been delared.''' - - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - # first declaration - datanames = ["i1"] - sub.add(DeclGen(sub, datatype="integer", - entity_decls=datanames)) - gen = str(sub.root) - # second declaration - datanames = ["i1", "i2"] - sub.add(DeclGen(sub, datatype="integer", - entity_decls=datanames)) - gen = str(sub.root) - print(gen) - expected = ( - " INTEGER i2\n" - " INTEGER i1") - assert expected in gen - # check input data is not modified - assert datanames == ["i1", "i2"] - - -def test_declgen_multiple_use2(): - '''Check that we don't correctly handle the case where data of a - different type has already been delared. ''' - - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - # first declaration - datanames = ["data1"] - sub.add(DeclGen(sub, datatype="real", - entity_decls=datanames)) - gen = str(sub.root) - # second declaration - datanames = ["data1", "data2"] - sub.add(DeclGen(sub, datatype="integer", - entity_decls=datanames)) - gen = str(sub.root) - print(gen) - expected = ( - " INTEGER data1, data2\n" - " REAL data1") - assert expected in gen - # check input data is not modified - assert datanames == ["data1", "data2"] - - -@pytest.mark.xfail(reason="No way to add body of DEFAULT clause") -def test_selectiongen(): - ''' Check that SelectionGen works as expected ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sgen = SelectionGen(sub, expr="my_var") - sub.add(sgen) - agen = AssignGen(sgen, lhs="happy", rhs=".TRUE.") - sgen.addcase("1", [agen]) - # TODO how do we specify what happens in the default case? - sgen.adddefault() - gen = str(sub.root) - print(gen) - expected = ("SELECT CASE ( my_var )\n" - "CASE ( 1 )\n" - " happy = .TRUE.\n" - "CASE DEFAULT\n" - " END SELECT") - assert expected in gen - assert False - - -def test_selectiongen_addcase(): - ''' Check that SelectionGen.addcase() works as expected when no - content is supplied''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sgen = SelectionGen(sub, expr="my_var") - sub.add(sgen) - sgen.addcase("1") - gen = str(sub.root) - print(gen) - expected = (" SELECT CASE ( my_var )\n" - " CASE ( 1 )\n" - " END SELECT") - assert expected in gen - - -@pytest.mark.xfail(reason="Adding a CASE to a SELECT TYPE does not work") -def test_typeselectiongen(): - ''' Check that SelectionGen works as expected for a type ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - sgen = SelectionGen(sub, expr="my_var=>another_var", typeselect=True) - sub.add(sgen) - agen = AssignGen(sgen, lhs="happy", rhs=".TRUE.") - sgen.addcase("fspace", [agen]) - sgen.adddefault() - gen = str(sub.root) - print(gen) - assert "SELECT TYPE ( my_var=>another_var )" in gen - assert "TYPE IS ( fspace )" in gen - - -def test_modulegen_add_wrong_parent(): - ''' Check that attempting to add an object to a ModuleGen fails - if the object's parent is not that ModuleGen ''' - module = ModuleGen(name="testmodule") - module_wrong = ModuleGen(name="another_module") - sub = SubroutineGen(module_wrong, name="testsubroutine") - with pytest.raises(RuntimeError) as err: - module.add(sub) - assert ("because it is not a descendant of it or of any of" - in str(err.value)) - - -def test_do_loop_with_increment(): - ''' Test that we correctly generate code for a do loop with - non-unit increment ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsub") - module.add(sub) - dogen = DoGen(sub, "it", "1", "10", step="2") - sub.add(dogen) - count = count_lines(sub.root, "DO it=1,10,2") - assert count == 1 - - -def test_do_loop_add_after(): - ''' Test that we correctly generate code for a do loop when adding a - child to it with position *after* ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsub") - module.add(sub) - dogen = DoGen(sub, "it", "1", "10", step="2") - sub.add(dogen) - assign1 = AssignGen(dogen, lhs="happy", rhs=".TRUE.") - dogen.add(assign1) - assign2 = AssignGen(dogen, lhs="sad", rhs=".FALSE.") - dogen.add(assign2, position=["before", assign1.root]) - a1_line = line_number(sub.root, "happy = ") - a2_line = line_number(sub.root, "sad = ") - assert a1_line > a2_line - - -def test_basegen_previous_loop_no_loop(): - '''Check that we raise an error when requesting the position of the - previous loop if we don't have a loop ''' - module = ModuleGen(name="testmodule") - sub = SubroutineGen(module, name="testsubroutine") - module.add(sub) - # Request the position of the last loop - # even though we haven't got one - with pytest.raises(RuntimeError) as err: - sub.previous_loop() - assert "no loop found - there is no previous loop" in str(err.value) - - -def test_psyirgen_node(): - '''Check that the PSyIRGen prints the content of the provided PSyIR - node inside the f2pygen node. - ''' - module = ModuleGen(name="testmodule") - subroutine = SubroutineGen(module, name="testsubroutine") - module.add(subroutine) - - # Now add a PSyIR node inside the f2pygen tree - node = Return() - subroutine.add(PSyIRGen(subroutine, node)) - - generated_code = str(module.root) - expected = '''\ - MODULE testmodule - IMPLICIT NONE - CONTAINS - SUBROUTINE testsubroutine() - RETURN - END SUBROUTINE testsubroutine - END MODULE testmodule''' - - assert generated_code == expected - - -def test_psyirgen_multiple_fparser_nodes(): - '''Check that the PSyIRGen prints the content of the provided PSyIR - node inside the f2pygen node when the PSyIR node maps to more than - one fparser nodes. - ''' - module = ModuleGen(name="testmodule") - subroutine = SubroutineGen(module, name="testsubroutine") - module.add(subroutine) - - # Create single PSyIR node that maps to 2 fparser nodes: a comment - # statement and a return statement. - node = Return() - node.preceding_comment = "Comment statement" - - subroutine.add(PSyIRGen(subroutine, node)) - - generated_code = str(module.root) - expected = '''\ - MODULE testmodule - IMPLICIT NONE - CONTAINS - SUBROUTINE testsubroutine() - ! Comment statement - RETURN - END SUBROUTINE testsubroutine - END MODULE testmodule''' - - assert generated_code == expected - - -def test_psyirgen_backendchecks(monkeypatch): - '''Check that PSyIRGen uses the configuration object to determine - whether or not to disable checks in the PSyIR backend. - ''' - config = Config.get() - - module = ModuleGen(name="testmodule") - subroutine = SubroutineGen(module, name="testsubroutine") - module.add(subroutine) - node = Return() - - # monkeypatch the `validate_global_constraints` method of the Return node - # so that it always raises an error. - def fake_validate(): - raise GenerationError("This is just a test") - - monkeypatch.setattr(node, "validate_global_constraints", fake_validate) - - # monkeypatch Config to turn off validation checks. - monkeypatch.setattr(config, "_backend_checks_enabled", False) - # Constructing the PSyIRGen node should succed. - pgen = PSyIRGen(subroutine, node) - assert isinstance(pgen, PSyIRGen) - # monkeypatch Config to turn on validation checks. - monkeypatch.setattr(config, "_backend_checks_enabled", True) - # Construction should now fail. - with pytest.raises(GenerationError) as err: - PSyIRGen(subroutine, node) - assert "This is just a test" in str(err.value)