diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index c4e42142..3a978349 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -19,6 +19,7 @@ from metavar import Var, VarDictionary, ccpp_standard_var from parse_tools import ParseContext, ParseSource from parse_tools import ParseInternalError, CCPPError +from parse_tools import ParseMetadataErrors from parse_tools import read_xml_file, validate_xml_file, find_schema_version from parse_tools import init_log, set_log_to_null from suite_objects import CallList, Group, Scheme @@ -167,7 +168,7 @@ def new_group(self, group_string, transition, run_env): # end if group = Group(gxml, transition, self, self.__context, run_env) for svar in CCPP_REQUIRED_VARS: - group.add_call_list_variable(svar) + group.add_call_list_variable(svar, None) # end for if transition != RUN_PHASE_NAME: self.__full_groups[group.name] = group @@ -319,7 +320,7 @@ def find_variable(self, standard_name=None, source_var=None, # end if return var - def analyze(self, host_model, scheme_library, ddt_library, run_env): + def analyze(self, host_model, scheme_library, ddt_library, run_env, parse_errors): """Collect all information needed to write a suite file >>> CCPP_STATE_MACH.transition_match('init') 'initialize' @@ -433,7 +434,7 @@ def analyze(self, host_model, scheme_library, ddt_library, run_env): for x in item.schemes()])) item.analyze(phase, self, scheme_library, ddt_library, self.check_suite_state(phase), - self.set_suite_state(phase)) + self.set_suite_state(phase), parse_errors) # Look for group variables that need to be promoted to the suite # We need to promote any variable used later to the suite, however, # we do not yet know if it will be used. @@ -626,13 +627,19 @@ def __init__(self, sdfs, host_model, scheme_headers, run_env): raise CCPPError(errmsg.format(header.title)) # end if # end for + # Initialize parse error object + parse_errors = ParseMetadataErrors() # Turn the SDF files into Suites for sdf in sdfs: suite = Suite(sdf, self, run_env) suite.analyze(self.host_model, scheme_library, - self.__ddt_lib, run_env) + self.__ddt_lib, run_env, parse_errors) self.__suites.append(suite) # end for + if parse_errors.has_errors(): + errmsg = parse_errors.errstr() + raise CCPPError(errmsg) + # end if # We will need the correct names for errmsg and errcode evar = self.host_model.find_variable(standard_name='ccpp_error_message') subst_dict = {'intent':'out'} diff --git a/scripts/metavar.py b/scripts/metavar.py index d2c50a5c..28785b35 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -370,7 +370,7 @@ def __init__(self, prop_dict, source, run_env, context=None, context=self.context) from cperr # end try - def compatible(self, other, run_env): + def compatible(self, other, run_env, parse_errors): """Return a VarCompatObj object which describes the equivalence, compatibility, or incompatibility between and . """ @@ -392,7 +392,7 @@ def compatible(self, other, run_env): odims = other.get_dimensions() compat = VarCompatObj(sstd_name, stype, skind, sunits, sdims, sloc_name, stopp, ostd_name, otype, okind, ounits, odims, oloc_name, otopp, - run_env, + run_env, parse_errors, v1_context=self.context, v2_context=other.context) if (not compat) and (run_env.logger is not None): incompat_str = compat.incompat_reason @@ -1578,7 +1578,7 @@ def variable_list(self, recursive=False, # end for return vlist - def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, + def add_variable(self, newvar, run_env, parse_errors=None, exists_ok=False, gen_unique=False, adjust_intent=False): """Add if it does not conflict with existing entries If is True, attempting to add an identical copy is okay. @@ -1602,7 +1602,7 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, token=standard_name, context=newvar.context) # end if if cvar is not None: - compat = cvar.compatible(newvar, run_env) + compat = cvar.compatible(newvar, run_env, parse_errors) if compat: # Check for intent mismatch vintent = cvar.get_prop_value('intent') diff --git a/scripts/parse_tools/__init__.py b/scripts/parse_tools/__init__.py index 309bbda0..2014755f 100644 --- a/scripts/parse_tools/__init__.py +++ b/scripts/parse_tools/__init__.py @@ -9,6 +9,7 @@ from parse_source import ParseContext, ParseSource from parse_source import ParseSyntaxError, ParseInternalError from parse_source import CCPPError, context_string, type_name +from parse_source import ParseMetadataErrors from parse_source import unique_standard_name, reset_standard_name_counter from parse_object import ParseObject from parse_checkers import check_fortran_id, FORTRAN_ID diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 2ed0e5b8..16e34398 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -116,6 +116,59 @@ def type_name(obj): """Return the name of the type of """ return type(obj).__name__ +############################################################################### +class ParseMetadataErrors(): + """Class to hold all errors to return to user during parsing""" + def __init__(self): + """Initialize object""" + self.__stdname_errors = [] + self.__unit_errors = [] + self.__misc_errors = [] + + def append_error(self, message, errtype=""): + """Append message to list of error messages""" + if (errtype == 'units'): + self.__unit_errors.append(message) + elif (errtype == 'standard names'): + self.__stdname_errors.append(message) + else: + self.__misc_errors(message) + # endif + + def has_errors(self): + """Return true iff errors array is not empty""" + if len(self.__unit_errors) > 0 or len(self.__stdname_errors) > 0 or \ + len(self.__misc_errors) > 0: + return True + else: + return False + #end if + + def stdname_errors(self): + """Return string of all error messages related to standard names""" + errstr = f"\n".join(self.__stdname_errors) + return errstr + + def unit_errors(self): + """Return string of all error messages related to units""" + errstr = f"\n".join(self.__unit_errors) + return errstr + + def errstr(self): + """Return string of all error messages""" + errstr = '' + if len(self.__stdname_errors) > 0: + errstr += "\nSTANDARD NAME ERRORS:\n" + errstr += self.stdname_errors() + # end if + if len(self.__unit_errors) > 0: + errstr += "\nUNIT ERRORS:\n" + errstr += self.unit_errors() + # end if + return errstr + +############################################################################### + ############################################################################### class CCPPError(ValueError): """Class so programs can log user errors without backtrace""" diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 922fbad1..b08f5184 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -91,7 +91,7 @@ def add_vars(self, call_list, run_env, gen_unique=False): # end if # end for - def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, + def add_variable(self, newvar, run_env, parse_errors=None, exists_ok=False, gen_unique=False, adjust_intent=False): """Add as for VarDictionary but make sure that the variable has an intent with the default being intent(in). @@ -104,7 +104,7 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, source_type=_API_GROUP_VAR_NAME, context=oldvar.context) # end if - super().add_variable(newvar, run_env, exists_ok=exists_ok, + super().add_variable(newvar, run_env, parse_errors=parse_errors, exists_ok=exists_ok, gen_unique=gen_unique, adjust_intent=adjust_intent) def call_string(self, cldicts=None, is_func_call=False, subname=None): @@ -395,7 +395,7 @@ def is_local_variable(self, var): # end while return lvar - def add_call_list_variable(self, newvar, exists_ok=False, + def add_call_list_variable(self, newvar, parse_errors=None, exists_ok=False, gen_unique=False, subst_dict=None): """Add to this SuiteObject's call_list. If this SuiteObject does not have a call list, recursively try the SuiteObject's parent @@ -439,7 +439,7 @@ def add_call_list_variable(self, newvar, exists_ok=False, newvar = oldvar.clone(subst_dict, source_name=self.name, source_type=stype, context=self.context) # end if - self.call_list.add_variable(newvar, self.run_env, + self.call_list.add_variable(newvar, self.run_env, parse_errors, exists_ok=exists_ok, gen_unique=gen_unique, adjust_intent=True) @@ -479,7 +479,7 @@ def add_call_list_variable(self, newvar, exists_ok=False, subst_dict=subst_dict) # end if - def add_variable_to_call_tree(self, var, vmatch=None, subst_dict=None): + def add_variable_to_call_tree(self, var, parse_errors=None, vmatch=None, subst_dict=None): """Add to 's call_list (or a parent if does not have an active call_list). If is not None, also add the loop substitution variables @@ -489,7 +489,7 @@ def add_variable_to_call_tree(self, var, vmatch=None, subst_dict=None): """ found_dims = False if var is not None: - self.add_call_list_variable(var, exists_ok=True, + self.add_call_list_variable(var, parse_errors, exists_ok=True, gen_unique=True, subst_dict=subst_dict) found_dims = True # end if @@ -817,7 +817,7 @@ def find_variable(self, standard_name=None, source_var=None, # end if return found_var - def match_variable(self, var, run_env): + def match_variable(self, var, run_env, parse_errors): """Try to find a source for in this SuiteObject's dictionary tree. Several items are returned: found_var: True if a match was found @@ -873,7 +873,7 @@ def match_variable(self, var, run_env): match = True # Create compatability object, containing any necessary forward/reverse # transforms from and - compat_obj = var.compatible(dict_var, run_env) + compat_obj = var.compatible(dict_var, run_env, parse_errors) # end if # Add the variable to the parent call tree @@ -882,7 +882,7 @@ def match_variable(self, var, run_env): else: sdict = {'dimensions':new_dict_dims} # end if - found_var = self.parent.add_variable_to_call_tree(var, + found_var = self.parent.add_variable_to_call_tree(var, parse_errors, subst_dict=sdict) if not match: found_var = False @@ -1118,7 +1118,7 @@ def is_local_variable(self, var): This is an override of the SuiteObject version""" return None - def analyze(self, phase, group, scheme_library, suite_vars, level): + def analyze(self, phase, group, scheme_library, suite_vars, level, parse_errors): """Analyze the scheme's interface to prepare for writing""" self.__group = group my_header = None @@ -1150,7 +1150,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): def_val = var.get_prop_value('default_value') vdims = var.get_dimensions() vintent = var.get_prop_value('intent') - args = self.match_variable(var, self.run_env) + args = self.match_variable(var, self.run_env, parse_errors) found, dict_var, vert_dim, new_dims, missing_vert, compat_obj = args if found: if self.__group.run_env.debug: @@ -1195,27 +1195,25 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # We still need it in our call list (the group uses a clone) self.add_call_list_variable(var) else: - errmsg = 'Input argument for {}, {}, not found.' + errmsg = f'Input argument for {self.subroutine_name}, {vstdname}, not found.' if self.find_variable(source_var=var) is not None: # The variable exists, maybe it is dim mismatch lname = var.get_prop_value('local_name') - emsg = '\nCheck for dimension mismatch in {}' - errmsg += emsg.format(lname) + errmsg += f'\nCheck for dimension mismatch in {lname}' # end if if ((not self.run_phase()) and (vstdname in CCPP_LOOP_VAR_STDNAMES)): - emsg = '\nLoop variables not allowed in {} phase.' - errmsg += emsg.format(self.phase()) + errmsg += f'\nLoop variables not allowed in {self.phase()} phase.' # end if - raise CCPPError(errmsg.format(self.subroutine_name, - vstdname)) + parse_errors.append_error(errmsg, 'standard names') # end if # end if # Are there any forward/reverse transforms for this variable? if compat_obj is not None and (compat_obj.has_vert_transforms or compat_obj.has_unit_transforms or compat_obj.has_kind_transforms): - self.add_var_transform(var, compat_obj, vert_dim) + if not parse_errors.has_errors(): + self.add_var_transform(var, compat_obj, vert_dim) # end for if self.needs_vertical is not None: @@ -1223,7 +1221,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): if isinstance(self.parent, VerticalLoop): # Restart the loop analysis scheme_mods = self.parent.analyze(phase, group, scheme_library, - suite_vars, level) + suite_vars, level, parse_errors) # end if # end if return scheme_mods @@ -1542,7 +1540,6 @@ def add_var_transform(self, var, compat_obj, vert_dim): rindices[vdim] = '1:'+vname if compat_obj.has_vert_transforms: rindices[vdim] = vname+':1:-1' - # If needed, modify horizontal dimension for loop substitution. # NOT YET IMPLEMENTED #hdim = find_horizontal_dimension(var.get_dimensions()) @@ -1707,7 +1704,7 @@ def __init__(self, index_name, context, parent, run_env, items=None): self.add_part(item) # end for - def analyze(self, phase, group, scheme_library, suite_vars, level): + def analyze(self, phase, group, scheme_library, suite_vars, level, parse_errors): """Analyze the VerticalLoop's interface to prepare for writing""" # Handle all the suite objects inside of this subcycle scheme_mods = set() @@ -1737,7 +1734,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # Analyze our internal items for item in self.parts: smods = item.analyze(phase, group, scheme_library, - suite_vars, level+1) + suite_vars, level+1, parse_errors) for smod in smods: scheme_mods.add(smod) # end for @@ -1802,7 +1799,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): scheme_mods = set() for item in self.parts: smods = item.analyze(phase, group, scheme_library, - suite_vars, level+1) + suite_vars, level+1, standard_names_errors) for smod in smods: scheme_mods.add(smod) # end for @@ -1844,7 +1841,7 @@ def __init__(self, sub_xml, context, parent, run_env): self.add_part(new_item) # end for - def analyze(self, phase, group, scheme_library, suite_vars, level): + def analyze(self, phase, group, scheme_library, suite_vars, level, parse_errors): # Unused arguments are for consistent analyze interface # pylint: disable=unused-argument """Analyze the TimeSplit's interface to prepare for writing""" @@ -1852,7 +1849,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): scheme_mods = set() for item in self.parts: smods = item.analyze(phase, group, scheme_library, - suite_vars, level+1) + suite_vars, level+1, parse_errors) for smod in smods: scheme_mods.add(smod) # end for @@ -2070,7 +2067,7 @@ def manage_variable(self, newvar): # end if def analyze(self, phase, suite_vars, scheme_library, ddt_library, - check_suite_state, set_suite_state): + check_suite_state, set_suite_state, parse_errors): """Analyze the Group's interface to prepare for writing""" self._ddt_library = ddt_library # Sanity check for Group @@ -2083,7 +2080,7 @@ def analyze(self, phase, suite_vars, scheme_library, ddt_library, # Items can be schemes, subcycles or other objects # All have the same interface and return a set of module use # statements (lschemes) - lschemes = item.analyze(phase, self, scheme_library, suite_vars, 1) + lschemes = item.analyze(phase, self, scheme_library, suite_vars, 1, parse_errors) for lscheme in lschemes: self._local_schemes.add(lscheme) # end for diff --git a/scripts/var_props.py b/scripts/var_props.py index c61d9ae4..18d2cbee 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -782,8 +782,10 @@ class VarCompatObj: # Test that we can create a standard VarCompatObj object >>> from parse_tools import init_log, set_log_to_null + >>> from parse_tools import ParseMetadataErrors >>> _DOCTEST_LOGGING = init_log('var_props') >>> set_log_to_null(_DOCTEST_LOGGING) + >>> parse_errors = ParseMetadataErrors() >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ ndict={'host_files':'', \ 'scheme_files':'', \ @@ -793,46 +795,46 @@ class VarCompatObj: "kind_host=REAL64"]) >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", [], "var1_lname", False,\ "var_stdname", "real", "kind_phys", "m", [], "var2_lname", False,\ - _DOCTEST_RUNENV) #doctest: +ELLIPSIS + _DOCTEST_RUNENV, parse_errors) #doctest: +ELLIPSIS # Test that a 2-D var with no horizontal transform works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", ['horizontal_dimension'], "var1_lname", False, \ "var_stdname", "real", "kind_phys", "m", ['horizontal_dimension'], "var2_lname", False, \ - _DOCTEST_RUNENV) #doctest: +ELLIPSIS + _DOCTEST_RUNENV, parse_errors) #doctest: +ELLIPSIS # Test that a 2-D var with a horizontal transform works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", ['horizontal_dimension'], "var1_lname", False, \ "var_stdname", "real", "kind_phys", "m", ['horizontal_loop_extent'], "var2_lname", False, \ - _DOCTEST_RUNENV) #doctest: +ELLIPSIS + _DOCTEST_RUNENV, parse_errors) #doctest: +ELLIPSIS # Test that a 2-D var with unit conversion m->km works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", ['horizontal_dimension'], "var1_lname", False, \ "var_stdname", "real", "kind_phys", "km", ['horizontal_dimension'], "var2_lname", False, \ - _DOCTEST_RUNENV) #doctest: +ELLIPSIS + _DOCTEST_RUNENV, parse_errors) #doctest: +ELLIPSIS # Test that a 2-D var with unit conversion m->km works and that it # produces the correct forward transformation >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", ['horizontal_dimension'], "var1_lname", False, \ "var_stdname", "real", "kind_phys", "km", ['horizontal_dimension'], "var2_lname", False, \ - _DOCTEST_RUNENV).forward_transform("var1_lname", "var2_lname", 'i', 'i') + _DOCTEST_RUNENV, parse_errors).forward_transform("var1_lname", "var2_lname", 'i', 'i') 'var1_lname(i) = 1.0E-3_kind_phys*var2_lname(i)' # Test that a 3-D var with unit conversion m->km and vertical flipping # works and that it produces the correct reverse transformation >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", ['horizontal_dimension', 'vertical_layer_dimension'], "var1_lname", False,\ "var_stdname", "real", "kind_phys", "km",['horizontal_dimension', 'vertical_layer_dimension'], "var2_lname", True, \ - _DOCTEST_RUNENV).reverse_transform("var1_lname", "var2_lname", ('i','k'), ('i','nk-k+1')) + _DOCTEST_RUNENV, parse_errors).reverse_transform("var1_lname", "var2_lname", ('i','k'), ('i','nk-k+1')) 'var1_lname(i,nk-k+1) = 1.0E+3_kind_phys*var2_lname(i,k)' """ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, var1_dims, var1_lname, var1_top, var2_stdname, var2_type, var2_kind, - var2_units, var2_dims, var2_lname, var2_top, run_env, v1_context=None, - v2_context=None): + var2_units, var2_dims, var2_lname, var2_top, run_env, parse_errors, + v1_context=None, v2_context=None): """Initialize this object with information on the equivalence and/or conformability of two variables. variable 1 is described by , , , @@ -916,7 +918,8 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, self.__equiv = False # Try to find a set of unit conversions self.__unit_transforms = self._get_unit_convstrs(var1_units, - var2_units) + var2_units, + parse_errors) # end if # end if if self.__compat: @@ -1034,6 +1037,7 @@ def _get_kind_convstrs(self, var1_kind, var2_kind, run_env): # Initial setup >>> from parse_tools import init_log, set_log_to_null + >>> from parse_tools import ParseMetadataErrors >>> _DOCTEST_LOGGING = init_log('var_props') >>> set_log_to_null(_DOCTEST_LOGGING) >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ @@ -1045,10 +1049,12 @@ def _get_kind_convstrs(self, var1_kind, var2_kind, run_env): "kind_host=REAL64"]) >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> parse_errors = ParseMetadataErrors() >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ "m", [], "var1_lname", False, "var_stdname", \ "real", "kind_phys", "m", [], \ "var2_lname", False, _DOCTEST_RUNENV, \ + parse_errors, \ v1_context=_DOCTEST_CONTEXT1, \ v2_context=_DOCTEST_CONTEXT2) @@ -1081,13 +1087,14 @@ def _get_kind_convstrs(self, var1_kind, var2_kind, run_env): # end if return None - def _get_unit_convstrs(self, var1_units, var2_units): + def _get_unit_convstrs(self, var1_units, var2_units, parse_errors): """Attempt to retrieve the forward and reverse unit transformations for transforming a variable in to / from a variable in . # Initial setup >>> from parse_tools import init_log, set_log_to_null + >>> from parse_tools import ParseMetadataErrors >>> _DOCTEST_LOGGING = init_log('var_props') >>> set_log_to_null(_DOCTEST_LOGGING) >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ @@ -1099,52 +1106,63 @@ def _get_unit_convstrs(self, var1_units, var2_units): "kind_host=REAL64"]) >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> parse_errors = ParseMetadataErrors() >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ "m", [], "var1_lname", False, "var_stdname", \ "real", "kind_phys", "m", [], \ "var2_lname", False, _DOCTEST_RUNENV, \ + parse_errors, \ v1_context=_DOCTEST_CONTEXT1, \ v2_context=_DOCTEST_CONTEXT2) # Try some working unit transforms - >>> _DOCTEST_VCOMPAT._get_unit_convstrs('m', 'mm') + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('m', 'mm', parse_errors) ('1.0E+3{kind}*{var}', '1.0E-3{kind}*{var}') - >>> _DOCTEST_VCOMPAT._get_unit_convstrs('kg kg-1', 'g kg-1') + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('kg kg-1', 'g kg-1', parse_errors) ('1.0E+3{kind}*{var}', '1.0E-3{kind}*{var}') - >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'K') + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'K', parse_errors) ('{var}+273.15{kind}', '{var}-273.15{kind}') # Try an invalid conversion - >>> _DOCTEST_VCOMPAT._get_unit_convstrs('1', 'none') #doctest: +ELLIPSIS - Traceback (most recent call last): - ... - parse_source.ParseSyntaxError: Unsupported unit conversion, '1' to 'none' for 'var_stdname' + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('1', 'none', parse_errors) + ('', '') + >>> print(parse_errors.errstr()) + + UNIT ERRORS: + Unsupported unit conversion, '1' to 'none' for 'var_stdname' # Try an unsupported conversion - >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'm') #doctest: +ELLIPSIS - Traceback (most recent call last): - ... - parse_source.ParseSyntaxError: Unsupported unit conversion, 'C' to 'm' for 'var_stdname' + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'm', parse_errors) + ('', '') + >>> print(parse_errors.errstr()) + + UNIT ERRORS: + Unsupported unit conversion, '1' to 'none' for 'var_stdname' + Unsupported unit conversion, 'C' to 'm' for 'var_stdname' """ - u1_str = self.units_to_string(var1_units, self.__v1_context) - u2_str = self.units_to_string(var2_units, self.__v2_context) + u1_str = self.units_to_string(var1_units, parse_errors, self.__v1_context) + u2_str = self.units_to_string(var2_units, parse_errors, self.__v2_context) unit_conv_str = "{0}__to__{1}".format(u1_str, u2_str) try: forward_transform = getattr(unit_conversion, unit_conv_str)() except AttributeError: - emsg = "Unsupported unit conversion, '{}' to '{}' for '{}'" - raise ParseSyntaxError(emsg.format(var1_units, var2_units, - self.__stdname, - context=self.__v2_context)) + emsg = f"Unsupported unit conversion, '{var1_units}' to '{var2_units}' for '{self.__stdname}'" + parse_errors.append_error(emsg, 'units') + return ('', '') +# raise ParseSyntaxError(emsg.format(var1_units, var2_units, +# self.__stdname, +# context=self.__v2_context)) # end if unit_conv_str = "{0}__to__{1}".format(u2_str, u1_str) try: reverse_transform = getattr(unit_conversion, unit_conv_str)() except AttributeError: - emsg = "Unsupported unit conversion, '{}' to '{}' for '{}'" - raise ParseSyntaxError(emsg.format(var2_units, var1_units, - self.__stdname, - context=self.__v1_context)) + emsg = f"Unsupported unit conversion, '{var2_units}' to '{var1_units}' for '{self.__stdname}'" + parse_errors.append_error(emsg, 'units') + return ('', '') +# raise ParseSyntaxError(emsg.format(var2_units, var1_units, +# self.__stdname, +# context=self.__v1_context)) # end if return (forward_transform, reverse_transform) @@ -1160,6 +1178,7 @@ def _get_dim_transforms(self, var1_dims, var2_dims): # Initial setup >>> from parse_tools import init_log, set_log_to_null + >>> from parse_tools import ParseMetadataErrors >>> _DOCTEST_LOGGING = init_log('var_props') >>> set_log_to_null(_DOCTEST_LOGGING) >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ @@ -1171,10 +1190,12 @@ def _get_dim_transforms(self, var1_dims, var2_dims): "kind_host=REAL64"]) >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> parse_errors = ParseMetadataErrors() >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ "m", [], "var1_lname", False, "var_stdname", \ "real", "kind_phys", "m", [], \ "var2_lname", False, _DOCTEST_RUNENV, \ + parse_errors, \ v1_context=_DOCTEST_CONTEXT1, \ v2_context=_DOCTEST_CONTEXT2) @@ -1287,7 +1308,7 @@ def char_kind_check(kind_str): # end if (no else, kind_ok already False) return kind_ok - def units_to_string(self, units, context=None): + def units_to_string(self, units, parse_errors, context=None): """Replace variable unit description with string that is a legal Python identifier. If the resulting string is a Python keyword, raise an exception.""" @@ -1303,15 +1324,17 @@ def units_to_string(self, units, context=None): # end if # Test that the resulting string is a valid Python identifier if not string.isidentifier(): - emsg = "Unsupported units entry for {}, '{}'{}" ctx = context_string(context) - raise ParseSyntaxError(emsg.format(self.__stdname, units ,ctx)) + emsg = f"Unsupported units entry for {self.__stdname}, '{units}'{ctx}" + parse_errors.append_error(emsg, 'units') +# raise ParseSyntaxError(emsg.format(self.__stdname, units ,ctx)) # end if # Test that the resulting string is NOT a Python keyword if keyword.iskeyword(string): - emsg = "Invalid units entry, '{}', Python identifier" - raise ParseSyntaxError(emsg.format(units), - context=context) + emsg = "Invalid units entry, '{units}', Python identifier" + parse_errors.append_error(emsg, 'units') +# raise ParseSyntaxError(emsg.format(units), +# context=context) # end if return string