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

Commit db944887 authored by eckhart's avatar eckhart

- step one for all of mandatory support

parent 10e26fc0
......@@ -1076,8 +1076,7 @@ class EBNFCompiler(Compiler):
def on_term(self, node) -> str:
filtered_result, custom_args = self._error_customization(node)
mock_node = Node(node.parser, filtered_result)
compiled = self.non_terminal(mock_node, 'Series', custom_args)
return compiled
return self.non_terminal(mock_node, 'Series', custom_args)
def on_factor(self, node: Node) -> str:
......@@ -1155,13 +1154,14 @@ class EBNFCompiler(Compiler):
# return self.non_terminal(node, 'Unordered')
assert len(node.children) == 1
nd = node.children[0]
for child in nd.children:
if child.parser.ptype == TOKEN_PTYPE and nd.content == "§":
self.tree.new_error(node, "No mandatory items § allowed in Unordered sequences.")
args = ', '.join(self.compile(child) for child in nd.children)
if nd.parser.name == "term":
return "AllOf(" + args + ")"
filtered_result, custom_args = self._error_customization(nd)
mock_node = Node(nd.parser, filtered_result)
return self.non_terminal(mock_node, 'AllOf', custom_args)
elif nd.parser.name == "expression":
if any(c.parser.ptype == TOKEN_PTYPE and nd.content == '§' for c in nd.children):
self.tree.new_error(node, "No mandatory items § allowed in SomeOf-operator!")
args = ', '.join(self.compile(child) for child in nd.children)
return "SomeOf(" + args + ")"
else:
self.tree.new_error(node, "Unordered sequence or alternative "
......
......@@ -1347,6 +1347,10 @@ class OneOrMore(UnaryOperator):
and not self.parser.name else self.parser.repr) + '}+'
MessagesType = List[Tuple[Union[str, Any], str]]
NO_MANDATORY = 1000
class Series(NaryOperator):
r"""
Matches if each of a series of parsers matches exactly in the order of
......@@ -1356,7 +1360,7 @@ class Series(NaryOperator):
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
is isssued. The default value is NO_MANDATORY, 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
......@@ -1375,27 +1379,28 @@ class Series(NaryOperator):
EBNF-Example: ``series = letter letter_or_digit``
"""
RX_ARGUMENT = re.compile(r'\s(\S)')
NOPE = 1000
MessagesType = List[Tuple[Union[str, Any], str]]
def __init__(self, *parsers: Parser,
mandatory: int = NOPE,
mandatory: int = NO_MANDATORY,
err_msgs: MessagesType=[],
skip: ResumeList = []) -> None:
super().__init__(*parsers)
assert not (mandatory == Series.NOPE and err_msgs), \
length = len(self.parsers)
if mandatory < 0:
mandatory += length
assert not (mandatory == NO_MANDATORY and err_msgs), \
'Custom error messages require that parameter "mandatory" is set!'
assert not (mandatory == Series.NOPE and skip), \
assert not (mandatory == NO_MANDATORY and skip), \
'Search expressions for skipping text require that parameter "mandatory" is set!'
length = len(self.parsers)
assert length > 0, \
'Length of series %i is below minimum length of 1' % length
assert length < Series.NOPE, \
'Length %i of series exceeds maximum length of %i' % (length, Series.NOPE)
if mandatory < 0:
mandatory += length
assert 0 <= mandatory < length or mandatory == Series.NOPE
assert length < NO_MANDATORY, \
'Length %i of series exceeds maximum length of %i' % (length, NO_MANDATORY)
assert 0 <= mandatory < length or mandatory == NO_MANDATORY
self.mandatory = mandatory # type: int
self.err_msgs = err_msgs # type: Series.MessagesType
self.skip = skip # type: ResumeList
......@@ -1460,7 +1465,7 @@ class Series(NaryOperator):
def __repr__(self):
return " ".join([parser.repr for parser in self.parsers[:self.mandatory]]
+ (['§'] if self.mandatory != Series.NOPE else [])
+ (['§'] if self.mandatory != NO_MANDATORY else [])
+ [parser.repr for parser in self.parsers[self.mandatory:]])
# The following operator definitions add syntactical sugar, so one can write:
......@@ -1473,13 +1478,13 @@ class Series(NaryOperator):
parsers `left` and `right` are joined to a sequence.
"""
left_mandatory, left_length = (left.mandatory, len(left.parsers)) \
if isinstance(left, Series) else (Series.NOPE, 1)
if left_mandatory != Series.NOPE:
if isinstance(left, Series) else (NO_MANDATORY, 1)
if left_mandatory != NO_MANDATORY:
return left_mandatory
right_mandatory = right.mandatory if isinstance(right, Series) else Series.NOPE
if right_mandatory != Series.NOPE:
right_mandatory = right.mandatory if isinstance(right, Series) else NO_MANDATORY
if right_mandatory != NO_MANDATORY:
return right_mandatory + left_length
return Series.NOPE
return NO_MANDATORY
def __add__(self, other: Parser) -> 'Series':
other_parsers = cast('Series', other).parsers if isinstance(other, Series) \
......@@ -1586,18 +1591,50 @@ class AllOf(NaryOperator):
EBNF-Example: ``set = <letter letter_or_digit>``
"""
def __init__(self, *parsers: Parser) -> None:
if len(parsers) == 1 and isinstance(parsers[0], Series):
def __init__(self, *parsers: Parser,
mandatory: int = NO_MANDATORY,
err_msgs: MessagesType = [],
skip: ResumeList = []) -> None:
if len(parsers) == 1:
assert isinstance(parsers[0], Series), \
"Parser-specification Error: No single arguments other than a Series " \
"allowed as arguments for AllOf-Parser !"
series = cast(Series, parsers[0])
assert series.mandatory == Series.NOPE, \
"AllOf cannot contain mandatory (§) elements!"
"AllOf should be initialized either with a series or with more than one parser!"
series = cast(Series, parsers[0]) # type: Series
if mandatory == NO_MANDATORY:
mandatory = series.mandatory
if not err_msgs:
err_msgs = series.err_msgs
if not skip:
skip = series.skip
assert series.mandatory == NO_MANDATORY or mandatory == series.mandatory, \
"If AllOf is initialized with a series, parameter 'mandatory' must be the same!"
assert not series.err_msgs or err_msgs == series.err_msgs, \
"If AllOf is initialized with a series, 'err_msg' must empty or the same!"
assert not series.skip or skip == series.skip, \
"If AllOf is initialized with a series, 'skip' must empty or the same!"
parsers = series.parsers
super().__init__(*parsers)
num = len(self.parsers)
if mandatory < 0:
mandatory += num
assert not (mandatory == NO_MANDATORY and err_msgs), \
'Custom error messages require that parameter "mandatory" is set!'
assert not (mandatory == NO_MANDATORY and skip), \
'Search expressions for skipping text require that parameter "mandatory" is set!'
assert num > 0, \
'Number of elements %i is below minimum of 1' % num
assert num < NO_MANDATORY, \
'Number of elemnts %i of exceeds maximum of %i' % (num, NO_MANDATORY)
assert 0 <= mandatory < num or mandatory == NO_MANDATORY
self.mandatory = mandatory # type: int
self.err_msgs = err_msgs # type: Series.MessagesType
self.skip = skip # type: ResumeList
# TODO: Add check for mandatory items here, just like with Series-Operator
def __call__(self, text: StringView) -> Tuple[Optional[Node], StringView]:
results = () # type: Tuple[Node, ...]
text_ = text # type: StringView
......
......@@ -522,21 +522,21 @@ class TestErrorCustomization:
class TestCustomizedResumeParsing:
def setup(self):
lang = """
@ alpha_resume = 'BETA', GAMMA_STR
@ beta_resume = GAMMA_RE
@ bac_resume = /GA\w+/
document = alpha [beta] gamma "."
alpha = "ALPHA" abc
abc = §"a" "b" "c"
beta = "BETA" (bac | bca)
bac = "b" "a" §"c"
bca = "b" "c" §"a"
gamma = "GAMMA" §(cab | cba)
cab = "c" "a" §"b"
cba = "c" "b" §"a"
GAMMA_RE = /GA\w+/
GAMMA_STR = "GAMMA"
"""
@ alpha_resume = 'BETA', GAMMA_STR
@ beta_resume = GAMMA_RE
@ bac_resume = /GA\w+/
document = alpha [beta] gamma "."
alpha = "ALPHA" abc
abc = §"a" "b" "c"
beta = "BETA" (bac | bca)
bac = "b" "a" §"c"
bca = "b" "c" §"a"
gamma = "GAMMA" §(cab | cba)
cab = "c" "a" §"b"
cba = "c" "b" §"a"
GAMMA_RE = /GA\w+/
GAMMA_STR = "GAMMA"
"""
try:
self.gr = grammar_provider(lang)()
except CompilationError as ce:
......@@ -575,12 +575,11 @@ class TestCustomizedResumeParsing:
class TestInSeriesResume:
def setup(self):
lang = """
document = series
@series_skip = /B/, /C/, /D/, /E/, /F/, /G/
series = "A" §"B" "C" "D" "E" "F" "G"
"""
document = series
@series_skip = /B/, /C/, /D/, /E/, /F/, /G/
series = "A" §"B" "C" "D" "E" "F" "G"
"""
try:
result, _, _ = compile_ebnf(lang)
self.gr = grammar_provider(lang)()
except CompilationError as ce:
print(ce)
......@@ -614,6 +613,25 @@ class TestInSeriesResume:
errors = st.collect_errors()
assert len(errors) >= 1 # cannot really recover from permutation errors
class TestInAllOfResume:
def setup(self):
lang = """
document = allof
@allof_skip = /A/, /B/, /C/, /D/, /E/, /F/, /G/
allof = "A" "B" § "C" "D" "E" "F" "G"
"""
try:
self.gr = grammar_provider(lang)()
except CompilationError as ce:
print(ce)
def test_garbage_added(self):
st = self.gr('GFCB XYZ AED')
errors = st.collect_errors()
print(errors)
if __name__ == "__main__":
from DHParser.testing import runner
......
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