Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concatenate errors during parsing #14

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions scripts/ccpp_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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'}
Expand Down
8 changes: 4 additions & 4 deletions scripts/metavar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <self> and <other>.
"""
Expand All @@ -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
Expand Down Expand Up @@ -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 <newvar> if it does not conflict with existing entries
If <exists_ok> is True, attempting to add an identical copy is okay.
Expand All @@ -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')
Expand Down
1 change: 1 addition & 0 deletions scripts/parse_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions scripts/parse_tools/parse_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,59 @@ def type_name(obj):
"""Return the name of the type of <obj>"""
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"""
Expand Down
53 changes: 25 additions & 28 deletions scripts/suite_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <newvar> as for VarDictionary but make sure that the variable
has an intent with the default being intent(in).
Expand All @@ -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):
Expand Down Expand Up @@ -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 <newvar> to this SuiteObject's call_list. If this SuiteObject
does not have a call list, recursively try the SuiteObject's parent
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 <var> to <self>'s call_list (or a parent if <self> does not
have an active call_list).
If <vmatch> is not None, also add the loop substitution variables
Expand All @@ -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
Expand Down Expand Up @@ -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 <var> in this SuiteObject's dictionary
tree. Several items are returned:
found_var: True if a match was found
Expand Down Expand Up @@ -873,7 +873,7 @@ def match_variable(self, var, run_env):
match = True
# Create compatability object, containing any necessary forward/reverse
# transforms from <var> and <dict_var>
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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1195,35 +1195,33 @@ 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:
self.parent.add_part(self, replace=True) # Should add a vloop
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
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1844,15 +1841,15 @@ 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"""
# Handle all the suite objects inside of this group
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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading
Loading