def capgen(run_env): ############################################################################### """Parse indicated host, scheme, and suite files. Generate code to allow host model to run indicated CCPP suites.""" ## A few sanity checks ## Make sure output directory is legit if os.path.exists(run_env.output_dir): if not os.path.isdir(run_env.output_dir): errmsg = "output-root, '{}', is not a directory" raise CCPPError(errmsg.format(run_env.output_root)) # end if if not os.access(run_env.output_dir, os.W_OK): errmsg = "Cannot write files to output-root ({})" raise CCPPError(errmsg.format(run_env.output_root)) # end if (output_dir is okay) else: # Try to create output_dir (let it crash if it fails) os.makedirs(run_env.output_dir) # end if host_files = run_env.host_files host_name = run_env.host_name scheme_files = run_env.scheme_files # We need to create three lists of files, hosts, schemes, and SDFs host_files = create_file_list(run_env.host_files, ['meta'], 'Host', run_env.logger) scheme_files = create_file_list(run_env.scheme_files, ['meta'], 'Scheme', run_env.logger) sdfs = create_file_list(run_env.suites, ['xml'], 'Suite', run_env.logger) check_for_writeable_file(run_env.datatable_file, "Cap output datatable") ##XXgoldyXX: Temporary warning if run_env.generate_docfiles: raise CCPPError("--generate-docfiles not yet supported") # end if # First up, handle the host files host_model = parse_host_model_files(host_files, host_name, run_env) # Next, parse the scheme files scheme_headers, scheme_tdict = parse_scheme_files(scheme_files, run_env) ddts = host_model.ddt_lib.keys() if ddts and run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): run_env.logger.debug("DDT definitions = {}".format(ddts)) # end if plist = host_model.prop_list('local_name') if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): run_env.logger.debug("{} variables = {}".format( host_model.name, plist)) run_env.logger.debug("schemes = {}".format( [x.title for x in scheme_headers])) # Finally, we can get on with writing suites # Make sure to write to temporary location if files exist in <output_dir> if not os.path.exists(run_env.output_dir): # Try to create output_dir (let it crash if it fails) os.makedirs(run_env.output_dir) # Nothing here, use it for output outtemp_dir = run_env.output_dir elif not os.listdir(run_env.output_dir): # Nothing here, use it for output outtemp_dir = run_env.output_dir else: # We need to create a temporary staging area, create it here outtemp_name = "ccpp_temp_scratch_dir" outtemp_dir = os.path.join(run_env.output_dir, outtemp_name) if os.path.exists(outtemp_dir): remove_dir(outtemp_dir, force=True) # end if os.makedirs(outtemp_dir) # end if ccpp_api = API(sdfs, host_model, scheme_headers, run_env) cap_filenames = ccpp_api.write(outtemp_dir, run_env) if run_env.generate_host_cap: # Create a cap file host_files = [ write_host_cap(host_model, ccpp_api, outtemp_dir, run_env) ] else: host_files = list() # end if # Create the kinds file kinds_file = create_kinds_file(run_env, outtemp_dir) # Move any changed files to output_dir and remove outtemp_dir move_modified_files(outtemp_dir, run_env.output_dir, overwrite=run_env.force_overwrite, remove_src=True) # We have to rename the files we created if outtemp_dir != run_env.output_dir: replace_paths(cap_filenames, outtemp_dir, run_env.output_dir) replace_paths(host_files, outtemp_dir, run_env.output_dir) kinds_file = kinds_file.replace(outtemp_dir, run_env.output_dir) # end if # Finally, create the database of generated files and caps # This can be directly in output_dir because it will not affect dependencies src_dir = os.path.join(__FRAMEWORK_ROOT, "src") generate_ccpp_datatable(run_env, host_model, ccpp_api, scheme_headers, scheme_tdict, host_files, cap_filenames, kinds_file, src_dir)
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 write_host_cap(host_model, api, output_dir, run_env): ############################################################################### """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 run_env.logger is not None: msg = 'Writing CCPP Host Model Cap for {} to {}' run_env.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, run_env) 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), run_env) # 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
def compare_fheader_to_mheader(meta_header, fort_header, logger): ############################################################################### """Compare a metadata header against the header generated from the corresponding code in the associated Fortran file. Return a string with any errors found (empty string is no errors). """ errors_found = '' title = meta_header.title mht = meta_header.header_type fht = fort_header.header_type if mht != fht: # Special case, host metadata can be in a Fortran module or scheme if (mht != 'host') or (fht not in ('module', SCHEME_HEADER_TYPE)): errmsg = 'Metadata table type mismatch for {}, {} != {}{}' ctx = meta_header.start_context() raise CCPPError( errmsg.format(title, meta_header.header_type, fort_header.header_type, ctx)) # end if else: # The headers should have the same variables in the same order # The exception is that a Fortran module can have variable declarations # after all the metadata variables. mlist = meta_header.variable_list() mlen = len(mlist) flist = fort_header.variable_list() flen = len(flist) # Remove array references from mlist before checking lengths for mvar in mlist: if is_arrayspec(mvar.get_prop_value('local_name')): mlen -= 1 # end if # end for list_match = mlen == flen # Check for optional Fortran variables that are not in metadata if flen > mlen: for find, fvar in enumerate(flist): lname = fvar.get_prop_value('local_name') _, mind = find_var_in_list(lname, mlist) if mind < 0: if fvar.get_prop_value('optional'): # This is an optional variable flen -= 1 # end if # end if # end for list_match = mlen == flen # end if if not list_match: if fht in _EXTRA_VARIABLE_TABLE_TYPES: if flen > mlen: list_match = True else: etype = 'Fortran {}'.format(fht) # end if elif flen > mlen: etype = 'metadata header' else: etype = 'Fortran {}'.format(fht) # end if # end if if not list_match: errmsg = 'Variable mismatch in {}, variables missing from {}.' errors_found = add_error(errors_found, errmsg.format(title, etype)) # end if for mind, mvar in enumerate(mlist): lname = mvar.get_prop_value('local_name') arrayref = is_arrayspec(lname) fvar, find = find_var_in_list(lname, flist) if mind >= flen: if arrayref: # Array reference, variable not in Fortran table pass elif fvar is None: errmsg = 'No Fortran variable for {} in {}' errors_found = add_error(errors_found, errmsg.format(lname, title)) # end if (no else, we already reported an out-of-place error # Do not break to collect all missing variables continue # end if # At this point, we should have a Fortran variable if (not arrayref) and (fvar is None): errmsg = 'Variable mismatch in {}, no Fortran variable {}.' errors_found = add_error(errors_found, errmsg.format(title, lname)) continue # end if # Check order dependence if fht in _ORDERED_TABLE_TYPES: if find != mind: errmsg = 'Out of order argument, {} in {}' errors_found = add_error(errors_found, errmsg.format(lname, title)) continue # end if # end if if arrayref: # Array reference, do not look for this in Fortran table continue # end if errs = var_comp('local_name', mvar, fvar, title) if errs: errors_found = add_error(errors_found, errs) else: errs = var_comp('type', mvar, fvar, title) if errs: errors_found = add_error(errors_found, errs) # end if errs = var_comp('kind', mvar, fvar, title) if errs: errors_found = add_error(errors_found, errs) # end if if meta_header.header_type == SCHEME_HEADER_TYPE: errs = var_comp('intent', mvar, fvar, title) if errs: errors_found = add_error(errors_found, errs) # end if # end if # Compare dimensions errs = dims_comp(meta_header, mvar, fvar, title, logger) if errs: errors_found = add_error(errors_found, errs) # end if # end if # end for # end if return errors_found
def add_constituent_vars(cap, host_model, suite_list, run_env): ############################################################################### """Create a DDT library containing array reference variables for each constituent field for all suites in <suite_list>. Create and return a dictionary containing an index variable for each of the constituents as well as the variables from the DDT object. Also, write declarations for these variables to <cap>. Since the constituents are in a DDT (ccpp_constituent_properties_t), create a metadata table with the required information, then parse it to create the dictionary. """ # First create a MetadataTable for the constituents DDT stdname_layer = "ccpp_constituents_num_layer_consts" stdname_interface = "ccpp_constituents_num_interface_consts" stdname_2d = "ccpp_constituents_num_2d_consts" horiz_dim = "horizontal_dimension" vert_layer_dim = "vertical_layer_dimension" vert_interface_dim = "vertical_interface_dimension" array_layer = "vars_layer" array_interface = "vars_interface" array_2d = "vars_2d" # Table preamble (leave off ccpp-table-properties header) ddt_mdata = [ #"[ccpp-table-properties]", " name = {}".format(CONST_DDT_NAME), " type = ddt", "[ccpp-arg-table]", " name = {}".format(CONST_DDT_NAME), " type = ddt", "[ num_layer_vars ]", " standard_name = {}".format(stdname_layer), " units = count", " dimensions = ()", " type = integer", "[ num_interface_vars ]", " standard_name = {}".format(stdname_interface), " units = count", " dimensions = ()", " type = integer", "[ num_2d_vars ]", " standard_name = {}".format(stdname_2d), " units = count", " dimensions = ()", " type = integer", "[ {} ]".format(array_layer), " standard_name = ccpp_constituents_array_of_layer_consts", " units = none", " dimensions = ({}, {}, {})".format(horiz_dim, vert_layer_dim, stdname_layer), " type = real", " kind = kind_phys", "[ {} ]".format(array_interface), " standard_name = ccpp_constituents_array_of_interface_consts", " units = none", " dimensions = ({}, {}, {})".format(horiz_dim, vert_interface_dim, stdname_interface), " type = real", " kind = kind_phys", "[ {} ]".format(array_2d), " standard_name = ccpp_constituents_array_of_2d_consts", " units = none", " dimensions = ({}, {})".format(horiz_dim, stdname_2d), " type = real", " kind = kind_phys"] # Add entries for each constituent (once per standard name) const_stdnames = set() for suite in suite_list: if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): lmsg = "Adding constituents from {} to {}" run_env.logger.debug(lmsg.format(suite.name, host_model.name)) # end if scdict = suite.constituent_dictionary() for cvar in scdict.variable_list(): std_name = cvar.get_prop_value('standard_name') if std_name not in const_stdnames: # Add a metadata entry for this constituent # Check dimensions and figure vertical dimension # Currently, we only support variables with first dimension, # horizontal_dimension, and second (optional) dimension, # vertical_layer_dimension or vertical_interface_dimension dims = cvar.get_dimensions() if (len(dims) < 1) or (len(dims) > 2): emsg = "Unsupported constituent dimensions, '{}'" dimstr = "({})".format(", ".join(dims)) raise CCPPError(emsg.format(dimstr)) # end if hdim = dims[0].split(':')[-1] if hdim != 'horizontal_dimension': emsg = "Unsupported first constituent dimension, '{}', " emsg += "must be 'horizontal_dimension'" raise CCPPError(emsg.format(hdim)) # end if if len(dims) > 1: vdim = dims[1].split(':')[-1] if vdim == vert_layer_dim: cvar_array_name = array_layer elif vdim == vert_interface_dim: cvar_array_name = array_interface else: emsg = "Unsupported vertical constituent dimension, " emsg += "'{}', must be '{}' or '{}'" raise CCPPError(emsg.format(vdim, vert_layer_dim, vert_interface_dim)) # end if else: cvar_array_name = array_2d # end if # First, create an index variable for <cvar> ind_std_name = "index_of_{}".format(std_name) loc_name = "{}(:,:,{})".format(cvar_array_name, ind_std_name) ddt_mdata.append("[ {} ]".format(loc_name)) ddt_mdata.append(" standard_name = {}".format(std_name)) units = cvar.get_prop_value('units') ddt_mdata.append(" units = {}".format(units)) dimstr = "({})".format(", ".join(dims)) ddt_mdata.append(" dimensions = {}".format(dimstr)) vtype = cvar.get_prop_value('type') vkind = cvar.get_prop_value('kind') ddt_mdata.append(" type = {} | kind = {}".format(vtype, vkind)) const_stdnames.add(std_name) # end if # end for # end for # Parse this table using a fake filename parse_obj = ParseObject("{}_constituent_mod.meta".format(host_model.name), ddt_mdata) ddt_table = MetadataTable(run_env, parse_object=parse_obj) ddt_name = ddt_table.sections()[0].title ddt_lib = DDTLibrary('{}_constituent_ddtlib'.format(host_model.name), run_env, ddts=ddt_table.sections()) # A bit of cleanup del parse_obj del ddt_mdata # Now, create the "host constituent module" dictionary const_dict = VarDictionary("{}_constituents".format(host_model.name), run_env, parent_dict=host_model) # Add in the constituents object prop_dict = {'standard_name' : "ccpp_model_constituents_object", 'local_name' : constituent_model_object_name(host_model), 'dimensions' : '()', 'units' : "None", 'ddt_type' : ddt_name} const_var = Var(prop_dict, _API_SOURCE, run_env) const_var.write_def(cap, 1, const_dict) ddt_lib.collect_ddt_fields(const_dict, const_var, run_env) # Declare variable for the constituent standard names array max_csname = max([len(x) for x in const_stdnames]) if const_stdnames else 0 num_const_fields = len(const_stdnames) cs_stdname = constituent_model_const_stdnames(host_model) const_list = sorted(const_stdnames) if const_list: const_strs = ['"{}{}"'.format(x, ' '*(max_csname - len(x))) for x in const_list] cs_stdame_initstr = " = (/ " + ", ".join(const_strs) + " /)" else: cs_stdame_initstr = "" # end if cap.write("character(len={}) :: {}({}){}".format(max_csname, cs_stdname, num_const_fields, cs_stdame_initstr), 1) # Declare variable for the constituent standard names array array_name = constituent_model_const_indices(host_model) cap.write("integer :: {}({}) = -1".format(array_name, num_const_fields), 1) # Add individual variables for each index var to the const_dict for index, std_name in enumerate(const_list): ind_std_name = "index_of_{}".format(std_name) ind_loc_name = "{}({})".format(array_name, index + 1) prop_dict = {'standard_name' : ind_std_name, 'local_name' : ind_loc_name, 'dimensions' : '()', 'units' : 'index', 'protected' : "True", 'type' : 'integer', 'kind' : ''} ind_var = Var(prop_dict, _API_SOURCE, run_env) const_dict.add_variable(ind_var, run_env) # end for # Add vertical dimensions for DDT call strings pver = host_model.find_variable(standard_name=vert_layer_dim, any_scope=False) if pver is not None: prop_dict = {'standard_name' : vert_layer_dim, 'local_name' : pver.get_prop_value('local_name'), 'units' : 'count', 'type' : 'integer', 'protected' : 'True', 'dimensions' : '()'} if const_dict.find_variable(standard_name=vert_layer_dim, any_scope=False) is None: ind_var = Var(prop_dict, _API_SOURCE, _API_DUMMY_RUN_ENV) const_dict.add_variable(ind_var, run_env) # end if # end if pver = host_model.find_variable(standard_name=vert_interface_dim, any_scope=False) if pver is not None: prop_dict = {'standard_name' : vert_interface_dim, 'local_name' : pver.get_prop_value('local_name'), 'units' : 'count', 'type' : 'integer', 'protected' : 'True', 'dimensions' : '()'} if const_dict.find_variable(standard_name=vert_interface_dim, any_scope=False) is None: ind_var = Var(prop_dict, _API_SOURCE, run_env) const_dict.add_variable(ind_var, run_env) # end if # end if return const_dict
def parse_module(pobj, statements, run_env): """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 run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): ctx = context_string(pobj, nodir=True) msg = "Parsing Fortran module, {}{}" run_env.logger.debug(msg.format(mod_name, ctx)) # End if # After the module name is the specification part statements, mtables = parse_specification(pobj, statements[1:], run_env, mod_name=mod_name) # 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, run_env) 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 run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): mtype = mheader.table_type ctx = mheader.start_context() msg = "Adding metadata from {}, {}{}" run_env.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 parse_specification(pobj, statements, run_env, mod_name=None, prog_name=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 run_env.logger is not None: ctx = context_string(pobj, nodir=True) msg = "Parsing specification of {}{}" run_env.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, run_env) 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 run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): ctx = tbl.start_context() mtype = tbl.table_type msg = "Adding metadata from {}, {}{}" run_env.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_scheme_metadata(statements, pobj, spec_name, table_name, run_env): "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 run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): ctx = context_string(pobj, nodir=True) msg = "Parsing specification of {}{}" run_env.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, run_env)) and is_dummy_argument_statement(statement)): dvars = parse_fortran_var_decl(statement, psrc, run_env) 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, run_env, variables=vdict) if (scheme_name is not None) and (var_dict is not None): mheader = MetadataTable(run_env, table_name_in=scheme_name, table_type_in='scheme', module=spec_name, var_dict=var_dict) # End if return statements, mheader
def __init__(self, meta_tables, name_in, run_env): """Initialize this HostModel object. <meta_tables> is a list of parsed host metadata tables. <name_in> is the name for this host model. <run_env> is the CCPPFrameworkEnv object for this framework run. """ 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 self.__run_env = run_env # 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().__init__(self.name, run_env) self.__ddt_lib = DDTLibrary( '{}_ddts'.format(self.name), run_env, ddts=[d for d in meta_headers if d.header_type == 'ddt']) self.__ddt_dict = VarDictionary("{}_ddt_vars".format(self.name), run_env, parent_dict=self) # 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 run_env.logger is not None: msg = 'Adding {} {} to host model' run_env.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, run_env) 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, run_env) # 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, run_env) self.ddt_lib.check_ddt_type(var, header) if var.is_ddt(): self.ddt_lib.collect_ddt_fields( self.__ddt_dict, var, run_env) # 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, run_env) # 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, run_env) 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