def match(node): ''' Whether or not the AST fragment pointed to by node represents a kernel. A kernel is defined as a section of code that sits within a recognised loop structure and does not itself contain loops or IO operations. :param node: Node in fparser2 AST to check. :type node: :py:class:`fparser.two.Fortran2003.Base` :returns: True if this node conforms to the rules for a kernel. :rtype: bool ''' from fparser.two.Fortran2003 import Subscript_Triplet, \ Block_Nonlabel_Do_Construct, Write_Stmt, Read_Stmt child_loops = walk_ast( node.content, [Block_Nonlabel_Do_Construct, Write_Stmt, Read_Stmt]) if child_loops: # A kernel cannot contain other loops or reads or writes return False # Currently a kernel cannot contain implicit loops. # TODO we may have to differentiate between implicit loops over # grid points and any other implicit loop. Possibly using the # scope of the array being accessed? impl_loops = walk_ast(node.content, [Subscript_Triplet]) if impl_loops: return False return True
def test_subroutine_comments(): ''' Tests for comments in subroutines ''' source = '''\ subroutine my_mod() ! First comment implicit none integer my_mod ! Body comment my_mod = 1 ! Inline comment ! Ending comment end subroutine my_mod ''' from fparser.two.Fortran2003 import Subroutine_Subprogram reader = get_reader(source, isfree=True, ignore_comments=False) fn_unit = Subroutine_Subprogram(reader) assert isinstance(fn_unit, Subroutine_Subprogram) walk_ast(fn_unit.content, [Comment], debug=True) spec_part = fn_unit.content[1] comment = spec_part.content[0].content[0] assert isinstance(comment, Comment) assert "! First comment" in str(comment) comment = spec_part.content[2].content[0] assert isinstance(comment, Comment) assert "! Body comment" in str(comment) exec_part = fn_unit.content[2] comment = exec_part.content[1] assert isinstance(comment, Comment) assert "! Inline comment" in str(comment)
def match(node): ''' Checks whether the supplied node in the fparser2 AST represents an implicit loop (using Fortran array syntax). :param node: node in the fparser2 AST to check :type node: :py:class:`fparser.two.Fortran2003.Assignment_Stmt` :returns: True if the node does represent an implicit loop. :rtype: bool ''' if not isinstance(node, Fortran2003.Assignment_Stmt): return False # We are expecting something like: # array(:,:,jk) = some_expression # but we have to beware of cases like the following: # array(1,:,:) = a_func(array2(:,:,:), mask(:,:)) # where a_func is an array-valued function and `array2(:,:,:)` # could just be `array2`. # We check the left-hand side... lhs = node.items[0] if not isinstance(lhs, Fortran2003.Part_Ref): # LHS is not an array reference return False colons = walk_ast(lhs.items, [Fortran2003.Subscript_Triplet]) if not colons: # LHS does not use array syntax return False # Now check the right-hand side... rhs = node.items[2] colons = walk_ast(rhs.items, [Fortran2003.Subscript_Triplet]) if not colons: # We don't have any array syntax on the RHS return True # Check that we haven't got array syntax used within the index # expression to another array. Array references are represented by # Part_Ref nodes in the fparser2 AST. This would be easier to do # if the fparser2 AST carried parent information with each node. # As it is we have to walk down the tree rather than come back up # from each colon. # Find all array references array_refs = [] if isinstance(rhs, Fortran2003.Part_Ref): # Since walk_ast is slightly clunky we have to manually allow # for the top-level "rhs" node being an array reference array_refs.append(rhs) array_refs += walk_ast(rhs.items, [Fortran2003.Part_Ref]) for ref in array_refs: nested_refs = walk_ast(ref.items, [Fortran2003.Part_Ref]) # Do any of these nested array references use array syntax? for nested_ref in nested_refs: colons = walk_ast(nested_ref.items, [Fortran2003.Subscript_Triplet]) if colons: return False return True
def test_new_kernel_file(tmpdir, monkeypatch): ''' Check that we write out the transformed kernel to the CWD. ''' from fparser.two import Fortran2003, parser from fparser.common.readfortran import FortranFileReader # Ensure kernel-output directory is uninitialised config = Config.get() monkeypatch.setattr(config, "_kernel_output_dir", "") monkeypatch.setattr(config, "_kernel_naming", "multiple") # Change to temp dir (so kernel written there) old_cwd = tmpdir.chdir() psy, invoke = get_invoke("nemolite2d_alg_mod.f90", api="gocean1.0", idx=0) sched = invoke.schedule kern = sched.children[0].loop_body[0].loop_body[0] rtrans = ACCRoutineTrans() _, _ = rtrans.apply(kern) # Generate the code (this triggers the generation of a new kernel) code = str(psy.gen).lower() # Work out the value of the tag used to re-name the kernel tag = re.search('use continuity(.+?)_mod', code).group(1) assert ("use continuity{0}_mod, only: continuity{0}_code".format(tag) in code) assert "call continuity{0}_code(".format(tag) in code # The kernel and module name should have gained the tag just identified # and be written to the CWD filename = os.path.join(str(tmpdir), "continuity{0}_mod.f90".format(tag)) assert os.path.isfile(filename) # Parse the new kernel file f2003_parser = parser.ParserFactory().create() reader = FortranFileReader(filename) prog = f2003_parser(reader) # Check that the module has the right name modules = walk_ast(prog.content, [Fortran2003.Module_Stmt]) assert str(modules[0].items[1]) == "continuity{0}_mod".format(tag) # Check that the subroutine has the right name subs = walk_ast(prog.content, [Fortran2003.Subroutine_Stmt]) found = False for sub in subs: if str(sub.items[1]) == "continuity{0}_code".format(tag): found = True break assert found # Check that the kernel type has been re-named dtypes = walk_ast(prog.content, [Fortran2003.Derived_Type_Def]) names = walk_ast(dtypes[0].content, [Fortran2003.Type_Name]) assert str(names[0]) == "continuity{0}_type".format(tag) from gocean1p0_build import GOcean1p0Build # If compilation fails this will raise an exception GOcean1p0Build(tmpdir).compile_file(filename) old_cwd.chdir()
def test_prog_comments(): ''' Unit tests for lines in programs containing comments ''' cls = Program reader = get_reader('''\ ! A troublesome comment program foo ! A full comment line write(*,*) my_int ! An in-line comment end program foo ! A really problematic comment ''', isfree=True, ignore_comments=False) obj = cls(reader) assert type(obj) == Program # Check that the AST has the expected structure: # Program # |--> Comment # |--> Main_Program # . |--> Program_Stmt # . |--> Specification_Part # . . \--> Implicit_Part # . . \--> Comment # |--> Execution_Part # | |--> Write_Stmt # | \--> Comment # . . # . # |--> Comment from fparser.two.Fortran2003 import Main_Program, Write_Stmt, \ End_Program_Stmt walk_ast(obj.content, [Comment], debug=True) assert type(obj.content[0]) == Comment assert str(obj.content[0]) == "! A troublesome comment" assert type(obj.content[1]) == Main_Program main_prog = obj.content[1] assert type(main_prog.content[1].content[0].content[0]) == Comment assert (str( main_prog.content[1].content[0].content[0]) == "! A full comment line") exec_part = main_prog.content[2] assert type(exec_part.content[0]) == Write_Stmt # Check that we have the in-line comment as a second statement assert len(exec_part.content) == 2 assert type(exec_part.content[1]) == Comment assert type(main_prog.content[3]) == End_Program_Stmt assert "! An in-line comment" in str(obj) # Check that we still have the ending comment assert type(obj.content[-1]) == Comment assert str(obj).endswith("! A really problematic comment")
def valid_kernel(node): ''' Whether the sub-tree that has `node` at its root is eligible to be enclosed within an OpenACC KERNELS directive. :param node: the node in the PSyIR to check. :type node: :py:class:`psyclone.psyGen.Node` :returns: True if the sub-tree can be enclosed in a KERNELS region. :rtype: bool ''' from psyclone.psyGen import CodeBlock, IfBlock from fparser.two.utils import walk_ast from fparser.two import Fortran2003 # PGI (18.10) often produces code that fails at run time if a Kernels # region includes If constructs. excluded_node_types = (CodeBlock, IfBlock) if node.walk(excluded_node_types): return False # Check that there are no derived-type references in the sub-tree (because # PGI deep-copy doesn't like them). # TODO #365 - this check should be part of our identification of valid # NEMO kernels. if walk_ast([node.ast], [Fortran2003.Data_Ref]): return False return True
def test_accroutine(): ''' Test that we can transform a kernel by adding a "!$acc routine" directive to it. ''' from psyclone.gocean1p0 import GOKern from fparser.two import Fortran2003 _, invoke = get_invoke("nemolite2d_alg_mod.f90", api="gocean1.0", idx=0) sched = invoke.schedule kern = sched.children[0].loop_body[0].loop_body[0] assert isinstance(kern, GOKern) rtrans = ACCRoutineTrans() assert rtrans.name == "ACCRoutineTrans" new_kern, _ = rtrans.apply(kern) # The transformation should have populated the fparser2 AST of # the kernel... assert new_kern._fp2_ast assert isinstance(new_kern._fp2_ast, Fortran2003.Program) # Check AST contains directive comments = walk_ast(new_kern._fp2_ast.content, [Fortran2003.Comment]) assert len(comments) == 1 assert str(comments[0]) == "!$acc routine" # Check that directive is in correct place (end of declarations) gen = str(new_kern._fp2_ast) assert ("REAL(KIND = go_wp), DIMENSION(:, :), INTENT(IN) :: sshn, sshn_u, " "sshn_v, hu, hv, un, vn\n" " !$acc routine\n" " ssha (ji, jj) = 0.0_go_wp\n" in gen)
def __init__(self, ast): # pylint: disable=super-init-not-called from fparser.two.Fortran2003 import Main_Program, \ Subroutine_Subprogram, Function_Subprogram, Function_Stmt, Name self.invoke_map = {} self.invoke_list = [] # Keep a pointer to the whole fparser2 AST self._ast = ast # Find all the subroutines contained in the file routines = walk_ast(ast.content, [Subroutine_Subprogram, Function_Subprogram]) # Add the main program as a routine to analyse - take care # here as the Fortran source file might not contain a # main program (might just be a subroutine in a module) main_prog = get_child(ast, Main_Program) if main_prog: routines.append(main_prog) # Analyse each routine we've found for subroutine in routines: # Get the name of this subroutine, program or function substmt = subroutine.content[0] if isinstance(substmt, Function_Stmt): for item in substmt.items: if isinstance(item, Name): sub_name = str(item) break else: sub_name = str(substmt.get_name()) my_invoke = NemoInvoke(subroutine, name=sub_name) self.invoke_map[sub_name] = my_invoke self.invoke_list.append(my_invoke)
def get_integer_array(self, name): ''' Parse the kernel meta-data and find the values of the integer array variable with the supplied name. Returns an empty list if no matching variable is found. :param str name: the name of the integer array to find. :returns: list of values. :rtype: list of str. :raises InternalError: if we fail to parse the LHS of the array \ declaration or the array constructor. :raises ParseError: if the RHS of the declaration is not an array \ constructor. ''' # Ensure the classes are setup for the Fortran2003 parser _ = ParserFactory().create() for statement, _ in fpapi.walk(self._ktype, -1): if not isinstance(statement, fparser1.typedecl_statements.Integer): # This isn't an integer declaration so skip it continue # fparser only goes down to the statement level. We use fparser2 to # parse the statement itself. assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0]) names = walk_ast(assign.items, [Fortran2003.Name]) if not names: raise InternalError( "Unsupported assignment statement: '{0}'".format( str(assign))) if str(names[0]) == name: # This is the variable declaration we're looking for if not isinstance(assign.items[2], Fortran2003.Array_Constructor): raise ParseError( "get_integer_array: RHS of assignment is not " "an array constructor: '{0}'".format(str(assign))) # fparser2 AST for Array_Constructor is: # Array_Constructor('[', Ac_Value_List(',', (Name('w0'), # Name('w1'))), ']') # Construct a list of the names in the array constructor names = walk_ast(assign.items[2].items, [Fortran2003.Name]) if not names: raise InternalError("Failed to parse array constructor: " "'{0}'".format(str(assign.items[2]))) return [str(name) for name in names] return []
def __init__(self, ast): names = walk_ast(ast.content, [Fortran2003.Name]) # The name of the program unit will be the first in the list if not names: raise InternalError("Found no names in supplied Fortran - should " "be impossible!") self._name = str(names[0]) + "_psy" self._invokes = NemoInvokes(ast) self._ast = ast
def test_identify_implicit_loop(parser): ''' Check that we correctly identify implicit loops in the fparser2 AST ''' reader = FortranStringReader("program test_prog\n" "umask(:, :, :, :) = 0.0D0\n" "do jk = 1, jpk\n" " umask(1,1,jk) = -1.0d0\n" "end do\n" "end program test_prog\n") ast = parser(reader) assert not nemo.NemoImplicitLoop.match(ast) stmts = walk_ast(ast.content, [Fortran2003.Assignment_Stmt]) assert not nemo.NemoImplicitLoop.match(stmts[1]) assert nemo.NemoImplicitLoop.match(stmts[0])
def gen(self): '''Return modified algorithm code. :returns: The modified algorithm specification as an fparser2 \ parse tree. :rtype: :py:class:`fparser.two.utils.Base` ''' from fparser.two.utils import walk_ast # pylint: disable=no-name-in-module from fparser.two.Fortran2003 import Call_Stmt, Section_Subscript_List idx = 0 # Walk through all statements looking for procedure calls for statement in walk_ast(self._ast.content, [Call_Stmt]): # found a Fortran call statement call_name = str(statement.items[0]) if call_name.lower() == self._invoke_name.lower(): # The call statement is an invoke # Get the PSy callee name and argument list and # replace the existing algorithm invoke call with # these. psy_invoke_info = self._psy.invokes.invoke_list[idx] new_name = psy_invoke_info.name new_args = Section_Subscript_List(", ".join( psy_invoke_info.alg_unique_args)) statement.items = (new_name, new_args) # The PSy-layer generates a subroutine within a module # so we need to add a 'use module_name, only : # subroutine_name' to the algorithm layer. adduse(self._ast, statement, self._psy.name, only=True, funcnames=[psy_invoke_info.name]) idx += 1 if idx == 0: raise NoInvokesError( "Algorithm file contains no invoke() calls: refusing to " "generate empty PSy code") return self._ast
def __init__(self, ast, parent=None): from fparser.two.Fortran2003 import Loop_Control Loop.__init__(self, parent=parent, valid_loop_types=VALID_LOOP_TYPES) NemoFparser2ASTProcessor.__init__(self) # Keep a ptr to the corresponding node in the AST self._ast = ast # Get the loop variable ctrl = walk_ast(ast.content, [Loop_Control]) # Second element of items member of Loop Control is itself a tuple # containing: # Loop variable, [start value expression, end value expression, step # expression] # Loop variable will be an instance of Fortran2003.Name loop_var = str(ctrl[0].items[1][0]) self._variable_name = str(loop_var) # Identify the type of loop if self._variable_name in NEMO_LOOP_TYPE_MAPPING: self.loop_type = NEMO_LOOP_TYPE_MAPPING[self._variable_name] else: self.loop_type = "unknown" # Get the loop limits. These are given in a list which is the second # element of a tuple which is itself the second element of the items # tuple: # (None, (Name('jk'), [Int_Literal_Constant('1', None), Name('jpk'), # Int_Literal_Constant('1', None)]), None) limits_list = ctrl[0].items[1][1] self._start = str(limits_list[0]) self._stop = str(limits_list[1]) if len(limits_list) == 3: self._step = str(limits_list[2]) else: # Default loop increment is 1 self._step = "1" # Is this loop body a kernel? if NemoKern.match(self._ast): self.addchild(NemoKern(self._ast, parent=self)) return # It's not - walk on down the AST... self.process_nodes(self, self._ast.content, self._ast)
def perform(self, targs): loops = {} for path, tree in self.env['astlist'].items(): l = [] loops[path] = l for node in walk_ast(tree.content): for x in getattr(node, "content", []): if isinstance(x, Base): x.parent = node for x in getattr(node, "items", []): if isinstance(x, Base): x.parent = node if isinstance(node, (Label_Do_Stmt, Nonlabel_Do_Stmt)): l.append(node) return 0, {"astlist": self.env['astlist'], "loops": loops}
def test_get_child(f2003_create): ''' Test the get_child() utility. ''' from fparser.two import Fortran2003 from fparser.two.utils import get_child, walk_ast reader = get_reader("program hello\n" "write(*,*) 'hello'\n" "write(*,*) 'goodbye'\n" "end program hello\n") main = Fortran2003.Program(reader) prog = get_child(main, Fortran2003.Main_Program) exe = get_child(prog, Fortran2003.Execution_Part) assert isinstance(exe, Fortran2003.Execution_Part) write_stmt = get_child(exe, Fortran2003.Write_Stmt) # Check that we got the first write and not the second assert "goodbye" not in str(write_stmt) # The top level has no Io_Control_Spec children assert not get_child(main, Fortran2003.Io_Control_Spec) # Check functionality when node has children in `items` and # not in `content` io_nodes = walk_ast(main.content, my_types=[Fortran2003.Io_Control_Spec]) assert not hasattr(io_nodes[0], "content") io_unit = get_child(io_nodes[0], Fortran2003.Io_Unit) assert isinstance(io_unit, Fortran2003.Io_Unit)
def test_exp_loop_missing_spec(parser): '''Test that the ExplicitLoop transformation still works when the fparser2 AST is missing a Specification_Part for the routine. ''' from fparser.two.utils import walk_ast reader = FortranStringReader("program atest\nreal :: umask(1,1,1,1)\n" "umask(:, :, :) = 0.0\nend program atest\n") prog = parser(reader) psy = PSyFactory(API).create(prog) sched = psy.invokes.invoke_list[0].schedule # Remove the specification part spec = walk_ast(prog.content, [Fortran2003.Specification_Part]) prog.content[0].content.remove(spec[0]) # Check that we can transform OK exp_trans = TransInfo().get_trans_name('NemoExplicitLoopTrans') _, _ = exp_trans.apply(sched.children[0]) gen_code = str(psy.gen) assert ("PROGRAM atest\n" " INTEGER :: jk\n" " DO jk = 1, jpk, 1\n" " umask(:, :, jk) = 0.0\n" " END DO\n" "END PROGRAM atest" in gen_code)
def match(node): ''' Checks whether the supplied fparser2 AST represents an if-block that must be represented in the Schedule AST. If-blocks that do not contain kernels are just treated as code blocks. :param node: the node in the fparser2 AST representing an if-block :type node: :py:class:`fparser.two.Fortran2003.If_Construct` :returns: True if this if-block must be represented in the PSyIRe :rtype: bool ''' if not isinstance(node, Fortran2003.If_Construct): return False # We only care about if-blocks if they contain something significant # i.e. a recognised type of loop (whether implicit or explicit). loops = walk_ast(node.content, [ Fortran2003.Subscript_Triplet, Fortran2003.Block_Nonlabel_Do_Construct ]) if loops: return True return False
def update(self): ''' Update the underlying fparser2 parse tree to implement the profiling region represented by this Node. This involves adding the necessary module use statement as well as the calls to the profiling API. TODO #435 - remove this whole method once the NEMO API uses the Fortran backend of the PSyIR. :raises NotImplementedError: if the routine which is to have \ profiling added to it does not already have a \ Specification Part (i.e. some declarations). :raises NotImplementedError: if there would be a name clash with \ existing variable/module names in the code to \ be transformed. :raises InternalError: if we fail to find the node in the parse tree \ corresponding to the end of the profiling region. ''' from fparser.common.sourceinfo import FortranFormat from fparser.common.readfortran import FortranStringReader from fparser.two.utils import walk_ast from fparser.two import Fortran2003 from psyclone.psyGen import object_index, Schedule, InternalError # Ensure child nodes are up-to-date super(ProfileNode, self).update() # Get the parse tree of the routine containing this region ptree = self.root.invoke._ast # Rather than repeatedly walk the tree, we do it once for all of # the node types we will be interested in... node_list = walk_ast([ptree], [ Fortran2003.Main_Program, Fortran2003.Subroutine_Stmt, Fortran2003.Function_Stmt, Fortran2003.Specification_Part, Fortran2003.Use_Stmt, Fortran2003.Name ]) for node in node_list: if isinstance( node, (Fortran2003.Main_Program, Fortran2003.Subroutine_Stmt, Fortran2003.Function_Stmt)): names = walk_ast([node], [Fortran2003.Name]) routine_name = str(names[0]).lower() break for node in node_list: if isinstance(node, Fortran2003.Specification_Part): spec_part = node break else: # This limitation will be removed when we use the Fortran # backend of the PSyIR (#435) raise NotImplementedError( "Addition of profiling regions to routines without any " "existing declarations is not supported and '{0}' has no " "Specification-Part".format(routine_name)) # Get the existing use statements found = False for node in node_list[:]: if isinstance(node, Fortran2003.Use_Stmt) and \ self.fortran_module == str(node.items[2]).lower(): # Check that the use statement matches the one we would # insert (i.e. the code doesn't already contain a module # with the same name as that used by the profiling API) if str(node).lower() != self.use_stmt.lower(): raise NotImplementedError( "Cannot add profiling to '{0}' because it already " "'uses' a module named '{1}'".format( routine_name, self.fortran_module)) found = True # To make our check on name clashes below easier, remove # the Name nodes associated with this use from our # list of nodes. names = walk_ast([node], [Fortran2003.Name]) for name in names: node_list.remove(name) if not found: # We don't already have a use for the profiling module so # add one. reader = FortranStringReader( "use profile_mod, only: ProfileData, ProfileStart, ProfileEnd") # Tell the reader that the source is free format reader.set_format(FortranFormat(True, False)) use = Fortran2003.Use_Stmt(reader) spec_part.content.insert(0, use) # Check that we won't have any name-clashes when we insert the # symbols required for profiling. This check uses the list of symbols # that we created before adding the `use profile_mod...` statement. if not self.root.profiling_name_clashes_checked: for node in node_list: if isinstance(node, Fortran2003.Name): text = str(node).lower() # Check for the symbols we import from the profiling module for symbol in self.profiling_symbols: if text == symbol.lower(): raise NotImplementedError( "Cannot add profiling to '{0}' because it " "already contains a symbol that clashes with " "one of those ('{1}') that must be imported " "from the PSyclone profiling module.".format( routine_name, symbol)) # Check for the name of the profiling module itself if text == self.fortran_module: raise NotImplementedError( "Cannot add profiling to '{0}' because it already " "contains a symbol that clashes with the name of " "the PSyclone profiling module ('profile_mod')". format(routine_name)) # Check for the names of profiling variables if text.startswith(self.profiling_var): raise NotImplementedError( "Cannot add profiling to '{0}' because it already" " contains symbols that potentially clash with " "the variables we will insert for each profiling " "region ('{1}*').".format(routine_name, self.profiling_var)) # Flag that we have now checked for name clashes so that if there's # more than one profiling node we don't fall over on the symbols # we've previous inserted. self.root.profiling_name_clashes_checked = True # Create a name for this region by finding where this profiling # node is in the list of profiling nodes in this Invoke. sched = self.root pnodes = sched.walk(ProfileNode) region_idx = pnodes.index(self) region_name = "r{0}".format(region_idx) var_name = "psy_profile{0}".format(region_idx) # Create a variable for this profiling region reader = FortranStringReader( "type(ProfileData), save :: {0}".format(var_name)) # Tell the reader that the source is free format reader.set_format(FortranFormat(True, False)) decln = Fortran2003.Type_Declaration_Stmt(reader) spec_part.content.append(decln) # Find the parent in the parse tree - first get a pointer to the # AST for the content of this region. if isinstance(self.children[0], Schedule) and \ not self.children[0].ast: # TODO #435 Schedule should really have a valid ast pointer. content_ast = self.children[0][0].ast else: content_ast = self.children[0].ast # Now store the parent of this region fp_parent = content_ast._parent # Find the location of the AST of our first child node in the # list of child nodes of our parent in the fparser parse tree. ast_start_index = object_index(fp_parent.content, content_ast) # Finding the location of the end is harder as it might be the # end of a clause within an If or Select block. We therefore # work back up the fparser2 parse tree until we find a node that is # a direct child of the parent node. ast_end_index = None if self.children[-1].ast_end: ast_end = self.children[-1].ast_end else: ast_end = self.children[-1].ast # Keep a copy of the pointer into the parse tree in case of errors ast_end_copy = ast_end while ast_end_index is None: try: ast_end_index = object_index(fp_parent.content, ast_end) except ValueError: # ast_end is not a child of fp_parent so go up to its parent # and try again if hasattr(ast_end, "_parent") and ast_end._parent: ast_end = ast_end._parent else: raise InternalError( "Failed to find the location of '{0}' in the fparser2 " "Parse Tree:\n{1}\n".format(str(ast_end_copy), str(fp_parent.content))) # Add the profiling-end call reader = FortranStringReader("CALL ProfileEnd({0})".format(var_name)) # Tell the reader that the source is free format reader.set_format(FortranFormat(True, False)) pecall = Fortran2003.Call_Stmt(reader) fp_parent.content.insert(ast_end_index + 1, pecall) # Add the profiling-start call reader = FortranStringReader( "CALL ProfileStart('{0}', '{1}', {2})".format( routine_name, region_name, var_name)) reader.set_format(FortranFormat(True, False)) pscall = Fortran2003.Call_Stmt(reader) fp_parent.content.insert(ast_start_index, pscall)
def adduse(parse_tree, location, name, only=None, funcnames=None): '''Add a Fortran 'use' statement to an existing fparser2 parse tree. This will be added at the first valid location before the current location. This function should be part of the fparser2 replacement for f2pygen (which uses fparser1) but is kept here until this is developed, see issue #240. The 'parse_tree' argument is only required as fparser2 currently does not connect a child to a parent. This will be addressed in issue fparser:#102. :param parse_tree: The full parse tree of the associated code :type parse_tree: :py:class:`fparser.two.utils.Base` :param location: The current location (node) in the parse tree \ provided in the parse_tree argument :type location: :py:class:`fparser.two.utils.Base` :param str name: The name of the use statement :param bool only: Whether to include the 'only' clause in the use \ statement or not. Defaults to None which will result in only being \ added if funcnames has content and not being added otherwise. :param funcnames: A list of names to include in the use statement's \ only list. If the list is empty or None then nothing is \ added. Defaults to None. :type funcnames: list of str :raises GenerationError: if the location is not part of the parse \ tree. :raises GenerationError: if the location is not a valid location \ to add a use statement. :raises NotImplementedError: if the type of parent node is not \ supported. :raises InternalError: if the type of parent node does not have \ the expected structure. ''' # pylint: disable=too-many-locals # pylint: disable=too-many-branches from fparser.two.utils import walk_ast from fparser.two.Fortran2003 import Main_Program, Module, \ Subroutine_Subprogram, Function_Subprogram, Use_Stmt, \ Specification_Part from psyclone.psyGen import GenerationError, InternalError if location is None: raise GenerationError("alg_gen.py:adduse: Location argument must " "not be None.") if funcnames: # funcnames have been provided for the only clause. if only is False: # However, the only clause has been explicitly set to False. raise GenerationError( "alg_gen.py:adduse: If the 'funcnames' argument is provided " "and has content, then the 'only' argument must not be set " "to 'False'.") if only is None: # only has not been specified so set it to True as it is # required when funcnames has content. only = True if only is None: # only has not been specified and we can therefore infer that # funcnames is empty or is not provided (as earlier code would # have set only to True otherwise) so only is not required. only = False # Create the specified use statement only_str = "" if only: only_str = ", only :" my_funcnames = funcnames if funcnames is None: my_funcnames = [] use = Use_Stmt("use {0}{1} {2}".format(name, only_str, ", ".join(my_funcnames))) # find the parent program statement containing the specified location parent_prog_statement = None found = False for child in walk_ast(parse_tree.content): if child == location: found = True break if isinstance(child, (Main_Program, Module, Subroutine_Subprogram, Function_Subprogram)): parent_prog_statement = child if not found: raise GenerationError("alg_gen.py:adduse: The specified location is " "not in the parse tree.") if not parent_prog_statement: raise GenerationError( "alg_gen.py:adduse: The specified location is invalid as it has " "no parent in the parse tree that is a program, module, " "subroutine or function.") if not isinstance( parent_prog_statement, (Main_Program, Subroutine_Subprogram, Function_Subprogram)): # We currently only support program, subroutine and function # as ancestors raise NotImplementedError( "alg_gen.py:adduse: Unsupported parent code found '{0}'. " "Currently support is limited to program, subroutine and " "function.".format(str(type(parent_prog_statement)))) if not isinstance(parent_prog_statement.content[1], Specification_Part): raise InternalError( "alg_gen.py:adduse: The second child of the parent code " "(content[1]) is expected to be a specification part but " "found '{0}'.".format(repr(parent_prog_statement.content[1]))) # add the use statement as the first child of the specification # part of the program spec_part = parent_prog_statement.content[1] spec_part.content.insert(0, use) return parse_tree
def parse(self, alg_filename): '''Takes a PSyclone conformant algorithm file as input and outputs a parse tree of the code contained therein and an object containing information about the 'invoke' calls in the algorithm file and any associated kernels within the invoke calls. :param str alg_filename: The file containing the algorithm code. :returns: 2-tuple consisting of the fparser2 parse tree of the \ algorithm code and an object holding details of the algorithm \ code and the invokes found within it. :rtype: (:py:class:`fparser.two.Fortran2003.Program`, \ :py:class:`psyclone.parse.FileInfo`) :raises ParseError: if a program, module, subroutine or \ function is not found in the input file. ''' self._alg_filename = alg_filename if self._line_length: # Make sure the code conforms to the line length limit. check_line_length(alg_filename) alg_parse_tree = parse_fp2(alg_filename) if self._api == "nemo": # For this API we just parse the NEMO code and return the resulting # fparser2 AST with None for the Algorithm AST. return None, alg_parse_tree # Find the first program, module, subroutine or function in the # parse tree. The assumption here is that the first is the one # that is required. See issue #307. container_name = None for child in alg_parse_tree.content: if isinstance(child, (Main_Program, Module, Subroutine_Subprogram, Function_Subprogram)): container_name = str(child.content[0].items[1]) break if not container_name: # Nothing relevant found. raise ParseError( "algorithm.py:parser:parse: Program, module, function or " "subroutine not found in parse tree for file " "'{0}'".format(alg_filename)) self._unique_invoke_labels = [] self._arg_name_to_module_name = {} invoke_calls = [] for statement in walk_ast(alg_parse_tree.content): if isinstance(statement, Use_Stmt): # found a Fortran use statement self.update_arg_to_module_map(statement) if isinstance(statement, Call_Stmt): # found a Fortran call statement call_name = str(statement.items[0]) if call_name.lower() == self._invoke_name.lower(): # The call statement is an invoke invoke_call = self.create_invoke_call(statement) invoke_calls.append(invoke_call) return alg_parse_tree, FileInfo(container_name, invoke_calls)
def test_intrinsic_recognised(f2003_create): '''Test that an intrinsic is picked up when used in a program.''' reader = get_reader("subroutine sub()\na = sin(b)\nend subroutine sub\n") ast = Program(reader) assert walk_ast([ast], [Intrinsic_Function_Reference])
def get_kernel(parse_tree, alg_filename): '''Takes the parse tree of an invoke kernel argument and returns the name of the kernel and a list of Arg instances which capture the relevant information about the arguments associated with the kernel. :param parse_tree: Parse tree of an invoke argument. This \ should contain a kernel name and associated arguments. :type argument: :py:class:`fparser.two.Fortran2003.Part_Ref` :param str alg_filename: The file containing the algorithm code. :returns: a 2-tuple with the name of the kernel being called and a \ list of 'Arg' instances containing the required information for \ the arguments being passed from the algorithm layer. The list \ order is the same as the argument order. :rtype: (str, list of :py:class:`psyclone.parse.algorithm.Arg`) :raises InternalError: if the parse tree is of the wrong type. :raises InternalError: if an unsupported argument format is found. ''' # pylint: disable=too-many-branches if not isinstance(parse_tree, Part_Ref): raise InternalError( "algorithm.py:get_kernel: Expected a parse tree (type Part_Ref) " "but found instance of '{0}'.".format(type(parse_tree))) if len(parse_tree.items) != 2: raise InternalError( "algorithm.py:get_kernel: Expected Part_Ref to have 2 children " "but found {0}.".format(len(parse_tree.items))) kernel_name = str(parse_tree.items[0]) # Extract argument list. This can be removed when # fparser#170 is implemented argument_list = [] if isinstance(parse_tree.items[1], Section_Subscript_List): argument_list = parse_tree.items[1].items else: # Expecting a single entry rather than a list argument_list = [parse_tree.items[1]] arguments = [] for argument in argument_list: if isinstance(argument, (Real_Literal_Constant, Int_Literal_Constant)): # A simple constant e.g. 1.0, or 1_i_def arguments.append(Arg('literal', argument.tostr().lower())) elif isinstance(argument, Name): # A simple variable e.g. arg full_text = str(argument).lower() var_name = full_text arguments.append(Arg('variable', full_text, var_name)) elif isinstance(argument, Part_Ref): # An indexed variable e.g. arg(n) full_text = argument.tostr().lower() var_name = str(argument.items[0]).lower() arguments.append(Arg('indexed_variable', full_text, var_name)) elif isinstance(argument, Function_Reference): # A function reference e.g. func() full_text = argument.tostr().lower() designator = argument.items[0] lhs = designator.items[0] lhs = create_var_name(lhs) rhs = str(designator.items[2]) var_name = "{0}_{1}".format(lhs, rhs) var_name = var_name.lower() arguments.append(Arg('indexed_variable', full_text, var_name)) elif isinstance(argument, Data_Ref): # A structure dereference e.g. base%arg, base%arg(n) full_text = argument.tostr().lower() var_name = create_var_name(argument).lower() arguments.append(Arg('variable', full_text, var_name)) elif isinstance(argument, (Level_2_Unary_Expr, Add_Operand, Parenthesis)): # An expression e.g. -1, 1*n, ((1*n)/m). Note, for some # reason Add_Operation represents binary expressions in # fparser2. Walk the tree to look for an argument. if not walk_ast([argument], [Name]): # This is a literal so store the full expression as a # string arguments.append(Arg('literal', argument.tostr().lower())) else: raise NotImplementedError( "algorithm.py:get_kernel: Expressions containing " "variables are not yet supported '{0}', value '{1}', " "kernel '{2}' in file '{3}'.".format( type(argument), str(argument), parse_tree, alg_filename)) else: raise InternalError( "algorithm.py:get_kernel: Unsupported argument structure " "'{0}', value '{1}', kernel '{2}' in file '{3}'.".format( type(argument), str(argument), parse_tree, alg_filename)) return kernel_name, arguments
mod_index = fortran_module(libuq, 'mod_index') code = """ subroutine test(nfterme, npterme, np, mmax) integer, dimension(0:np) :: jterme, jtermo integer :: jterme2(0:np), jterme3(4) integer nfterme, npterme, mmax integer np end subroutine """ fortmod = mod_index fcode = code tree = f2003_parser(FortranStringReader(fcode)) func = walk_ast(tree.content, [Subroutine_Stmt])[0] arglist = walk_ast(func.items, [Dummy_Arg_List])[0] args = [a.string for a in arglist.items] """ Possiblilities: * dtype :: varname * dtype :: varname(dim) * dtype :: var1name(dim1), var2name(dim2) * dtype, dimension(dim) :: varname * dtype, dimension(dim) :: var1name, var2name """ typelist = walk_ast(tree.content, [Type_Declaration_Stmt]) for typestmt in typelist: