diff --git a/DHParser/parse.py b/DHParser/parse.py index 62dfaab27bfb360406029374ae080fae964479a1..9c8892864afb0133ac107d3dafc94612952d1a90 100644 --- a/DHParser/parse.py +++ b/DHParser/parse.py @@ -436,9 +436,9 @@ class Parser: location = grammar.document_length__ - text._len # faster then len(text)? try: - # rollback variable changing operation if parser backtracks - # to a position before the variable changing operation occurred - if grammar.last_rb__loc__ > location: + # rollback variable changing operation if parser backtracks to a position + # before or at the location where the variable changing operation occurred + if grammar.last_rb__loc__ >= location: grammar.rollback_to__(location) # if location has already been visited by the current parser, return saved result @@ -3080,7 +3080,10 @@ class Pop(Retrieve): node, txt = self.retrieve_and_match(text) if node is not None and not id(node) in self.grammar.tree__.error_nodes: self.values.append(self.grammar.variables__[self.symbol_pname].pop()) - location = self.grammar.document_length__ - text.__len__() + location = self.grammar.document_length__ - text.__len__() - 1 + # if node is not EMPTY_NODE and len(node) == 0: + # location = self.grammar.document_length__ - txt.__len__() - 1 + # print('PUSH:', self.symbol_pname, location, self.values[-1]) self.grammar.push_rollback__(location, self._rollback) # lambda: stack.append(value)) return node, txt @@ -3210,7 +3213,7 @@ class Forward(UnaryParser): location = grammar.document_length__ - text._len # rollback variable changing operation if parser backtracks # to a position before the variable changing operation occurred - if grammar.last_rb__loc__ > location: + if grammar.last_rb__loc__ >= location: grammar.rollback_to__(location) # if location has already been visited by the current parser, return saved result diff --git a/tests/test_parse.py b/tests/test_parse.py index 961326f9c95d1c90b8730bd48d37a0e837fb4c29..02621dacedeb07a7bea1289bd977eb171ea60707 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -113,9 +113,9 @@ class TestParserClass: class TestInfiLoopsAndRecursion: def setup(self): pass - set_config_value('history_tracking', True) + # set_config_value('history_tracking', True) # set_config_value('resume_notices', True) - start_logging('LOGS') + # start_logging('LOGS') def test_very_simple(self): minilang = """ @@ -204,7 +204,7 @@ class TestInfiLoopsAndRecursion: # BEWARE: EXPERIMENTAL TEST can be long running def test_indirect_left_recursion2(self): - arithmetic_syntax = """@literalws = right + arithmetic_syntax = r"""@literalws = right expression = addition | subtraction # | term addition = (expression | term) "+" (expression | term) subtraction = (expression | term) "-" (expression | term) @@ -226,7 +226,7 @@ class TestInfiLoopsAndRecursion: log_parsing_history(arithmetic, "test_LeftRecursion_indirect2") def test_indirect_left_recursion3(self): - arithmetic_syntax = """@literalws = right + arithmetic_syntax = r"""@literalws = right expression = addition | subtraction | term addition = (expression | term) "+" (expression | term) subtraction = (expression | term) "-" (expression | term) @@ -718,7 +718,7 @@ class TestPopRetrieve: mini_language = r""" document = { text | codeblock } codeblock = delimiter { text | (!:delimiter delimiter_sign) } ::delimiter - delimiter = delimiter_sign # never use delimiter between capture and pop except for retrival! + delimiter = delimiter_sign # never use delimiter between capture and pop except for retrieval! delimiter_sign = /`+/ text = /[^`]+/ """ @@ -805,21 +805,31 @@ class TestPopRetrieve: assert not syntax_tree.error_flag, str(syntax_tree.errors_sorted) def test_optional_match(self): - test1 = 'Hey, you' - st = self.minilang_parser4(test1) - assert not st.error_flag, str(st.errors_sorted) - test12 = 'Hey, you' - st = self.minilang_parser4(test1) - assert not st.error_flag + # from DHParser.dsl import compileEBNF + # src = compileEBNF(self.mini_lang4) + # print(src) + # return + + # test1 = 'Hey, you' + # st = self.minilang_parser4(test1) + # assert not st.error_flag, str(st.errors_sorted) + # test12 = 'Hey, you' + # st = self.minilang_parser4(test1) + # assert not st.error_flag test2 = 'Hey, you' + set_config_value('history_tracking', True) + set_tracer(self.minilang_parser4, trace_history) + start_logging('LOGS') st = self.minilang_parser4(test2) + log_parsing_history(self.minilang_parser4, "optional_match") + print(st.as_sxpr()) assert not st.error_flag - test3 = 'Hey, you' - st = self.minilang_parser4(test3) - assert not st.error_flag - test4 = 'Hey, you' - st = self.minilang_parser4(test4) - assert not st.error_flag + # test3 = 'Hey, you' + # st = self.minilang_parser4(test3) + # assert not st.error_flag + # test4 = 'Hey, you' + # st = self.minilang_parser4(test4) + # assert not st.error_flag def test_rollback_behaviour_of_optional_match(self): test1 = 'Hey, you' @@ -831,7 +841,7 @@ class TestPopRetrieve: assert not self.minilang_parser4.variables__['name'] assert st.error_flag - def test_cache_neutrality(self): + def test_cache_neutrality_1(self): """Test that packrat-caching does not interfere with the variable- changing parsers: Capture and Retrieve.""" lang = r"""@literalws = right @@ -848,6 +858,25 @@ class TestPopRetrieve: st = gr(case) assert not st.error_flag, str(st.errors_sorted) + def test_cache_neutrality_2(self): + lang = r'''document = variantA | variantB + variantA = delimiter `X` ::delimiter `!` + variantB = `A` delimiter ::delimiter `!` + delimiter = `A` | `X` + ''' + gr = grammar_provider(lang)() + case = 'AXA!' + st = gr(case) + assert not st.errors + + case = 'AXX!' + set_config_value('history_tracking', True) + start_logging('LOGS') + set_tracer(gr, trace_history) + st = gr(case) + log_parsing_history(gr, 'test_cache_neutrality_2') + print(st.as_sxpr()) + def test_single_line(self): teststr = "Anfang ```code block `` <- keine Ende-Zeichen ! ``` Ende" syntax_tree = self.minilang_parser(teststr) @@ -1502,3 +1531,4 @@ class TestStaticAnalysis: if __name__ == "__main__": from DHParser.testing import runner runner("", globals()) +