Loading DHParser/parse.py +3 −15 Original line number Diff line number Diff line Loading @@ -1935,27 +1935,15 @@ class MandatoryElementsParser(NaryParser): for pname, _ in reversed(grammar.call_stack__): if not pname.startswith(':'): break msg = '%s expected by parser %s, »%s« found!' % (expected, pname, found) msg = '%s expected by parser %s, »%s« found!' % (expected, repr(pname), found) else: msg = '%s expected, »%s« found!' % (expected, found) error = Error(msg, location, Error.MANDATORY_CONTINUATION_AT_EOF if (failed_on_lookahead and not text_) else Error.MANDATORY_CONTINUATION) grammar.tree__.add_error(err_node, error) if reloc >= 0: errors = [error] if grammar.resume_notices__: target = text_[reloc:] if len(target) >= 10: target = target[:7] + '...' notice = Error('Skipping within parser {} to point {}' .format(self.pname or self.ptype, repr(target)), self._grammar.document_length__ - len(text_), Error.RESUME_NOTICE) grammar.tree__.add_error(err_node, notice) errors.append(notice) if grammar.history_tracking__: lc = line_col(grammar.document_lbreaks__, location) grammar.history__.append(HistoryRecord( grammar.call_stack__, None, text_, lc, errors)) # signal error to tracer directly, because this error is not raised! grammar.most_recent_error__ = ParserError(err_node, text_, error, first_throw=False) return error, err_node, text_[i:] Loading DHParser/trace.py +35 −14 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ from typing import Tuple, Optional, List, Iterable, Union from DHParser.error import Error, line_col from DHParser.stringview import StringView from DHParser.syntaxtree import Node, REGEXP_PTYPE, TOKEN_PTYPE, WHITESPACE_PTYPE, PLACEHOLDER from DHParser.syntaxtree import Node, REGEXP_PTYPE, TOKEN_PTYPE, WHITESPACE_PTYPE, ZOMBIE_TAG from DHParser.log import HistoryRecord from DHParser.parse import Grammar, Parser, ParserError, ParseFunc Loading @@ -45,18 +45,37 @@ def trace_history(self: Parser, text: StringView) -> Tuple[Optional[Node], Strin # `parse.MandatoryElementsParser.mandatory_violation()` pe = grammar.most_recent_error__ grammar.most_recent_error__ = None errors = [pe.error] # ignore inflated length due to gap jumping (see parse.Parser.__call__) l = sum(len(nd) for nd in pe.node.select_if(lambda n: True, include_root=True) if not nd.children and nd.tag_name != ZOMBIE_TAG) text_ = pe.rest[l:] lc = line_col(grammar.document_lbreaks__, pe.error.pos) # grammar.history__.append( # HistoryRecord(grammar.call_stack__, pe.node, text_, lc, [pe.error])) if grammar.resume_notices__: text_ = pe.rest[len(pe.node):] target = text_ target = text if len(target) >= 10: target = target[:7] + '...' if pe.first_throw: # resume notice notice = Error('Resuming from parser "{}" with parser "{}" at point: {}' .format(pe.node.tag_name, grammar.call_stack__[-1][0], repr(target)), .format(pe.node.tag_name, grammar.call_stack__[-1][0], repr(target)), grammar.document_length__ - len(text_), Error.RESUME_NOTICE) else: # skip notice notice = Error('Skipping within parser {} to point {}' .format(grammar.call_stack__[-1][0], repr(target)), self._grammar.document_length__ - len(text_), Error.RESUME_NOTICE) grammar.tree__.add_error(pe.node, notice) lc = line_col(grammar.document_lbreaks__, location) errors.append(notice) grammar.history__.append(HistoryRecord( grammar.call_stack__, None, text_, lc, [notice])) grammar.call_stack__, pe.node, text_, lc, errors)) grammar.call_stack__.append( ((self.repr if self.tag_name in (REGEXP_PTYPE, TOKEN_PTYPE) Loading @@ -64,16 +83,18 @@ def trace_history(self: Parser, text: StringView) -> Tuple[Optional[Node], Strin grammar.moving_forward__ = True try: node, rest = self._parse(text) node, rest = self._parse(text) # <===== call to the actual parser! except ParserError as pe: grammar.call_stack__.pop() if pe.first_throw: # add error message to history grammar.most_recent_error__ = pe lc = line_col(grammar.document_lbreaks__, pe.error.pos) nd = pe.node if self == grammar.start_parser__: fe = grammar.most_recent_error__ lc = line_col(grammar.document_lbreaks__, fe.error.pos) # TODO: get the call stack from when the error occured, here nd = fe.node grammar.history__.append( HistoryRecord(grammar.call_stack__, nd, pe.rest[len(nd):], lc, [pe.error])) HistoryRecord(grammar.call_stack__, nd, fe.rest[len(nd):], lc, [fe.error])) raise pe # Mind that memoized parser calls will not appear in the history record! Loading test/test_trace.py +21 −14 Original line number Diff line number Diff line Loading @@ -26,12 +26,15 @@ import sys scriptpath = os.path.dirname(__file__) or '.' sys.path.append(os.path.abspath(os.path.join(scriptpath, '..'))) REVEAL = False from DHParser import grammar_provider, all_descendants, \ set_tracer, trace_history, log_parsing_history, start_logging, log_dir, \ set_config_value, resume_notices_on, Error def get_history(name, show: bool = False) -> str: def get_history(name, show: bool = REVEAL) -> str: history_fname = os.path.join(log_dir() or '', name + "_full_parser.log.html") if show: import webbrowser, time Loading @@ -42,6 +45,12 @@ def get_history(name, show: bool = False) -> str: return history_file def reveal(grammar, name): if REVEAL: log_parsing_history(grammar, name) get_history(name, show=True) class TestTrace: def setup(self): start_logging() Loading Loading @@ -186,8 +195,7 @@ class TestErrorReporting: break else: assert False, "Missing Error!" # log_parsing_history(gr, 'trace_noskip') # get_history('trace_noskip') reveal(gr, 'trace_noskip') def test_trace_skip_clause(self): lang = """ Loading @@ -197,15 +205,15 @@ class TestErrorReporting: """ gr = grammar_provider(lang)() resume_notices_on(gr) _ = gr('AB_D') st = gr('AB_D') assert 'Skipping' in str(st.errors_sorted[1]) for record in gr.history__: if record.status.startswith(record.ERROR): assert record.excerpt == '_D' break else: assert False, "Missing Error!" # log_parsing_history(gr, 'trace_skip_clause') # get_history('trace_skip_clause') reveal(gr, 'trace_skip_clause') def test_trace_resume(self): gr = self.gr; gr.resume_rules = dict() Loading @@ -218,8 +226,7 @@ class TestErrorReporting: assert cst.pick('alpha').content.startswith('ALPHA') # because of resuming, there should be only one error message assert len([err for err in cst.errors_sorted if err.code >= 1000]) == 1 # log_parsing_history(gr, 'trace_resume') # get_history('trace_resume') reveal(gr, 'trace_resume') def test_trace_resume_complex_case(self): lang = r""" Loading @@ -232,19 +239,19 @@ class TestErrorReporting: def mini_suite(grammar): tree = grammar('abc/*x*/xyz') assert not tree.errors tree = grammar('abDxyz') mandatory_cont = (Error.MANDATORY_CONTINUATION, Error.MANDATORY_CONTINUATION_AT_EOF) assert len(tree.errors) > 1 and tree.errors[0].code in mandatory_cont # log_parsing_history(grammar, 'trace_resume_complex_1') # get_history('trace_resume_complex_1') reveal(grammar, 'trace_resume_complex_1') tree = grammar('abD/*x*/xyz') assert len(tree.errors) > 1 and tree.errors[0].code in mandatory_cont # log_parsing_history(grammar, 'trace_resume_complex_2') # get_history('trace_resume_complex_2', show=True) reveal(grammar, 'trace_resume_complex_2') tree = grammar('aD /*x*/ c /* a */ /*x*/xyz') assert len(tree.errors) > 1 and tree.errors[0].code in mandatory_cont # log_parsing_history(grammar, 'trace_resume_complex_3') # get_history('trace_resume_complex_3') reveal(grammar, 'trace_resume_complex_3') # test regex-defined resume rule grammar = grammar_provider(lang)() Loading Loading
DHParser/parse.py +3 −15 Original line number Diff line number Diff line Loading @@ -1935,27 +1935,15 @@ class MandatoryElementsParser(NaryParser): for pname, _ in reversed(grammar.call_stack__): if not pname.startswith(':'): break msg = '%s expected by parser %s, »%s« found!' % (expected, pname, found) msg = '%s expected by parser %s, »%s« found!' % (expected, repr(pname), found) else: msg = '%s expected, »%s« found!' % (expected, found) error = Error(msg, location, Error.MANDATORY_CONTINUATION_AT_EOF if (failed_on_lookahead and not text_) else Error.MANDATORY_CONTINUATION) grammar.tree__.add_error(err_node, error) if reloc >= 0: errors = [error] if grammar.resume_notices__: target = text_[reloc:] if len(target) >= 10: target = target[:7] + '...' notice = Error('Skipping within parser {} to point {}' .format(self.pname or self.ptype, repr(target)), self._grammar.document_length__ - len(text_), Error.RESUME_NOTICE) grammar.tree__.add_error(err_node, notice) errors.append(notice) if grammar.history_tracking__: lc = line_col(grammar.document_lbreaks__, location) grammar.history__.append(HistoryRecord( grammar.call_stack__, None, text_, lc, errors)) # signal error to tracer directly, because this error is not raised! grammar.most_recent_error__ = ParserError(err_node, text_, error, first_throw=False) return error, err_node, text_[i:] Loading
DHParser/trace.py +35 −14 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ from typing import Tuple, Optional, List, Iterable, Union from DHParser.error import Error, line_col from DHParser.stringview import StringView from DHParser.syntaxtree import Node, REGEXP_PTYPE, TOKEN_PTYPE, WHITESPACE_PTYPE, PLACEHOLDER from DHParser.syntaxtree import Node, REGEXP_PTYPE, TOKEN_PTYPE, WHITESPACE_PTYPE, ZOMBIE_TAG from DHParser.log import HistoryRecord from DHParser.parse import Grammar, Parser, ParserError, ParseFunc Loading @@ -45,18 +45,37 @@ def trace_history(self: Parser, text: StringView) -> Tuple[Optional[Node], Strin # `parse.MandatoryElementsParser.mandatory_violation()` pe = grammar.most_recent_error__ grammar.most_recent_error__ = None errors = [pe.error] # ignore inflated length due to gap jumping (see parse.Parser.__call__) l = sum(len(nd) for nd in pe.node.select_if(lambda n: True, include_root=True) if not nd.children and nd.tag_name != ZOMBIE_TAG) text_ = pe.rest[l:] lc = line_col(grammar.document_lbreaks__, pe.error.pos) # grammar.history__.append( # HistoryRecord(grammar.call_stack__, pe.node, text_, lc, [pe.error])) if grammar.resume_notices__: text_ = pe.rest[len(pe.node):] target = text_ target = text if len(target) >= 10: target = target[:7] + '...' if pe.first_throw: # resume notice notice = Error('Resuming from parser "{}" with parser "{}" at point: {}' .format(pe.node.tag_name, grammar.call_stack__[-1][0], repr(target)), .format(pe.node.tag_name, grammar.call_stack__[-1][0], repr(target)), grammar.document_length__ - len(text_), Error.RESUME_NOTICE) else: # skip notice notice = Error('Skipping within parser {} to point {}' .format(grammar.call_stack__[-1][0], repr(target)), self._grammar.document_length__ - len(text_), Error.RESUME_NOTICE) grammar.tree__.add_error(pe.node, notice) lc = line_col(grammar.document_lbreaks__, location) errors.append(notice) grammar.history__.append(HistoryRecord( grammar.call_stack__, None, text_, lc, [notice])) grammar.call_stack__, pe.node, text_, lc, errors)) grammar.call_stack__.append( ((self.repr if self.tag_name in (REGEXP_PTYPE, TOKEN_PTYPE) Loading @@ -64,16 +83,18 @@ def trace_history(self: Parser, text: StringView) -> Tuple[Optional[Node], Strin grammar.moving_forward__ = True try: node, rest = self._parse(text) node, rest = self._parse(text) # <===== call to the actual parser! except ParserError as pe: grammar.call_stack__.pop() if pe.first_throw: # add error message to history grammar.most_recent_error__ = pe lc = line_col(grammar.document_lbreaks__, pe.error.pos) nd = pe.node if self == grammar.start_parser__: fe = grammar.most_recent_error__ lc = line_col(grammar.document_lbreaks__, fe.error.pos) # TODO: get the call stack from when the error occured, here nd = fe.node grammar.history__.append( HistoryRecord(grammar.call_stack__, nd, pe.rest[len(nd):], lc, [pe.error])) HistoryRecord(grammar.call_stack__, nd, fe.rest[len(nd):], lc, [fe.error])) raise pe # Mind that memoized parser calls will not appear in the history record! Loading
test/test_trace.py +21 −14 Original line number Diff line number Diff line Loading @@ -26,12 +26,15 @@ import sys scriptpath = os.path.dirname(__file__) or '.' sys.path.append(os.path.abspath(os.path.join(scriptpath, '..'))) REVEAL = False from DHParser import grammar_provider, all_descendants, \ set_tracer, trace_history, log_parsing_history, start_logging, log_dir, \ set_config_value, resume_notices_on, Error def get_history(name, show: bool = False) -> str: def get_history(name, show: bool = REVEAL) -> str: history_fname = os.path.join(log_dir() or '', name + "_full_parser.log.html") if show: import webbrowser, time Loading @@ -42,6 +45,12 @@ def get_history(name, show: bool = False) -> str: return history_file def reveal(grammar, name): if REVEAL: log_parsing_history(grammar, name) get_history(name, show=True) class TestTrace: def setup(self): start_logging() Loading Loading @@ -186,8 +195,7 @@ class TestErrorReporting: break else: assert False, "Missing Error!" # log_parsing_history(gr, 'trace_noskip') # get_history('trace_noskip') reveal(gr, 'trace_noskip') def test_trace_skip_clause(self): lang = """ Loading @@ -197,15 +205,15 @@ class TestErrorReporting: """ gr = grammar_provider(lang)() resume_notices_on(gr) _ = gr('AB_D') st = gr('AB_D') assert 'Skipping' in str(st.errors_sorted[1]) for record in gr.history__: if record.status.startswith(record.ERROR): assert record.excerpt == '_D' break else: assert False, "Missing Error!" # log_parsing_history(gr, 'trace_skip_clause') # get_history('trace_skip_clause') reveal(gr, 'trace_skip_clause') def test_trace_resume(self): gr = self.gr; gr.resume_rules = dict() Loading @@ -218,8 +226,7 @@ class TestErrorReporting: assert cst.pick('alpha').content.startswith('ALPHA') # because of resuming, there should be only one error message assert len([err for err in cst.errors_sorted if err.code >= 1000]) == 1 # log_parsing_history(gr, 'trace_resume') # get_history('trace_resume') reveal(gr, 'trace_resume') def test_trace_resume_complex_case(self): lang = r""" Loading @@ -232,19 +239,19 @@ class TestErrorReporting: def mini_suite(grammar): tree = grammar('abc/*x*/xyz') assert not tree.errors tree = grammar('abDxyz') mandatory_cont = (Error.MANDATORY_CONTINUATION, Error.MANDATORY_CONTINUATION_AT_EOF) assert len(tree.errors) > 1 and tree.errors[0].code in mandatory_cont # log_parsing_history(grammar, 'trace_resume_complex_1') # get_history('trace_resume_complex_1') reveal(grammar, 'trace_resume_complex_1') tree = grammar('abD/*x*/xyz') assert len(tree.errors) > 1 and tree.errors[0].code in mandatory_cont # log_parsing_history(grammar, 'trace_resume_complex_2') # get_history('trace_resume_complex_2', show=True) reveal(grammar, 'trace_resume_complex_2') tree = grammar('aD /*x*/ c /* a */ /*x*/xyz') assert len(tree.errors) > 1 and tree.errors[0].code in mandatory_cont # log_parsing_history(grammar, 'trace_resume_complex_3') # get_history('trace_resume_complex_3') reveal(grammar, 'trace_resume_complex_3') # test regex-defined resume rule grammar = grammar_provider(lang)() Loading