2.12.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

Commit fcb93994 authored by Eckhart Arnold's avatar Eckhart Arnold
Browse files

- support for user defined error messages added

parent e3c33e3d
......@@ -423,7 +423,8 @@ class EBNFCompiler(Compiler):
'comment': '',
'literalws': {'right'},
'tokens': set(), # alt. 'preprocessor_tokens'
'filter': dict()} # alt. 'filter'
'filter': dict(), # alt. 'filter'
'error': dict()} # customized error messages
# self.directives['ignorecase']: False
self.defined_directives = set() # type: Set[str]
self.grammar_id += 1
......@@ -793,6 +794,17 @@ class EBNFCompiler(Compiler):
% (key, str(filter_set)))
self.directives['filter'][key[:-7]] = filter_set.pop()
elif key.endswith('_error'):
error_msg = node.children[1].content
if not isinstance(error_msg, str):
self.tree.new_error(node, 'Directive "%s" requires message string as argument'
% (key, str(filter_set)))
symbol = key[:-6]
if symbol in self.rules:
self.tree.new_error(node, 'Custom error message for symbol "%s"' % symbol
+ 'must be defined before the symbol!')
self.directives['error'][symbol] = error_msg
else:
self.tree.new_error(node, 'Unknown directive %s ! (Known ones are %s .)' %
(key, ', '.join(list(self.directives.keys()))))
......@@ -837,6 +849,12 @@ class EBNFCompiler(Compiler):
compiled = self.non_terminal(node, 'Required')
else:
custom_args = ['mandatory=%i' % mandatory_marker[0]] if mandatory_marker else []
# add custom error message if it has been declared for the currend definition
if custom_args:
current_symbol = next(reversed(self.rules.keys()))
msg = self.directives['error'].get(current_symbol, '')
if msg:
custom_args.append('errmsg=' + msg)
compiled = self.non_terminal(node, 'Series', custom_args)
node.result = saved_result
return compiled
......
......@@ -1219,7 +1219,14 @@ class Series(NaryOperator):
the series.
Attributes:
mandatory (int): Starting
mandatory (int): Number of the element statring at which the element
and all following elements are considered "mandatory". This
means that rather than returning a non-match an error message
is isssued. The default value is Series.NOPE, which means that
no elements are mandatory.
errmsg (str): An optional error message that overrides the default
message for mandatory continuation errors. This can be used to
provide more helpful error messages to the user.
Example::
......@@ -1236,7 +1243,7 @@ class Series(NaryOperator):
RX_ARGUMENT = re.compile(r'\s(\S)')
NOPE = 1000
def __init__(self, *parsers: Parser, mandatory: int = NOPE) -> None:
def __init__(self, *parsers: Parser, mandatory: int = NOPE, errmsg: str="") -> None:
super().__init__(*parsers)
length = len(self.parsers)
assert 1 <= length < Series.NOPE, \
......@@ -1245,10 +1252,11 @@ class Series(NaryOperator):
mandatory += length
assert 0 <= mandatory < length or mandatory == Series.NOPE
self.mandatory = mandatory
self.errmsg = errmsg
def __deepcopy__(self, memo):
parsers = copy.deepcopy(self.parsers, memo)
duplicate = self.__class__(*parsers, mandatory=self.mandatory)
duplicate = self.__class__(*parsers, mandatory=self.mandatory, errmsg=self.errmsg)
duplicate.name = self.name
duplicate.ptype = self.ptype
return duplicate
......@@ -1272,8 +1280,12 @@ class Series(NaryOperator):
node.errors.append(Error("§ %s violation" % parser.repr,
location, Error.MESSAGE))
if not mandatory_violation:
msg = '%s expected, "%s" found!' \
% (parser.repr, text_[:10].replace('\n', '\\n '))
found = text_[:10].replace('\n', '\\n ')
if self.errmsg:
msg = self.errmsg % found if self.errmsg.find("%s") >= 0 \
else self.errmsg
else:
msg = '%s expected, "%s" found!' % (parser.repr, found)
mandatory_violation = Error(msg, location, Error.MANDATORY_CONTINUATION)
text_ = text_[i:]
results += (node,)
......@@ -1681,6 +1693,14 @@ class Retrieve(Parser):
`Retrieve` and the `Pop` parser.)
The constructor parameter `symbol` determines which variable is
used.
Attributes:
symbol: The parser that has stored the value to be retrieved, in
other words: "the observed parser"
rfilter: a procedure that through which the processing to the
retrieved symbols is channeld. In the simplemost case it merely
returns the last string stored by the observed parser. This can
be (mis-)used to execute any kind of semantic action.
"""
def __init__(self, symbol: Parser, rfilter: RetrieveFilter = None) -> None:
......
......@@ -2,3 +2,4 @@
# CFLAGS="-O3 -march=native -mtune=native"
python3 setup.py build_ext --inplace
strip `ls DHParser/*.so`
......@@ -29,7 +29,8 @@ try:
except ImportError:
import re
from DHParser.error import linebreaks, line_col
from DHParser.error import linebreaks, line_col, Error
from DHParser.dsl import grammar_provider, CompilationError
class TestErrorSupport:
......@@ -70,6 +71,50 @@ class TestErrorSupport:
self.mini_suite(s, linebreaks(s), 1)
class TestCuratedErrors:
"""
Cureted Errors replace existing errors with alternative
error codes and messages that are more helptful to the user.
"""
def test_user_error_declaration(self):
lang = """
document = series | /.*/
series = "X" | head §"C" "D"
head = "A" "B"
@series_error = "a user defined error message"
"""
try:
parser = grammar_provider(lang)()
assert False, "Error definition after symbol definition should fail!"
except CompilationError as e:
pass
def test_curated_mandatory_continuation(self):
lang = """
document = series | /.*/
@series_error = "a user defined error message"
series = "X" | head §"C" "D"
head = "A" "B"
"""
# from DHParser.dsl import compileDSL
# from DHParser.preprocess import nil_preprocessor
# from DHParser.ebnf import get_ebnf_grammar, get_ebnf_transformer, get_ebnf_compiler
# grammar_src = compileDSL(lang, nil_preprocessor, get_ebnf_grammar(),
# get_ebnf_transformer(), get_ebnf_compiler("test", lang))
# print(grammar_src)
parser = grammar_provider(lang)()
st = parser("X"); assert not st.error_flag
st = parser("ABCD"); assert not st.error_flag
st = parser("A_CD"); assert not st.error_flag
st = parser("AB_D"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "a user defined error message"
# 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 == "a user defined error message"
if __name__ == "__main__":
from DHParser.testing import runner
runner("", globals())
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