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 cd2e8f1d authored by di68kap's avatar di68kap
Browse files

- syntaxtree.py: added RootNode

parent 5d7bfd01
...@@ -42,6 +42,7 @@ __all__ = ('ParserBase', ...@@ -42,6 +42,7 @@ __all__ = ('ParserBase',
'ZOMBIE_PARSER', 'ZOMBIE_PARSER',
'ZOMBIE_NODE', 'ZOMBIE_NODE',
'Node', 'Node',
'RootNode',
'parse_sxpr', 'parse_sxpr',
'flatten_sxpr') 'flatten_sxpr')
...@@ -207,13 +208,6 @@ class Node(collections.abc.Sized): ...@@ -207,13 +208,6 @@ class Node(collections.abc.Sized):
parsing (i.e. AST-transformation and compiling), for parsing (i.e. AST-transformation and compiling), for
example by calling ``isinstance(node.parer, ...)``. example by calling ``isinstance(node.parer, ...)``.
errors (list): A list of parser- or compiler-errors:
tuple(position, string) attached to this node
error_flag (int): 0 if no error occurred in either the node
itself or any of its descendants. Otherwise contains the
highest warning or error level or all errors that occurred.
len (int): The full length of the node's string result if the len (int): The full length of the node's string result if the
node is a leaf node or, otherwise, the concatenated string node is a leaf node or, otherwise, the concatenated string
result's of its descendants. The figure always represents result's of its descendants. The figure always represents
...@@ -243,16 +237,13 @@ class Node(collections.abc.Sized): ...@@ -243,16 +237,13 @@ class Node(collections.abc.Sized):
_parent (Node): SLOT RESERVED FOR FUTURE USE! _parent (Node): SLOT RESERVED FOR FUTURE USE!
""" """
__slots__ = ['_result', 'children', '_errors', '_len', '_pos', 'parser', 'error_flag', __slots__ = ['_result', 'children', '_len', '_pos', 'parser', '_xml_attr', '_content']
'_xml_attr', '_content', '_parent']
def __init__(self, parser, result: ResultType, leafhint: bool = False) -> None: def __init__(self, parser, result: ResultType, leafhint: bool = False) -> None:
""" """
Initializes the ``Node``-object with the ``Parser``-Instance Initializes the ``Node``-object with the ``Parser``-Instance
that generated the node and the parser's result. that generated the node and the parser's result.
""" """
self.error_flag = 0 # type: int
self._errors = [] # type: List[Error]
self._pos = -1 # type: int self._pos = -1 # type: int
# Assignment to self.result initializes the attributes _result, children and _len # Assignment to self.result initializes the attributes _result, children and _len
# The following if-clause is merely an optimization, i.e. a fast-path for leaf-Nodes # The following if-clause is merely an optimization, i.e. a fast-path for leaf-Nodes
...@@ -469,53 +460,13 @@ class Node(collections.abc.Sized): ...@@ -469,53 +460,13 @@ class Node(collections.abc.Sized):
return self return self
@property # @property
def errors(self) -> List[Error]: # def errors(self) -> List[Error]:
""" # """
Returns the errors that occurred at this Node, # Returns the errors that occurred at this Node,
not including any errors from child nodes. # not including any errors from child nodes.
""" # """
return self._errors.copy() # return self._errors.copy()
def add_error(self,
message: str,
code: int = Error.ERROR) -> 'Node':
"""
Adds an error to this Node.
Parameters:
message(str): A string with the error message.abs
code(int): An error code to identify the kind of error
"""
self._errors.append(Error(message, code))
self.error_flag = max(self.error_flag, self._errors[-1].code)
return self
def collect_errors(self, clear_errors=False) -> List[Error]:
"""
Recursively adds line- and column-numbers to all error objects.
Returns all errors of this node or any child node in the form
of a set of tuples (position, error_message), where position
is always relative to this node.
"""
errors = self.errors
for err in errors:
err.pos = self.pos
if self.children:
for child in self.children:
errors.extend(child.collect_errors(clear_errors))
if clear_errors:
self._errors = []
self.error_flag = 0
else:
if self._errors:
self.error_flag = max(err.code for err in self.errors)
if self.children:
max_child_error = max(child.error_flag for child in self.children)
self.error_flag = max(self.error_flag, max_child_error)
return errors
@property @property
def attributes(self): def attributes(self):
...@@ -727,6 +678,56 @@ class Node(collections.abc.Sized): ...@@ -727,6 +678,56 @@ class Node(collections.abc.Sized):
return sum(child.tree_size() for child in self.children) + 1 return sum(child.tree_size() for child in self.children) + 1
class RootNode(Node):
"""
errors (list): A list of parser- or compiler-errors:
tuple(position, string) attached to this node
error_flag (int): 0 if no error occurred in either the node
itself or any of its descendants. Otherwise contains the
highest warning or error level or all errors that occurred.
"""
def __init__(self):
super().__init__(ZOMBIE_PARSER, '')
self.errors = []
self.error_flag = 0
def swallow(self, node):
self._result = node._result
self.children = node.children
self._len = node._len
self._pos = node._pos
self.parser = node.parser
self._xml_attr = node._xml_attr
self._content = node._content
def add_error(self,
pos: int,
message: str,
code: int = Error.ERROR) -> 'Node':
"""
Adds an error to this tree.
Parameters:
pos(int): The position of the error in the source text
message(str): A string with the error message.abs
code(int): An error code to identify the kind of error
"""
self.errors.append(Error(message, code, pos))
self.error_flag = max(self.error_flag, code)
return self
def collect_errors(self, clear_errors=False) -> List[Error]:
"""Returns the list of errors, ordered bv their position.
"""
self.errors.sort(key=lambda e: e.pos)
errors = self.errors
if clear_errors:
self.errors = []
self.error_flag = 0
return errors
ZOMBIE_NODE = Node(ZOMBIE_PARSER, '') ZOMBIE_NODE = Node(ZOMBIE_PARSER, '')
......
...@@ -535,6 +535,31 @@ class TestWhitespaceHandling: ...@@ -535,6 +535,31 @@ class TestWhitespaceHandling:
assert st.error_flag assert st.error_flag
class TestErrorReporting:
grammar = """
root = series alpha | anything
series = subseries &alpha
subseries = alpha §beta
alpha = /[a-z]+/
beta = /[A-Z]+/
anything = /.*/
"""
def setup(self):
self.parser = grammar_provider(self.grammar)()
def test_error_propagation(self):
testcode1 = "halloB"
testcode2 = "XYZ"
testcode3 = "hallo "
cst = self.parser(testcode1)
assert not cst.error_flag, str(cst.collect_errors())
cst = self.parser(testcode2)
assert not cst.error_flag
cst = self.parser(testcode3)
assert cst.error_flag
class TestBorderlineCases: class TestBorderlineCases:
def test_not_matching(self): def test_not_matching(self):
minilang = """parser = /X/""" minilang = """parser = /X/"""
......
...@@ -24,7 +24,7 @@ import sys ...@@ -24,7 +24,7 @@ import sys
sys.path.extend(['../', './']) sys.path.extend(['../', './'])
from DHParser.error import Error from DHParser.error import Error
from DHParser.syntaxtree import Node, parse_sxpr, flatten_sxpr, TOKEN_PTYPE from DHParser.syntaxtree import Node, RootNode, parse_sxpr, flatten_sxpr, TOKEN_PTYPE
from DHParser.transform import traverse, reduce_single_child, \ from DHParser.transform import traverse, reduce_single_child, \
replace_by_single_child, flatten, remove_expendables replace_by_single_child, flatten, remove_expendables
from DHParser.ebnf import get_ebnf_grammar, get_ebnf_transformer, get_ebnf_compiler from DHParser.ebnf import get_ebnf_grammar, get_ebnf_transformer, get_ebnf_compiler
...@@ -127,6 +127,34 @@ class TestNode: ...@@ -127,6 +127,34 @@ class TestNode:
assert nd1.pos == 0, "Expected Node.pos == 0, got %i" % nd1.pos assert nd1.pos == 0, "Expected Node.pos == 0, got %i" % nd1.pos
assert nd2.pos == 3, "Expected Node.pos == 3, got %i" % nd2.pos assert nd2.pos == 3, "Expected Node.pos == 3, got %i" % nd2.pos
class TestRootNode:
def test_error_handling(self):
root = RootNode()
root.add_error(4, "error B")
root.add_error(2, "error A")
assert root.error_flag
errors = root.collect_errors(False)
assert root.error_flag
assert errors == root.collect_errors(True)
assert not root.error_flag and not root.collect_errors()
error_str = "\n".join(str(e) for e in errors)
assert error_str.find("A") < error_str.find("B")
class TestErrorHandling:
def test_error_flag_propagation(self):
tree = parse_sxpr('(a (b c) (d (e (f (g h)))))')
def find_h(context):
node = context[-1]
if node.result == "h":
node.add_error("an error deep inside the syntax tree")
assert not tree.error_flag
traverse(tree, {"*": find_h})
assert tree.error_flag, tree.as_sxpr()
def test_collect_errors(self): def test_collect_errors(self):
tree = parse_sxpr('(A (B 1) (C (D (E 2) (F 3))))') tree = parse_sxpr('(A (B 1) (C (D (E 2) (F 3))))')
A = tree A = tree
...@@ -145,20 +173,6 @@ class TestNode: ...@@ -145,20 +173,6 @@ class TestNode:
assert not D.error_flag assert not D.error_flag
class TestErrorHandling:
def test_error_flag_propagation(self):
tree = parse_sxpr('(a (b c) (d (e (f (g h)))))')
def find_h(context):
node = context[-1]
if node.result == "h":
node.add_error("an error deep inside the syntax tree")
assert not tree.error_flag
traverse(tree, {"*": find_h})
assert tree.error_flag, tree.as_sxpr()
class TestNodeFind(): class TestNodeFind():
"""Test the select-functions of class Node. """Test the select-functions of class Node.
""" """
...@@ -206,7 +220,10 @@ class TestNodeFind(): ...@@ -206,7 +220,10 @@ class TestNodeFind():
class TestSerialization: class TestSerialization:
def test_attributes(self): def test_sxpr_roundtrip(self):
pass
def test_sexpr_attributes(self):
tree = parse_sxpr('(A "B")') tree = parse_sxpr('(A "B")')
tree.attributes['attr'] = "value" tree.attributes['attr'] = "value"
tree2 = parse_sxpr('(A `(attr "value") "B")') tree2 = parse_sxpr('(A `(attr "value") "B")')
...@@ -216,6 +233,7 @@ class TestSerialization: ...@@ -216,6 +233,7 @@ class TestSerialization:
assert tree.as_sxpr() == tree3.as_sxpr() assert tree.as_sxpr() == tree3.as_sxpr()
if __name__ == "__main__": if __name__ == "__main__":
from DHParser.testing import runner from DHParser.testing import runner
runner("", globals()) runner("", globals())
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