Commit d5c9fa47 authored by Igor Dejanovic's avatar Igor Dejanovic

Parse tree navigation using dot syntax + unit tests.

parent 6f1c305e
......@@ -677,50 +677,73 @@ class Terminal(ParseTreeNode):
class NonTerminal(ParseTreeNode, list):
"""
Non-leaf node of the Parse Tree. Represents language syntax construction.
At the same time used in ParseTreeNode navigation expressions.
See test_ptnode_navigation_expressions.py for examples of navigation expressions.
Attributes:
nodes (list of ParseTreeNode): Children parse tree nodes.
_filtered (bool): Is this NT a dynamically created filtered NT.
This is used internally.
"""
def __init__(self, rule, position, nodes, error=False):
def __init__(self, rule, position, nodes, error=False, _filtered=False):
super(NonTerminal, self).__init__(rule, position, error)
self.extend(flatten([nodes]))
self._filtered = _filtered
# Child nodes cache. Used for lookup by rule name.
self._child_cache = {}
# Navigation expression cache. Used for lookup by rule name.
self._expr_cache = {}
@property
def value(self):
"""Terminal protocol."""
return str(self)
@property
def desc(self):
return self.name
# def __iter__(self):
# return self
def __str__(self):
return " | ".join([str(x) for x in self])
def __repr__(self):
return "[ %s ]" % ", ".join([repr(x) for x in self])
def __getattr__(self, item):
def __getattr__(self, rule_name):
"""
Find a child (non)terminal by the rule name.
Args:
item(str): The name of the child node.
rule_name(str): The name of the rule that is referenced from
this node rule.
"""
# Prevent infinite recursion
if rule_name == '_expr_cache':
raise AttributeError
# First check the cache
if item in self._child_cache:
return self._child_cache[item]
if rule_name in self._expr_cache:
return self._expr_cache[rule_name]
# If not found in the cache find it and store it in the
# cache for later.
# If result is not found in the cache collect all nodes
# with the given rule name and create new NonTerminal
# and cache it for later access.
nodes = []
for n in self:
if n.rule == item:
self._child_cache[item] = n
return n
if self._filtered:
# For filtered NT rule_name is a rule on
# each of its children
for m in n:
if m.rule == rule_name:
nodes.append(m)
else:
if n.rule == rule_name:
nodes.append(n)
raise AttributeError
# For expression NonTerminals instances position does not have any sense.
result = NonTerminal(rule=rule_name, position=None, nodes=nodes, _filtered=True)
self._expr_cache[rule_name] = result
return result
# ----------------------------------------------------
......
# -*- coding: utf-8 -*-
#######################################################################
# Name: test_ptnode_navigation_expressions
# Purpose: Test ParseTreeNode navigation expressions.
# Author: Igor R. Dejanović <igor DOT dejanovic AT gmail DOT com>
# Copyright: (c) 2014 Igor R. Dejanović <igor DOT dejanovic AT gmail DOT com>
# License: MIT License
#######################################################################
import pytest
# Grammar
from arpeggio import ParserPython, ZeroOrMore, ParseTreeNode, NonTerminal
from arpeggio.export import PTDOTExporter
def foo(): return "a", bar, "b", baz, bar2, ZeroOrMore(bar)
def bar(): return [bla, bum], baz, "c"
def bar2():return ZeroOrMore(bla)
def baz(): return "d"
def bla(): return "bla"
def bum(): return ["bum", "bam"]
def test_lookup_single():
parser = ParserPython(foo, reduce_tree=False)
result = parser.parse("a bum d c b d bla bum d c")
# Uncomment following line to visualize the parse tree in graphviz
# PTDOTExporter().exportFile(result, 'test_ptnode_navigation_expressions.dot')
assert isinstance(result, ParseTreeNode)
assert isinstance(result.bar, NonTerminal)
# dot access
assert result.bar.rule == 'bar'
# Index access
assert result[1].rule == 'bar'
# There are six children from result
assert len(result) == 6
# There is two bar matched from result (at the begging and from ZeroOrMore)
# Dot access collect all NTs from the given path
assert len(result.bar) == 2
# Verify position
assert result.bar[0].position == 2
assert result.bar[1].position == 18
# Multilevel dot access returns all elements from all previous ones.
# For example this returns all bum from all bar in result
assert len(result.bar.bum) == 2
# Verify that proper bum are returned
assert result.bar.bum[0].rule == 'bum'
assert result.bar.bum[1].position == 18
# The same for all bla from all bar2
assert len(result.bar2.bla) == 1
assert hasattr(result, "bar")
assert hasattr(result, "baz")
# -*- coding: utf-8 -*-
#######################################################################
# Name: test_peg_parser
# Purpose: Test for parser constructed using PEG textual grammars.
# Author: Igor R. Dejanović <igor DOT dejanovic AT gmail DOT com>
# Copyright: (c) 2014 Igor R. Dejanović <igor DOT dejanovic AT gmail DOT com>
# License: MIT License
#######################################################################
import pytest
# Grammar
from arpeggio import ParserPython, ZeroOrMore
def foo(): return "a", bar, "b", baz
def bar(): return "c"
def baz(): return "d"
def test_lookup_single():
parser = ParserPython(foo)
result = parser.parse("a c b d")
assert hasattr(result, "bar")
assert hasattr(result, "baz")
assert not hasattr(result, "unexisting")
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