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

- DHParser/parse.py Parse.__init__() and Parse.reset() move clearing of...

- DHParser/parse.py Parse.__init__() and Parse.reset() move clearing of cycle-detection buffer to __init__ to avoid infinite loop if reset() is called during the Parser.apply() for some reason
parent 0c797ed8
......@@ -94,7 +94,6 @@ class Error:
orig_pos: int = -1, line: int = -1, column: int = -1) -> None:
assert isinstance(code, ErrorCode)
assert not isinstance(pos, ErrorCode)
assert pos >= 0
assert code >= 0
self.message = message # type: str
self._pos = pos # type: int
......@@ -117,6 +116,12 @@ class Error:
def pos(self):
return self._pos
@pos.setter
def pos(self, value: int):
self._pos = value
# reset line and column values, because they might now not be valid any more
self.line, self.column = -1, -1
@property
def severity(self):
"""Returns a string representation of the error level, e.g. "warning"."""
......
......@@ -221,8 +221,9 @@ class Parser:
def __init__(self) -> None:
# assert isinstance(name, str), str(name)
self.pname = '' # type: str
self.tag_name = self.ptype # type: str
self.pname = '' # type: str
self.tag_name = self.ptype # type: str
self.cycle_detection = set() # type: Set[ApplyFunc]
try:
self._grammar = GRAMMAR_PLACEHOLDER # type: Grammar
except NameError:
......@@ -264,7 +265,6 @@ class Parser:
`reset()`-method of the derived class."""
self.visited = dict() # type: Dict[int, Tuple[Optional[Node], StringView]]
self.recursion_counter = defaultdict(int) # type: DefaultDict[int, int]
self.cycle_detection = set() # type: Set[ApplyFunc]
@cython.locals(location=cython.int, gap=cython.int, i=cython.int)
def __call__(self: 'Parser', text: StringView) -> Tuple[Optional[Node], StringView]:
......@@ -710,6 +710,7 @@ class Grammar:
# some default values
# COMMENT__ = r'' # type: str # r'#.*(?:\n|$)'
# WSP_RE__ = mixin_comment(whitespace=r'[\t ]*', comment=COMMENT__) # type: str
static_analysis_done__ = False
@classmethod
......@@ -770,6 +771,15 @@ class Grammar:
assert 'root_parser__' in self.__dict__
assert self.root_parser__ == self.__dict__['root_parser__']
if not self.__class__.static_analysis_done__:
try:
result = self.static_analysis()
if result:
raise AssertionError(str(result))
self.__class__.static_analysis_done__ = True
except (NameError, AttributeError):
pass # don't fail the initialization of PLACEHOLDER
def __getitem__(self, key):
try:
......@@ -784,9 +794,11 @@ class Grammar:
return self[key]
raise UnknownParserError('Unknown parser "%s" !' % key)
def __contains__(self, key):
return key in self.__dict__ or hasattr(self, key)
def _reset__(self):
self.tree__ = RootNode() # type: RootNode
self.document__ = EMPTY_STRING_VIEW # type: StringView
......@@ -1009,6 +1021,37 @@ class Grammar:
return line_col(self.document_lbreaks__, self.document_length__ - len(text))
def static_analysis(self) -> List[Tuple[str, Parser, Error]]:
"""
Checks the parser tree statically for possible errors. At the moment only
infinite loops will be detected.
:return: a list of error-tuples consisting of the narrowest containing
named parser (i.e. the symbol on which the failure occurred),
the actual parser that failed and an error object.
"""
containing_named_parser = '' # type: str
error_list = [] # type: List[Tuple[str, Parser, Error]]
def visit_parser(parser: Parser) -> None:
nonlocal containing_named_parser, error_list
if parser.pname:
containing_named_parser = parser.pname
if isinstance(parser, ZeroOrMore) or isinstance(parser, OneOrMore):
inner_parser = cast(UnaryParser, parser).parser
tree = self('', inner_parser)
if not tree.error_flag:
if not parser.pname:
msg = 'Parser "%s" in %s can become caught up in an infinite loop!' \
% (str(parser), containing_named_parser)
else:
msg = 'Parser "%s" can become caught up in an infinite loop!' % str(parser)
error_list.append((containing_named_parser, parser,
Error(msg, -1, Error.INFINITE_LOOP)))
self.root_parser__.apply(visit_parser)
return error_list
def dsl_error_msg(parser: Parser, error_str: str) -> str:
"""
Returns an error message for errors in the parser configuration,
......
......@@ -113,11 +113,17 @@ class TestInfiLoopsAndRecursion:
log_parsing_history(parser, "test_LeftRecursion_indirect")
def test_infinite_loops(self):
minilang = """not_forever = { // } \n"""
minilang = """forever = { // } \n"""
snippet = " "
parser = grammar_provider(minilang)()
syntax_tree = parser(snippet)
assert syntax_tree.error_flag
assert any(e.code == Error.INFINITE_LOOP for e in syntax_tree.errors)
res = parser.static_analysis()
assert res and res[0][2].code == Error.INFINITE_LOOP
minilang = """not_forever = { / / } \n"""
parser = grammar_provider(minilang)()
res = parser.static_analysis()
assert not res
class TestFlowControl:
......
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