Currently job artifacts in CI/CD pipelines on LRZ GitLab never expire. Starting from Wed 26.1.2022 the default expiration time will be 30 days (GitLab default). Currently existing 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 dbd41bc6 authored by di68kap's avatar di68kap
Browse files

- bugfix: multiple error messages now accepted

parent deb408cc
...@@ -906,11 +906,11 @@ class EBNFCompiler(Compiler): ...@@ -906,11 +906,11 @@ class EBNFCompiler(Compiler):
key = node.children[0].content key = node.children[0].content
assert key not in self.directives.tokens assert key not in self.directives.tokens
if key not in self.REPEATABLE_DIRECTIVES: if key not in self.REPEATABLE_DIRECTIVES and not key.endswith('_error'):
if key in self.defined_directives: if key in self.defined_directives:
self.tree.new_error(node, 'Directive "%s" has already been defined earlier. ' self.tree.new_error(node, 'Directive "%s" has already been defined earlier. '
% key + 'Later definition will be ignored!', % key + 'Later definition will be ignored!',
code=Error.REDEFINED_DIRECTIVE_WARNING) code=Error.REDEFINED_DIRECTIVE)
return "" return ""
self.defined_directives.add(key) self.defined_directives.add(key)
...@@ -986,9 +986,6 @@ class EBNFCompiler(Compiler): ...@@ -986,9 +986,6 @@ class EBNFCompiler(Compiler):
elif key.endswith('_skip'): elif key.endswith('_skip'):
symbol = key[:-5] symbol = key[:-5]
if symbol in self.directives.skip:
self.tree.new_error(node, 'In-series resuming for "%s" has already been defined'
' earlier!' % symbol)
if symbol in self.rules: if symbol in self.rules:
self.tree.new_error(node, 'Skip list for resuming in series for symbol "{}"' self.tree.new_error(node, 'Skip list for resuming in series for symbol "{}"'
'must be defined before the symbol!'.format(symbol)) 'must be defined before the symbol!'.format(symbol))
...@@ -996,11 +993,7 @@ class EBNFCompiler(Compiler): ...@@ -996,11 +993,7 @@ class EBNFCompiler(Compiler):
elif key.endswith('_resume'): elif key.endswith('_resume'):
symbol = key[:-7] symbol = key[:-7]
if symbol in self.directives.resume: self.directives.resume[symbol] = self._gen_search_list(node.children[1:])
self.tree.new_error(node, 'Reentry conditions for "%s" have already been defined'
' earlier!' % symbol)
else:
self.directives.resume[symbol] = self._gen_search_list(node.children[1:])
else: else:
self.tree.new_error(node, 'Unknown directive %s ! (Known ones are %s .)' % self.tree.new_error(node, 'Unknown directive %s ! (Known ones are %s .)' %
......
...@@ -71,7 +71,6 @@ class Error: ...@@ -71,7 +71,6 @@ class Error:
# warning codes # warning codes
REDEFINED_DIRECTIVE_WARNING = ErrorCode(110)
REDECLARED_TOKEN_WARNING = ErrorCode(120) REDECLARED_TOKEN_WARNING = ErrorCode(120)
UNUSED_ERROR_HANDLING_WARNING = ErrorCode(130) UNUSED_ERROR_HANDLING_WARNING = ErrorCode(130)
...@@ -87,6 +86,7 @@ class Error: ...@@ -87,6 +86,7 @@ class Error:
CAPTURE_STACK_NOT_EMPTY = ErrorCode(1050) CAPTURE_STACK_NOT_EMPTY = ErrorCode(1050)
MALFORMED_ERROR_STRING = ErrorCode(1060) MALFORMED_ERROR_STRING = ErrorCode(1060)
AMBIGUOUS_ERROR_HANDLING = ErrorCode(1070) AMBIGUOUS_ERROR_HANDLING = ErrorCode(1070)
REDEFINED_DIRECTIVE = ErrorCode(1080)
def __init__(self, message: str, pos, code: ErrorCode = ERROR, def __init__(self, message: str, pos, code: ErrorCode = ERROR,
orig_pos: int = -1, line: int = -1, column: int = -1) -> None: orig_pos: int = -1, line: int = -1, column: int = -1) -> None:
......
...@@ -208,7 +208,6 @@ class Parser: ...@@ -208,7 +208,6 @@ class Parser:
def __init__(self) -> None: def __init__(self) -> None:
# assert isinstance(name, str), str(name) # assert isinstance(name, str), str(name)
self.pname = '' # type: str self.pname = '' # type: str
self.ptype = ':' + self.__class__.__name__ # type: str
self.tag_name = self.ptype # type: str self.tag_name = self.ptype # type: str
if not hasattr(self.__class__, 'alive'): if not hasattr(self.__class__, 'alive'):
self._grammar = ZOMBIE_GRAMMAR # type: Grammar self._grammar = ZOMBIE_GRAMMAR # type: Grammar
...@@ -223,7 +222,6 @@ class Parser: ...@@ -223,7 +222,6 @@ class Parser:
""" """
duplicate = self.__class__() duplicate = self.__class__()
duplicate.pname = self.pname duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name duplicate.tag_name = self.tag_name
return duplicate return duplicate
...@@ -233,6 +231,12 @@ class Parser: ...@@ -233,6 +231,12 @@ class Parser:
def __str__(self): def __str__(self):
return self.pname + (' = ' if self.pname else '') + repr(self) return self.pname + (' = ' if self.pname else '') + repr(self)
@property
def ptype(self) -> str:
"""Returns a type name for the parser. By default this is the name of
the parser class with an added leading colon ':'. """
return ':' + self.__class__.__name__
@property @property
def repr(self) -> str: def repr(self) -> str:
"""Returns the parser's name if it has a name and self.__repr___() otherwise.""" """Returns the parser's name if it has a name and self.__repr___() otherwise."""
...@@ -459,7 +463,6 @@ class ZombieParser(Parser): ...@@ -459,7 +463,6 @@ class ZombieParser(Parser):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.pname = ZOMBIE self.pname = ZOMBIE
self.ptype = ':' + ZOMBIE
self.tag_name = ZOMBIE self.tag_name = ZOMBIE
# no need to call super class constructor # no need to call super class constructor
assert not self.__class__.alive, "There can be only one!" assert not self.__class__.alive, "There can be only one!"
...@@ -476,8 +479,13 @@ class ZombieParser(Parser): ...@@ -476,8 +479,13 @@ class ZombieParser(Parser):
def __call__(self, text): def __call__(self, text):
raise AssertionError("Better call Saul ;-)") raise AssertionError("Better call Saul ;-)")
def _grammar_assigned_notifier(self): @property
raise AssertionError("No zombies allowed in any grammar!") def grammar(self) -> 'Grammar':
raise AssertionError("Zombie parser doesn't have a grammar!")
@grammar.setter
def grammar(self, grammar: 'Grammar'):
raise AssertionError('Cannot assign a grammar a zombie parser or vice versa!')
def apply(self, func: ApplyFunc): def apply(self, func: ApplyFunc):
return "Eaten alive..." return "Eaten alive..."
...@@ -1048,8 +1056,8 @@ class PreprocessorToken(Parser): ...@@ -1048,8 +1056,8 @@ class PreprocessorToken(Parser):
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
duplicate = self.__class__(self.pname) duplicate = self.__class__(self.pname)
duplicate.pname = self.pname # duplicate.pname = self.pname # will be written by the constructor, anyway
duplicate.ptype = self.ptype duplicate.tage_name = self.tag_name
return duplicate return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]: def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
...@@ -1102,7 +1110,7 @@ class Token(Parser): ...@@ -1102,7 +1110,7 @@ class Token(Parser):
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
duplicate = self.__class__(self.text) duplicate = self.__class__(self.text)
duplicate.pname = self.pname duplicate.pname = self.pname
duplicate.ptype = self.ptype duplicate.tag_name = self.tag_name
return duplicate return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]: def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
...@@ -1146,7 +1154,7 @@ class RegExp(Parser): ...@@ -1146,7 +1154,7 @@ class RegExp(Parser):
regexp = self.regexp.pattern regexp = self.regexp.pattern
duplicate = self.__class__(regexp) duplicate = self.__class__(regexp)
duplicate.pname = self.pname duplicate.pname = self.pname
duplicate.ptype = self.ptype duplicate.tag_name = self.tag_name
return duplicate return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]: def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
...@@ -1240,7 +1248,7 @@ class UnaryOperator(Parser): ...@@ -1240,7 +1248,7 @@ class UnaryOperator(Parser):
parser = copy.deepcopy(self.parser, memo) parser = copy.deepcopy(self.parser, memo)
duplicate = self.__class__(parser) duplicate = self.__class__(parser)
duplicate.pname = self.pname duplicate.pname = self.pname
duplicate.ptype = self.ptype duplicate.tag_name = self.tag_name
return duplicate return duplicate
def _apply(self, func: ApplyFunc, flip: FlagFunc) -> bool: def _apply(self, func: ApplyFunc, flip: FlagFunc) -> bool:
...@@ -1271,7 +1279,7 @@ class NaryOperator(Parser): ...@@ -1271,7 +1279,7 @@ class NaryOperator(Parser):
parsers = copy.deepcopy(self.parsers, memo) parsers = copy.deepcopy(self.parsers, memo)
duplicate = self.__class__(*parsers) duplicate = self.__class__(*parsers)
duplicate.pname = self.pname duplicate.pname = self.pname
duplicate.ptype = self.ptype duplicate.tag_name = self.tag_name
return duplicate return duplicate
def _apply(self, func: ApplyFunc, flip: FlagFunc) -> bool: def _apply(self, func: ApplyFunc, flip: FlagFunc) -> bool:
...@@ -1314,7 +1322,7 @@ class Option(UnaryOperator): ...@@ -1314,7 +1322,7 @@ class Option(UnaryOperator):
super().__init__(parser) super().__init__(parser)
# assert isinstance(parser, Parser) # assert isinstance(parser, Parser)
assert not isinstance(parser, Option), \ assert not isinstance(parser, Option), \
"Redundant nesting of options: %s%s" % (str(parser.pname), str(self.ptype)) "Redundant nesting of options: %s(%s)" % (self.ptype, parser.pname)
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]: def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
node, text = self.parser(text) node, text = self.parser(text)
...@@ -1393,7 +1401,7 @@ class OneOrMore(UnaryOperator): ...@@ -1393,7 +1401,7 @@ class OneOrMore(UnaryOperator):
super().__init__(parser) super().__init__(parser)
assert not isinstance(parser, Option), \ assert not isinstance(parser, Option), \
"Use ZeroOrMore instead of nesting OneOrMore and Option: " \ "Use ZeroOrMore instead of nesting OneOrMore and Option: " \
"%s(%s)" % (str(self.ptype), str(parser.pname)) "%s(%s)" % (self.ptype, parser.pname)
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]: def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
results = () # type: Tuple[Node, ...] results = () # type: Tuple[Node, ...]
...@@ -1513,7 +1521,7 @@ class Series(NaryOperator): ...@@ -1513,7 +1521,7 @@ class Series(NaryOperator):
duplicate = self.__class__(*parsers, mandatory=self.mandatory, duplicate = self.__class__(*parsers, mandatory=self.mandatory,
err_msgs=self.err_msgs, skip=self.skip) err_msgs=self.err_msgs, skip=self.skip)
duplicate.pname = self.pname duplicate.pname = self.pname
duplicate.ptype = self.ptype duplicate.tag_name = self.tag_name
return duplicate return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]: def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
...@@ -1722,7 +1730,7 @@ class AllOf(NaryOperator): ...@@ -1722,7 +1730,7 @@ class AllOf(NaryOperator):
duplicate = self.__class__(*parsers, mandatory=self.mandatory, duplicate = self.__class__(*parsers, mandatory=self.mandatory,
err_msgs=self.err_msgs, skip=self.skip) err_msgs=self.err_msgs, skip=self.skip)
duplicate.pname = self.pname duplicate.pname = self.pname
duplicate.ptype = self.ptype duplicate.tag_name = self.tag_name
duplicate.num_parsers = self.num_parsers duplicate.num_parsers = self.num_parsers
return duplicate return duplicate
...@@ -2014,7 +2022,7 @@ class Retrieve(Parser): ...@@ -2014,7 +2022,7 @@ class Retrieve(Parser):
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
duplicate = self.__class__(self.symbol, self.filter) duplicate = self.__class__(self.symbol, self.filter)
duplicate.pname = self.pname duplicate.pname = self.pname
duplicate.ptype = self.ptype duplicate.tag_name = self.tag_name
return duplicate return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]: def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
...@@ -2132,7 +2140,7 @@ class Forward(Parser): ...@@ -2132,7 +2140,7 @@ class Forward(Parser):
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
duplicate = self.__class__() duplicate = self.__class__()
# duplicate.pname = self.pname # Forward-Parsers should never have a name! # duplicate.pname = self.pname # Forward-Parsers should never have a name!
duplicate.ptype = self.ptype duplicate.tag_name = self.tag_name
memo[id(self)] = duplicate memo[id(self)] = duplicate
parser = copy.deepcopy(self.parser, memo) parser = copy.deepcopy(self.parser, memo)
duplicate.set(parser) duplicate.set(parser)
......
...@@ -495,10 +495,37 @@ class TestErrorCustomization: ...@@ -495,10 +495,37 @@ class TestErrorCustomization:
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "a user defined error message" assert st.collect_errors()[0].message == "a user defined error message"
def test_multiple_error_messages(self):
lang = """
document = series | /.*/
@series_error = '_', "the underscore is wrong in this place"
@series_error = '*', "the asterix is wrong in this place"
@series_error = /\w/, "wrong letter {0} in place of {1}"
@series_error = "fallback error message"
series = "X" | head §"C" "D"
head = "A" "B"
"""
parser = grammar_provider(lang)()
st = parser("AB*D"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "the asterix is wrong in this place"
# transitivity of mandatory-operator
st = parser("ABC_"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "the underscore is wrong in this place"
st = parser("ABiD"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message.startswith('wrong letter')
st = parser("AB+D"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "fallback error message"
class TestErrorCustomizationErrors:
def test_ambiguous_error_customization(self): def test_ambiguous_error_customization(self):
lang = """ lang = """
document = series document = series
@series_error = "ambiguous error message: does it apply to 'one' or 'two'?" @series_error = "ambiguous error message: does it apply to first or second '§'?"
series = "A" § "B" "C" | "X" § "Y" "Z" series = "A" § "B" "C" | "X" § "Y" "Z"
""" """
try: try:
...@@ -518,6 +545,26 @@ class TestErrorCustomization: ...@@ -518,6 +545,26 @@ class TestErrorCustomization:
result, messages, ast = compile_ebnf(lang) result, messages, ast = compile_ebnf(lang)
assert messages[0].code == Error.UNUSED_ERROR_HANDLING_WARNING assert messages[0].code == Error.UNUSED_ERROR_HANDLING_WARNING
def test_multiple_resume_definitions(self):
lang = """
document = series
@series_resume = /B/, /C/, /D/, /E/, /F/, /G/
@series_resume = /X/, /Y/
series = "A" §"B" "C" "D" "E" "F" "G"
"""
result, messages, ast = compile_ebnf(lang)
assert messages[0].code == Error.REDEFINED_DIRECTIVE
def test_multiple_skip_definitions(self):
lang = """
document = series
@series_skip = /B/, /C/, /D/, /E/, /F/, /G/
@series_skip = /X/, /Y/
series = "A" §"B" "C" "D" "E" "F" "G"
"""
result, messages, ast = compile_ebnf(lang)
assert messages[0].code == Error.REDEFINED_DIRECTIVE
class TestCustomizedResumeParsing: class TestCustomizedResumeParsing:
def setup(self): def setup(self):
......
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