Commit dbd41bc6 authored by di68kap's avatar di68kap

- bugfix: multiple error messages now accepted

parent deb408cc
......@@ -906,11 +906,11 @@ class EBNFCompiler(Compiler):
key = node.children[0].content
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:
self.tree.new_error(node, 'Directive "%s" has already been defined earlier. '
% key + 'Later definition will be ignored!',
code=Error.REDEFINED_DIRECTIVE_WARNING)
code=Error.REDEFINED_DIRECTIVE)
return ""
self.defined_directives.add(key)
......@@ -986,9 +986,6 @@ class EBNFCompiler(Compiler):
elif key.endswith('_skip'):
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:
self.tree.new_error(node, 'Skip list for resuming in series for symbol "{}"'
'must be defined before the symbol!'.format(symbol))
......@@ -996,11 +993,7 @@ class EBNFCompiler(Compiler):
elif key.endswith('_resume'):
symbol = key[:-7]
if symbol in self.directives.resume:
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:])
self.directives.resume[symbol] = self._gen_search_list(node.children[1:])
else:
self.tree.new_error(node, 'Unknown directive %s ! (Known ones are %s .)' %
......
......@@ -71,7 +71,6 @@ class Error:
# warning codes
REDEFINED_DIRECTIVE_WARNING = ErrorCode(110)
REDECLARED_TOKEN_WARNING = ErrorCode(120)
UNUSED_ERROR_HANDLING_WARNING = ErrorCode(130)
......@@ -87,6 +86,7 @@ class Error:
CAPTURE_STACK_NOT_EMPTY = ErrorCode(1050)
MALFORMED_ERROR_STRING = ErrorCode(1060)
AMBIGUOUS_ERROR_HANDLING = ErrorCode(1070)
REDEFINED_DIRECTIVE = ErrorCode(1080)
def __init__(self, message: str, pos, code: ErrorCode = ERROR,
orig_pos: int = -1, line: int = -1, column: int = -1) -> None:
......
......@@ -208,7 +208,6 @@ class Parser:
def __init__(self) -> None:
# assert isinstance(name, str), str(name)
self.pname = '' # type: str
self.ptype = ':' + self.__class__.__name__ # type: str
self.tag_name = self.ptype # type: str
if not hasattr(self.__class__, 'alive'):
self._grammar = ZOMBIE_GRAMMAR # type: Grammar
......@@ -223,7 +222,6 @@ class Parser:
"""
duplicate = self.__class__()
duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name
return duplicate
......@@ -233,6 +231,12 @@ class Parser:
def __str__(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
def repr(self) -> str:
"""Returns the parser's name if it has a name and self.__repr___() otherwise."""
......@@ -459,7 +463,6 @@ class ZombieParser(Parser):
def __init__(self):
super().__init__()
self.pname = ZOMBIE
self.ptype = ':' + ZOMBIE
self.tag_name = ZOMBIE
# no need to call super class constructor
assert not self.__class__.alive, "There can be only one!"
......@@ -476,8 +479,13 @@ class ZombieParser(Parser):
def __call__(self, text):
raise AssertionError("Better call Saul ;-)")
def _grammar_assigned_notifier(self):
raise AssertionError("No zombies allowed in any grammar!")
@property
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):
return "Eaten alive..."
......@@ -1048,8 +1056,8 @@ class PreprocessorToken(Parser):
def __deepcopy__(self, memo):
duplicate = self.__class__(self.pname)
duplicate.pname = self.pname
duplicate.ptype = self.ptype
# duplicate.pname = self.pname # will be written by the constructor, anyway
duplicate.tage_name = self.tag_name
return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
......@@ -1102,7 +1110,7 @@ class Token(Parser):
def __deepcopy__(self, memo):
duplicate = self.__class__(self.text)
duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name
return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
......@@ -1146,7 +1154,7 @@ class RegExp(Parser):
regexp = self.regexp.pattern
duplicate = self.__class__(regexp)
duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name
return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
......@@ -1240,7 +1248,7 @@ class UnaryOperator(Parser):
parser = copy.deepcopy(self.parser, memo)
duplicate = self.__class__(parser)
duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name
return duplicate
def _apply(self, func: ApplyFunc, flip: FlagFunc) -> bool:
......@@ -1271,7 +1279,7 @@ class NaryOperator(Parser):
parsers = copy.deepcopy(self.parsers, memo)
duplicate = self.__class__(*parsers)
duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name
return duplicate
def _apply(self, func: ApplyFunc, flip: FlagFunc) -> bool:
......@@ -1314,7 +1322,7 @@ class Option(UnaryOperator):
super().__init__(parser)
# assert isinstance(parser, Parser)
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]:
node, text = self.parser(text)
......@@ -1393,7 +1401,7 @@ class OneOrMore(UnaryOperator):
super().__init__(parser)
assert not isinstance(parser, 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]:
results = () # type: Tuple[Node, ...]
......@@ -1513,7 +1521,7 @@ class Series(NaryOperator):
duplicate = self.__class__(*parsers, mandatory=self.mandatory,
err_msgs=self.err_msgs, skip=self.skip)
duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name
return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
......@@ -1722,7 +1730,7 @@ class AllOf(NaryOperator):
duplicate = self.__class__(*parsers, mandatory=self.mandatory,
err_msgs=self.err_msgs, skip=self.skip)
duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name
duplicate.num_parsers = self.num_parsers
return duplicate
......@@ -2014,7 +2022,7 @@ class Retrieve(Parser):
def __deepcopy__(self, memo):
duplicate = self.__class__(self.symbol, self.filter)
duplicate.pname = self.pname
duplicate.ptype = self.ptype
duplicate.tag_name = self.tag_name
return duplicate
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
......@@ -2132,7 +2140,7 @@ class Forward(Parser):
def __deepcopy__(self, memo):
duplicate = self.__class__()
# 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
parser = copy.deepcopy(self.parser, memo)
duplicate.set(parser)
......
......@@ -495,10 +495,37 @@ class TestErrorCustomization:
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
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):
lang = """
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"
"""
try:
......@@ -518,6 +545,26 @@ class TestErrorCustomization:
result, messages, ast = compile_ebnf(lang)
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:
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