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 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).lower() elif (pmatch is not None) or is_contains_statement( statement, inspec): # We are done with the specification inspec = False if len(var_dict.variable_list()) > 0: mheader = MetadataHeader(title=spec_name, type_in='MODULE', module=spec_name, var_dict=var_dict, logger=logger) mheaders.append(mheader) # End if break elif (type_def is not None) and (type_def[0].lower() == active_table): statements, ddt = parse_type_def(statements, type_def, spec_name, pobj, logger) mheaders.append(ddt) active_table = None else: # We should have a variable definition to add if ((not is_comment_statement(statement, logger)) and (not parse_use_statement({}, statement, pobj, logger)) and (active_table == spec_name)): vars = parse_fortran_var_decl(statement, psrc, logger=logger) for var in vars: var_dict.add_variable(var) # End for # End if # End if # End while if inspec and (len(statements) == 0): statements = read_statements(pobj) # End if # End while return statements, mheaders
def write_host_cap(host_model, api, output_dir, logger): ############################################################################### """Write an API to allow <host_model> to call any configured CCPP suite""" module_name = "{}_ccpp_cap".format(host_model.name) cap_filename = os.path.join(output_dir, '{}.F90'.format(module_name)) if logger is not None: msg = 'Writing CCPP Host Model Cap for {} to {}' logger.info(msg.format(host_model.name, cap_filename)) # End if header = _HEADER.format(host_model=host_model.name) with FortranWriter(cap_filename, 'w', header, module_name) as cap: # Write module use statements maxmod = len(KINDS_MODULE) cap.write(' use {kinds}'.format(kinds=KINDS_MODULE), 1) modules = host_model.variable_locations() if modules: mlen = max([len(x[0]) for x in modules]) maxmod = max(maxmod, mlen) # End if mlen = max([len(x.module) for x in api.suites]) maxmod = max(maxmod, mlen) maxmod = max(maxmod, len(CONST_DDT_MOD)) for mod in modules: mspc = (maxmod - len(mod[0]))*' ' cap.write("use {}, {}only: {}".format(mod[0], mspc, mod[1]), 1) # End for mspc = ' '*(maxmod - len(CONST_DDT_MOD)) cap.write("use {}, {}only: {}".format(CONST_DDT_MOD, mspc, CONST_DDT_NAME), 1) cap.write_preamble() max_suite_len = 0 for suite in api.suites: max_suite_len = max(max_suite_len, len(suite.module)) # End for cap.write("! Public Interfaces", 1) # CCPP_STATE_MACH.transitions represents the host CCPP interface for stage in CCPP_STATE_MACH.transitions(): stmt = "public :: {host_model}_ccpp_physics_{stage}" cap.write(stmt.format(host_model=host_model.name, stage=stage), 1) # End for API.declare_inspection_interfaces(cap) # Write the host-model interfaces for constituents reg_name = constituent_register_subname(host_model) cap.write("public :: {}".format(reg_name), 1) numconsts_name = constituent_num_consts_funcname(host_model) cap.write("public :: {}".format(numconsts_name), 1) copyin_name = constituent_copyin_subname(host_model) cap.write("public :: {}".format(copyin_name), 1) copyout_name = constituent_copyout_subname(host_model) cap.write("public :: {}".format(copyout_name), 1) cap.write("", 0) cap.write("! Private module variables", 1) const_dict = add_constituent_vars(cap, host_model, api.suites, logger) cap.end_module_header() for stage in CCPP_STATE_MACH.transitions(): # Create a dict of local variables for stage host_local_vars = VarDictionary("{}_{}".format(host_model.name, stage)) # Create part call lists # Look for any loop-variable mismatch for suite in api.suites: spart_list = suite_part_list(suite, stage) for spart in spart_list: spart_args = spart.call_list.variable_list() for sp_var in spart_args: stdname = sp_var.get_prop_value('standard_name') hvar = const_dict.find_variable(standard_name=stdname, any_scope=True) if hvar is None: errmsg = 'No host model variable for {} in {}' raise CCPPError(errmsg.format(stdname, spart.name)) # End if # End for (loop over part variables) # End for (loop of suite parts) # End for (loop over suites) run_stage = stage == 'run' # All interfaces need the suite name apivars = [_SUITE_NAME_VAR] if run_stage: # Only the run phase needs a suite part name apivars.append(_SUITE_PART_VAR) # End if # Create a list of dummy arguments with correct intent settings callvars = host_model.call_list(stage) # Host interface dummy args hdvars = list() subst_dict = {} for hvar in callvars: protected = hvar.get_prop_value('protected') stdname = hvar.get_prop_value('standard_name') if stdname in CCPP_LOOP_VAR_STDNAMES: protected = True # Cannot modify a loop variable # End if if protected: subst_dict['intent'] = 'in' else: subst_dict['intent'] = 'inout' # End if hdvars.append(hvar.clone(subst_dict, source_name=_API_SRC_NAME)) # End for lnames = [x.get_prop_value('local_name') for x in apivars + hdvars] api_vlist = ", ".join(lnames) cap.write(_SUBHEAD.format(api_vars=api_vlist, host_model=host_model.name, stage=stage), 1) # Write out any suite part use statements for suite in api.suites: mspc = (max_suite_len - len(suite.module))*' ' spart_list = suite_part_list(suite, stage) for spart in spart_list: stmt = "use {}, {}only: {}" cap.write(stmt.format(suite.module, mspc, spart.name), 2) # End for # End for # Write out any host model DDT input var use statements host_model.ddt_lib.write_ddt_use_statements(hdvars, cap, 2, pad=max_suite_len) cap.write("", 1) # Write out dummy arguments for var in apivars: var.write_def(cap, 2, host_model) # End for for var in hdvars: var.write_def(cap, 2, host_model) # End for for var in host_local_vars.variable_list(): var.write_def(cap, 2, host_model) # End for cap.write('', 0) # Write out the body clauses errmsg_name, errflg_name = api.get_errinfo_names() # Initialize err variables cap.write('{errflg} = 0'.format(errflg=errflg_name), 2) cap.write('{errmsg} = ""'.format(errmsg=errmsg_name), 2) else_str = '' for suite in api.suites: stmt = "{}if (trim(suite_name) == '{}') then" cap.write(stmt.format(else_str, suite.name), 2) if stage == 'run': el2_str = '' spart_list = suite_part_list(suite, stage) for spart in spart_list: pname = spart.name[len(suite.name)+1:] stmt = "{}if (trim(suite_part) == '{}') then" cap.write(stmt.format(el2_str, pname), 3) call_str = suite_part_call_list(host_model, const_dict, spart, True) cap.write("call {}({})".format(spart.name, call_str), 4) el2_str = 'else ' # End for cap.write("else", 3) emsg = "write({errmsg}, '(3a)')".format(errmsg=errmsg_name) emsg += '"No suite part named ", ' emsg += 'trim(suite_part), ' emsg += '" found in suite {sname}"'.format(sname=suite.name) cap.write(emsg, 4) cap.write("{errflg} = 1".format(errflg=errflg_name), 4) cap.write("end if", 3) else: spart = suite.phase_group(stage) call_str = suite_part_call_list(host_model, const_dict, spart, False) stmt = "call {}_{}({})" cap.write(stmt.format(suite.name, stage, call_str), 3) # End if else_str = 'else ' # End for cap.write("else", 2) emsg = "write({errmsg}, '(3a)')".format(errmsg=errmsg_name) emsg += '"No suite named ", ' emsg += 'trim(suite_name), "found"' cap.write(emsg, 3) cap.write("{errflg} = 1".format(errflg=errflg_name), 3) cap.write("end if", 2) cap.write(_SUBFOOT.format(host_model=host_model.name, stage=stage), 1) # End for # Write the API inspection routines (e.g., list of suites) api.write_inspection_routines(cap) # Write the constituent initialization interfaces err_vars = host_model.find_error_variables() const_obj_name = constituent_model_object_name(host_model) cap.write("", 0) const_names_name = constituent_model_const_stdnames(host_model) const_indices_name = constituent_model_const_indices(host_model) ConstituentVarDict.write_host_routines(cap, host_model, reg_name, numconsts_name, copyin_name, copyout_name, const_obj_name, const_names_name, const_indices_name, api.suites, err_vars) # End with return cap_filename
class MetadataHeader(ParseSource): """Class to hold all information from a metadata header >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "type = scheme", "module = foo", \ "[ im ]", "standard_name = horizontal_loop_extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in"])) #doctest: +ELLIPSIS <__main__.MetadataHeader foo / foobar at 0x...> >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "type = scheme", "module = foobar", \ "[ im ]", "standard_name = horizontal_loop_extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in"])).get_var(standard_name='horizontal_loop_extent') #doctest: +ELLIPSIS <metavar.Var horizontal_loop_extent: im at 0x...> >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "module = foo", \ "[ im ]", "standard_name = horizontal_loop_extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ " subroutine foo()"])).get_var(standard_name='horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Missing metadata header type, at foobar.txt:7 >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "type = scheme", "module=foobar", \ "[ im ]", "standard_name = horizontal_loop_extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ ""], line_start=0)).get_var(standard_name='horizontal_loop_extent').get_prop_value('local_name') 'im' >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "type = scheme" \ "[ im ]", "standard_name = horizontal loop extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ ""], line_start=0)).get_var(standard_name='horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid variable property value, 'horizontal loop extent', at foobar.txt:2 >>> MetadataHeader(ParseObject("foobar.txt", \ ["[ccpp-arg-table]", "name = foobar", "type = scheme" \ "[ im ]", "standard_name = horizontal loop extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ ""], line_start=0)).get_var(standard_name='horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid property syntax, '[ccpp-arg-table]', at foobar.txt:1 >>> MetadataHeader(ParseObject("foobar.txt", \ ["name = foobar", "module = foo" \ "[ im ]", "standard_name = horizontal loop extent", \ "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in", \ ""], line_start=0)).get_var(standard_name='horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid metadata header start, no table type, at foobar.txt:2 >>> MetadataHeader.__var_start__.match('[ qval ]') #doctest: +ELLIPSIS <_sre.SRE_Match object at 0x...> >>> MetadataHeader.__var_start__.match('[ qval(hi_mom) ]') #doctest: +ELLIPSIS <_sre.SRE_Match object at 0x...> """ __header_start__ = re.compile(r"(?i)\s*\[\s*ccpp-arg-table\s*\]") __var_start__ = re.compile(r"^\[\s*(" + FORTRAN_ID + r"|" + LITERAL_INT + r"|" + FORTRAN_SCALAR_REF + r")\s*\]$") __blank_line__ = re.compile(r"\s*[#;]") __html_template__ = """ <html> <head> <title>{title}</title> <meta charset="UTF-8"> </head> <body> <table border="1"> {header}{contents}</table> </body> </html> """ def __init__(self, parse_object=None, title=None, type_in=None, module=None, var_dict=None, logger=None): self._pobj = parse_object """If <parse_object> is not None, initialize from the current file and location in <parse_object>. If <parse_object> is None, initialize from <title>, <type>, <module>, and <var_dict>. Note that if <parse_object> is not None, <title>, <type>, <module>, and <var_dict> are ignored. """ if parse_object is None: if title is None: raise ParseInternalError('MetadataHeader requires a title') else: self._table_title = title # End if if type_in is None: raise ParseInternalError( 'MetadataHeader requires a header type') else: self._header_type = type # End if if module is None: raise ParseInternalError( 'MetadataHeader requires a module name') else: self._module_name = module # End if # Initialize our ParseSource parent super(MetadataHeader, self).__init__(self.title, self.header_type, self._pobj) self._variables = VarDictionary(self.title, logger=logger) for var in var_dict.variable_list(): # Let this crash if no dict self._variables.add_variable(var) # End for else: self.__init_from_file__(parse_object, logger) # End if # Categorize the variables self._var_intents = {'in': list(), 'out': list(), 'inout': list()} for var in self.variable_list(): intent = var.get_prop_value('intent') if intent is not None: self._var_intents[intent].append(var) # End if # End for def __init_from_file__(self, parse_object, logger): # Read the table preamble, assume the caller already figured out # the first line of the header using the table_start method. curr_line, curr_line_num = self._pobj.next_line() self._table_title = None self._header_type = None self._module_name = None while (curr_line is not None) and (not self.variable_start(curr_line)) and ( not MetadataHeader.table_start(curr_line)): for property in self.parse_config_line(curr_line): # Manually parse name, type, and module properties key = property[0].strip().lower() value = property[1].strip() if key == 'name': self._table_title = value elif key == 'type': if value not in ['module', 'scheme', 'ddt']: raise ParseSyntaxError("metadata table type", token=value, context=self._pobj) # End if self._header_type = value elif key == 'module': if value == "None": raise ParseSyntaxError("metadata table, no module", context=self._pobj) else: self._module_name = value # End if else: raise ParseSyntaxError("metadata table start property", token=value, context=self._pobj) # End if # End for curr_line, curr_line_num = self._pobj.next_line() # End while if self.title is None: raise ParseSyntaxError("metadata header start, no table name", token=curr_line, context=self._pobj) elif self.header_type is None: raise ParseSyntaxError("metadata header start, no table type", token=curr_line, context=self._pobj) elif self.header_type == "ddt": register_fortran_ddt_name(self.title) # End if # Initialize our ParseSource parent super(MetadataHeader, self).__init__(self.title, self.header_type, self._pobj) # Read the variables valid_lines = True self._variables = VarDictionary(self.title, logger=logger) while valid_lines: newvar, curr_line = self.parse_variable(curr_line) valid_lines = newvar is not None if valid_lines: self._variables.add_variable(newvar) # Check to see if we hit the end of the table valid_lines = not MetadataHeader.table_start(curr_line) # No else, we just run off the end of the table # End if # End while def parse_config_line(self, line): "Parse a config line and return a list of keyword value pairs." parse_items = list() if line is None: pass # No properties on this line elif MetadataHeader.is_blank(line): pass # No properties on this line else: properties = line.strip().split('|') for property in properties: pitems = property.split('=', 1) if len(pitems) < 2: raise ParseSyntaxError("variable property syntax", token=property, context=self._pobj) else: parse_items.append(pitems) # End if # End for # End if return parse_items def parse_variable(self, curr_line): # The header line has the format [ <valid_fortran_symbol> ] # Parse header valid_line = (curr_line is not None) and ( not MetadataHeader.table_start(curr_line)) if valid_line: local_name = self.variable_start( curr_line) # caller handles exception else: local_name = None # End if if local_name is None: # This is not a valid variable line, punt (should be end of table) valid_line = False # End if # Parse lines until invalid line is found # NB: Header variables cannot have embedded blank lines if valid_line: var_props = {} var_props['local_name'] = local_name else: var_props = None # End if while valid_line: curr_line, curr_line_num = self._pobj.next_line() valid_line = ((curr_line is not None) and (not MetadataHeader.is_blank(curr_line)) and (not MetadataHeader.table_start(curr_line)) and (self.variable_start(curr_line) is None)) # A valid line may have multiple properties (separated by '|') if valid_line: properties = self.parse_config_line(curr_line) for property in properties: try: pname = property[0].strip() pval_str = property[1].strip() # Make sure this is a match hp = Var.get_prop(pname) if hp is not None: pval = hp.valid_value(pval_str) else: raise ParseSyntaxError("variable property name", token=pname, context=self._pobj) # End if if pval is None: raise ParseSyntaxError( "'{}' property value".format(pname), token=pval_str, context=self._pobj) # End if except ParseSyntaxError as p: raise p # If we get this far, we have a valid property. var_props[pname] = pval # End for # End if # End while if var_props is None: return None, curr_line else: try: newvar = Var(var_props, source=self) except CCPPError as ve: raise ParseSyntaxError(ve, context=self._pobj) return newvar, curr_line # End if def variable_list(self): "Return an ordered list of the header's variables" return self._variables.variable_list() def to_html(self, outdir, props): """Write html file for metadata table and return filename. Skip metadata headers without variables""" if not self._variables.variable_list(): return None # Write table header header = "<tr>" for prop in props: header += "<th>{}</th>".format(prop) header += "</tr>\n" # Write table contents, one row per variable contents = "" for var in self._variables.variable_list(): row = "<tr>" for prop in props: value = var.get_prop_value(prop) # Pretty-print for dimensions if prop == 'dimensions': value = '(' + ', '.join(value) + ')' elif value is None: value = "n/a" row += "<td>{}</td>".format(value) row += "</tr>\n" contents += row filename = os.path.join(outdir, self.title + '.html') with open(filename, "w") as f: f.writelines( self.__html_template__.format(title=self.title + ' argument table', header=header, contents=contents)) return filename def get_var(self, standard_name=None, intent=None): if standard_name is not None: var = self._variables.find_variable(standard_name) return var elif intent is not None: if intent not in self._var_intents: raise ParseInternalError( "Illegal intent type, '{}', in {}".format( intent, self.title), context=self._pobj) # End if return self._var_intents[intent] else: return None def prop_list(self, prop_name): "Return list of <prop_name> values for this scheme's arguments" return self._variables.prop_list(prop_name) def variable_start(self, line): """Return variable name if <line> is an interface metadata table header """ if line is None: match = None else: match = MetadataHeader.__var_start__.match(line) # End if if match is not None: name = match.group(1) if not MetadataHeader.is_scalar_reference(name): raise ParseSyntaxError("local variable name", token=name, context=self._pobj) # End if else: name = None # End if return name def __repr__(self): base = super(MetadataHeader, self).__repr__() pind = base.find(' object ') if pind >= 0: pre = base[0:pind] else: pre = '<MetadataHeader' # End if bind = base.find('at 0x') if bind >= 0: post = base[bind:] else: post = '>' # End if return '{} {} / {} {}'.format(pre, self.module, self.title, post) def __del__(self): try: del self._variables super(MetadataHeader, self).__del__() except Exception as e: pass # Python does not guarantee much about __del__ conditions # End try @property def title(self): 'Return the name of the metadata arg_table' return self._table_title @property def module(self): 'Return the module name for this header (if it exists)' return self._module_name @property def header_type(self): 'Return the type of structure this header documents' return self._header_type @classmethod def is_blank(cls, line): "Return True iff <line> is a valid config format blank or comment line" return (len(line) == 0) or (cls.__blank_line__.match(line) is not None) @classmethod def table_start(cls, line): """Return variable name if <line> is an interface metadata table header """ if (line is None) or cls.is_blank(line): match = None else: match = MetadataHeader.__header_start__.match(line) # End if return match is not None @classmethod def is_scalar_reference(cls, test_val): return check_fortran_ref(test_val) is not None @classmethod def parse_metadata_file(cls, filename): "Parse <filename> and return list of parsed metadata headers" # Read all lines of the file at once mheaders = list() with open(filename, 'r') as file: fin_lines = file.readlines() for index in range(len(fin_lines)): fin_lines[index] = fin_lines[index].rstrip('\n') # End for # End with # Look for a header start parse_obj = ParseObject(filename, fin_lines) curr_line, curr_line_num = parse_obj.curr_line() while curr_line is not None: if MetadataHeader.table_start(curr_line): mheaders.append(MetadataHeader(parse_obj)) curr_line, curr_line_num = parse_obj.curr_line() else: curr_line, curr_line_num = parse_obj.next_line() # End if # End while return mheaders