def __init__(self, name, ddts=None, logger=None): "Our dict is DDT definition headers, key is type" self._name = '{}_ddt_lib'.format(name) self._ddt_fields = {} # DDT field to DDT access map self._max_mod_name_len = 0 super(DDTLibrary, self).__init__() if ddts is None: ddts = list() elif not isinstance(ddts, list): ddts = [ddts] # End if # Add all the DDT headers, then process for ddt in ddts: if not isinstance(ddt, MetadataSection): errmsg = 'Invalid DDT metadata type, {}' raise ParseInternalError(errmsg.format(type(ddt))) # End if if not ddt.header_type == 'ddt': errmsg = 'Metadata table header is for a {}, should be DDT' raise ParseInternalError(errmsg.format(ddt.header_type)) # End if if ddt.title in self: errmsg = "Duplicate DDT, {}, found{}, original{}" ctx = context_string(ddt.source.context) octx = context_string(self[ddt.title].source.context) raise CCPPError(errmsg.format(ddt.title, ctx, octx)) # End if if logger is not None: lmsg = 'Adding DDT {} to {}' logger.debug(lmsg.format(ddt.title, self.name)) # End if self[ddt.title] = ddt dlen = len(ddt.module) if dlen > self._max_mod_name_len: self._max_mod_name_len = dlen
def add_variable(self, newvar, exists_ok=False, gen_unique=False, adjust_intent=False): """Add <newvar> if it does not conflict with existing entries. For the host model, this includes entries in used DDT variables. If <exists_ok> is True, attempting to add an identical copy is okay. If <gen_unique> is True, a new local_name will be created if a local_name collision is detected. if <adjust_intent> is True, adjust conflicting intents to inout.""" standard_name = newvar.get_prop_value('standard_name') cvar = self.find_variable(standard_name=standard_name, any_scope=False) if cvar is None: # Check the DDT dictionary cvar = self.__ddt_dict.find_variable(standard_name=standard_name, any_scope=False) # end if if cvar and (not exists_ok): emsg = "Attempt to add duplicate host model variable, {}{}." emsg += "\nVariable originally defined{}" ntx = context_string(newvar.context) ctx = context_string(cvar.context) raise CCPPError(emsg.format(standard_name, ntx, ctx)) # end if # No collision, proceed normally super(HostModel, self).add_variable(newvar=newvar, exists_ok=exists_ok, gen_unique=gen_unique, adjust_intent=False)
def get_default_val(self, prop_dict, context=None): if self._default_fn is not None: return self._default_fn(prop_dict, context) elif self._default is not None: return self._default else: ctxt = context_string(context) raise CCPPError('No default for variable property {}{}'.format(self.name, ctxt))
def dims_comp(mheader, mvar, fvar, title, logger, case_sensitive=False): ############################################################################### "Compare the dimensions attribute of two variables" errors = '' mdims = mvar.get_dimensions() fdims = mheader.convert_dims_to_standard_names(fvar, logger=logger) comp = len(mdims) == len(fdims) if not comp: errmsg = 'Error: rank mismatch in {}/{} ({} != {}){}' stdname = mvar.get_prop_value('standard_name') ctx = context_string(mvar.context) errors = add_error( errors, errmsg.format(title, stdname, len(mdims), len(fdims), ctx)) # end if if comp: # Now, compare the dims for dim_ind, mdim in enumerate(mdims): if ':' in mdim: mdim = ':'.join([x.strip() for x in mdim.split(':')]) # end if fdim = fdims[dim_ind].strip() if ':' in fdim: fdim = ':'.join([x.strip() for x in fdim.split(':')]) # end if if not case_sensitive: mdim = mdim.lower() fdim = fdim.lower() # end if # Naked colon is okay for Fortran side comp = fdim in (':', fdim) if not comp: errmsg = 'Error: dim {} mismatch ({} != {}) in {}/{}{}' stdname = mvar.get_prop_value('standard_name') ctx = context_string(mvar.context) errmsg = errmsg.format(dim_ind + 1, mdim, fdims[dim_ind], title, stdname, ctx) errors = add_error(errors, errmsg) # end if # end for # end if return errors
def default_kind_val(prop_dict, context=None): ######################################################################## """Choose a default kind based on a variable's type >>> default_kind_val({'type':'REAL'}) 'kind_phys' >>> default_kind_val({'type':'complex'}) 'kind_phys' >>> default_kind_val({'type':'double precision'}) 'kind_phys' >>> default_kind_val({'type':'integer'}) '' >>> default_kind_val({'type':'character'}) '' >>> default_kind_val({'type':'logical'}) '' >>> default_kind_val({'local_name':'foo'}) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: No type to find default kind for foo >>> default_kind_val({}) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: No type to find default kind >>> default_kind_val({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: No type to find default kind for foo at foo.F90:3 >>> default_kind_val({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: No type to find default kind at foo.F90:3 """ if 'type' in prop_dict: vtype = prop_dict['type'].lower() if vtype == 'real': kind = 'kind_phys' elif vtype == 'complex': kind = 'kind_phys' elif FORTRAN_DP_RE.match(vtype) is not None: kind = 'kind_phys' else: kind = '' # End if else: kind = '' if 'local_name' in prop_dict: lname = ' {}'.format(prop_dict['local_name']) else: lname = '' # End if ctxt = context_string(context) raise CCPPError('No type to find default kind for {}{}'.format(lname, ctxt)) # End if return kind
def check_ddt_type(self, var, header, lname=None): """If <var> is a DDT, check to make sure it is in this DDT library. If not, raise an exception. """ if var.is_ddt(): # Make sure we know this DDT type vtype = var.get_prop_value('type') if vtype not in self: if lname is None: lname = var.get_prop_value('local_name') # End if errmsg = 'Variable {} is of unknown type ({}) in {}' ctx = context_string(var.context) raise CCPPError(errmsg.format(lname, vtype, header.title, ctx))
def collect_ddt_fields(self, var_dict, var, run_env, ddt=None): """Add all the reachable fields from DDT variable <var> of type, <ddt> to <var_dict>. Each field is added as a VarDDT. """ if ddt is None: vtype = var.get_prop_value('type') if vtype in self: ddt = self[vtype] else: lname = var.get_prop_value('local_name') ctx = context_string(var.context) errmsg = "Variable, {}, is not a known DDT{}" raise ParseInternalError(errmsg.format(lname, ctx)) # End if # End if for dvar in ddt.variable_list(): subvar = VarDDT(dvar, var, self.run_env) dvtype = dvar.get_prop_value('type') if (dvar.is_ddt()) and (dvtype in self): # If DDT in our library, we need to add sub-fields recursively. subddt = self[dvtype] self.collect_ddt_fields(var_dict, subvar, run_env, ddt=subddt) else: # add_variable only checks the current dictionary. For a # DDT, the variable also cannot be in our parent dictionaries. stdname = dvar.get_prop_value('standard_name') pvar = var_dict.find_variable(standard_name=stdname, any_scope=True) if pvar: emsg = "Attempt to add duplicate DDT sub-variable, {}{}." emsg += "\nVariable originally defined{}" ntx = context_string(dvar.context) ctx = context_string(pvar.context) raise CCPPError(emsg.format(stdname, ntx, ctx)) # end if # Add this intrinsic to <var_dict> var_dict.add_variable(subvar, run_env)
def find_loop_subst(self, standard_name, any_scope=True, context=None): """If <standard_name> is of the form <standard_name>_extent and that variable is not in the dictionary, substitute a tuple of variables, (<standard_name>_begin, <standard_name>_end), if those variables are in the dictionary. If <standard_name>_extent *is* present, return that variable as a range, (__var_one, <standard_name>_extent) In other cases, return None """ loop_var = VarDictionary.loop_subst_match(standard_name) logger_str = None if loop_var is not None: # Let us see if we can fix a loop variable dict_var = self.find_variable(standard_name, any_scope=any_scope, loop_subst=False) if dict_var is not None: my_var = (VarDictionary.__var_one, dict_var) if self._logger is not None: logger_str = "loop_subst: found {}{}".format(standard_name, context_string(context)) # End if else: my_vars = [self.find_variable(x) for x in loop_var] if None not in my_vars: my_var = tuple(my_vars) if self._logger is not None: names = [x.get_prop_value('local_name') for x in my_vars] logger_str = "loop_subst: {} ==> (){}".format(standard_name, ', '.join(names), context_string(context)) # End if else: if self._logger is not None: logger_str = "loop_subst: {} ==> ({}, {}) FAILED{}".format(standard_name, beg_name, end_name, context_string(context)) # End if my_var = None # End if # End if else: if self._logger is not None: logger_str = "loop_subst: {} is not a loop variable{}".format(standard_name, context_string(context)) # End if my_var = None # End if if logger_str is not None: self._logger.debug(logger_str) # End if return my_var
def standard_name_to_long_name(prop_dict, context=None): ######################################################################## """Translate a standard_name to its default long_name >>> standard_name_to_long_name({'standard_name':'cloud_optical_depth_layers_from_0p55mu_to_0p99mu'}) 'Cloud optical depth layers from 0.55mu to 0.99mu' >>> standard_name_to_long_name({'local_name':'foo'}) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: No standard name to convert foo to long name >>> standard_name_to_long_name({}) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: No standard name to convert to long name >>> standard_name_to_long_name({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: No standard name to convert foo to long name at foo.F90:3 >>> standard_name_to_long_name({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: No standard name to convert to long name at foo.F90:3 """ # We assume that standar_name has been checked for validity # Make the first char uppercase and replace each underscore with a space if 'standard_name' in prop_dict: standard_name = prop_dict['standard_name'] if len(standard_name) > 0: long_name = standard_name[0].upper() + re.sub("_", " ", standard_name[1:]) else: long_name = '' # End if # Next, substitute a decimal point for the p in [:digit]p[:digit] match = real_subst_re.match(long_name) while match is not None: long_name = match.group(1) + '.' + match.group(2) match = real_subst_re.match(long_name) # End while else: long_name = '' if 'local_name' in prop_dict: lname = ' {}'.format(prop_dict['local_name']) else: lname = '' # End if ctxt = context_string(context) raise CCPPError('No standard name to convert{} to long name{}'.format(lname, ctxt)) # End if return long_name
def parse_program(pobj, statements, logger=None): """Parse a Fortran PROGRAM and return any leftover statements and metadata tables encountered in the PROGRAM.""" # The first statement should be a program statement, grab the name pmatch = _PROGRAM_RE.match(statements[0]) if pmatch is None: raise ParseSyntaxError('PROGRAM statement', statements[0]) # End if prog_name = pmatch.group(1) pobj.enter_region('PROGRAM', region_name=prog_name, nested_ok=False) if logger is not None: ctx = context_string(pobj, nodir=True) msg = "Parsing Fortran program, {}{}" logger.debug(msg.format(prog_name, ctx)) # End if # After the program name is the specification part statements, mtables = parse_specification(pobj, statements[1:], prog_name=prog_name, logger=logger) # We really cannot have tables inside a program's executable section # Just read until end statements = read_statements(pobj, statements) inprogram = True while inprogram and (statements is not None): while len(statements) > 0: statement = statements.pop(0) # End program pmatch = _ENDPROGRAM_RE.match(statement) if pmatch is not None: prog_name = pmatch.group(1) pobj.leave_region('PROGRAM', region_name=prog_name) inprogram = False # End if # End while if inprogram and (len(statements) == 0): statements = read_statements(pobj) # End if # End while return statements, mtables
def var_comp(prop_name, mvar, fvar, title, case_sensitive=False): ############################################################################### "Compare a property between two variables" errors = '' mprop = mvar.get_prop_value(prop_name) fprop = fvar.get_prop_value(prop_name) if not case_sensitive: if isinstance(mprop, str): mprop = mprop.lower() # end if if isinstance(fprop, str): fprop = fprop.lower() # end if # end if comp = mprop == fprop if not comp: errmsg = '{} mismatch ({} != {}) in {}{}' ctx = context_string(mvar.context) errors = add_error(errors, errmsg.format(prop_name, mprop, fprop, title, ctx)) # end if return errors
def parse_preamble_data(statements, pobj, spec_name, endmatch, logger): """Parse module variables or DDT definitions from a module preamble or parse program variables from the beginning of a program. """ inspec = True mheaders = list() var_dict = VarDictionary(spec_name) psrc = ParseSource(spec_name, 'MODULE', pobj) active_table = None if logger is not None: ctx = context_string(pobj, nodir=True) msg = "Parsing preamble variables of {}{}" logger.debug(msg.format(spec_name, ctx)) # End if while inspec and (statements is not None): while len(statements) > 0: statement = statements.pop(0) # End program or module pmatch = endmatch.match(statement) asmatch = _ARG_TABLE_START_RE.match(statement) type_def = fortran_type_definition(statement) if asmatch is not None: active_table = asmatch.group(1) elif (pmatch is not None) or is_contains_statement(statement, inspec): # We are done with the specification inspec = False # Put statement back so caller knows where we are statements.insert(0, statement) # Add the header (even if we found no variables) mheader = MetadataTable(table_name_in=spec_name, table_type_in='module', module=spec_name, var_dict=var_dict, logger=logger) mheaders.append(mheader) if logger is not None: ctx = context_string(pobj, nodir=True) msg = 'Adding header {}{}' logger.debug(msg.format(mheader.table_name, ctx)) break elif ((type_def is not None) and (type_def[0].lower() == active_table.lower())): # Put statement back so caller knows where we are statements.insert(0, statement) statements, ddt = parse_type_def(statements, type_def, spec_name, pobj, logger) if ddt is None: ctx = context_string(pobj, nodir=True) msg = "No DDT found at '{}'{}" raise CCPPError(msg.format(statement, ctx)) # End if mheaders.append(ddt) if logger is not None: ctx = context_string(pobj, nodir=True) msg = 'Adding DDT {}{}' logger.debug(msg.format(ddt.table_name, ctx)) # End if active_table = None elif active_table is not None: # We should have a variable definition to add if ((not is_comment_statement(statement)) and (not parse_use_statement(statement, logger)) and (active_table.lower() == spec_name.lower())): dvars = parse_fortran_var_decl(statement, psrc, logger=logger) for var in dvars: var_dict.add_variable(var) # End for # End if # End if (else we are not in an active table so just skip) # End while if inspec and (len(statements) == 0): statements = read_statements(pobj) # End if # End while return statements, mheaders
def parse_specification(pobj, statements, mod_name=None, prog_name=None, logger=None): """Parse specification part of a module or (sub)program""" if (mod_name is not None) and (prog_name is not None): raise ParseInternalError("<mod_name> and <prog_name> cannot both be used") # end if if mod_name is not None: spec_name = mod_name endmatch = _ENDMODULE_RE inmod = True elif prog_name is not None: spec_name = prog_name endmatch = _ENDPROGRAM_RE inmod = False else: raise ParseInternalError("One of <mod_name> or <prog_name> must be used") # End if if logger is not None: ctx = context_string(pobj, nodir=True) msg = "Parsing specification of {}{}" logger.debug(msg.format(spec_name, ctx)) # End if inspec = True mtables = list() while inspec and (statements is not None): while len(statements) > 0: statement = statements.pop(0) # End program or module pmatch = endmatch.match(statement) asmatch = _ARG_TABLE_START_RE.match(statement) if pmatch is not None: # We never found a contains statement inspec = False break elif asmatch is not None: # Put table statement back to re-read statements.insert(0, statement) statements, new_tbls = parse_preamble_data(statements, pobj, spec_name, endmatch, logger) for tbl in new_tbls: title = tbl.table_name if title in mtables: errmsg = duplicate_header(mtables[title], tbl) raise CCPPError(errmsg) # end if if logger is not None: ctx = tbl.start_context() mtype = tbl.table_type msg = "Adding metadata from {}, {}{}" logger.debug(msg.format(mtype, title, ctx)) # End if mtables.append(tbl) # End if inspec = pobj.in_region('MODULE', region_name=mod_name) break elif is_contains_statement(statement, inmod): inspec = False break # End if # End while if inspec and (len(statements) == 0): statements = read_statements(pobj) # End if # End while return statements, mtables
def parse_module(pobj, statements, logger=None): """Parse a Fortran MODULE and return any leftover statements and metadata tables encountered in the MODULE.""" # The first statement should be a module statement, grab the name pmatch = _MODULE_RE.match(statements[0]) if pmatch is None: raise ParseSyntaxError('MODULE statement', statements[0]) # End if mod_name = pmatch.group(1) pobj.enter_region('MODULE', region_name=mod_name, nested_ok=False) if logger is not None: ctx = context_string(pobj, nodir=True) msg = "Parsing Fortran module, {}{}" logger.debug(msg.format(mod_name, ctx)) # End if # After the module name is the specification part statements, mtables = parse_specification(pobj, statements[1:], mod_name=mod_name, logger=logger) # Look for metadata tables statements = read_statements(pobj, statements) inmodule = pobj.in_region('MODULE', region_name=mod_name) active_table = None while inmodule and (statements is not None): while statements: statement = statements.pop(0) # End module pmatch = _ENDMODULE_RE.match(statement) asmatch = _ARG_TABLE_START_RE.match(statement) if asmatch is not None: active_table = asmatch.group(1) elif pmatch is not None: mod_name = pmatch.group(1) pobj.leave_region('MODULE', region_name=mod_name) inmodule = False break elif active_table is not None: statements, mheader = parse_scheme_metadata(statements, pobj, mod_name, active_table, logger) if mheader is not None: title = mheader.table_name if title in mtables: errmsg = duplicate_header(mtables[title], mheader) raise CCPPError(errmsg) # end if if logger is not None: mtype = mheader.table_type ctx = mheader.start_context() msg = "Adding metadata from {}, {}{}" logger.debug(msg.format(mtype, title, ctx)) # End if mtables.append(mheader) # End if active_table = None inmodule = pobj.in_region('MODULE', region_name=mod_name) break # End if # End while if inmodule and (statements is not None) and (len(statements) == 0): statements = read_statements(pobj) # End if # End while return statements, mtables
def __init__(self, prop_dict, source, invalid_ok=False, logger=None): """NB: invalid_ok=True is dangerous because it allows creation of a Var object with invalid properties. In order to prevent silent failures, invalid_ok requires a logger in order to take effect.""" if source.type is 'SCHEME': required_props = Var.__required_var_props master_propdict = Var.__var_propdict else: required_props = Var.__required_spec_props master_propdict = Var.__spec_propdict # End if self._source = source # Grab a frozen copy of the context self._context = ParseContext(context=source.context) # First, check the input if 'ddt_type' in prop_dict: # Special case to bypass normal type rules if 'type' not in prop_dict: prop_dict['type'] = prop_dict['ddt_type'] # End if if 'units' not in prop_dict: prop_dict['units'] = "" # End if prop_dict['kind'] = prop_dict['ddt_type'] del prop_dict['ddt_type'] # End if for key in prop_dict: if Var.get_prop(key) is None: raise ParseSyntaxError("Invalid metadata variable property, '{}'".format(key), context=self.context) # End if # End for # Make sure required properties are present for propname in required_props: if propname not in prop_dict: if invalid_ok and (logger is not None): ctx = context_string(self.context) logger.warning("Required property, '{}', missing{}".format(propname, ctx)) else: raise ParseSyntaxError("Required property, '{}', missing".format(propname), context=self.context) # End if # End if # End for # Check for any mismatch if ('constant' in prop_dict) and ('intent' in prop_dict): if prop_dict['intent'].lower() != 'in': if invalid_ok and (logger is not None): ctx = context_string(self.context) logger.warning("{} is marked constant but is intent {}{}".format(prop_dict['local_name'], prop_dict['intent'], ctx)) else: raise ParseSyntaxError("{} is marked constant but is intent {}".format(prop_dict['local_name'], prop_dict['intent']), context=self.context) # End if # End if # End if # Steal dict from caller self._prop_dict = prop_dict # Fill in default values for missing properties for propname in master_propdict: if (propname not in prop_dict) and master_propdict[propname].optional: self._prop_dict[propname] = master_propdict[propname].get_default_val(self._prop_dict, context=self.context) # End if # End for # Make sure all the variable values are valid try: for prop in self._prop_dict.keys(): check = Var.get_prop(prop).valid_value(self._prop_dict[prop], error=True) # End for except CCPPError as cp: if invalid_ok and (logger is not None): ctx = context_string(self.context) logger.warning("{}: {}{}".format(self._prop_dict['local_name'], cp, ctx)) else: raise ParseSyntaxError("{}: {}".format(self._prop_dict['local_name'], cp), context=self.context)
def parse_fortran_var_decl(line, source, logger=None): ######################################################################## """Parse a Fortran variable declaration line and return a list of Var objects representing the variables declared on <line>. >>> _var_id_re_.match('foo') #doctest: +ELLIPSIS <_sre.SRE_Match object at 0x...> >>> _var_id_re_.match("foo()") >>> _var_id_re_.match('foo').group(1) 'foo' >>> _var_id_re_.match('foo').group(2) >>> _var_id_re_.match("foo(bar)").group(1) 'foo' >>> _var_id_re_.match("foo(bar)").group(2) '(bar)' >>> _var_id_re_.match("foo(bar)").group(2) '(bar)' >>> _var_id_re_.match("foo(bar, baz)").group(2) '(bar, baz)' >>> _var_id_re_.match("foo(bar : baz)").group(2) '(bar : baz)' >>> _var_id_re_.match("foo(bar:)").group(2) '(bar:)' >>> _var_id_re_.match("foo(: baz)").group(2) '(: baz)' >>> _var_id_re_.match("foo(:, :,:)").group(2) '(:, :,:)' >>> _var_id_re_.match("foo(8)").group(2) '(8)' >>> _var_id_re_.match("foo(::,a:b,a:,:b)").group(2) '(::,a:b,a:,:b)' >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('local_name') 'foo' >>> parse_fortran_var_decl("integer :: foo = 0", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('local_name') 'foo' >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('optional') False >>> parse_fortran_var_decl("integer, optional :: foo", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('optional') 'True' >>> parse_fortran_var_decl("integer, dimension(:) :: foo", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions') '(:)' >>> parse_fortran_var_decl("integer, dimension(:) :: foo(bar)", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions') '(bar)' >>> parse_fortran_var_decl("integer, dimension(:) :: foo(:,:), baz", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions') '(:,:)' >>> parse_fortran_var_decl("integer, dimension(:) :: foo(:,:), baz", ParseSource('foo.F90', 'MODULE', ParseContext()))[1].get_prop_value('dimensions') '(:)' >>> parse_fortran_var_decl("real (kind=kind_phys), pointer :: phii (:,:) => null() !< interface geopotential height", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions') '(:,:)' >>> parse_fortran_var_decl("real(kind=kind_phys), dimension(im, levs, ntrac), intent(in) :: qgrs", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions') '(im, levs, ntrac)' >>> parse_fortran_var_decl("character(len=*), intent(out) :: errmsg", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('local_name') 'errmsg' """ context = source.context sline = line.strip() # Strip comments first if '!' in sline: sline = sline[0:sline.index('!')].rstrip() # End if tobject = Ftype_factory(sline, context) newvars = list() if tobject is not None: varprops = sline[tobject.type_len:].strip() def_dims = None # Default dimensions intent = None dimensions = None if '::' in varprops: elements = varprops.split('::') varlist = elements[1].strip() varprops = Ftype.parse_attr_specs(elements[0].strip(), context) for prop in varprops: if prop[0:6] == 'intent': intent = prop[6:].strip()[1:-1].strip() elif prop[0:9:] == 'dimension': dimensions = prop[9:].strip() # End if # End for else: # No attr_specs varlist = varprops varprops = list() # End if # Create Vars from these pieces # We may need to reassemble multi-dimensional specs vars = Ftype.reassemble_parens(varlist, 'variable_list', context) for var in vars: prop_dict = {} if '=' in var: # We do not care about initializers var = var[0:var.rindex('=')].rstrip() # End if # Scan <var> and gather variable pieces inchar = None # Character context var_len = len(var) ploc = var.find('(') if ploc < 0: varname = var.strip() dimspec = None else: varname = var[0:ploc].strip() begin, end = check_balanced_paren(var) if (begin < 0) or (end < 0): if logger is not None: ctx = context_string(context) logger.warning( "WARNING: Invalid variable declaration, {}{}". format(var, ctx)) else: raise ParseSyntaxError('variable declaration', token=var, context=context) # End if else: dimspec = var[begin:end + 1] # End if # End if prop_dict['local_name'] = varname prop_dict['standard_name'] = Ftype.unique_standard_name() prop_dict['units'] = '' prop_dict['type'] = tobject.typestr if tobject.kind is not None: prop_dict['kind'] = tobject.kind # End if if 'optional' in varprops: prop_dict['optional'] = 'True' # End if if 'allocatable' in varprops: prop_dict['allocatable'] = 'True' # End if if intent is not None: prop_dict['intent'] = intent # End if if dimspec is not None: prop_dict['dimensions'] = dimspec elif dimensions is not None: prop_dict['dimensions'] = dimensions else: prop_dict['dimensions'] = '()' # End if # XXgoldyXX: I am nervous about allowing invalid Var objects here newvars.append( Var(prop_dict, source, invalid_ok=(logger is not None), logger=logger)) # End for # No else (not a variable declaration) # End if return newvars
def find_dimension_subst(self, standard_name, any_scope=True, context=None): """If <standard_name> is of the form <standard_name>_loop_extent attempt to find a variable of the form <standard_name>_dimension and return that. If such a variable is not found, raise an exception. If <standard_name> is not of the form <standard_name>_extent, return None. """ loop_var = standard_name in VarDictionary.__ccpp_dim_subst__ logger_str = None if loop_var: # Let us see if we can replace the variable dim_name = VarDictionary.__ccpp_dim_subst__[standard_name] my_var = self.find_variable(dim_name, any_scope=any_scope) if my_var is None: raise CCPPError("Dimension variable, {} not found{}".format(dim_name, context_string(context))) # End if else: my_var = None # End if return my_var
def parse_scheme_metadata(statements, pobj, spec_name, table_name, logger): "Parse dummy argument information from a subroutine" psrc = None mheader = None var_dict = None scheme_name = None # Find the subroutine line, should be first executable statement inpreamble = False insub = True if logger is not None: ctx = context_string(pobj, nodir=True) msg = "Parsing specification of {}{}" logger.debug(msg.format(table_name, ctx)) # End if ctx = context_string(pobj) # Save initial context with directory vdict = None # Initialized when we parse the subroutine arguments while insub and (statements is not None): while statements: statement = statements.pop(0) smatch = _SUBROUTINE_RE.match(statement) esmatch = _END_SUBROUTINE_RE.match(statement) pmatch = _ENDMODULE_RE.match(statement) asmatch = _ARG_TABLE_START_RE.match(statement) if asmatch is not None: # We have run off the end of something, hope that is okay # Put this statement back for the caller to deal with statements.insert(0, statement) insub = False break # End if if pmatch is not None: # We have run off the end of the module, hope that is okay pobj.leave_region('MODULE', region_name=spec_name) insub = False break # End if if smatch is not None: scheme_name = smatch.group(1) inpreamble = scheme_name.lower() == table_name.lower() if inpreamble: if smatch.group(2) is not None: smstr = smatch.group(2).strip() if len(smstr) > 0: smlist = smstr.strip().split(',') else: smlist = list() # End if scheme_args = [x.strip().lower() for x in smlist] else: scheme_args = list() # End if # Create a dict template with all the scheme's arguments # in the correct order vdict = OrderedDict() for arg in scheme_args: if len(arg) == 0: errmsg = 'Empty argument{}' raise ParseInternalError(errmsg.format(pobj)) # End if if arg in vdict: errmsg = 'Duplicate dummy argument, {}' raise ParseSyntaxError(errmsg.format(arg), context=pobj) # End if vdict[arg] = None # End for psrc = ParseSource(scheme_name, 'scheme', pobj) # End if elif inpreamble: # Process a preamble statement (use or argument declaration) if esmatch is not None: inpreamble = False insub = False elif ((not is_comment_statement(statement)) and (not parse_use_statement(statement, logger)) and is_dummy_argument_statement(statement)): dvars = parse_fortran_var_decl(statement, psrc, logger=logger) for var in dvars: lname = var.get_prop_value('local_name').lower() if lname in vdict: if vdict[lname] is not None: emsg = "Error: duplicate dummy argument, {}" raise ParseSyntaxError(emsg.format(lname), context=pobj) # End if vdict[lname] = var else: raise ParseSyntaxError('dummy argument', token=lname, context=pobj) # End if # End for # End if # End if # End while if insub and (len(statements) == 0): statements = read_statements(pobj) # End if # End while # Check for missing declarations missing = list() if vdict is None: errmsg = 'Subroutine, {}, not found{}' raise CCPPError(errmsg.format(scheme_name, ctx)) # End if for lname in vdict.keys(): if vdict[lname] is None: missing.append(lname) # End if # End for if len(missing) > 0: errmsg = 'Missing local_variables, {} in {}' raise CCPPError(errmsg.format(missing, scheme_name)) # End if var_dict = VarDictionary(scheme_name, variables=vdict) if (scheme_name is not None) and (var_dict is not None): mheader = MetadataTable(table_name_in=scheme_name, table_type_in='scheme', module=spec_name, var_dict=var_dict, logger=logger) # End if return statements, mheader
def parse_fortran_var_decl(line, source, run_env): ######################################################################## """Parse a Fortran variable declaration line and return a list of Var objects representing the variables declared on <line>. >>> _VAR_ID_RE.match('foo') #doctest: +ELLIPSIS <re.Match object; span=(0, 3), match='foo'> >>> _VAR_ID_RE.match("foo()") >>> _VAR_ID_RE.match('foo').group(1) 'foo' >>> _VAR_ID_RE.match('foo').group(2) >>> _VAR_ID_RE.match("foo(bar)").group(1) 'foo' >>> _VAR_ID_RE.match("foo(bar)").group(2) '(bar)' >>> _VAR_ID_RE.match("foo(bar)").group(2) '(bar)' >>> _VAR_ID_RE.match("foo(bar, baz)").group(2) '(bar, baz)' >>> _VAR_ID_RE.match("foo(bar : baz)").group(2) '(bar : baz)' >>> _VAR_ID_RE.match("foo(bar:)").group(2) '(bar:)' >>> _VAR_ID_RE.match("foo(: baz)").group(2) '(: baz)' >>> _VAR_ID_RE.match("foo(:, :,:)").group(2) '(:, :,:)' >>> _VAR_ID_RE.match("foo(8)").group(2) '(8)' >>> _VAR_ID_RE.match("foo(::,a:b,a:,:b)").group(2) '(::,a:b,a:,:b)' >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') 'foo' >>> parse_fortran_var_decl("integer :: foo = 0", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') 'foo' >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('optional') >>> parse_fortran_var_decl("integer, optional :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('optional') 'True' >>> parse_fortran_var_decl("integer, dimension(:) :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') '(:)' >>> parse_fortran_var_decl("integer, dimension(:) :: foo(bar)", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') '(bar)' >>> parse_fortran_var_decl("integer, dimension(:) :: foo(:,:), baz", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') '(:,:)' >>> parse_fortran_var_decl("integer, dimension(:) :: foo(:,:), baz", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[1].get_prop_value('dimensions') '(:)' >>> parse_fortran_var_decl("real (kind=kind_phys), pointer :: phii (:,:) => null() !< interface geopotential height", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') '(:,:)' >>> parse_fortran_var_decl("real(kind=kind_phys), dimension(im, levs, ntrac), intent(in) :: qgrs", ParseSource('foo.F90', 'scheme', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') '(im, levs, ntrac)' >>> parse_fortran_var_decl("character(len=*), intent(out) :: errmsg", ParseSource('foo.F90', 'scheme', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') 'errmsg' >>> parse_fortran_var_decl("character(len=512), intent(out) :: errmsg", ParseSource('foo.F90', 'scheme', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('kind') 'len=512' >>> parse_fortran_var_decl("real(kind_phys), intent(out) :: foo(8)", ParseSource('foo.F90', 'scheme', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') '(8)' >>> parse_fortran_var_decl("real(kind_phys), intent(out) :: foo(8)", ParseSource('foo.F90', 'scheme', ParseContext()), _DUMMY_RUN_ENV)[0].get_dimensions() ['8'] >>> parse_fortran_var_decl("character(len=*), intent(out) :: errmsg", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid variable declaration, character(len=*), intent(out) :: errmsg, intent not allowed in module variable, in <standard input> ## NB: Expressions (including function calls) not currently supported here #>>> parse_fortran_var_decl("real(kind_phys), intent(out) :: foo(size(bar))", ParseSource('foo.F90', 'scheme', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') #'(size(bar))' """ context = source.context sline = line.strip() # Strip comments first if '!' in sline: sline = sline[0:sline.index('!')].rstrip() # end if tobject = ftype_factory(sline, context) newvars = list() if tobject is not None: varprops = sline[tobject.type_len:].strip() def_dims = None # Default dimensions intent = None dimensions = None if '::' in varprops: elements = varprops.split('::') varlist = elements[1].strip() varprops = Ftype.parse_attr_specs(elements[0].strip(), context) for prop in varprops: if prop[0:6] == 'intent': if source.type != 'scheme': typ = source.type errmsg = 'Invalid variable declaration, {}, intent' errmsg = errmsg + ' not allowed in {} variable' if run_env.logger is not None: ctx = context_string(context) errmsg = "WARNING: " + errmsg + "{}" run_env.logger.warning( errmsg.format(sline, typ, ctx)) else: raise ParseSyntaxError(errmsg.format(sline, typ), context=context) # end if else: intent = prop[6:].strip()[1:-1].strip() # end if elif prop[0:9:] == 'dimension': dimensions = prop[9:].strip() # end if # end for else: # No attr_specs varlist = varprops varprops = list() # end if # Create Vars from these pieces # We may need to reassemble multi-dimensional specs var_list = Ftype.reassemble_parens(varlist, 'variable_list', context) for var in var_list: prop_dict = {} if '=' in var: # We do not care about initializers var = var[0:var.rindex('=')].rstrip() # end if # Scan <var> and gather variable pieces inchar = None # Character context var_len = len(var) ploc = var.find('(') if ploc < 0: varname = var.strip() dimspec = None else: varname = var[0:ploc].strip() begin, end = check_balanced_paren(var) if (begin < 0) or (end < 0): if run_env.logger is not None: ctx = context_string(context) errmsg = "WARNING: Invalid variable declaration, {}{}" run_env.logger.warning(errmsg.format(var, ctx)) else: raise ParseSyntaxError('variable declaration', token=var, context=context) # end if else: dimspec = var[begin:end + 1] # end if # end if prop_dict['local_name'] = varname prop_dict['standard_name'] = unique_standard_name() prop_dict['units'] = '' if isinstance(tobject, FtypeTypeDecl): prop_dict['ddt_type'] = tobject.typestr else: prop_dict['type'] = tobject.typestr # end if if tobject.kind() is not None: prop_dict['kind'] = tobject.kind() # end if if 'optional' in varprops: prop_dict['optional'] = 'True' # end if if 'allocatable' in varprops: prop_dict['allocatable'] = 'True' # end if if intent is not None: prop_dict['intent'] = intent # end if if dimspec is not None: prop_dict['dimensions'] = dimspec elif dimensions is not None: prop_dict['dimensions'] = dimensions else: prop_dict['dimensions'] = '()' # end if # XXgoldyXX: I am nervous about allowing invalid Var objects here # Also, this tends to cause an exception that ends up back here # which is not a good idea. var = FortranVar(prop_dict, source, run_env) newvars.append(var) # end for # No else (not a variable declaration) # end if return newvars
def __init__(self, meta_tables, name_in, logger): self.__name = name_in self.__var_locations = {} # Local name to module map self.__loop_vars = None # Loop control vars in interface calls self.__used_variables = None # Local names which have been requested self.__deferred_finds = None # Used variables that were missed at first # First, process DDT headers meta_headers = list() for sect in [x.sections() for x in meta_tables.values()]: meta_headers.extend(sect) # end for # Initialize our dictionaries # Initialize variable dictionary super(HostModel, self).__init__(self.name, logger=logger) self.__ddt_lib = DDTLibrary( '{}_ddts'.format(self.name), ddts=[d for d in meta_headers if d.header_type == 'ddt'], logger=logger) self.__ddt_dict = VarDictionary("{}_ddt_vars".format(self.name), parent_dict=self, logger=logger) # Now, process the code headers by type self.__metadata_tables = meta_tables for header in [h for h in meta_headers if h.header_type != 'ddt']: title = header.title if logger is not None: msg = 'Adding {} {} to host model' logger.debug(msg.format(header.header_type, title)) # End if if header.header_type == 'module': # Set the variable modules modname = header.title for var in header.variable_list(): self.add_variable(var) lname = var.get_prop_value('local_name') self.__var_locations[lname] = modname self.ddt_lib.check_ddt_type(var, header, lname=lname) if var.is_ddt(): self.ddt_lib.collect_ddt_fields(self.__ddt_dict, var) # End if # End for elif header.header_type == 'host': if self.__name is None: # Grab the first host name we see self.__name = header.name # End if for var in header.variable_list(): self.add_variable(var) self.ddt_lib.check_ddt_type(var, header) if var.is_ddt(): self.ddt_lib.collect_ddt_fields(self.__ddt_dict, var) # End if # End for loop_vars = header.variable_list(std_vars=False, loop_vars=True, consts=False) if loop_vars: # loop_vars are part of the host-model interface call # at run time. As such, they override the host-model # array dimensions. self.__loop_vars = VarDictionary(self.name) # End if for hvar in loop_vars: std_name = hvar.get_prop_value('standard_name') if std_name not in self.__loop_vars: self.__loop_vars.add_variable(hvar) else: ovar = self.__loop_vars[std_name] ctx1 = context_string(ovar.context) ctx2 = context_string(hvar.context) lname1 = ovar.get_prop_value('local_name') lname2 = hvar.get_prop_value('local_name') errmsg = ("Duplicate host loop var for {n}:\n" " Dup: {l1}{c1}\n Orig: {l2}{c2}") raise CCPPError( errmsg.format(n=self.name, l1=lname1, c1=ctx1, l2=lname2, c2=ctx2)) # End if # End for else: errmsg = "Invalid host model metadata header type, {} ({}){}" errmsg += "\nType must be 'module' or 'host'" ctx = context_string(header.context) raise CCPPError( errmsg.format(header.title, header.header_type, ctx)) # End if # End while if self.name is None: errmsg = 'No name found for host model, add a host metadata entry' raise CCPPError(errmsg) # End if # Finally, turn on the use meter so we know which module variables # to 'use' in a host cap. self.__used_variables = set() # Local names which have been requested self.__deferred_finds = set( ) # Used variables that were missed at first