Commit 61e8e4f8 authored by eckhart's avatar eckhart

- 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