From 572ef3ebad57654d608ceb428a6fe71e09f9adf3 Mon Sep 17 00:00:00 2001 From: Steve Goldhaber Date: Tue, 13 Jul 2021 20:48:52 -0600 Subject: [PATCH 1/2] Changed ddt_type metadata keyword to just type. Added infrastructure and a method to find the intrinsic elements of a compound array or DDT --- scripts/ccpp_suite.py | 312 +++++++++++++++--- scripts/ddt_library.py | 49 ++- scripts/fortran_tools/__init__.py | 3 +- scripts/metadata_table.py | 5 +- scripts/metavar.py | 105 +++++- scripts/parse_tools/__init__.py | 10 +- scripts/parse_tools/parse_checkers.py | 9 +- scripts/parse_tools/parse_object.py | 6 +- test/advection_test/test_host.F90 | 4 +- test/advection_test/test_host_mod.meta | 2 +- test/capgen_test/make_ddt.meta | 6 +- test/capgen_test/test_host.F90 | 39 +-- test/capgen_test/test_host_mod.meta | 2 +- .../test_multi_ccpp_arg_tables.meta | 4 +- .../sample_files/test_unknown_ddt_type.meta | 2 +- test/unit_tests/test_metadata_table.py | 42 +-- 16 files changed, 459 insertions(+), 141 deletions(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 5c21e1c2..a84cf037 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -829,10 +829,6 @@ def match_variable(self, var, vstdname=None, vdims=None): dict_var = self.find_variable(source_var=var, any_scope=True) if dict_var is None: # No existing variable but add loop var match to call tree -# XXgoldyXX: v debug only - if vstdname == 'horizontal_loop_begin': - raise ValueError("{}".format(vmatch)) -# XXgoldyXX: ^ debug only found_var = self.parent.add_variable_to_call_tree(dict_var, vmatch=vmatch) new_vdims = vdims @@ -2506,14 +2502,6 @@ def write(self, output_dir, logger): out_file_name = suite.write(output_dir, logger) api_filenames.append(out_file_name) # end for -# XXgoldyXX: v needed? -# # Write out the constituent file -# cmod_name = self.host_model.constituent_module -# const_file = ConstituentVarDict.write_constituent_module(output_dir, -# cmod_name, -# self.suites) -# api_filenames.append(const_file) -# XXgoldyXX: ^ needed? return api_filenames @classmethod @@ -2530,12 +2518,23 @@ def get_errinfo_names(self): return (errmsg_name, errflg_name) @staticmethod - def write_var_set_loop(ofile, varlist_name, var_list, indent): - """Write code to allocate and set to """ - ofile.write("allocate({}({}))".format(varlist_name, len(var_list)), - indent) + def write_var_set_loop(ofile, varlist_name, var_list, indent, + add_allocate=True, start_index=1, start_var=None): + """Write code to allocate (if is True) and set + to . Elements of are set + beginning at . + """ + if add_allocate: + ofile.write("allocate({}({}))".format(varlist_name, len(var_list)), + indent) + # end if for ind, var in enumerate(var_list): - ofile.write("{}({}) = '{}'".format(varlist_name, ind+1, var), + if start_var: + ind_str = "{} + {}".format(start_var, ind + start_index) + else: + ind_str = "{}".format(ind + start_index) + # end if + ofile.write("{}({}) = '{}'".format(varlist_name, ind_str, var), indent) # end for @@ -2581,7 +2580,7 @@ def write_inspection_routines(self, ofile): ofile.write("end subroutine {}".format(API.__part_fname), 1) # Write out the suite required variable subroutine oline = "suite_name, variable_list, {errmsg}, {errflg}" - oline += ", input_vars_in, output_vars_in" + oline += ", input_vars, output_vars, struct_elements" inargs = oline.format(errmsg=errmsg_name, errflg=errflg_name) ofile.write("\nsubroutine {}({})".format(API.__vars_fname, inargs), 1) ofile.write("! Dummy arguments", 2) @@ -2589,36 +2588,47 @@ def write_inspection_routines(self, ofile): ofile.write(oline, 2) oline = "character(len=*), allocatable, intent(out) :: variable_list(:)" ofile.write(oline, 2) - self._errmsg_var.write_def(ofile, 2, self) - self._errflg_var.write_def(ofile, 2, self) - oline = "logical, optional, intent(in) :: input_vars_in" + self._errmsg_var.write_def(ofile, 2, self, extra_space=22) + self._errflg_var.write_def(ofile, 2, self, extra_space=22) + oline = "logical, optional, intent(in) :: input_vars" ofile.write(oline, 2) - oline = "logical, optional, intent(in) :: output_vars_in" + oline = "logical, optional, intent(in) :: output_vars" + ofile.write(oline, 2) + oline = "logical, optional, intent(in) :: struct_elements" ofile.write(oline, 2) ofile.write("! Local variables", 2) - ofile.write("logical {}:: input_vars".format(' '*34), 2) - ofile.write("logical {}:: output_vars".format(' '*34), 2) - ofile.write("\n", 0) + ofile.write("logical {}:: input_vars_use".format(' '*34), 2) + ofile.write("logical {}:: output_vars_use".format(' '*34), 2) + ofile.write("logical {}:: struct_elements_use".format(' '*34), 2) + ofile.write("integer {}:: num_vars".format(' '*34), 2) + ofile.write("", 0) ename = self._errflg_var.get_prop_value('local_name') ofile.write("{} = 0".format(ename), 2) ename = self._errmsg_var.get_prop_value('local_name') ofile.write("{} = ''".format(ename), 2) - ofile.write("if (present(input_vars_in)) then", 2) - ofile.write("input_vars = input_vars_in", 3) + ofile.write("if (present(input_vars)) then", 2) + ofile.write("input_vars_use = input_vars", 3) + ofile.write("else", 2) + ofile.write("input_vars_use = .true.", 3) + ofile.write("end if", 2) + ofile.write("if (present(output_vars)) then", 2) + ofile.write("output_vars_use = output_vars", 3) ofile.write("else", 2) - ofile.write("input_vars = .true.", 3) + ofile.write("output_vars_use = .true.", 3) ofile.write("end if", 2) - ofile.write("if (present(output_vars_in)) then", 2) - ofile.write("output_vars = output_vars_in", 3) + ofile.write("if (present(struct_elements)) then", 2) + ofile.write("struct_elements_use = struct_elements", 3) ofile.write("else", 2) - ofile.write("output_vars = .true.", 3) + ofile.write("struct_elements_use = .true.", 3) ofile.write("end if", 2) else_str = '' for suite in self.suites: parent = suite.parent # Collect all the suite variables oline = "{}if(trim(suite_name) == '{}') then" - suite_vars = [set(), set()] # input, output + input_vars = [set(), set(), set()] # leaves, arrrays, leaf elements + inout_vars = [set(), set(), set()] # leaves, arrrays, leaf elements + output_vars = [set(), set(), set()] # leaves, arrrays, leaf elements for part in suite.groups: for var in part.call_list.variable_list(): stdname = var.get_prop_value("standard_name") @@ -2630,27 +2640,235 @@ def write_inspection_routines(self, ofile): protected = pvar.get_prop_value("protected") # end if # end if - if (intent in ['in', 'inout']) and (not protected): - suite_vars[0].add(stdname) - # end if - if intent in ['inout', 'out']: - suite_vars[1].add(stdname) + elements = var.intrinsic_elements(check_dict=self.parent) + if (intent == 'in') and (not protected): + if isinstance(elements, list): + input_vars[1].add(stdname) + input_vars[2].update(elements) + else: + input_vars[0].add(stdname) + # end if + elif intent == 'inout': + if isinstance(elements, list): + inout_vars[1].add(stdname) + inout_vars[2].update(elements) + else: + inout_vars[0].add(stdname) + # end if + elif intent == 'out': + if isinstance(elements, list): + output_vars[1].add(stdname) + output_vars[2].update(elements) + else: + output_vars[0].add(stdname) + # end if # end if # end for # end for + # Figure out how many total variables to return and allocate + # variable_list to that size ofile.write(oline.format(else_str, suite.name), 2) - ofile.write("if (input_vars .and. output_vars) then", 3) - var_list = list(suite_vars[0].union(suite_vars[1])) - API.write_var_set_loop(ofile, 'variable_list', var_list, 4) - ofile.write("else if (input_vars) then", 3) - var_list = list(suite_vars[0]) - API.write_var_set_loop(ofile, 'variable_list', var_list, 4) - ofile.write("else if (output_vars) then", 3) - var_list = list(suite_vars[1]) - API.write_var_set_loop(ofile, 'variable_list', var_list, 4) + ofile.write("if (input_vars_use .and. output_vars_use) then", 3) + have_elems = input_vars[2] or inout_vars[2] or output_vars[2] + if have_elems: + ofile.write("if (struct_elements_use) then", 4) + numvars = len(input_vars[0] | input_vars[2] | inout_vars[0] | + inout_vars[2] | output_vars[0] | output_vars[2]) + ofile.write("num_vars = {}".format(numvars), 5) + ofile.write("else", 4) + # end if + numvars = len(input_vars[0] | input_vars[1] | inout_vars[0] | + inout_vars[1] | output_vars[0] | output_vars[1]) + ofile.write("num_vars = {}".format(numvars), 5 if have_elems else 4) + if have_elems: + ofile.write("end if", 4) + # end if + ofile.write("else if (input_vars_use) then", 3) + have_elems = input_vars[2] or inout_vars[2] + if have_elems: + ofile.write("if (struct_elements_use) then", 4) + numvars = len(input_vars[0] | input_vars[2] | + inout_vars[0] | inout_vars[2]) + ofile.write("num_vars = {}".format(numvars), 5) + ofile.write("else", 4) + # end if + numvars = len(input_vars[0] | input_vars[1] | + inout_vars[0] | inout_vars[1]) + ofile.write("num_vars = {}".format(numvars), 5 if have_elems else 4) + if have_elems: + ofile.write("end if", 4) + # end if + ofile.write("else if (output_vars_use) then", 3) + have_elems = inout_vars[2] or output_vars[2] + if have_elems: + ofile.write("if (struct_elements_use) then", 4) + numvars = len(inout_vars[0] | inout_vars[2] | + output_vars[0] | output_vars[2]) + ofile.write("num_vars = {}".format(numvars), 5) + ofile.write("else", 4) + # end if + numvars = len(inout_vars[0] | inout_vars[1] | + output_vars[0] | output_vars[1]) + ofile.write("num_vars = {}".format(numvars), 5 if have_elems else 4) + if have_elems: + ofile.write("end if", 4) + # end if ofile.write("else", 3) - ofile.write("allocate(variable_list(0))\n", 4) + ofile.write("num_vars = 0", 4) ofile.write("end if", 3) + ofile.write("allocate(variable_list(num_vars))", 3) + # Now, fill in the variable_list array + # Start with inout variables + elem_start = 1 + leaf_start = 1 + leaf_written_set = inout_vars[0].copy() + elem_written_set = inout_vars[0].copy() + leaf_list = sorted(inout_vars[0]) + if inout_vars[0] or inout_vars[1] or inout_vars[2]: + ofile.write("if (input_vars_use .or. output_vars_use) then", 3) + API.write_var_set_loop(ofile, 'variable_list', leaf_list, 4, + add_allocate=False, + start_index=leaf_start) + # end if + leaf_start += len(leaf_list) + elem_start += len(leaf_list) + # elements which have not been written out + elem_list = sorted(inout_vars[2] - elem_written_set) + elem_written_set = elem_written_set | inout_vars[2] + leaf_list = sorted(inout_vars[1] - leaf_written_set) + leaf_written_set = leaf_written_set | inout_vars[1] + if elem_list or leaf_list: + ofile.write("if (struct_elements_use) then", 4) + API.write_var_set_loop(ofile, 'variable_list', elem_list, 5, + add_allocate=False, + start_index=elem_start) + elem_start += len(elem_list) + ofile.write("num_vars = {}".format(elem_start - 1), 5) + ofile.write("else", 4) + API.write_var_set_loop(ofile, 'variable_list', leaf_list, 5, + add_allocate=False, + start_index=leaf_start) + leaf_start += len(leaf_list) + ofile.write("num_vars = {}".format(leaf_start - 1), 5) + ofile.write("end if", 4) + else: + ofile.write("num_vars = {}".format(len(leaf_written_set)), + 4 if leaf_written_set else 3) + # end if + if inout_vars[0] or inout_vars[1] or inout_vars[2]: + ofile.write("end if", 3) + # end if + # Write input variables + leaf_list = sorted(input_vars[0] - leaf_written_set) + # Are there any output variables which are also input variables + # (e.g., for a different part (group) of the suite)? + # We need to collect them now in case is selected + # but not . + leaf_cross_set = output_vars[0] & input_vars[0] + simp_cross_set = (output_vars[1] & input_vars[1]) - leaf_cross_set + elem_cross_set = (output_vars[2] & input_vars[2]) - leaf_cross_set + # Subtract the variables which have already been written out + leaf_cross_list = sorted(leaf_cross_set - leaf_written_set) + simp_cross_list = sorted(simp_cross_set - leaf_written_set) + elem_cross_list = sorted(elem_cross_set - elem_written_set) + # Next move back to processing the input variables + leaf_written_set = leaf_written_set | input_vars[0] + elem_list = sorted(input_vars[2] - elem_written_set) + elem_written_set = elem_written_set | input_vars[0] | input_vars[2] + have_inputs = elem_list or leaf_list + if have_inputs: + ofile.write("if (input_vars_use) then", 3) + # elements which have not been written out + # end if + API.write_var_set_loop(ofile, 'variable_list', leaf_list, 4, + add_allocate=False, start_var="num_vars", + start_index=1) + if leaf_list: + ofile.write("num_vars = num_vars + {}".format(len(leaf_list)), + 4) + # end if + leaf_start += len(leaf_list) + elem_start += len(leaf_list) + leaf_list = input_vars[1].difference(leaf_written_set) + leaf_written_set.union(input_vars[1]) + if elem_list or leaf_list: + ofile.write("if (struct_elements_use) then", 4) + API.write_var_set_loop(ofile, 'variable_list', elem_list, 5, + add_allocate=False, + start_index=elem_start) + elem_start += len(elem_list) - 1 + ofile.write("num_vars = {}".format(elem_start), 5) + ofile.write("else", 4) + API.write_var_set_loop(ofile, 'variable_list', leaf_list, 5, + add_allocate=False, + start_index=leaf_start) + leaf_start += len(leaf_list) - 1 + ofile.write("num_vars = {}".format(leaf_start), 5) + ofile.write("end if", 4) + # end if + if have_inputs: + ofile.write("end if", 3) + # end if + # Write output variables + leaf_list = sorted(output_vars[0].difference(leaf_written_set)) + leaf_written_set = leaf_written_set.union(output_vars[0]) + elem_written_set = elem_written_set.union(output_vars[0]) + elem_list = sorted(output_vars[2].difference(elem_written_set)) + elem_written_set = elem_written_set.union(output_vars[2]) + have_outputs = elem_list or leaf_list + if have_outputs: + ofile.write("if (output_vars_use) then", 3) + # end if + leaf_start = 1 + API.write_var_set_loop(ofile, 'variable_list', leaf_list, 4, + add_allocate=False, start_var="num_vars", + start_index=leaf_start) + leaf_start += len(leaf_list) + elem_start = leaf_start + leaf_list = output_vars[1].difference(leaf_written_set) + leaf_written_set.union(output_vars[1]) + if elem_list or leaf_list: + ofile.write("if (struct_elements_use) then", 4) + API.write_var_set_loop(ofile, 'variable_list', elem_list, 5, + add_allocate=False, start_var="num_vars", + start_index=elem_start) + elem_start += len(elem_list) + ofile.write("else", 4) + API.write_var_set_loop(ofile, 'variable_list', leaf_list, 5, + add_allocate=False, start_var="num_vars", + start_index=leaf_start) + leaf_start += len(leaf_list) + ofile.write("end if", 4) + # end if + if leaf_cross_list or elem_cross_list: + ofile.write("if (.not. input_vars_use) then", 4) + API.write_var_set_loop(ofile, 'variable_list', leaf_cross_list, + 5, add_allocate=False, + start_var="num_vars", + start_index=leaf_start) + leaf_start += len(leaf_cross_list) + elem_start += len(leaf_cross_list) + if elem_cross_list or simp_cross_list: + ofile.write("if (struct_elements_use) then", 5) + API.write_var_set_loop(ofile, 'variable_list', + elem_cross_list, 6, + add_allocate=False, + start_var="num_vars", + start_index=elem_start) + elem_start += len(elem_list) + ofile.write("else", 5) + API.write_var_set_loop(ofile, 'variable_list', + leaf_cross_list, 6, + add_allocate=False, + start_var="num_vars", + start_index=leaf_start) + leaf_start += len(leaf_list) + ofile.write("end if", 5) + # end if + ofile.write("end if", 4) + if have_outputs: + ofile.write("end if", 3) + # end if else_str = 'else ' # end for ofile.write("else", 2) diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 2fa7899d..a569e1f8 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -19,27 +19,31 @@ class VarDDT(Var): """A class to store a variable that is a component of a DDT (at any DDT nesting level). - is the DDT component. - is a Var or VarDDT whose root originates in a model - dictionary. - The structure of the VarDDT object is: - The super class Var object is a copy of the model root Var. - The self._var_ref object is a VarDDT containing the top-level - field that leads to this component. - Thus, (a Var) ends up at the end of a VarDDT chain. """ def __init__(self, new_field, var_ref, logger=None, recur=False): - self._field = None + """Initialize a new VarDDT object. + is the DDT component. + is a Var or VarDDT whose root originates in a model + dictionary. + The structure of the VarDDT object is: + The super class Var object is a copy of the model root Var. + The (a Var) ends up at the end of a VarDDT chain. + """ + self.__field = None # Grab the info from the root of source = var_ref.source super(VarDDT, self).__init__(var_ref, source, context=source.context, logger=logger) # Find the correct place for if isinstance(var_ref, Var): - self._field = new_field + # We are at a top level DDT var, set our field + self.__field = new_field else: - self._field = VarDDT(new_field, var_ref.field, + # Recurse to find correct (tail) location for + self.__field = VarDDT(new_field, var_ref.field, logger=logger, recur=True) # End if if (not recur) and (logger is not None): @@ -47,7 +51,7 @@ def __init__(self, new_field, var_ref, logger=None, recur=False): # End if def is_ddt(self): - '''Return True iff is a DDT type.''' + """Return True iff is a DDT type.""" return True def get_parent_prop(self, name): @@ -65,6 +69,17 @@ def get_prop_value(self, name): # End if return pvalue + def intrinsic_elements(self, check_dict=None): + """Return the Var intrinsic elements for the leaf Var object. + See Var.intrinsic_elem for details + """ + if self.field is None: + pvalue = super(VarDDT, self).intrinsic_elements(check_dict=check_dict) + else: + pvalue = self.field.intrinsic_elements(check_dict=check_dict) + # End if + return pvalue + def clone(self, subst_dict, source_name=None, source_type=None, context=None): """Create a clone of this VarDDT object's leaf Var with properties @@ -100,9 +115,9 @@ def call_string(self, var_dict, loop_vars=None): return call_str def write_def(self, outfile, indent, ddict, allocatable=False, dummy=False): - '''Write the definition line for this DDT. + """Write the definition line for this DDT. The type of this declaration is the type of the Var at the - end of the chain of references.''' + end of the chain of references.""" if self.field is None: super(VarDDT, self).write_def(outfile, indent, ddict, allocatable=allocatable, dummy=dummy) @@ -135,7 +150,7 @@ def __var_rep(var, prefix=""): return lstr def __repr__(self): - '''Print representation for VarDDT objects''' + """Print representation for VarDDT objects""" # Note, recursion would be messy because of formatting issues lstr = "" sep = "" @@ -153,7 +168,7 @@ def __repr__(self): return "".format(lstr) def __str__(self): - '''Print string for VarDDT objects''' + """Print string for VarDDT objects""" return self.__repr__() @property @@ -164,7 +179,7 @@ def var(self): @property def field(self): "Return this objects field object, or None" - return self._field + return self.__field ############################################################################### class DDTLibrary(dict): diff --git a/scripts/fortran_tools/__init__.py b/scripts/fortran_tools/__init__.py index 8ac08f29..904581b4 100644 --- a/scripts/fortran_tools/__init__.py +++ b/scripts/fortran_tools/__init__.py @@ -1,7 +1,8 @@ """Public API for the fortran_parser library """ -import os +from __future__ import absolute_import import sys +import os.path sys.path.insert(0, os.path.dirname(__file__)) # pylint: disable=wrong-import-position diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index d6955872..320c7816 100644 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -132,6 +132,7 @@ from parse_tools import ParseInternalError, ParseSyntaxError, CCPPError from parse_tools import FORTRAN_ID, FORTRAN_SCALAR_REF, FORTRAN_SCALAR_REF_RE from parse_tools import check_fortran_ref, check_fortran_id +from parse_tools import check_fortran_intrinsic from parse_tools import register_fortran_ddt_name, unique_standard_name ######################################################################## @@ -843,9 +844,11 @@ def parse_variable(self, curr_line, known_ddts): for prop in properties: pname = prop[0].strip().lower() pval_str = prop[1].strip() - if pname == 'ddt_type': + if ((pname == 'type') and + (not check_fortran_intrinsic(pval_str, error=False))): if pval_str in known_ddts: pval = pval_str + pname = 'ddt_type' else: errmsg = "Unknown DDT type, {}".format(pval_str) self.__pobj.add_syntax_err(errmsg) diff --git a/scripts/metavar.py b/scripts/metavar.py index 20df279c..175a03f7 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -567,14 +567,19 @@ class Var(object): # All constituent props are optional so no check def __init__(self, prop_dict, source, context=None, - invalid_ok=False, logger=None): + invalid_ok=False, logger=None, clone_source=None): """Initialize a new Var object. - 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 is really a Var object, use that object's prop_dict.""" - self._parent_var = None # for array references + NB: =True is dangerous because it allows creation + of a Var object with invalid properties. + In order to prevent silent failures, requires a logger + (passed through the input) in order to take effect. + If is really a Var object, use that object's prop_dict. + If this Var object is a clone, record the original Var object + for reference + """ + self.__parent_var = None # for array references + self.__children = list() # This Var's array references + self.__clone_source = clone_source if isinstance(prop_dict, Var): prop_dict = prop_dict.copy_prop_dict() # end if @@ -920,7 +925,7 @@ def clone(self, subst_dict=None, remove_intent=False, # end if psource = ParseSource(source_name, source_type, context) - return Var(cprop_dict, psource) + return Var(cprop_dict, psource, clone_source=self) def get_prop_value(self, name): """Return the value of key, if is in this variable's @@ -1186,6 +1191,56 @@ def array_ref(self, local_name=None): match = FORTRAN_SCALAR_REF_RE.match(local_name) return match + def intrinsic_elements(self, check_dict=None): + """Return a list of the standard names of this Var object's 'leaf' + intrinsic elements or this Var object's standard name if it is an + intrinsic 'leaf' variable. + If this Var object cannot be reduced to one or more intrinsic 'leaf' + variables (e.g., a DDT Var with no named elements), return None. + A 'leaf' intrinsic Var is a Var of intrinsic Fortran type which has + no children. If a Var has children, those children will be searched + to find leaves. If a Var is a DDT, its named elements are searched. + If is not None, it is checked for children if none are + found in this variable (via finding a variable in with + the same standard name). + Currently, an array of DDTs is not processeed (return None) since + Fortran does not support a way to reference those elements. + """ + if self.is_ddt(): + element_names = None + raise ValueError("shouldn't happen?") + # To Do, find and process named elements of DDT + else: + children = self.children() + if (not children) and check_dict: + stdname = self.get_prop_value("standard_name") + pvar = check_dict.find_variable(standard_name=stdname, + any_scope=True) + if pvar: + children = pvar.children() + # end if + # end if + if children: + element_names = list() + for child in children: + child_elements = child.intrinsic_elements() + if isinstance(child_elements, str): + child_elements = [child_elements] + # end if + if child_elements: + for elem in child_elements: + if elem: + element_names.append(elem) + # end if + # end for + # end if + # end for + else: + element_names = self.get_prop_value('standard_name') + # end if + # end if + return element_names + @classmethod def constituent_property_names(cls): """Return a list of the names of constituent properties""" @@ -1194,24 +1249,44 @@ def constituent_property_names(cls): @property def parent(self): """Return this variable's parent variable (or None)""" - return self._parent_var + return self.__parent_var @parent.setter def parent(self, parent_var): """Set this variable's parent if not already set""" - if self._parent_var is not None: + if self.__parent_var is not None: emsg = 'Attempting to set parent for {} but parent already set' lname = self.get_prop_value('local_name') raise ParseInternalError(emsg.format(lname)) # end if if isinstance(parent_var, Var): - self._parent_var = parent_var + self.__parent_var = parent_var + parent_var._add_child(self) else: emsg = 'Attempting to set parent for {}, bad parent type, {}' lname = self.get_prop_value('local_name') raise ParseInternalError(emsg.format(lname, type(parent_var))) # end if + def _add_child(self, cvar): + """Add as a child of this Var object""" + if cvar not in self.__children: + self.__children.append(cvar) + # end if + + def children(self): + """Return an iterator over this object's children or None if the + object has no children.""" + children = self.__children + if not children: + pvar = self + while (not children) and pvar.__clone_source: + pvar = pvar.__clone_source + children = pvar.children() + # end while + # end if + return iter(children) if children else None + @property def context(self): """Return this variable's parsed context""" @@ -1290,7 +1365,7 @@ def has_vertical_dimension(self, dims=None): return Var.find_vertical_dimension(vdims)[0] def write_def(self, outfile, indent, wdict, allocatable=False, - dummy=False, add_intent=None): + dummy=False, add_intent=None, extra_space=0): """Write the definition line for the variable to . If is True, include the variable's intent. If is True but the variable has no intent, add the @@ -1368,14 +1443,14 @@ def write_def(self, outfile, indent, wdict, allocatable=False, # end if if self.is_ddt(): dstr = "type({kind}){cspc}{intent} :: {name}{dims} ! {sname}" - cspc = comma + ' '*(13 - len(kind)) + cspc = comma + ' '*(extra_space + 13 - len(kind)) else: if kind: dstr = "{type}({kind}){cspc}{intent} :: {name}{dims} ! {sname}" - cspc = comma + ' '*(17 - len(vtype) - len(kind)) + cspc = comma + ' '*(extra_space + 17 - len(vtype) - len(kind)) else: dstr = "{type}{cspc}{intent} :: {name}{dims} ! {sname}" - cspc = comma + ' '*(19 - len(vtype)) + cspc = comma + ' '*(extra_space + 19 - len(vtype)) # end if # end if outfile.write(dstr.format(type=vtype, kind=kind, intent=intent_str, diff --git a/scripts/parse_tools/__init__.py b/scripts/parse_tools/__init__.py index 1cb24251..5c566c45 100644 --- a/scripts/parse_tools/__init__.py +++ b/scripts/parse_tools/__init__.py @@ -54,7 +54,7 @@ 'FORTRAN_ID', 'FORTRAN_SCALAR_REF', 'FORTRAN_SCALAR_REF_RE', - 'initLog', + 'init_log', 'ParseContext', 'ParseInternalError', 'ParseSource', @@ -65,10 +65,10 @@ 'read_xml_file', 'registered_fortran_ddt_name', 'reset_standard_name_counter', - 'setLogLevel', - 'setLogToFile', - 'setLogToNull', - 'setLogToStdout', + 'set_log_level', + 'set_log_to_file', + 'set_log_to_null', + 'set_log_to_stdout', 'unique_standard_name', 'validate_xml_file' ] diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index a46cc2eb..cf5a6607 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -471,11 +471,12 @@ def check_fortran_intrinsic(typestr, error=False): >>> check_fortran_intrinsic("complex(kind=r8)") """ - match = typestr.strip().lower() in FORTRAN_INTRINSIC_TYPES - if (not match) and (typestr.lower()[0:6] == 'double'): + chk_type = typestr.strip().lower() + match = chk_type in FORTRAN_INTRINSIC_TYPES + if (not match) and (chk_type[0:6] == 'double'): # Special case for double precision - match = FORTRAN_DP_RE.match(typestr.strip()) is not None - # end if + match = FORTRAN_DP_RE.match(chk_type) is not None + # End if if not match: if error: raise CCPPError("'{}' is not a valid Fortran type".format(typestr)) diff --git a/scripts/parse_tools/parse_object.py b/scripts/parse_tools/parse_object.py index de74ca86..6f13dd64 100644 --- a/scripts/parse_tools/parse_object.py +++ b/scripts/parse_tools/parse_object.py @@ -55,6 +55,10 @@ def last_line_num(self): """Return the last line parsed""" return self.__line_end + def valid_line(self): + """Return True if the current line is valid""" + return (self.line_num >= 0) and (self.line_num < self.__num_lines) + @property def file_name(self): """Return this object's filename""" @@ -68,7 +72,7 @@ def error_message(self): def curr_line(self): """Return the current line (if valid) and the current line number. If the current line is invalid, return None""" - valid_line = (self.line_num >= 0) and (self.line_num < self.__num_lines) + valid_line = self.valid_line() _curr_line = None _my_curr_lineno = self.line_num if valid_line: diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 7057d597..3e9add58 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -140,7 +140,7 @@ logical function check_suite(test_suite) end if ! Check the input variables call ccpp_physics_suite_variables(test_suite%suite_name, test_list, & - errmsg, errflg, input_vars_in=.true., output_vars_in=.false.) + errmsg, errflg, input_vars=.true., output_vars=.false.) if (errflg == 0) then check = check_list(test_list, test_suite%suite_input_vars, & 'input variable names', suite_name=test_suite%suite_name) @@ -154,7 +154,7 @@ logical function check_suite(test_suite) end if ! Check the output variables call ccpp_physics_suite_variables(test_suite%suite_name, test_list, & - errmsg, errflg, input_vars_in=.false., output_vars_in=.true.) + errmsg, errflg, input_vars=.false., output_vars=.true.) if (errflg == 0) then check = check_list(test_list, test_suite%suite_output_vars, & 'output variable names', suite_name=test_suite%suite_name) diff --git a/test/advection_test/test_host_mod.meta b/test/advection_test/test_host_mod.meta index 5dcd06c4..9f04a6fc 100644 --- a/test/advection_test/test_host_mod.meta +++ b/test/advection_test/test_host_mod.meta @@ -49,7 +49,7 @@ [ phys_state ] standard_name = physics_state_derived_type long_name = Physics State DDT - ddt_type = physics_state + type = physics_state dimensions = () [ num_model_times ] standard_name = number_of_model_times diff --git a/test/capgen_test/make_ddt.meta b/test/capgen_test/make_ddt.meta index 3f4d7452..2d1f766d 100644 --- a/test/capgen_test/make_ddt.meta +++ b/test/capgen_test/make_ddt.meta @@ -50,7 +50,7 @@ [ vmr ] standard_name = volume_mixing_ratio_ddt dimensions = () - ddt_type = vmr_type + type = vmr_type intent = inout [ errmsg ] standard_name = ccpp_error_message @@ -79,7 +79,7 @@ [ vmr ] standard_name = volume_mixing_ratio_ddt dimensions = () - ddt_type = vmr_type + type = vmr_type intent = out [ errmsg ] standard_name = ccpp_error_message @@ -108,7 +108,7 @@ [ vmr ] standard_name = volume_mixing_ratio_ddt dimensions = () - ddt_type = vmr_type + type = vmr_type intent = in [ errmsg ] standard_name = ccpp_error_message diff --git a/test/capgen_test/test_host.F90 b/test/capgen_test/test_host.F90 index dc3a01bb..3681ce8b 100644 --- a/test/capgen_test/test_host.F90 +++ b/test/capgen_test/test_host.F90 @@ -118,6 +118,7 @@ logical function check_suite(test_suite) character(len=128), allocatable :: test_list(:) check_suite = .true. + write(6, *) "Checking suite ", trim(test_suite%suite_name) ! First, check the suite parts call ccpp_physics_suite_part_list(test_suite%suite_name, test_list, & errmsg, errflg) @@ -134,7 +135,7 @@ logical function check_suite(test_suite) end if ! Check the input variables call ccpp_physics_suite_variables(test_suite%suite_name, test_list, & - errmsg, errflg, input_vars_in=.true., output_vars_in=.false.) + errmsg, errflg, input_vars=.true., output_vars=.false.) if (errflg == 0) then check = check_list(test_list, test_suite%suite_input_vars, & 'input variable names', suite_name=test_suite%suite_name) @@ -148,7 +149,7 @@ logical function check_suite(test_suite) end if ! Check the output variables call ccpp_physics_suite_variables(test_suite%suite_name, test_list, & - errmsg, errflg, input_vars_in=.false., output_vars_in=.true.) + errmsg, errflg, input_vars=.false., output_vars=.true.) if (errflg == 0) then check = check_list(test_list, test_suite%suite_output_vars, & 'output variable names', suite_name=test_suite%suite_name) @@ -354,44 +355,44 @@ program test character(len=cs), target :: test_parts1(1) = (/ 'physics ' /) character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) character(len=cm), target :: test_invars1(6) = (/ & - 'potential_temperature_increment ', & 'potential_temperature ', & - 'time_step_for_physics ', & 'potential_temperature_at_interface ', & 'surface_air_pressure ', & - 'water_vapor_specific_humidity ' /) + 'water_vapor_specific_humidity ', & + 'potential_temperature_increment ', & + 'time_step_for_physics ' /) character(len=cm), target :: test_outvars1(6) = (/ & - 'ccpp_error_message ', & - 'ccpp_error_flag ', & - 'potential_temperature_at_interface ', & 'potential_temperature ', & + 'potential_temperature_at_interface ', & 'surface_air_pressure ', & - 'water_vapor_specific_humidity ' /) + 'water_vapor_specific_humidity ', & + 'ccpp_error_flag ', & + 'ccpp_error_message ' /) character(len=cm), target :: test_reqvars1(8) = (/ & - 'potential_temperature_increment ', & 'potential_temperature ', & - 'time_step_for_physics ', & 'potential_temperature_at_interface ', & 'surface_air_pressure ', & 'water_vapor_specific_humidity ', & - 'ccpp_error_message ', & - 'ccpp_error_flag ' /) + 'potential_temperature_increment ', & + 'time_step_for_physics ', & + 'ccpp_error_flag ', & + 'ccpp_error_message ' /) character(len=cm), target :: test_invars2(3) = (/ & - 'surface_air_pressure ', & + 'model_times ', & 'number_of_model_times ', & - 'model_times ' /) + 'surface_air_pressure ' /) character(len=cm), target :: test_outvars2(4) = (/ & 'ccpp_error_flag ', & 'ccpp_error_message ', & - 'number_of_model_times ', & - 'model_times ' /) + 'model_times ', & + 'number_of_model_times ' /) character(len=cm), target :: test_reqvars2(5) = (/ & - 'surface_air_pressure ', & - 'number_of_model_times ', & 'model_times ', & + 'number_of_model_times ', & + 'surface_air_pressure ', & 'ccpp_error_flag ', & 'ccpp_error_message ' /) type(suite_info) :: test_suites(2) diff --git a/test/capgen_test/test_host_mod.meta b/test/capgen_test/test_host_mod.meta index 6759ff50..0d53f320 100644 --- a/test/capgen_test/test_host_mod.meta +++ b/test/capgen_test/test_host_mod.meta @@ -77,7 +77,7 @@ [ phys_state ] standard_name = physics_state_derived_type long_name = Physics State DDT - ddt_type = physics_state + type = physics_state dimensions = () [ num_model_times ] standard_name = number_of_model_times diff --git a/test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta b/test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta index 7f0a92bb..7d81172e 100644 --- a/test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta +++ b/test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta @@ -50,7 +50,7 @@ [ vmr ] standard_name = volume_mixing_ratio_ddt dimensions = () - ddt_type = vmr_type + type = vmr_type intent = inout [ errmsg ] standard_name = ccpp_error_message @@ -77,7 +77,7 @@ [ vmr ] standard_name = volume_mixing_ratio_ddt dimensions = () - ddt_type = vmr_type + type = vmr_type intent = out [ errmsg ] standard_name = ccpp_error_message diff --git a/test/unit_tests/sample_files/test_unknown_ddt_type.meta b/test/unit_tests/sample_files/test_unknown_ddt_type.meta index ef3f3e0d..e41eeefb 100644 --- a/test/unit_tests/sample_files/test_unknown_ddt_type.meta +++ b/test/unit_tests/sample_files/test_unknown_ddt_type.meta @@ -10,5 +10,5 @@ [ vmr ] standard_name = volume_mixing_ratio_ddt dimensions = () - ddt_type = banana + type = banana intent = inout diff --git a/test/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index 028633d7..30af3bcb 100644 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -82,7 +82,7 @@ def test_bad_type_name(self): #Exercise with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #Verify #print("The exception is", context.exception) @@ -95,7 +95,7 @@ def test_double_header(self): filename = os.path.join(SAMPLE_FILES_DIR, "double_header.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) self.assertTrue('table already contains \'test_host\'' in str(context.exception)) @@ -107,7 +107,7 @@ def test_bad_dimension(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_bad_dimension.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) self.assertTrue('Invalid \'dimensions\' property value, \'' in str(context.exception)) @@ -119,7 +119,7 @@ def test_duplicate_variable(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_duplicate_variable.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) self.assertTrue('Invalid (duplicate) standard name in temp_calc_adjust_run, defined at ' in str(context.exception)) @@ -131,7 +131,7 @@ def test_invalid_intent(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_invalid_intent.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) self.assertTrue('Invalid \'intent\' property value, \'banana\', at ' in str(context.exception)) @@ -143,7 +143,7 @@ def test_missing_intent(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_missing_intent.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Required property, 'intent', missing, at " @@ -156,7 +156,7 @@ def test_missing_units(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_missing_units.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Required property, 'units', missing, at" @@ -169,7 +169,7 @@ def test_missing_table_type(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_missing_table_type.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Invalid section type, 'None'" @@ -182,7 +182,7 @@ def test_bad_table_type(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_bad_table_type.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Section type, 'host', does not match table type, 'scheme'" @@ -195,7 +195,7 @@ def test_missing_table_name(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_missing_table_name.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Section name, 'None', does not match table title, 'test_missing_table_name'" @@ -208,7 +208,7 @@ def test_bad_table_key(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_bad_table_key.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Invalid metadata table start property, 'something', at " @@ -221,20 +221,20 @@ def test_bad_line_split(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_bad_line_split.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Invalid variable property syntax, \'\', at " self.assertTrue(emsg in str(context.exception)) def test_unknown_ddt_type(self): - """Test that a ddt_type = banana returns expected error""" + """Test that a DDT type = banana returns expected error""" known_ddts = list() logger = None filename = os.path.join(SAMPLE_FILES_DIR, "test_unknown_ddt_type.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Unknown DDT type, banana, at " @@ -247,7 +247,7 @@ def test_bad_var_property_name(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_bad_var_property_name.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Invalid variable property name, 'none', at " @@ -302,7 +302,7 @@ def test_bad_1st_ccpp_arg_table(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_bad_1st_arg_table_header.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Invalid variable property syntax, '[ccpp-farg-table]', at " @@ -315,7 +315,7 @@ def test_bad_2nd_ccpp_arg_table(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_bad_2nd_arg_table_header.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Invalid variable property syntax, '[ccpp-farg-table]', at " @@ -328,7 +328,7 @@ def test_mismatch_section_table_title(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_mismatch_section_table_title.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Section name, 'test_host', does not match table title, 'banana', at " @@ -341,7 +341,7 @@ def test_double_table_properties(self): filename = os.path.join(SAMPLE_FILES_DIR, "double_table_properties.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Duplicate metadata table, test_host, at " @@ -354,7 +354,7 @@ def test_missing_table_properties(self): filename = os.path.join(SAMPLE_FILES_DIR, "missing_table_properties.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Invalid CCPP metadata line, '[ccpp-arg-table]', at " @@ -389,7 +389,7 @@ def test_invalid_table_properties_type(self): filename = os.path.join(SAMPLE_FILES_DIR, "test_invalid_table_properties_type.meta") with self.assertRaises(Exception) as context: - parse_metadata_file(filename, known_ddts, logger) + tables = parse_metadata_file(filename, known_ddts, logger) #print("The exception is", context.exception) emsg = "Invalid metadata table type, 'banana', at " From ab9987690e04e50edff96678f826729b80b5518f Mon Sep 17 00:00:00 2001 From: Steve Goldhaber Date: Fri, 30 Jul 2021 16:40:27 -0600 Subject: [PATCH 2/2] Fix typo in method doc string --- scripts/metavar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 175a03f7..bd9f55b2 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1203,7 +1203,7 @@ def intrinsic_elements(self, check_dict=None): If is not None, it is checked for children if none are found in this variable (via finding a variable in with the same standard name). - Currently, an array of DDTs is not processeed (return None) since + Currently, an array of DDTs is not processed (return None) since Fortran does not support a way to reference those elements. """ if self.is_ddt():