Commit e51e674c authored by Eckhart Arnold's avatar Eckhart Arnold

- Errors now centrally managed by RootNode object

parent 97d7f3bc
......@@ -225,16 +225,17 @@ def compile_source(source: str,
log_ST(syntax_tree, log_file_name + '.cst')
log_parsing_history(parser, log_file_name)
assert is_error(syntax_tree.error_flag) or str(syntax_tree) == strip_tokens(source_text)
assert is_error(syntax_tree.error_flag) or str(syntax_tree) == strip_tokens(source_text), \
str(syntax_tree)
# only compile if there were no syntax errors, for otherwise it is
# likely that error list gets littered with compile error messages
result = None
# efl = syntax_tree.error_flag
# messages = syntax_tree.collect_errors(clear_errors=True)
# messages = syntax_tree.errors(clear_errors=True)
if not is_error(syntax_tree.error_flag):
transformer(syntax_tree)
# efl = max(efl, syntax_tree.error_flag)
# messages.extend(syntax_tree.collect_errors(clear_errors=True))
# messages.extend(syntax_tree.errors(clear_errors=True))
if is_logging():
log_ST(syntax_tree, log_file_name + '.ast')
if not is_error(syntax_tree.error_flag):
......@@ -242,10 +243,10 @@ def compile_source(source: str,
ast = copy.deepcopy(syntax_tree)
result = compiler(syntax_tree)
# print(syntax_tree.as_sxpr())
# messages.extend(syntax_tree.collect_errors())
# messages.extend(syntax_tree.errors())
# syntax_tree.error_flag = max(syntax_tree.error_flag, efl)
messages = syntax_tree.collect_errors()
messages = syntax_tree.errors()
adjust_error_locations(messages, original_text, source_mapping)
return result, messages, ast
......
......@@ -221,7 +221,7 @@ def adjust_error_locations(errors: List[Error],
Args:
errors: The list of errors as returned by the method
``collect_errors()`` of a Node object
``errors()`` of a Node object
original_text: The source text on which the errors occurred.
(Needed in order to determine the line and column numbers.)
source_mapping: A function that maps error positions to their
......
......@@ -53,6 +53,7 @@ import contextlib
import html
import os
from DHParser.error import Error
from DHParser.stringview import StringView
from DHParser.syntaxtree import Node
from DHParser.toolkit import is_filename, escape_control_characters, GLOBALS, typing
......@@ -192,7 +193,7 @@ class HistoryRecord:
parser call, which ist either MATCH, FAIL (i.e. no match)
or ERROR.
"""
__slots__ = ('call_stack', 'node', 'text', 'line_col')
__slots__ = ('call_stack', 'node', 'text', 'line_col', 'errors')
MATCH = "MATCH"
ERROR = "ERROR"
......@@ -221,12 +222,14 @@ class HistoryRecord:
def __init__(self, call_stack: List[str],
node: Node,
text: StringView,
line_col: Tuple[int, int]) -> None:
line_col: Tuple[int, int],
errors: List[Error] = []) -> None:
# copy call stack, dropping uninformative Forward-Parsers
self.call_stack = [tn for tn in call_stack if tn != ":Forward"] # type: List[str]
self.node = node # type: Node
self.text = text # type: StringView
self.line_col = line_col # type: Tuple[int, int]
self.errors = errors # type: List[Error]
def __str__(self):
return '%4i, %2i: %s; %s; "%s"' % self.as_tuple()
......@@ -278,7 +281,7 @@ class HistoryRecord:
for cls, item in zip(tpl._fields, tpl)] + ['</tr>'])
def err_msg(self) -> str:
return self.ERROR + ": " + "; ".join(str(e) for e in (self.node.errors))
return self.ERROR + ": " + "; ".join(str(e) for e in (self.errors))
@property
def stack(self) -> str:
......@@ -287,7 +290,7 @@ class HistoryRecord:
@property
def status(self) -> str:
return self.FAIL if self.node is None else \
('"%s"' % self.err_msg()) if self.node.errors else self.MATCH
('"%s"' % self.err_msg()) if self.errors else self.MATCH
@property
def excerpt(self):
......
......@@ -11,8 +11,6 @@ cdef class Parser:
cdef object recursion_counter
cdef object cycle_detection
cpdef _return_node(self, node)
cpdef _return_node_from_results(self, results)
cpdef _parse(self, text)
cpdef reset(self)
cpdef _apply(self, func, flip)
......@@ -58,55 +56,59 @@ cdef class RegExp(Parser):
cdef class Whitespace(RegExp):
pass
cdef class UnaryOperator(Parser):
cdef class MetaParser(Parser):
cpdef _return_value(self, node)
cpdef _return_values(self, results)
cdef class UnaryParser(MetaParser):
cdef public object parser
cdef class NaryOperator(Parser):
cdef class NaryParser(MetaParser):
cdef public object parsers
cdef class Option(UnaryOperator):
cdef class Option(UnaryParser):
pass
cdef class ZeroOrMore(Option):
pass
cdef class OneOrMore(UnaryOperator):
cdef class OneOrMore(UnaryParser):
pass
cdef class Series(NaryOperator):
cdef class Series(NaryParser):
cdef public int mandatory
cdef public object err_msgs
cdef public object skip
cdef class Alternative(NaryOperator):
cdef class Alternative(NaryParser):
pass
cdef class AllOf(NaryOperator):
cdef class AllOf(NaryParser):
cdef public int num_parsers
cdef public int mandatory
cdef public object err_msgs
cdef public object skip
cdef class SomeOf(NaryOperator):
cdef class SomeOf(NaryParser):
pass
cdef class FlowOperator(UnaryOperator):
cdef class FlowParser(UnaryParser):
pass
cdef class Lookahead(FlowOperator):
cdef class Lookahead(FlowParser):
pass
cdef class NegativeLookahead(Lookahead):
pass
cdef class Lookbehind(FlowOperator):
cdef class Lookbehind(FlowParser):
cdef public object regexp
cdef public str text
cdef class NegativeLookbehind(Lookbehind):
pass
cdef class Capture(UnaryOperator):
cdef class Capture(UnaryParser):
pass
cdef class Retrieve(Parser):
......@@ -116,7 +118,7 @@ cdef class Retrieve(Parser):
cdef class Pop(Retrieve):
cdef public list values
cdef class Synonym(UnaryOperator):
cdef class Synonym(UnaryParser):
pass
cdef class Forward(Parser):
......
......@@ -55,6 +55,7 @@ __all__ = ('Parser',
'Whitespace',
'DropWhitespace',
'mixin_comment',
'MetaParser',
'UnaryParser',
'NaryParser',
'Synonym',
......@@ -352,11 +353,17 @@ class Parser:
if grammar.history_tracking__:
# don't track returning parsers except in case an error has occurred
# remaining = len(rest)
if (grammar.moving_forward__ or (node and node.errors)):
if grammar.moving_forward__:
record = HistoryRecord(grammar.call_stack__, node or EMPTY_NODE, text,
grammar.line_col__(text))
grammar.history__.append(record)
# print(record.stack, record.status, rest[:20].replace('\n', '|'))
elif node:
nid = id(node) # type: int
if nid in grammar.tree__.error_nodes:
record = HistoryRecord(grammar.call_stack__, node or EMPTY_NODE, text,
grammar.line_col__(text),
grammar.tree__.error_nodes[nid])
grammar.history__.append(record)
grammar.moving_forward__ = False
grammar.call_stack__.pop()
......@@ -937,7 +944,7 @@ class Grammar:
else:
self.tree__.new_error(result, error_msg, error_code)
# result.pos = 0 # calculate all positions
# result.collect_errors(self.document__)
# result.errors(self.document__)
if result:
self.tree__.swallow(result)
self.start_parser__ = None
......@@ -1248,12 +1255,12 @@ class MetaParser(Parser):
return Node(self.tag_name, node) if self.pname else node
elif self.pname:
nd1 = Node(self.tag_name, ()) # type: Node
nd1.errors = node.errors
# nd1.errors = node.errors
return nd1
elif node.errors:
nd2 = Node(self.tag_name, ()) # type: Node
nd2.errors = node.errors
return nd2
# elif node.errors:
# nd2 = Node(self.tag_name, ()) # type: Node
# nd2.errors = node.errors
# return nd2
elif self.pname:
return Node(self.tag_name, ()) # type: Node
return EMPTY_NODE # avoid creation of a node object for anonymous empty nodes
......@@ -1585,7 +1592,7 @@ class Series(NaryParser):
else:
results += (node,)
break
if node._result or parser.pname or node.errors: # optimization
if node._result or parser.pname: # optimization
results += (node,)
# assert len(results) <= len(self.parsers) \
# or len(self.parsers) >= len([p for p in results if p.tag_name != ZOMBIE_TAG])
......@@ -1673,7 +1680,7 @@ class Alternative(NaryParser):
node, text_ = parser(text)
if node:
return Node(self.tag_name,
node if node._result or parser.pname or node.errors else ()), text_
node if node._result or parser.pname else ()), text_
return None, text
def __repr__(self):
......@@ -1784,7 +1791,7 @@ class AllOf(NaryParser):
for i, parser in enumerate(parsers):
node, text__ = parser(text_)
if node:
if node._result or parser.pname or node.errors:
if node._result or parser.pname:
results += (node,)
text_ = text__
del parsers[i]
......@@ -1849,7 +1856,7 @@ class SomeOf(NaryParser):
for i, parser in enumerate(parsers):
node, text__ = parser(text_)
if node:
if node._result or parser.pname or node.errors:
if node._result or parser.pname:
results += (node,)
text_ = text__
del parsers[i]
......@@ -2129,7 +2136,7 @@ class Pop(Retrieve):
def _parse(self, text: StringView) -> Tuple[Optional[Node], StringView]:
node, txt = self.retrieve_and_match(text)
if node and not node.errors:
if node 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__ - len(text)
self.grammar.push_rollback__(location, self._rollback) # lambda: stack.append(value))
......
......@@ -5,7 +5,6 @@
cdef class Node:
cdef public list errors
cdef public int _pos
cdef public object _result
cdef str _content
......@@ -16,6 +15,8 @@ cdef class Node:
cdef class RootNode(Node):
cdef public list all_errors
cdef public object error_nodes
cdef public object error_positions
cdef public int error_flag
cdef public set inline_tags
cdef public set omit_tags
......
This diff is collapsed.
......@@ -410,7 +410,7 @@ def grammar_unit(test_unit, parser_factory, transformer_factory, report=True, ve
transform(ast)
tests.setdefault('__ast__', {})[test_name] = ast
# log_ST(ast, "match_%s_%s.ast" % (parser_name, clean_test_name))
raw_errors = cst.collect_errors()
raw_errors = cst.errors()
if is_error(cst.error_flag) and not lookahead_artifact(parser, raw_errors):
errors = adjust_error_locations(raw_errors, test_code)
errata.append('Match test "%s" for parser "%s" failed:\n\tExpr.: %s\n\n\t%s\n\n' %
......@@ -454,7 +454,7 @@ def grammar_unit(test_unit, parser_factory, transformer_factory, report=True, ve
cst = RootNode(node).new_error(node, str(upe))
errata.append('Unknown parser "{}" in fail test "{}"!'.format(parser_name, test_name))
tests.setdefault('__err__', {})[test_name] = errata[-1]
if not is_error(cst.error_flag) and not lookahead_artifact(parser, cst.collect_errors()):
if not is_error(cst.error_flag) and not lookahead_artifact(parser, cst.errors()):
errata.append('Fail test "%s" for parser "%s" yields match instead of '
'expected failure!' % (test_name, parser_name))
tests.setdefault('__err__', {})[test_name] = errata[-1]
......@@ -463,7 +463,7 @@ def grammar_unit(test_unit, parser_factory, transformer_factory, report=True, ve
log_parsing_history(parser, "fail_%s_%s.log" % (parser_name, test_name))
if cst.error_flag:
tests.setdefault('__msg__', {})[test_name] = \
"\n".join(str(e) for e in cst.collect_errors())
"\n".join(str(e) for e in cst.errors())
if verbose:
write(infostr + ("OK" if len(errata) == errflag else "FAIL"))
......
#!/bin/sh
# rm DHParser/*.c
# rm DHParser/*.so
rm DHParser/*.c
rm DHParser/*.so
# for testing:
rm DHParser/parse.c
rm DHParser/parse.cpython*.so
# rm DHParser/parse.c
# rm DHParser/parse.cpython*.so
# rm DHParser/syntaxtree.c
# rm DHParser/syntaxtree.cpython*.so
# rm DHParser/transform.c
......
......@@ -207,4 +207,4 @@ syntax_tree = parser(markdown_text)
ASTTransform(syntax_tree, MDTransTable)
print(syntax_tree.as_sxpr())
print(error_messages(markdown_text, syntax_tree.collect_errors()))
print(error_messages(markdown_text, syntax_tree.errors()))
......@@ -283,4 +283,4 @@ syntax_tree = parse(markdown_text, parser)
ASTTransform(syntax_tree, MDTransTable)
print(syntax_tree.as_sxpr())
print(error_messages(markdown_text, syntax_tree.collect_errors()))
print(error_messages(markdown_text, syntax_tree.errors()))
......@@ -50,7 +50,7 @@ compiler = get_compiler()
def fail_on_error(src, result):
if result.error_flag:
print(result.as_sxpr())
for e in result.collect_errors():
for e in result.errors():
print(str(e))
sys.exit(1)
......
......@@ -36,7 +36,7 @@ def run_doctests(module):
if __name__ == "__main__":
if platform.system() != "Windows":
interpreters = ['pypy3 ', 'python3 ']
interpreters = ['python3 ', 'pypy3 ']
else:
interpreters = ['python.exe ']
......
......@@ -52,7 +52,7 @@ class TestCompileFunctions:
assert callable(factory)
parser = factory()
result = parser("5 + 3 * 4")
assert not result.error_flag, str(result.collect_errors())
assert not result.error_flag, str(result.errors())
result = parser("5A + 4B ** 4C")
assert is_error(result.error_flag)
......
......@@ -52,36 +52,36 @@ class TestDirectives:
assert parser
syntax_tree = parser("3 + 4 * 12")
# parser.log_parsing_history("WSP")
assert not syntax_tree.collect_errors()
assert not syntax_tree.errors()
syntax_tree = parser("3 + 4 \n * 12")
# parser.log_parsing_history("WSPLF")
assert not syntax_tree.collect_errors()
assert not syntax_tree.errors()
syntax_tree = parser("3 + 4 \n \n * 12")
assert syntax_tree.collect_errors()
assert syntax_tree.errors()
syntax_tree = parser("3 + 4 \n\n * 12")
assert syntax_tree.collect_errors()
assert syntax_tree.errors()
def test_whitespace_vertical(self):
lang = "@ whitespace = vertical\n" + self.mini_language
parser = grammar_provider(lang)()
assert parser
syntax_tree = parser("3 + 4 * 12")
assert not syntax_tree.collect_errors()
assert not syntax_tree.errors()
syntax_tree = parser("3 + 4 \n * 12")
assert not syntax_tree.collect_errors()
assert not syntax_tree.errors()
syntax_tree = parser("3 + 4 \n \n * 12")
assert not syntax_tree.collect_errors()
assert not syntax_tree.errors()
syntax_tree = parser("3 + 4 \n\n * 12")
assert not syntax_tree.collect_errors()
assert not syntax_tree.errors()
def test_whitespace_horizontal(self):
lang = "@ whitespace = horizontal\n" + self.mini_language
parser = grammar_provider(lang)()
assert parser
syntax_tree = parser("3 + 4 * 12")
assert not syntax_tree.collect_errors()
assert not syntax_tree.errors()
syntax_tree = parser("3 + 4 \n * 12")
assert syntax_tree.collect_errors()
assert syntax_tree.errors()
class TestReservedSymbols:
......@@ -176,7 +176,7 @@ class TestParserNameOverwriteBug:
get_ebnf_transformer()(st)
# print(st.as_sxpr())
result = get_ebnf_compiler()(st)
messages = st.collect_errors()
messages = st.errors()
assert not has_errors(messages), str(messages)
def test_single_mandatory_bug(self):
......@@ -194,9 +194,9 @@ class TestSemanticValidation:
def check(self, minilang, bool_filter=lambda x: x):
grammar = get_ebnf_grammar()
st = grammar(minilang)
assert not st.collect_errors()
assert not st.errors()
EBNFTransform()(st)
assert bool_filter(st.collect_errors())
assert bool_filter(st.errors())
def test_illegal_nesting(self):
self.check('impossible = { [ "an optional requirement" ] }')
......@@ -477,12 +477,12 @@ class TestErrorCustomization:
st = parser("ABCD"); assert not st.error_flag
st = parser("A_CD"); assert not st.error_flag
st = parser("AB_D"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "a user defined error message"
assert st.errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.errors()[0].message == "a user defined error message"
# transitivity of mandatory-operator
st = parser("ABC_"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "a user defined error message"
assert st.errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.errors()[0].message == "a user defined error message"
def test_customized_error_case_sensitive(self):
lang = """
......@@ -493,8 +493,8 @@ class TestErrorCustomization:
"""
parser = grammar_provider(lang)()
st = parser("ABC_"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "a user defined error message"
assert st.errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.errors()[0].message == "a user defined error message"
def test_multiple_error_messages(self):
lang = """
......@@ -509,21 +509,21 @@ class TestErrorCustomization:
"""
parser = grammar_provider(lang)()
st = parser("AB*D"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "the asterix is wrong in this place"
assert st.errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.errors()[0].message == "the asterix is wrong in this place"
# transitivity of mandatory-operator
st = parser("ABC_"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "the underscore is wrong in this place"
assert st.errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.errors()[0].message == "the underscore is wrong in this place"
st = parser("ABiD"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message.startswith('wrong letter')
assert st.errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.errors()[0].message.startswith('wrong letter')
st = parser("AB+D"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message == "fallback error message"
assert st.errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.errors()[0].message == "fallback error message"
st = parser("ABCi"); assert st.error_flag
assert st.collect_errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.collect_errors()[0].message.startswith('C cannot be followed by')
assert st.errors()[0].code == Error.MANDATORY_CONTINUATION
assert st.errors()[0].message.startswith('C cannot be followed by')
class TestErrorCustomizationErrors:
......@@ -600,7 +600,7 @@ class TestCustomizedResumeParsing:
assert cst.content == content
assert cst.pick('alpha').content.startswith('ALPHA')
# because of resuming, there should be only on error message
assert len(cst.collect_errors()) == 1
assert len(cst.errors()) == 1
content = 'ALPHA acb BETA bad GAMMA cab .'
cst = gr(content)
......@@ -609,7 +609,7 @@ class TestCustomizedResumeParsing:
assert cst.content == content
assert cst.pick('alpha').content.startswith('ALPHA')
# because of resuming, there should be only on error message
assert len(cst.collect_errors()) == 2
assert len(cst.errors()) == 2
content = 'ALPHA acb GAMMA cab .'
cst = gr(content)
......@@ -618,7 +618,7 @@ class TestCustomizedResumeParsing:
assert cst.content == content
assert cst.pick('alpha').content.startswith('ALPHA')
# because of resuming, there should be only on error message
assert len(cst.collect_errors()) == 1
assert len(cst.errors()) == 1
class TestInSeriesResume:
......@@ -634,29 +634,29 @@ class TestInSeriesResume:
st = self.gr('ABCDEFG')
assert not st.error_flag
st = self.gr('AB XYZ CDEFG')
errors = st.collect_errors()
errors = st.errors()
assert len(errors) == 1 and errors[0].code == Error.MANDATORY_CONTINUATION
st = self.gr('AB XYZ CDE XYZ FG')
errors = st.collect_errors()
errors = st.errors()
assert len(errors) == 2 and all(err.code == Error.MANDATORY_CONTINUATION for err in errors)
st = self.gr('AB XYZ CDE XNZ FG') # fails to resume parsing
errors = st.collect_errors()
errors = st.errors()
assert len(errors) >= 1 and errors[0].code == Error.MANDATORY_CONTINUATION
def test_series_gap(self):
st = self.gr('ABDEFG')
errors = st.collect_errors()
errors = st.errors()
assert len(errors) == 1 and errors[0].code == Error.MANDATORY_CONTINUATION
st = self.gr('ABXEFG') # two missing, one wrong element added
errors = st.collect_errors()
errors = st.errors()
assert len(errors) == 2 and all(err.code == Error.MANDATORY_CONTINUATION for err in errors)
st = self.gr('AB_DE_G')
errors = st.collect_errors()
errors = st.errors()
assert len(errors) == 2 and all(err.code == Error.MANDATORY_CONTINUATION for err in errors)
def test_series_permutation(self):
st = self.gr('ABEDFG')
errors = st.collect_errors()
errors = st.errors()
assert len(errors) >= 1 # cannot really recover from permutation errors
......@@ -674,7 +674,7 @@ class TestAllOfResume:
st = self.gr('GFCBAED')
assert not st.error_flag
st = self.gr('GFCB XYZ AED')
errors = st.collect_errors()
errors = st.errors()
assert errors[0].code == Error.MANDATORY_CONTINUATION
assert str(errors[0]).find(':-(') >= 0
......@@ -697,9 +697,9 @@ class TestAllOfResume:
assert not st.error_flag
st = gr('EDXYZ.')
assert st.error_flag
assert len(st.collect_errors()) == 1
assert len(st.errors()) == 1
st = gr('FCB_GAED.')
assert len(st.collect_errors()) == 1
assert len(st.errors()) == 1
def test_complex_resume_task(self):
......@@ -722,11 +722,11 @@ class TestAllOfResume:
assert not st.error_flag
st = gr('EDXYZ.')
assert st.error_flag
assert len(st.collect_errors()) == 1
assert len(st.errors()) == 1
st = gr('FCB_GAED.')
assert len(st.collect_errors()) == 2
assert len(st.errors()) == 2
st = gr('EXY EXYZ.')
assert len(st.collect_errors()) == 1
assert len(st.errors()) == 1
......
This diff is collapsed.
......@@ -156,7 +156,7 @@ class TestTokenParsing:
def test_parse_tokenized(self):
cst = self.grammar(self.tokenized)
# for e in cst.collect_errors(self.tokenized):
# for e in cst.errors(self.tokenized):
# print(e.visualize(self.tokenized) + str(e))
# print()
assert not cst.error_flag
......
......@@ -30,6 +30,7 @@ from DHParser.transform import traverse, reduce_single_child, \
from DHParser.ebnf import get_ebnf_grammar, get_ebnf_transformer, get_ebnf_compiler
from DHParser.dsl import grammar_provider
from DHParser.error import Error
from DHParser.parse import RE, Grammar
class TestParseSxpression:
......@@ -82,6 +83,7 @@ class TestNode:
def test_deepcopy(self):
tree = RootNode(parse_sxpr('(a (b c) (d (e f) (h i)))'))
tree.init_pos(0)
tree_copy = copy.deepcopy(tree)
assert tree == tree_copy
......@@ -196,13 +198,19 @@ class TestRootNode:
root.new_error(tree.children[0], "error B")
root.swallow(tree)
assert root.error_flag
errors = root.collect_errors()
errors = root.errors()
assert root.error_flag
# assert errors == root.collect_errors(True)
# assert not root.error_flag and not root.collect_errors()
# assert errors == root.errors(True)
# assert not root.error_flag and not root.errors()
error_str = "\n".join(str(e) for e in errors)
assert error_str.find("A") < error_str.find("B")
def test_error_reporting(self):
number = RE('\d+') | RE('\d+') + RE('\.') + RE('\d+')
result = str(Grammar(number)("3.1416"))
assert result == '3 <<< Error on ".141" | Parser stopped before end! trying to recover... >>> ', \
str(result)
class TestNodeFind():
"""Test the select-functions of class Node.
......
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