def __init__(self, ast, name=None): if name is None: # if no name is supplied then use the module name to # determine the type name. The assumed convention is that # the module is called <name/>_mod and the type is called # <name/>_type found = False for statement, _ in fpapi.walk(ast): if isinstance(statement, fparser1.block_statements.Module): module_name = statement.name found = True break if not found: raise ParseError( "Error KernelType, the file does not contain a module. " "Is it a Kernel file?") mn_len = len(module_name) if mn_len < 5: raise ParseError( "Error, module name '{0}' is too short to have '_mod' as " "an extension. This convention is assumed.".format( module_name)) base_name = module_name.lower()[:mn_len - 4] extension_name = module_name.lower()[mn_len - 4:mn_len] if extension_name != "_mod": raise ParseError( "Error, module name '{0}' does not have '_mod' as an " "extension. This convention is assumed.".format( module_name)) name = base_name + "_type" self._name = name self._ast = ast self._ktype = get_kernel_metadata(name, ast) operates_on = self.get_integer_variable("operates_on") # The GOcean API still uses the 'iterates_over' metadata entry # although this is deprecated in the LFRic API. # Validation is left to the API-specific code in either dynamo0p3.py # or gocean1p0.py. iterates_over = self.get_integer_variable("iterates_over") if operates_on: self._iterates_over = operates_on elif iterates_over: self._iterates_over = iterates_over else: # We don't raise an error here - we leave it to the API-specific # validation code. self._iterates_over = None # Although validation of the value given to operates_on or # iterates_over is API-specifc, we can check that the metadata doesn't # specify both of them because that doesn't make sense. if operates_on and iterates_over: raise ParseError("The metadata for kernel '{0}' contains both " "'operates_on' and 'iterates_over'. Only one of " "these is permitted.".format(name)) self._procedure = KernelProcedure(self._ktype, name, ast) self._inits = getkerneldescriptors(name, self._ktype) self._arg_descriptors = [] # this is set up by the subclasses
def get_kernel_metadata(name, ast): '''Takes the kernel module parse tree and returns the metadata part of the parse tree (a Fortran type) with the name 'name'. :param str name: the metadata name (of a Fortran type). Also \ the name referencing the kernel in the algorithm layer. The name \ provided and the name of the kernel in the parse tree are case \ insensitive in this function. :param ast: parse tree of the kernel module code :type ast: :py:class:`fparser.one.block_statements.BeginSource` :returns: Parse tree of the metadata (a Fortran type with name \ 'name') :rtype: :py:class:`fparser.one.block_statements.Type` :raises ParseError: if the metadata type name is not found in \ the kernel code parse tree ''' ktype = None for statement, _ in fpapi.walk(ast, -1): if isinstance(statement, fparser1.block_statements.Type) \ and statement.name.lower() == name.lower(): ktype = statement break if ktype is None: raise ParseError("Kernel type {0} does not exist".format(name)) return ktype
def get_integer_variable(self, name): ''' Parse the kernel meta-data and find the value of the integer variable with the supplied name. Return None if no matching variable is found. :param str name: the name of the integer variable to find. :returns: value of the specified integer variable or None. :rtype: str :raises ParseError: if the RHS of the assignment is not a Name. ''' # Ensure the Fortran2003 parser is initialised _ = ParserFactory().create() for statement, _ in fpapi.walk(self._ktype, -1): if isinstance(statement, fparser1.typedecl_statements.Integer): # fparser only goes down to the statement level. We use # fparser2 to parse the statement itself (eventually we'll # use fparser2 to parse the whole thing). assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0]) if str(assign.items[0]) == name: if not isinstance(assign.items[2], Fortran2003.Name): raise ParseError( "get_integer_variable: RHS of assignment is not " "a variable name: '{0}'".format(str(assign))) return str(assign.items[2]) return None
def gen(self): ''' Generate modified algorithm code :rtype: ast ''' from fparser import api from psyclone.f2pygen import adduse psy_name = self._psy.name # run through all statements looking for procedure calls idx = 0 for stmt, _ in api.walk(self._ast, -1): if isinstance(stmt, fparser.statements.Call): if stmt.designator == "invoke": invoke_info = self._psy.invokes.invoke_list[idx] stmt.designator = invoke_info.name stmt.items = invoke_info.alg_unique_args adduse(psy_name, stmt.parent, only=True, funcnames=[invoke_info.name]) idx += 1 if idx == 0: raise NoInvokesError( "Algorithm file contains no invoke() calls: refusing to " "generate empty PSy code") return self._ast
def getKernelMetadata(self, name, ast): ktype = None for statement, depth in fpapi.walk(ast, -1): if isinstance(statement, fparser1.block_statements.Type) \ and statement.name == name: ktype = statement if ktype is None: raise RuntimeError("Kernel type %s does not exist" % name) return ktype
def test_walk(): ''' Test the walk() method of the api module. ''' tree = api.parse(SOURCE_STR, isfree=True, isstrict=False, ignore_comments=False) for stmt, depth in api.walk(tree, 1): print(depth, stmt.item)
def test_walk(): source_str = '''\ ! before foo subroutine foo integer i, r do i = 1,100 r = r + 1 end do ! after end do end subroutine foo ''' tree = api.parse(source_str, isfree=True, isstrict=False, ignore_comments=False) for stmt, depth in api.walk(tree, 1): print depth, stmt.item
def get_integer_variable(self, name): ''' Parse the kernel meta-data and find the value of the integer variable with the supplied name. Return None if no matching variable is found.''' for statement, _ in fpapi.walk(self._ktype, -1): if isinstance(statement, fparser1.typedecl_statements.Integer): # fparser only goes down to the statement level. We use # the expression parser (expression.py) to parse the # statement itself. assign = expr.FORT_EXPRESSION.parseString( statement.entity_decls[0]) if assign[0].name == name: return assign[0].value return None
def test_empty_kernel_name(monkeypatch): ''' Check that we raise the correct error when we get a blank string for the name of the Kernel subroutine. ''' import fparser mdata = MDATA.replace("procedure, nopass :: code => testkern_eval_code", "procedure, nopass :: testkern_eval_code") ast = fpapi.parse(mdata) # Break the AST for statement, _ in fpapi.walk(ast, -1): if isinstance(statement, fparser.one.statements.SpecificBinding): monkeypatch.setattr(statement, "name", "") break with pytest.raises(InternalError) as err: _ = KernelType(ast) assert ("Empty Kernel name returned for Kernel type testkern_eval_type" in str(err))
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 get_procedure(ast, name, modast): bname = None for statement in ast.content: if isinstance(statement, fparser1.statements.SpecificBinding): if statement.name == "code" and statement.bname != "": # prototype gungho style bname = statement.bname elif statement.name.lower() != "code" \ and statement.bname != "": raise ParseError( "Kernel type %s binds to a specific procedure but " "does not use 'code' as the generic name." % name) else: # psyclone style bname = statement.name if bname is None: raise RuntimeError( "Kernel type %s does not bind a specific procedure" % name) if bname == '': raise ParseError( "Internal error: empty kernel name returned for Kernel type " "%s." % name) code = None default_public = True declared_private = False declared_public = False for statement, depth in fpapi.walk(modast, -1): if isinstance(statement, fparser1.statements.Private): if len(statement.items) == 0: default_public = False elif bname in statement.items: declared_private = True if isinstance(statement, fparser1.statements.Public): if len(statement.items) == 0: default_public = True elif bname in statement.items: declared_public = True if isinstance(statement, fparser1.block_statements.Subroutine) \ and statement.name == bname: if statement.is_public(): declared_public = True code = statement if code is None: raise RuntimeError("Kernel subroutine %s not implemented" % bname) if declared_private or (not default_public and not declared_public): raise ParseError("Kernel subroutine '%s' is not public" % bname) return code, bname
def test_walk(): source_str = '''\ ! before foo subroutine foo integer i, r do i = 1,100 r = r + 1 end do ! after end do end subroutine foo ''' tree = api.parse(source_str, isfree=True, isstrict=False, ignore_comments=False) for stmt, depth in api.walk(tree, 1): print(depth, stmt.item)
def checkMetadataPublic(self, name, ast): default_public = True declared_private = False declared_public = False for statement, depth in fpapi.walk(ast, -1): if isinstance(statement, fparser1.statements.Private): if len(statement.items) == 0: default_public = False elif name in statement.items: declared_private = True if isinstance(statement, fparser1.statements.Public): if len(statement.items) == 0: default_public = True elif name in statement.items: declared_public = True if isinstance(statement, fparser1.block_statements.Type) \ and statement.name == name and statement.is_public(): declared_public = True if declared_private or (not default_public and not declared_public): raise ParseError("Kernel type '%s' is not public" % name)
def __init__(self, ast, name=None): if name is None: # if no name is supplied then use the module name to # determine the type name. The assumed convention is that # the module is called <name/>_mod and the type is called # <name/>_type found = False for statement, depth in fpapi.walk(ast, -1): if isinstance(statement, fparser1.block_statements.Module): module_name = statement.name found = True break if not found: raise ParseError( "Error KernelType, the file does not contain a module. " "Is it a Kernel file?") mn_len = len(module_name) if mn_len < 5: raise ParseError( "Error, module name '{0}' is too short to have '_mod' as " "an extension. This convention is assumed.".format( module_name)) base_name = module_name.lower()[:mn_len - 4] extension_name = module_name.lower()[mn_len - 4:mn_len] if extension_name != "_mod": raise ParseError( "Error, module name '{0}' does not have '_mod' as an " "extension. This convention is assumed.".format( module_name)) name = base_name + "_type" self._name = name self._ast = ast self.checkMetadataPublic(name, ast) self._ktype = self.getKernelMetadata(name, ast) self._iterates_over = self.get_integer_variable("iterates_over") self._procedure = KernelProcedure(self._ktype, name, ast) self._inits = self.getkerneldescriptors(self._ktype) self._arg_descriptors = None # this is set up by the subclasses
def get_kernel_interface(name, ast): '''Takes the kernel module parse tree and returns the interface part of the parse tree. :param str name: The kernel name :param ast: parse tree of the kernel module code :type ast: :py:class:`fparser.one.block_statements.BeginSource` :returns: Name of the interface block and the names of the module \ procedures (lower case). Or None, None if there is no \ interface or the interface has no nodule procedures. :rtype: : `str`, list of str`. :raises ParseError: if more than one interface is found. ''' iname = None sname = None count = 0 for statement, _ in fpapi.walk(ast): if isinstance(statement, fparser1.block_statements.Interface): # count the interfaces, then can be only one! count = count + 1 if count >= 2: raise ParseError("Module containing kernel {0} has more than " "one interface, this is forbidden in the " "LFRic API.".format(name)) # Check the interfaces assigns one or more module procedures. if statement.a.module_procedures: iname = statement.name.lower() # If implicit interface (no name) set to none as there is no # procedure name for PSyclone to use. if iname == '': iname = None else: sname = [ str(sname).lower() for sname in statement.a.module_procedures ] return iname, sname
def parse(alg_filename, api="", invoke_name="invoke", inf_name="inf", kernel_path="", line_length=False, distributed_memory=config.DISTRIBUTED_MEMORY): '''Takes a GungHo algorithm specification as input and outputs an AST of this specification and an object containing information about the invocation calls in the algorithm specification and any associated kernel implementations. :param str alg_filename: The file containing the algorithm specification. :param str invoke_name: The expected name of the invocation calls in the algorithm specification :param str inf_name: The expected module name of any required infrastructure routines. :param str kernel_path: The path to search for kernel source files (if different from the location of the algorithm source). :param bool line_length: A logical flag specifying whether we care about line lengths being longer than 132 characters. If so, the input (algorithm and kernel) code is checked to make sure that it conforms and an error raised if not. The default is False. :rtype: ast,invoke_info :raises IOError: if the filename or search path does not exist :raises ParseError: if there is an error in the parsing :raises RuntimeError: if there is an error in the parsing For example: >>> from parse import parse >>> ast,info=parse("argspec.F90") ''' if distributed_memory not in [True, False]: raise ParseError( "The distributed_memory flag in parse() must be set to" " 'True' or 'False'") config.DISTRIBUTED_MEMORY = distributed_memory if api == "": from psyclone.config import DEFAULTAPI api = DEFAULTAPI else: check_api(api) # Get the names of the supported Built-in operations for this API builtin_names, builtin_defs_file = get_builtin_defs(api) # drop cache fparser1.parsefortran.FortranParser.cache.clear() fparser.logging.disable('CRITICAL') if not os.path.isfile(alg_filename): raise IOError("File %s not found" % alg_filename) try: ast = fpapi.parse(alg_filename, ignore_comments=False, analyze=False) # ast includes an extra comment line which contains file # details. This line can be long which can cause line length # issues. Therefore set the information (name) to be empty. ast.name = "" except: import traceback traceback.print_exc() raise ParseError("Fatal error in external fparser tool") if line_length: fll = FortLineLength() with open(alg_filename, "r") as myfile: code_str = myfile.read() if fll.long_lines(code_str): raise ParseError( "parse: the algorithm file does not conform to the specified" " {0} line length limit".format(str(fll.length))) name_to_module = {} try: from collections import OrderedDict except: try: from ordereddict import OrderedDict except: import sys python_version = sys.version_info if python_version[0] <= 2 and python_version[1] < 7: raise ParseError( "OrderedDict not provided natively pre python 2.7 " "(you are running {0}. Try installing with 'sudo " "pip install ordereddict'".format(python_version)) else: raise ParseError( "OrderedDict not found which is unexpected as it is " "meant to be part of the Python library from 2.7 onwards") invokecalls = OrderedDict() # Keep a list of the named invokes so that we can check that the same # name isn't used more than once unique_invoke_labels = [] container_name = None for child in ast.content: if isinstance(child, fparser1.block_statements.Program) or \ isinstance(child, fparser1.block_statements.Module) or \ isinstance(child, fparser1.block_statements.Subroutine): container_name = child.name break if container_name is None: raise ParseError( "Error, program, module or subroutine not found in ast") for statement, depth in fpapi.walk(ast, -1): if isinstance(statement, fparser1.statements.Use): for name in statement.items: name_to_module[name] = statement.name if isinstance(statement, fparser1.statements.Call) \ and statement.designator == invoke_name: statement_kcalls = [] invoke_label = None for arg in statement.items: # We expect each item in an invoke call to be either a # call to a kernel or the name of the invoke (specifed # as name="my_name") try: parsed = expr.FORT_EXPRESSION.parseString(arg)[0] except ParseException: raise ParseError("Failed to parse string: {0}".format(arg)) if isinstance(parsed, expr.NamedArg): if parsed.name.lower() != "name": raise ParseError( "The arguments to an invoke() must be either " "kernel calls or an (optional) name='invoke-name' " "but got '{0}' in file {1}".format( str(parsed), alg_filename)) if invoke_label: raise ParseError( "An invoke must contain one or zero 'name=xxx' " "arguments but found more than one in: {0} in " "file {1}".format(str(statement), alg_filename)) if not parsed.is_string: raise ParseError( "The (optional) name of an invoke must be " "specified as a string but got {0} in file {1}.". format(str(parsed), alg_filename)) # Store the supplied label. Use lower-case (as Fortran # is case insensitive) invoke_label = parsed.value.lower() # Remove any spaces. # TODO check with LFRic team - should we raise an error if # it contains spaces? invoke_label = invoke_label.replace(" ", "_") # Check that the resulting label is a valid Fortran Name from fparser.two import pattern_tools if not pattern_tools.abs_name.match(invoke_label): raise ParseError( "The (optional) name of an invoke must be a " "string containing a valid Fortran name (with " "any spaces replaced by underscores) but " "got '{0}' in file {1}".format( invoke_label, alg_filename)) # Check that it's not already been used in this Algorithm if invoke_label in unique_invoke_labels: raise ParseError( "Found multiple named invoke()'s with the same " "name ('{0}') when parsing {1}".format( invoke_label, alg_filename)) unique_invoke_labels.append(invoke_label) # Continue parsing the other arguments to this invoke call continue argname = parsed.name argargs = [] for a in parsed.args: if type(a) is str: # a literal is being passed by argument argargs.append(Arg('literal', a)) else: # assume argument parsed as a FunctionVar variableName = a.name if a.args is not None: # argument is an indexed array so extract the # full text fullText = "" for tok in a.walk_skipping_name(): fullText += str(tok) argargs.append( Arg('indexed_variable', fullText, variableName)) else: # argument is a standard variable argargs.append( Arg('variable', variableName, variableName)) if argname in builtin_names: if argname in name_to_module: raise ParseError("A built-in cannot be named in a use " "statement but '{0}' is used from " "module '{1}' in file {2}".format( argname, name_to_module[argname], alg_filename)) # this is a call to a built-in operation. The # KernelTypeFactory will generate appropriate meta-data statement_kcalls.append( BuiltInCall( BuiltInKernelTypeFactory(api=api).create( builtin_names, builtin_defs_file, name=argname), argargs)) else: try: modulename = name_to_module[argname] except KeyError: raise ParseError( "kernel call '{0}' must either be named in a use " "statement or be a recognised built-in " "(one of '{1}' for this API)".format( argname, builtin_names)) # Search for the file containing the kernel source import fnmatch # We only consider files with the suffixes .f90 and .F90 # when searching for the kernel source. search_string = "{0}.[fF]90".format(modulename) # Our list of matching files (should have length == 1) matches = [] # If a search path has been specified then we look there. # Otherwise we look in the directory containing the # algorithm definition file if len(kernel_path) > 0: cdir = os.path.abspath(kernel_path) if not os.access(cdir, os.R_OK): raise IOError( "Supplied kernel search path does not exist " "or cannot be read: {0}".format(cdir)) # We recursively search down through the directory # tree starting at the specified path if os.path.exists(cdir): for root, dirnames, filenames in os.walk(cdir): for filename in fnmatch.filter( filenames, search_string): matches.append(os.path.join( root, filename)) else: # We look *only* in the directory that contained the # algorithm file cdir = os.path.abspath(os.path.dirname(alg_filename)) filenames = os.listdir(cdir) for filename in fnmatch.filter(filenames, search_string): matches.append(os.path.join(cdir, filename)) # Check that we only found one match if len(matches) != 1: if len(matches) == 0: raise IOError( "Kernel file '{0}.[fF]90' not found in {1}". format(modulename, cdir)) else: raise IOError( "More than one match for kernel file " "'{0}.[fF]90' found!".format(modulename)) else: try: modast = fpapi.parse(matches[0]) # ast includes an extra comment line which # contains file details. This line can be # long which can cause line length # issues. Therefore set the information # (name) to be empty. modast.name = "" except: raise ParseError( "Failed to parse kernel code " "'{0}'. Is the Fortran correct?".format( matches[0])) if line_length: fll = FortLineLength() with open(matches[0], "r") as myfile: code_str = myfile.read() if fll.long_lines(code_str): raise ParseError( "parse: the kernel file '{0}' does not" " conform to the specified {1} line length" " limit".format(modulename, str(fll.length))) statement_kcalls.append( KernelCall( modulename, KernelTypeFactory(api=api).create(modast, name=argname), argargs)) invokecalls[statement] = InvokeCall(statement_kcalls, name=invoke_label) return ast, FileInfo(container_name, invokecalls)
#! /usr/bin/env python import fparser from fparser import api source_str = ''' subroutine foo integer i, r do i=1,100 r = r + i end do end ''' tree = api.parse(source_str) for stmt, depth in api.walk(tree): print depth, stmt.item
def get_procedure(ast, name, modast): ''' Get the name of the subroutine associated with the Kernel. This is a type-bound procedure in the meta-data which may take one of three forms: PROCEDURE, nopass :: code => <proc_name> or PROCEDURE, nopass :: <proc_name> or if there is no type-bound procedure, an interface may be used: INTERFACE <proc_name> :param ast: the fparser1 parse tree for the Kernel meta-data. :type ast: :py:class:`fparser.one.block_statements.Type` :param str name: the name of the Fortran type holding the Kernel \ meta-data. :param modast: the fparser1 parse tree for the module containing the \ Kernel routine. :type modast: :py:class:`fparser.one.block_statements.BeginSource` :returns: 2-tuple of the fparser1 parse tree of the Subroutine \ statement and the name of that Subroutine. :rtype: (:py:class:`fparser1.block_statements.Subroutine`, str) :raises ParseError: if the supplied Kernel meta-data does not \ have a type-bound procedure or interface. :raises ParseError: if no implementation is found for the \ type-bound procedure or interface module \ procedures. :raises ParseError: if the type-bound procedure specifies a binding \ name but the generic name is not "code". :raises InternalError: if we get an empty string for the name of the \ type-bound procedure. ''' bname = None # Search the the meta-data for a SpecificBinding for statement in ast.content: if isinstance(statement, fparser1.statements.SpecificBinding): # We support either: # PROCEDURE, nopass :: code => <proc_name> or # PROCEDURE, nopass :: <proc_name> if statement.bname: if statement.name.lower() != "code": raise ParseError( "Kernel type {0} binds to a specific procedure but" " does not use 'code' as the generic name.". format(name)) bname = statement.bname.lower() else: bname = statement.name.lower() break if bname is None: # If no type-bound procedure found, search for an explicit # interface that has module procedures. bname, subnames = get_kernel_interface(name, modast) if bname is None: # no interface found either raise ParseError( "Kernel type {0} does not bind a specific procedure or \ provide an explicit interface".format(name)) elif bname == '': raise InternalError( "Empty Kernel name returned for Kernel type {0}.".format(name)) else: # add the name of the tbp to the list of strings to search for. subnames = [bname] # walk the AST to check the subroutine names exist. procedure_count = 0 for subname in subnames: for statement, _ in fpapi.walk(modast): if isinstance(statement, fparser1.block_statements.Subroutine) \ and statement.name.lower() \ == subname: procedure_count = procedure_count + 1 if procedure_count == 1: # set code to statement if there is one procedure. code = statement else: code = None # set to None if there is more than one. break else: raise ParseError( "kernel.py:KernelProcedure:get_procedure: Kernel " "subroutine '{0}' not found.".format(subname)) return code, bname
def get_integer_array(self, name): ''' Parse the kernel meta-data and find the values of the integer array variable with the supplied name. Returns an empty list if no matching variable is found. The search is not case sensitive. :param str name: the name of the integer array to find. :return: list of values (lower-case). :rtype: list of str. :raises InternalError: if we fail to parse the LHS of the array \ declaration or the array constructor. :raises ParseError: if the array is not of rank 1. :raises ParseError: if the array extent is not specified using an \ integer literal. :raises ParseError: if the RHS of the declaration is not an array \ constructor. :raises InternalError: if the parse tree for the array constructor \ does not have the expected structure. :raises ParseError: if the number of items in the array constructor \ does not match the extent of the array. ''' # Ensure the classes are setup for the Fortran2008 parser _ = ParserFactory().create(std="f2008") # Fortran is not case sensitive so nor is our matching lower_name = name.lower() for statement, _ in fpapi.walk(self._ktype): if not isinstance(statement, fparser1.typedecl_statements.Integer): # This isn't an integer declaration so skip it continue # fparser only goes down to the statement level. We use fparser2 to # parse the statement itself. assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0]) names = walk(assign.children, Fortran2003.Name) if not names: raise InternalError("Unsupported assignment statement: '{0}'". format(str(assign))) if str(names[0]).lower() != lower_name: # This is not the variable declaration we're looking for continue if not isinstance(assign.children[0], Fortran2003.Part_Ref): # Not an array declaration return [] if not isinstance(assign.children[0].children[1], Fortran2003.Section_Subscript_List): raise InternalError( "get_integer_array: expected array declaration to have a " "Section_Subscript_List but found '{0}' for: '{1}'".format( type(assign.children[0].children[1]).__name__, str(assign))) dim_stmt = assign.children[0].children[1] if len(dim_stmt.children) != 1: raise ParseError( "get_integer_array: array must be 1D but found an array " "with {0} dimensions for name '{1}'".format( len(dim_stmt.children), name)) if not isinstance(dim_stmt.children[0], Fortran2003.Int_Literal_Constant): raise ParseError( "get_integer_array: array extent must be specified using " "an integer literal but found '{0}' for array '{1}'". format(str(dim_stmt.children[0]), name)) # Get the declared size of the array array_extent = int(str(dim_stmt.children[0])) if not isinstance(assign.children[2], Fortran2003.Array_Constructor): raise ParseError( "get_integer_array: RHS of assignment is not " "an array constructor: '{0}'".format(str(assign))) # fparser2 AST for Array_Constructor is: # Array_Constructor('[', Ac_Value_List(',', (Name('w0'), # Name('w1'))), ']') # Construct a list of the names in the array constructor names = walk(assign.children[2].children, Fortran2003.Name) if not names: raise InternalError("Failed to parse array constructor: " "'{0}'".format(str(assign.items[2]))) if len(names) != array_extent: # Ideally fparser would catch this but it isn't yet mature # enough. raise ParseError( "get_integer_array: declared length of array '{0}' is {1} " "but constructor only contains {2} names: '{3}'".format( name, array_extent, len(names), str(assign))) return [str(name).lower() for name in names] # No matching declaration for the provided name was found return []
def get_procedure(ast, name, modast): ''' Get the name of the subroutine associated with the Kernel. This is a type-bound procedure in the meta-data which may take one of two forms: PROCEDURE, nopass :: code => <proc_name> or PROCEDURE, nopass :: <proc_name> :param ast: the fparser1 parse tree for the Kernel meta-data. :type ast: :py:class:`fparser.one.block_statements.Type` :param str name: the name of the Fortran type holding the Kernel \ meta-data. :param modast: the fparser1 parse tree for the module containing the \ Kernel routine. :type modast: :py:class:`fparser.one.block_statements.BeginSource` :returns: 2-tuple of the fparser1 parse tree of the Subroutine \ statement and the name of that Subroutine. :rtype: (:py:class:`fparser1.block_statements.Subroutine`, str) :raises ParseError: if the supplied Kernel meta-data does not \ have a type-bound procedure. :raises ParseError: if no implementation is found for the \ type-bound procedure. :raises ParseError: if the type-bound procedure specifies a binding \ name but the generic name is not "code". :raises InternalError: if we get an empty string for the name of the \ type-bound procedure. ''' bname = None # Search the the meta-data for a SpecificBinding for statement in ast.content: if isinstance(statement, fparser1.statements.SpecificBinding): # We support either: # PROCEDURE, nopass :: code => <proc_name> or # PROCEDURE, nopass :: <proc_name> if statement.bname: if statement.name.lower() != "code": raise ParseError( "Kernel type {0} binds to a specific procedure but" " does not use 'code' as the generic name.".format( name)) bname = statement.bname else: bname = statement.name break if bname is None: raise ParseError( "Kernel type {0} does not bind a specific procedure".format( name)) if bname == '': raise InternalError( "Empty Kernel name returned for Kernel type {0}.".format(name)) code = None for statement, _ in fpapi.walk(modast, -1): if isinstance(statement, fparser1.block_statements.Subroutine) \ and statement.name == bname: code = statement break if not code: raise ParseError( "kernel.py:KernelProcedure:get_procedure: Kernel subroutine " "'{0}' not found.".format(bname)) return code, bname