The expiration time for new job artifacts in CI/CD pipelines is now 30 days (GitLab default). Previously generated artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

Commit c4a03af4 authored by eckhart's avatar eckhart
Browse files

- user configurable error messages and skip/resume points now also work in AllOf-parsers!

parent db944887
...@@ -1351,6 +1351,30 @@ MessagesType = List[Tuple[Union[str, Any], str]] ...@@ -1351,6 +1351,30 @@ MessagesType = List[Tuple[Union[str, Any], str]]
NO_MANDATORY = 1000 NO_MANDATORY = 1000
def mandatory_violation(grammar, text_, expected, err_msgs, reloc):
i = reloc if reloc >= 0 else 0
location = grammar.document_length__ - len(text_)
err_node = Node(None, text_[:i]).init_pos(location)
found = text_[:10].replace('\n', '\\n ')
for search, message in err_msgs:
rxs = not isinstance(search, str)
if rxs and text_.match(search) or (not rxs and text_.startswith(search)):
try:
msg = message.format(expected, found)
break
except (ValueError, KeyError, IndexError) as e:
error = Error("Malformed error format string '{}' lead to '{}'"
.format(message, str(e)),
location, Error.MALFORMED_ERROR_STRING)
grammar.tree__.add_error(err_node, error)
else:
msg = '%s expected, "%s" found!' % (expected, found)
error = Error(msg, location, Error.MANDATORY_CONTINUATION if text_
else Error.MANDATORY_CONTINUATION_AT_EOF)
grammar.tree__.add_error(err_node, error)
return error, err_node, text_[i:]
class Series(NaryOperator): class Series(NaryOperator):
r""" r"""
Matches if each of a series of parsers matches exactly in the order of Matches if each of a series of parsers matches exactly in the order of
...@@ -1416,38 +1440,18 @@ class Series(NaryOperator): ...@@ -1416,38 +1440,18 @@ class Series(NaryOperator):
def __call__(self, text: StringView) -> Tuple[Optional[Node], StringView]: def __call__(self, text: StringView) -> Tuple[Optional[Node], StringView]:
results = () # type: Tuple[Node, ...] results = () # type: Tuple[Node, ...]
text_ = text # type: StringView text_ = text # type: StringView
mandatory_violation = None error = None # type: Optional[Error]
for pos, parser in enumerate(self.parsers): for pos, parser in enumerate(self.parsers):
node, text_ = parser(text_) node, text_ = parser(text_)
if not node: if not node:
if pos < self.mandatory: if pos < self.mandatory:
return None, text return None, text
else: else:
k = reentry_point(text_, self.skip) if self.skip else -1 reloc = reentry_point(text_, self.skip) if self.skip else -1
i = k if k >= 0 else 0 error, node, text_ = mandatory_violation(
location = self.grammar.document_length__ - len(text_) self.grammar, text_, parser.repr, self.err_msgs, reloc)
node = Node(None, text_[:i]).init_pos(location)
found = text_[:10].replace('\n', '\\n ')
for search, message in self.err_msgs:
rxs = not isinstance(search, str)
if rxs and text_.match(search) or (not rxs and text_.startswith(search)):
try:
msg = message.format(parser.repr, found)
break
except (ValueError, KeyError, IndexError) as e:
error = Error("Malformed error format string '{}' lead to '{}'"
.format(message, str(e)),
location, Error.MALFORMED_ERROR_STRING)
self.grammar.tree__.add_error(node, error)
else:
msg = '%s expected, "%s" found!' % (parser.repr, found)
mandatory_violation = Error(msg, location,
Error.MANDATORY_CONTINUATION if text_
else Error.MANDATORY_CONTINUATION_AT_EOF)
self.grammar.tree__.add_error(node, mandatory_violation)
text_ = text_[i:]
# check if parsing of the series can be resumed somewhere # check if parsing of the series can be resumed somewhere
if k >= 0: if reloc >= 0:
nd, text_ = parser(text_) # try current parser again nd, text_ = parser(text_) # try current parser again
if nd: if nd:
results += (node,) results += (node,)
...@@ -1459,7 +1463,7 @@ class Series(NaryOperator): ...@@ -1459,7 +1463,7 @@ class Series(NaryOperator):
assert len(results) <= len(self.parsers) \ assert len(results) <= len(self.parsers) \
or len(self.parsers) >= len([p for p in results if p.parser != ZOMBIE_PARSER]) or len(self.parsers) >= len([p for p in results if p.parser != ZOMBIE_PARSER])
node = Node(self, results) node = Node(self, results)
if mandatory_violation: if error:
raise ParserError(node, text, first_throw=True) raise ParserError(node, text, first_throw=True)
return node, text_ return node, text_
...@@ -1616,29 +1620,38 @@ class AllOf(NaryOperator): ...@@ -1616,29 +1620,38 @@ class AllOf(NaryOperator):
parsers = series.parsers parsers = series.parsers
super().__init__(*parsers) super().__init__(*parsers)
num = len(self.parsers) self.num_parsers = len(self.parsers) # type: int
if mandatory < 0: if mandatory < 0:
mandatory += num mandatory += self.num_parsers
assert not (mandatory == NO_MANDATORY and err_msgs), \ assert not (mandatory == NO_MANDATORY and err_msgs), \
'Custom error messages require that parameter "mandatory" is set!' 'Custom error messages require that parameter "mandatory" is set!'
assert not (mandatory == NO_MANDATORY and skip), \ assert not (mandatory == NO_MANDATORY and skip), \
'Search expressions for skipping text require that parameter "mandatory" is set!' 'Search expressions for skipping text require that parameter "mandatory" is set!'
assert num > 0, \ assert self.num_parsers > 0, \
'Number of elements %i is below minimum of 1' % num 'Number of elements %i is below minimum of 1' % self.num_parsers
assert num < NO_MANDATORY, \ assert self.num_parsers < NO_MANDATORY, \
'Number of elemnts %i of exceeds maximum of %i' % (num, NO_MANDATORY) 'Number of elemnts %i of exceeds maximum of %i' % (self.num_parsers, NO_MANDATORY)
assert 0 <= mandatory < num or mandatory == NO_MANDATORY assert 0 <= mandatory < self.num_parsers or mandatory == NO_MANDATORY
self.mandatory = mandatory # type: int self.mandatory = mandatory # type: int
self.err_msgs = err_msgs # type: Series.MessagesType self.err_msgs = err_msgs # type: Series.MessagesType
self.skip = skip # type: ResumeList self.skip = skip # type: ResumeList
def __deepcopy__(self, memo):
parsers = copy.deepcopy(self.parsers, memo)
duplicate = self.__class__(*parsers, mandatory=self.mandatory,
err_msgs=self.err_msgs, skip=self.skip)
duplicate.name = self.name
duplicate.ptype = self.ptype
duplicate.num_parsers = self.num_parsers
return duplicate
def __call__(self, text: StringView) -> Tuple[Optional[Node], StringView]: def __call__(self, text: StringView) -> Tuple[Optional[Node], StringView]:
results = () # type: Tuple[Node, ...] results = () # type: Tuple[Node, ...]
text_ = text # type: StringView text_ = text # type: StringView
parsers = list(self.parsers) # type: List[Parser] parsers = list(self.parsers) # type: List[Parser]
error = None # type: Optional[Error]
while parsers: while parsers:
for i, parser in enumerate(parsers): for i, parser in enumerate(parsers):
node, text__ = parser(text_) node, text__ = parser(text_)
...@@ -1648,12 +1661,25 @@ class AllOf(NaryOperator): ...@@ -1648,12 +1661,25 @@ class AllOf(NaryOperator):
del parsers[i] del parsers[i]
break break
else: else:
return None, text if self.num_parsers - len(parsers) < self.mandatory:
assert len(results) <= len(self.parsers) return None, text
return Node(self, results), text_ else:
reloc = reentry_point(text_, self.skip) if self.skip else -1
expected = '< ' + ' '.join(parser.repr for parser in parsers) + ' >'
error, err_node, text_ = mandatory_violation(
self.grammar, text_, expected, self.err_msgs, reloc)
results += (err_node,)
if reloc < 0:
parsers = []
assert len(results) <= len(self.parsers) \
or len(self.parsers) >= len([p for p in results if p.parser != ZOMBIE_PARSER])
node = Node(self, results)
if error:
raise ParserError(node, text, first_throw=True)
return node, text_
def __repr__(self): def __repr__(self):
return '<' + ' '.join(parser.repr for parser in self.parsers) + '>' return '< ' + ' '.join(parser.repr for parser in self.parsers) + ' >'
class SomeOf(NaryOperator): class SomeOf(NaryOperator):
...@@ -1707,7 +1733,7 @@ class SomeOf(NaryOperator): ...@@ -1707,7 +1733,7 @@ class SomeOf(NaryOperator):
return None, text return None, text
def __repr__(self): def __repr__(self):
return '<' + ' | '.join(parser.repr for parser in self.parsers) + '>' return '< ' + ' | '.join(parser.repr for parser in self.parsers) + ' >'
def Unordered(parser: NaryOperator) -> NaryOperator: def Unordered(parser: NaryOperator) -> NaryOperator:
......
...@@ -78,12 +78,12 @@ class ArithmeticGrammar(Grammar): ...@@ -78,12 +78,12 @@ class ArithmeticGrammar(Grammar):
def get_grammar() -> ArithmeticGrammar: def get_grammar() -> ArithmeticGrammar:
global GLOBALS global GLOBALS
try: try:
grammar = GLOBALS.Arithmetic_1_grammar_singleton grammar = GLOBALS.Arithmetic_00000001_grammar_singleton
except AttributeError: except AttributeError:
GLOBALS.Arithmetic_1_grammar_singleton = ArithmeticGrammar() GLOBALS.Arithmetic_00000001_grammar_singleton = ArithmeticGrammar()
if hasattr(get_grammar, 'python_src__'): if hasattr(get_grammar, 'python_src__'):
GLOBALS.Arithmetic_1_grammar_singleton.python_src__ = get_grammar.python_src__ GLOBALS.Arithmetic_00000001_grammar_singleton.python_src__ = get_grammar.python_src__
grammar = GLOBALS.Arithmetic_1_grammar_singleton grammar = GLOBALS.Arithmetic_00000001_grammar_singleton
return grammar return grammar
......
...@@ -88,12 +88,12 @@ class EBNFGrammar(Grammar): ...@@ -88,12 +88,12 @@ class EBNFGrammar(Grammar):
def get_grammar() -> EBNFGrammar: def get_grammar() -> EBNFGrammar:
global GLOBALS global GLOBALS
try: try:
grammar = GLOBALS.EBNF_1_grammar_singleton grammar = GLOBALS.EBNF_00000001_grammar_singleton
except AttributeError: except AttributeError:
GLOBALS.EBNF_1_grammar_singleton = EBNFGrammar() GLOBALS.EBNF_00000001_grammar_singleton = EBNFGrammar()
if hasattr(get_grammar, 'python_src__'): if hasattr(get_grammar, 'python_src__'):
GLOBALS.EBNF_1_grammar_singleton.python_src__ = get_grammar.python_src__ GLOBALS.EBNF_00000001_grammar_singleton.python_src__ = get_grammar.python_src__
grammar = GLOBALS.EBNF_1_grammar_singleton grammar = GLOBALS.EBNF_00000001_grammar_singleton
return grammar return grammar
......
...@@ -155,12 +155,12 @@ class LaTeXGrammar(Grammar): ...@@ -155,12 +155,12 @@ class LaTeXGrammar(Grammar):
def get_grammar() -> LaTeXGrammar: def get_grammar() -> LaTeXGrammar:
global GLOBALS global GLOBALS
try: try:
grammar = GLOBALS.LaTeX_1_grammar_singleton grammar = GLOBALS.LaTeX_00000001_grammar_singleton
except AttributeError: except AttributeError:
GLOBALS.LaTeX_1_grammar_singleton = LaTeXGrammar() GLOBALS.LaTeX_00000001_grammar_singleton = LaTeXGrammar()
if hasattr(get_grammar, 'python_src__'): if hasattr(get_grammar, 'python_src__'):
GLOBALS.LaTeX_1_grammar_singleton.python_src__ = get_grammar.python_src__ GLOBALS.LaTeX_00000001_grammar_singleton.python_src__ = get_grammar.python_src__
grammar = GLOBALS.LaTeX_1_grammar_singleton grammar = GLOBALS.LaTeX_00000001_grammar_singleton
return grammar return grammar
......
...@@ -537,10 +537,7 @@ class TestCustomizedResumeParsing: ...@@ -537,10 +537,7 @@ class TestCustomizedResumeParsing:
GAMMA_RE = /GA\w+/ GAMMA_RE = /GA\w+/
GAMMA_STR = "GAMMA" GAMMA_STR = "GAMMA"
""" """
try: self.gr = grammar_provider(lang)()
self.gr = grammar_provider(lang)()
except CompilationError as ce:
print(ce)
def test_several_resume_rules_innermost_rule_matching(self): def test_several_resume_rules_innermost_rule_matching(self):
gr = self.gr gr = self.gr
...@@ -579,10 +576,7 @@ class TestInSeriesResume: ...@@ -579,10 +576,7 @@ class TestInSeriesResume:
@series_skip = /B/, /C/, /D/, /E/, /F/, /G/ @series_skip = /B/, /C/, /D/, /E/, /F/, /G/
series = "A" §"B" "C" "D" "E" "F" "G" series = "A" §"B" "C" "D" "E" "F" "G"
""" """
try: self.gr = grammar_provider(lang)()
self.gr = grammar_provider(lang)()
except CompilationError as ce:
print(ce)
def test_garbage_in_series(self): def test_garbage_in_series(self):
st = self.gr('ABCDEFG') st = self.gr('ABCDEFG')
...@@ -614,22 +608,74 @@ class TestInSeriesResume: ...@@ -614,22 +608,74 @@ class TestInSeriesResume:
assert len(errors) >= 1 # cannot really recover from permutation errors assert len(errors) >= 1 # cannot really recover from permutation errors
class TestInAllOfResume: class TestAllOfResume:
def setup(self): def setup(self):
lang = """ lang = """
document = allof document = allof
@allof_skip = /A/, /B/, /C/, /D/, /E/, /F/, /G/ @ allof_error = '{} erwartet, {} gefunden :-('
allof = "A" "B" § "C" "D" "E" "F" "G" @ allof_skip = /A/, /B/, /C/, /D/, /E/, /F/, /G/
allof = < "A" "B" § "C" "D" "E" "F" "G" >
""" """
try: self.gr = grammar_provider(lang)()
self.gr = grammar_provider(lang)()
except CompilationError as ce:
print(ce)
def test_garbage_added(self): def test_garbage_added(self):
st = self.gr('GFCBAED')
assert not st.error_flag
st = self.gr('GFCB XYZ AED') st = self.gr('GFCB XYZ AED')
errors = st.collect_errors() errors = st.collect_errors()
print(errors) assert errors[0].code == Error.MANDATORY_CONTINUATION
assert str(errors[0]).find(':-(') >= 0
def test_allof_resume_later(self):
lang = """
document = flow "."
@ flow_resume = '.'
flow = allof | series
@ allof_error = '{} erwartet, {} gefunden :-('
allof = < "A" "B" § "C" "D" "E" "F" "G" >
series = "E" "X" "Y" "Z"
"""
gr = grammar_provider(lang)()
st = gr('GFCBAED.')
assert not st.error_flag
st = gr('GFCBAED.')
assert not st.error_flag
st = gr('EXYZ.')
assert not st.error_flag
st = gr('EDXYZ.')
assert st.error_flag
assert len(st.collect_errors()) == 1
st = gr('FCB_GAED.')
assert len(st.collect_errors()) == 1
def test_complex_resume_task(self):
lang = """
document = flow { flow } "."
@ flow_resume = '.'
flow = allof | series
@ allof_error = '{} erwartet, {} gefunden :-('
@ allof_resume = 'E', 'A'
allof = < "A" "B" § "C" "D" "E" "F" "G" >
@ series_resume = 'E', 'A'
series = "E" "X" §"Y" "Z"
"""
gr = grammar_provider(lang)()
st = gr('GFCBAED.')
assert not st.error_flag
st = gr('GFCBAED.')
assert not st.error_flag
st = gr('EXYZ.')
assert not st.error_flag
st = gr('EDXYZ.')
assert st.error_flag
assert len(st.collect_errors()) == 1
st = gr('FCB_GAED.')
assert len(st.collect_errors()) == 2
st = gr('EXY EXYZ.')
assert len(st.collect_errors()) == 1
if __name__ == "__main__": if __name__ == "__main__":
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment