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 db944887 authored by eckhart's avatar eckhart
Browse files

- 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