2.12.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

Commit d93e749e authored by Eckhart Arnold's avatar Eckhart Arnold
Browse files

- syntaxtree: mock_syntax_tree renamed to parse_sxpr; ad-hoc xml-parser parse_xml added

parent ae91c19e
......@@ -54,7 +54,7 @@ def last_char(text, begin: int, end: int) -> int:
"""Returns the index of the first non-whitespace character in string
`text` within the bounds [begin, end].
"""
while end > begin and text[end] in ' \n\t':
while end > begin and text[end-1] in ' \n\t':
end -= 1
return end
......@@ -94,14 +94,17 @@ class StringView(collections.abc.Sized):
copying, i.e. slices are just a view on a section of the sliced
string.
"""
__slots__ = ['text', 'begin', 'end', 'len', 'fullstring_flag']
__slots__ = ['text', 'begin', 'end', 'len', 'fullstring']
def __init__(self, text: str, begin: Optional[int] = 0, end: Optional[int] = None) -> None:
assert isinstance(text, str)
# assert isinstance(text, str)
self.text = text # type: str
self.begin, self.end = real_indices(begin, end, len(text))
self.len = max(self.end - self.begin, 0) # type: int
self.fullstring_flag = (self.begin == 0 and self.len == len(self.text)) # type: bool
if (self.begin == 0 and self.len == len(self.text)):
self.fullstring = self.text # type: str
else:
self.fullstring = ''
def __bool__(self):
return self.end > self.begin # and bool(self.text)
......@@ -111,16 +114,12 @@ class StringView(collections.abc.Sized):
def __str__(self):
# PERFORMANCE WARNING: This creates a copy of the string-slice
if self.fullstring_flag: # optimization: avoid slicing/copying
return self.text
if self.fullstring: # optimization: avoid slicing/copying
return self.fullstring
# since the slice is being copyied now, anyway, the copy might
# as well be stored in the string view
self.text = self.text[self.begin:self.end]
self.begin = 0
self.len = len(self.text)
self.end = self.len
self.fullstring_flag = True
return self.text
self.fullstring = self.text[self.begin:self.end]
return self.fullstring
def __eq__(self, other):
# PERFORMANCE WARNING: This creates copies of the strings
......@@ -146,16 +145,19 @@ class StringView(collections.abc.Sized):
# assert isinstance(index, slice), "As of now, StringView only allows slicing."
# assert index.step is None or index.step == 1, \
# "Step sizes other than 1 are not yet supported by StringView"
try:
start, stop = real_indices(index.start, index.stop, self.len)
return StringView(self.text, self.begin + start, self.begin + stop)
except AttributeError:
return self.text[self.begin + index]
def count(self, sub: str, start=None, end=None) -> int:
"""Returns the number of non-overlapping occurrences of substring
`sub` in StringView S[start:end]. Optional arguments start and end
are interpreted as in slice notation.
"""
if self.fullstring_flag:
return self.text.count(sub, start, end)
if self.fullstring:
return self.fullstring.count(sub, start, end)
elif start is None and end is None:
return self.text.count(sub, self.begin, self.end)
else:
......@@ -168,8 +170,8 @@ class StringView(collections.abc.Sized):
arguments `start` and `end` are interpreted as in slice notation.
Returns -1 on failure.
"""
if self.fullstring_flag:
return self.text.find(sub, start, end)
if self.fullstring:
return self.fullstring.find(sub, start, end)
elif start is None and end is None:
return self.text.find(sub, self.begin, self.end) - self.begin
else:
......@@ -182,8 +184,8 @@ class StringView(collections.abc.Sized):
arguments `start` and `end` are interpreted as in slice notation.
Returns -1 on failure.
"""
if self.fullstring_flag:
return self.text.rfind(sub, start, end)
if self.fullstring:
return self.fullstring.rfind(sub, start, end)
if start is None and end is None:
return self.text.rfind(sub, self.begin, self.end) - self.begin
else:
......@@ -203,9 +205,11 @@ class StringView(collections.abc.Sized):
end = self.end if end is None else self.begin + end
return self.text.startswith(prefix, start, end)
def match(self, regex):
def match(self, regex, flags=0):
"""Executes `regex.match` on the StringView object and returns the
result, which is either a match-object or None.
WARNING: match.end(), match.span() etc. are mapped to the underlying text,
not the StringView-object!!!
"""
return regex.match(self.text, pos=self.begin, endpos=self.end)
......@@ -232,19 +236,36 @@ class StringView(collections.abc.Sized):
def search(self, regex):
"""Executes regex.search on the StringView object and returns the
result, which is either a match-object or None.
WARNING: match.end(), match.span() etc. are mapped to the underlying text,
not the StringView-object!!!
"""
return regex.search(self.text, pos=self.begin, endpos=self.end)
def finditer(self, regex):
"""Executes regex.finditer on the StringView object and returns the
iterator of match objects.
WARNING: match.end(), match.span() etc. are mapped to the underlying text,
not the StringView-object!!!
"""
return regex.finditer(self.text, pos=self.begin, endpos=self.end)
def strip(self):
"""Returns a copy of the StringView `self` with leading and trailing
whitespace removed.
"""
if self.fullstring_flag:
return self.text.strip()
else:
begin = first_char(self.text, self.begin, self.end)
end = last_char(self.text, self.begin, self.end)
return self.text[begin:end]
begin = first_char(self.text, self.begin, self.end) - self.begin
end = last_char(self.text, self.begin, self.end) - self.begin
return self if begin == 0 and end == self.len else self[begin:end]
def lstrip(self):
"""Returns a copy of `self` with leading whitespace removed."""
begin = first_char(self.text, self.begin, self.end) - self.begin
return self if begin == 0 else self[begin:]
def rstrip(self):
"""Returns a copy of `self` with trailing whitespace removed."""
end = last_char(self.text, self.begin, self.end) - self.begin
return self if end == self.len else self[:end]
def split(self, sep=None):
"""Returns a list of the words in `self`, using `sep` as the
......@@ -252,8 +273,8 @@ class StringView(collections.abc.Sized):
whitespace string is a separator and empty strings are
removed from the result.
"""
if self.fullstring_flag:
return self.text.split(sep)
if self.fullstring:
return self.fullstring.split(sep)
else:
pieces = []
l = len(sep)
......
......@@ -42,7 +42,7 @@ __all__ = ('ParserBase',
'ZOMBIE_PARSER',
'ZOMBIE_NODE',
'Node',
'mock_syntax_tree',
'parse_sxpr',
'flatten_sxpr')
......@@ -318,7 +318,7 @@ class Node(collections.abc.Sized):
Returns the child node with the given index if ``index_or_tagname`` is
an integer or the first child node with the given tag name. Examples::
>>> tree = mock_syntax_tree('(a (b "X") (X (c "d")) (e (X "F")))')
>>> tree = parse_sxpr('(a (b "X") (X (c "d")) (e (X "F")))')
>>> flatten_sxpr(tree[0].as_sxpr())
'(b "X")'
>>> flatten_sxpr(tree["X"].as_sxpr())
......@@ -591,13 +591,13 @@ class Node(collections.abc.Sized):
txt = [left_bracket, node.tag_name]
# s += " '(pos %i)" % node.pos
if hasattr(node, '_xml_attr'):
txt.extend(""" `(%s "%s")""" % (k, v) for k, v in node.attributes.items())
txt.extend(' `(%s "%s")' % (k, v) for k, v in node.attributes.items())
if src:
txt.append(" `(pos %i %i %i)" % (node.pos, *line_col(src, node.pos)))
# if node.error_flag: # just for debugging error collecting
# txt += " HAS ERRORS"
if showerrors and node.errors:
txt.append(" `(err %s)" % ' '.join(str(err) for err in node.errors))
txt.append(" `(err `%s)" % ' '.join(str(err) for err in node.errors))
return "".join(txt) + '\n'
def closing(node) -> str:
......@@ -680,7 +680,7 @@ class Node(collections.abc.Sized):
Examples::
>>> tree = mock_syntax_tree('(a (b "X") (X (c "d")) (e (X "F")))')
>>> tree = parse_sxpr('(a (b "X") (X (c "d")) (e (X "F")))')
>>> list(flatten_sxpr(item.as_sxpr()) for item in tree.select_by_tag("X", False))
['(X (c "d"))', '(X "F")']
>>> list(flatten_sxpr(item.as_sxpr()) for item in tree.select_by_tag({"X", "b"}, False))
......@@ -730,17 +730,22 @@ class Node(collections.abc.Sized):
ZOMBIE_NODE = Node(ZOMBIE_PARSER, '')
def mock_syntax_tree(sxpr: str) -> Node:
def parse_sxpr(sxpr: str) -> Node:
"""
Generates a tree of nodes from an S-expression. The main purpose of this is
to generate test data.
Generates a tree of nodes from an S-expression.
This can - among other things - be used for deserialization of trees that
have been serialized with `Node.as_sxpr()` or as a convenient way to
generate test data.
Example:
>>> mock_syntax_tree("(a (b c))").as_sxpr()
>>> parse_sxpr("(a (b c))").as_sxpr()
'(a\\n (b\\n "c"\\n )\\n)'
"""
sxpr = StringView(sxpr).strip()
mock_parsers = dict()
def next_block(s):
def next_block(s: StringView):
"""Generator that yields all characters until the next closing bracket
that does not match an opening bracket matched earlier within the same
package."""
......@@ -765,38 +770,43 @@ def mock_syntax_tree(sxpr: str) -> Node:
else 'Malformed S-expression. Closing bracket(s) ")" missing.'
raise AssertionError(errmsg)
sxpr = StringView(sxpr).strip()
def inner_parser(sxpr: StringView) -> Node:
if sxpr[0] != '(':
raise ValueError('"(" expected, not ' + sxpr[:10])
# assert sxpr[0] == '(', sxpr
sxpr = sxpr[1:].strip()
match = re.match(r'[\w:]+', sxpr)
match = sxpr.match(re.compile(r'[\w:]+'))
if match is None:
raise AssertionError('Malformed S-expression Node-tagname or identifier expected, '
'not "%s"' % sxpr[:40].replace('\n', ''))
name, class_name = (sxpr[:match.end()].split(':') + [''])[:2]
sxpr = sxpr[match.end():].strip()
end = match.end() - sxpr.begin
tagname = sxpr[:end]
name, class_name = (tagname.split(':') + [''])[:2]
sxpr = sxpr[end:].strip()
pos = 0
attributes = OrderedDict()
if sxpr[0] == '(':
result = tuple(mock_syntax_tree(block) for block in next_block(sxpr))
result = tuple(inner_parser(block) for block in next_block(sxpr))
for node in result:
node._pos = pos
pos += len(node)
else:
lines = []
while sxpr and sxpr[0] != ')':
while sxpr and sxpr[0:1] != ')':
# parse attributes
while sxpr[:2] == "`(":
i = sxpr.find('"')
k = sxpr.find(')')
if sxpr[2:5] == "pos" and (i < 0 or k < i):
# read very special attribute pos
if sxpr[2:5] == "pos" and 0 < i < k:
pos = int(sxpr[5:k].strip().split(' ')[0])
elif sxpr[2:5] == "err":
# ignore very special attribute err
elif sxpr[2:5] == "err" and 0 <= sxpr.find('`', 5) < k:
m = sxpr.find('(', 5)
while m >= 0 and m < k:
m = sxpr.find('(', k)
k = max(k, sxpr.find(')', max(m, 0)))
# read attributes
else:
attr = sxpr[2:i].strip()
value = sxpr[i:k].strip()[1:-1]
......@@ -804,24 +814,102 @@ def mock_syntax_tree(sxpr: str) -> Node:
sxpr = sxpr[k+1:].strip()
# parse content
for qtmark in ['"""', "'''", '"', "'"]:
match = re.match(qtmark + r'.*?' + qtmark, sxpr, re.DOTALL)
match = sxpr.match(re.compile(qtmark + r'.*?' + qtmark, re.DOTALL))
if match:
end = match.end() - sxpr.begin
i = len(qtmark)
lines.append(sxpr[i:match.end() - i])
sxpr = sxpr[match.end():].strip()
lines.append(str(sxpr[i:end - i]))
sxpr = sxpr[end:].strip()
break
else:
match = re.match(r'(?:(?!\)).)*', sxpr, re.DOTALL)
lines.append(sxpr[:match.end()])
sxpr = sxpr[match.end():]
match = sxpr.match(re.compile(r'(?:(?!\)).)*', re.DOTALL))
end = match.end() - sxpr.begin
lines.append(str(sxpr[:end]))
sxpr = sxpr[end:]
result = "\n".join(lines)
node = Node(MockParser(name, ':' + class_name), result)
node = Node(mock_parsers.setdefault(tagname, MockParser(name, ':' + class_name)), result)
if attributes:
node.attributes.update(attributes)
node._pos = pos
return node
return inner_parser(sxpr)
def parse_xml(xml: str) -> Node:
"""
Generates a tree of nodes from a (Pseudo-)XML-source.
"""
xml = StringView(xml)
PlainText = MockParser('', 'PlainText')
mock_parsers = {':PlainText': PlainText}
def parse_attributes(s: StringView) -> Tuple[StringView, OrderedDict]:
"""Parses a sqeuence of XML-Attributes. Returns the string-slice
beginning after the end of the attributes."""
attributes = OrderedDict()
restart = 0
for match in s.finditer(re.compile(r'\s*(?P<attr>\w+)\s*=\s*"(?P<value>.*)"\s*')):
d = match.groupdict()
attributes[d['attr']] = d['value']
restart = match.end() - s.begin
return (s[restart:], attributes)
def parse_opening_tag(s: StringView) -> Tuple[StringView, str, OrderedDict, bool]:
"""Parses an opening tag. Returns the string segment following the
the opening tag, the tag name, a dictionary of attributes and
a flag indicating whether the tag is actually a solitary tag as
indicated by a slash at the end, i.e. <br/>."""
match = s.match(re.compile(r'<\s*(?P<tagname>[\w:]+)\s*'))
assert match
tagname = match.groupdict()['tagname']
s, attributes = parse_attributes(s[match.end() - s.begin:])
i = s.find('>')
assert i >= 0
return s[i+1,], tagname, attributes, s[i-1] == "/"
def parse_closing_tag(s: StringView) -> Tuple[StringView, str]:
"""Parses a closing tag returns the string segment, just after
the closing tag."""
match = s.match(re.compile(r'</\s*(?P<tagname>[\w:]+)>'))
assert match
tagname = match.groupdict()['tagname']
return s[match.end() - s.begin:], tagname
def parse_leaf_content(s: StringView) -> Tuple[StringView, str]:
"""Parses a piece of the content of a tag, just until the next opening,
closing or solitary tag is reached."""
i = 0
while s[i] != "<" or s[max(0, i-1)] == "\\":
i = s.find("<", i)
return s[i:], s[:i]
def parse_full_content(s: StringView) -> Tuple[StringView, Node]:
"""Parses the full content of a tag, starting right at the beginning
of the opening tag and ending right after the closing tag.
"""
result = []
s, tagname, attributes, solitary = parse_opening_tag(s)
name, class_name = (tagname.split(":") + [''])[:2]
if not solitary:
while s and not s[:2] == "</":
s, leaf = parse_leaf_content(s)
if not s.match(re.compile("\s*$")):
result.append(Node(PlainText, leaf))
if s[:1] == "<" and s[:2] != "</":
s, child = parse_full_content(s)
result.append(child)
s, closing_tagname = parse_closing_tag(s)
assert tagname == closing_tagname
if len(result) == 1 and isinstance(result[0].parser == PlainText):
result = result[0].result
else:
result = tuple(result)
return Node(mock_parsers.setdefault(tagname, MockParser(name, ":" + class_name)), result)
return parse_full_content(xml[xml.search(re.compile(r'<(?!\?)')):])
# if __name__ == "__main__":
# st = mock_syntax_tree("(alpha (beta (gamma i\nj\nk) (delta y)) (epsilon z))")
# st = parse_sxpr("(alpha (beta (gamma i\nj\nk) (delta y)) (epsilon z))")
# print(st.as_sxpr())
# print(st.as_xml())
......@@ -39,7 +39,7 @@ import sys
from DHParser.error import is_error, adjust_error_locations
from DHParser.log import is_logging, clear_logs, log_ST, log_parsing_history
from DHParser.parse import UnknownParserError
from DHParser.syntaxtree import Node, mock_syntax_tree, flatten_sxpr, ZOMBIE_PARSER
from DHParser.syntaxtree import Node, parse_sxpr, flatten_sxpr, ZOMBIE_PARSER
from DHParser.toolkit import re, typing
from typing import Tuple
......@@ -315,12 +315,12 @@ def grammar_unit(test_unit, parser_factory, transformer_factory, report=True, ve
# write parsing-history log only in case of failure!
if is_logging():
log_parsing_history(parser, "match_%s_%s.log" % (parser_name, clean_test_name))
elif "cst" in tests and mock_syntax_tree(tests["cst"][test_name]) != cst:
elif "cst" in tests and parse_sxpr(tests["cst"][test_name]) != cst:
errata.append('Concrete syntax tree test "%s" for parser "%s" failed:\n%s' %
(test_name, parser_name, cst.as_sxpr()))
elif "ast" in tests:
try:
compare = mock_syntax_tree(tests["ast"][test_name])
compare = parse_sxpr(tests["ast"][test_name])
except KeyError:
pass
if compare != ast:
......
......@@ -728,7 +728,7 @@ node’s parser’s <cite>ptype</cite>.</p>
<dl class="function">
<dt id="syntaxtree.mock_syntax_tree">
<code class="descname">mock_syntax_tree</code><span class="sig-paren">(</span><em>sxpr</em><span class="sig-paren">)</span><a class="reference internal" href="_modules/syntaxtree.html#mock_syntax_tree"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#syntaxtree.mock_syntax_tree" title="Permalink to this definition"></a></dt>
<code class="descname">mock_syntax_tree</code><span class="sig-paren">(</span><em>sxpr</em><span class="sig-paren">)</span><a class="reference internal" href="_modules/syntaxtree.html#parse_sxpr"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#syntaxtree.parse_sxpr" title="Permalink to this definition"></a></dt>
<dd><p>Generates a tree of nodes from an S-expression. The main purpose of this is
to generate test data.</p>
<p>Example:
......
......@@ -561,7 +561,7 @@
</li>
<li><a href="ModuleReference.html#parse.mixin_comment">mixin_comment() (in module parse)</a>
</li>
<li><a href="ModuleReference.html#syntaxtree.mock_syntax_tree">mock_syntax_tree() (in module syntaxtree)</a>
<li><a href="ModuleReference.html#syntaxtree.mock_syntax_tree">parse_sxpr() (in module syntaxtree)</a>
</li>
<li><a href="ModuleReference.html#syntaxtree.MockParser">MockParser (class in syntaxtree)</a>
</li>
......
......@@ -20,12 +20,12 @@ limitations under the License.
"""
from DHParser import mock_syntax_tree, Compiler
from DHParser import parse_sxpr, Compiler
class TestCompilerClass:
def test_error_propagations(self):
tree = mock_syntax_tree('(A (B 1) (C (D (E 2) (F 3))))')
tree = parse_sxpr('(A (B 1) (C (D (E 2) (F 3))))')
A = tree
B = next(tree.select(lambda node: str(node) == "1"))
D = next(tree.select(lambda node: node.parser.name == "D"))
......
......@@ -123,12 +123,16 @@ class TestStringView:
assert EMPTY_STRING_VIEW.match(re.compile(r'.*'))
assert len(EMPTY_STRING_VIEW[0:1]) == 0
def text_strip(self):
def test_strip(self):
s = StringView(' test ', 1, -1)
assert s.strip() == "test"
assert s.lstrip() == "test "
assert s.rstrip() == " test"
s = StringView(' test ', 1, -1)
assert s.strip() == "test"
s = StringView('(a (b c))')
assert s.strip() == '(a (b c))'
assert s[1:].strip() == 'a (b c))'
def text_split(self):
s = StringView(' 1,2,3,4,5 ', 1, -1)
......
......@@ -24,7 +24,7 @@ import sys
sys.path.extend(['../', './'])
from DHParser.error import Error
from DHParser.syntaxtree import Node, mock_syntax_tree, flatten_sxpr, TOKEN_PTYPE
from DHParser.syntaxtree import Node, parse_sxpr, flatten_sxpr, TOKEN_PTYPE
from DHParser.transform import traverse, reduce_single_child, \
replace_by_single_child, flatten, remove_expendables
from DHParser.ebnf import get_ebnf_grammar, get_ebnf_transformer, get_ebnf_compiler
......@@ -33,11 +33,13 @@ from DHParser.dsl import grammar_provider
class TestMockSyntaxTree:
def test_mock_syntax_tree(self):
tree = mock_syntax_tree('(a (b c))')
tree = mock_syntax_tree('(a i\nj\nk)')
tree = parse_sxpr('(a (b c))')
assert flatten_sxpr(tree.as_sxpr()) == '(a (b "c"))', flatten_sxpr(tree.as_sxpr())
tree = parse_sxpr('(a i\nj\nk)')
assert flatten_sxpr(tree.as_sxpr()) == '(a "i" "j" "k")', flatten_sxpr(tree.as_sxpr())
try:
tree = mock_syntax_tree('a b c')
assert False, "mock_syntax_tree() should raise a ValueError " \
tree = parse_sxpr('a b c')
assert False, "parse_sxpr() should raise a ValueError " \
"if argument is not a tree!"
except ValueError:
pass
......@@ -49,9 +51,9 @@ class TestNode:
"""
def setup(self):
self.unique_nodes_sexpr = '(a (b c) (d e) (f (g h)))'
self.unique_tree = mock_syntax_tree(self.unique_nodes_sexpr)
self.unique_tree = parse_sxpr(self.unique_nodes_sexpr)
self.recurring_nodes_sexpr = '(a (b x) (c (d e) (b y)))'
self.recurr_tree = mock_syntax_tree(self.recurring_nodes_sexpr)
self.recurr_tree = parse_sxpr(self.recurring_nodes_sexpr)
def test_str(self):
assert str(self.unique_tree) == "ceh"
......@@ -68,8 +70,8 @@ class TestNode:
def test_equality1(self):
assert self.unique_tree == self.unique_tree
assert self.recurr_tree != self.unique_tree
assert mock_syntax_tree('(a (b c))') != mock_syntax_tree('(a (b d))')
assert mock_syntax_tree('(a (b c))') == mock_syntax_tree('(a (b c))')
assert parse_sxpr('(a (b c))') != parse_sxpr('(a (b d))')
assert parse_sxpr('(a (b c))') == parse_sxpr('(a (b c))')
def test_equality2(self):
ebnf = 'term = term ("*"|"/") factor | factor\nfactor = /[0-9]+/~'
......@@ -80,7 +82,7 @@ class TestNode:
parser = grammar_provider(ebnf)()
tree = parser("20 / 4 * 3")
traverse(tree, att)
compare_tree = mock_syntax_tree("(term (term (factor 20) (:Token /) (factor 4)) (:Token *) (factor 3))")
compare_tree = parse_sxpr("(term (term (factor 20) (:Token /) (factor 4)) (:Token *) (factor 3))")
assert tree == compare_tree, tree.as_sxpr()
def test_copy(self):
......@@ -126,7 +128,7 @@ class TestNode:
assert nd2.pos == 3, "Expected Node.pos == 3, got %i" % nd2.pos
def test_collect_errors(self):
tree = mock_syntax_tree('(A (B 1) (C (D (E 2) (F 3))))')
tree = parse_sxpr('(A (B 1) (C (D (E 2) (F 3))))')
A = tree
B = next(tree.select(lambda node: str(node) == "1"))
D = next(tree.select(lambda node: node.parser.name == "D"))
......@@ -145,7 +147,7 @@ class TestNode:
class TestErrorHandling:
def test_error_flag_propagation(self):
tree = mock_syntax_tree('(a (b c) (d (e (f (g h)))))')
tree = parse_sxpr('(a (b c) (d (e (f (g h)))))')
def find_h(context):
node = context[-1]
......@@ -154,7 +156,7 @@ class TestErrorHandling:
assert not tree.error_flag
traverse(tree, {"*": find_h})
assert tree.error_flag
assert tree.error_flag, tree.as_sxpr()
class TestNodeFind():
......@@ -165,33 +167,34 @@ class TestNodeFind():
def match_tag_name(node, tag_name):
return node.tag_name == tag_name
matchf = lambda node: match_tag_name(node, "X")
tree = mock_syntax_tree('(a (b X) (X (c d)) (e (X F)))')
tree = parse_sxpr('(a (b X) (X (c d)) (e (X F)))')
matches = list(tree.select(matchf))
assert len(matches) == 2, len(matches)
assert str(matches[0]) == 'd', str(matches[0])
assert str(matches[1]) == 'F', str(matches[1])
assert matches[0] == mock_syntax_tree('(X (c d))')
assert matches[1] == mock_syntax_tree('(X F)')
assert matches[0] == parse_sxpr('(X (c d))')
assert matches[1] == parse_sxpr('(X F)')
# check default: root is included in search:
matchf2 = lambda node: match_tag_name(node, 'a')
assert list(tree.select(matchf2))
assert not list(tree.select(matchf2, include_root=False))
def test_getitem(self):
tree = mock_syntax_tree('(a (b X) (X (c d)) (e (X F)))')
assert tree[0] == mock_syntax_tree('(b X)')
assert tree[2] == mock_syntax_tree('(e (X F))')
tree = parse_sxpr('(a (b X) (X (c d)) (e (X F)))')
# print(tree.as_sxpr())
assert tree[0] == parse_sxpr('(b X)')
assert tree[2] == parse_sxpr('(e (X F))')
try:
node = tree[3]
assert False, "IndexError expected!"
except IndexError:
pass
matches = list(tree.select_by_tag('X', False))
assert matches[0] == mock_syntax_tree('(X (c d))')
assert matches[1] == mock_syntax_tree('(X F)')
assert matches[0] == parse_sxpr('(X (c d))')
assert matches[1] == parse_sxpr('(X F)')
def test_contains(self):
tree = mock_syntax_tree('(a (b X) (X (c d)) (e (X F)))')
tree = parse_sxpr('(a (b X) (X (c d)) (e (X F)))')
assert 'a' not in tree
assert any(tree.select_by_tag('a', True))
assert not any(tree.select_by_tag('a', False))
......@@ -204,12 +207,12 @@ class TestNodeFind():
class TestSerialization:
def test_attributes(self):