Repository: lark-parser/lark
Pull request #466 (2019-10-31 12:19:33)

added visit_topdown methods to Visitor classes
I think it would be useful have visit_top_down methods in visitors.

erezsh: Looks good, but please include a test that shows that it works. You can modify the test used for bottom-up visits
llpinokio: @erezsh sure!, but where can i find it ?, i couldn't find in the tests or example folders...
i created this one

setup:
```python
from lark.tree import Tree
from lark.visitors import Visitor,Visitor_Recursive

class Printer(Visitor):
	def __default__(self,tree):
		print(tree.data)
class Printer_Recursive(Visitor_Recursive):
	def __default__(self,tree):
		print(tree.data)
tree=Tree("a",[
	Tree("b",[
		Tree("c",[]),
		Tree("d",[])
	]),
	Tree("e",[
		Tree("f",[]),
		Tree("g",[])
	])
])
```

iterative
```python
 Printer().visit_top_down(tree)
```
> a
b
c
d
e
f
g

recursive
```python
 Printer_Recursive().visit_top_down(tree)
```
> a
b
c
d
e
f
g

bottom-up
```python
Printer().visit(tree)
```
>c
d
b
f
g
e
a

evandrocoan: I think they are around here: https://github.com/lark-parser/lark/blob/master/tests/test_trees.py#L31 but I could not find the bottom up tests.
llpinokio: added on `tests/test_trees.py`
erezsh: Looks great! Just fix the formatting and I'll merge it in.

Patch:
diff --git a/lark/__init__.py b/lark/__init__.py
index ff24424..6d8b325 100644
--- a/lark/__init__.py
+++ b/lark/__init__.py
@@ -5,4 +5,4 @@ from .exceptions import ParseError, LexError, GrammarError, UnexpectedToken, Une
 from .lexer import Token
 from .lark import Lark
 
-__version__ = "0.7.8"
+__version__ = "0.7.7"
diff --git a/lark/load_grammar.py b/lark/load_grammar.py
index a65ca1e..1b4ab65 100644
--- a/lark/load_grammar.py
+++ b/lark/load_grammar.py
@@ -656,18 +656,16 @@ def resolve_term_references(term_defs):
                         raise GrammarError("Rules aren't allowed inside terminals (%s in %s)" % (item, name))
                     if item.type == 'TERMINAL':
                         term_value = term_dict[item]
-                        assert term_value is not None
                         exp.children[0] = term_value
                         changed = True
         if not changed:
             break
 
     for name, term in term_dict.items():
-        if term:    # Not just declared
-            for child in term.children:
-                ids = [id(x) for x in child.iter_subtrees()]
-                if id(term) in ids:
-                    raise GrammarError("Recursion in terminal '%s' (recursion is only allowed in rules, not terminals)" % name)
+        for child in term.children:
+            ids = [id(x) for x in child.iter_subtrees()]
+            if id(term) in ids:
+                raise GrammarError("Recursion in terminal '%s' (recursion is only allowed in rules, not terminals)" % name)
 
 
 def options_from_rule(name, *x):
diff --git a/lark/visitors.py b/lark/visitors.py
index 7d40e74..c6e4f6b 100644
--- a/lark/visitors.py
+++ b/lark/visitors.py
@@ -186,6 +186,11 @@ class Visitor(VisitorBase):
             self._call_userfunc(subtree)
         return tree
 
+    def visit_topdown(self,tree):
+        for subtree in tree.iter_subtrees_topdown():
+            self._call_userfunc(subtree)
+        return tree        
+
 class Visitor_Recursive(VisitorBase):
     """Bottom-up visitor, recursive
 
@@ -198,8 +203,16 @@ class Visitor_Recursive(VisitorBase):
             if isinstance(child, Tree):
                 self.visit(child)
 
-        f = getattr(self, tree.data, self.__default__)
-        f(tree)
+        self._call_userfunc(tree)
+        return tree
+
+    def visit_topdown(self,tree):
+        self._call_userfunc(tree)
+
+        for child in tree.children:
+            if isinstance(child, Tree):
+                self.visit_topdown(child)
+        
         return tree
 
 


Test patch:
diff --git a/tests/test_trees.py b/tests/test_trees.py
index 4216bd6..edd2a8b 100644
--- a/tests/test_trees.py
+++ b/tests/test_trees.py
@@ -7,7 +7,7 @@ import pickle
 import functools
 
 from lark.tree import Tree
-from lark.visitors import Transformer, Interpreter, visit_children_decor, v_args, Discard
+from lark.visitors import Visitor, Visitor_Recursive, Transformer, Interpreter, visit_children_decor, v_args, Discard
 
 
 class TestTrees(TestCase):
@@ -34,6 +34,43 @@ class TestTrees(TestCase):
         nodes = list(self.tree1.iter_subtrees_topdown())
         self.assertEqual(nodes, expected)
 
+    def test_visitor(self):
+        class Visitor1(Visitor):
+            def __init__(self):
+                self.nodes=[]
+
+            def __default__(self,tree):
+                self.nodes.append(tree)
+        class Visitor1_Recursive(Visitor_Recursive):
+            def __init__(self):
+                self.nodes=[]
+
+            def __default__(self,tree):
+                self.nodes.append(tree)
+
+        visitor1=Visitor1()
+        visitor1_recursive=Visitor1_Recursive()
+
+        expected_top_down = [Tree('a', [Tree('b', 'x'), Tree('c', 'y'), Tree('d', 'z')]),
+                    Tree('b', 'x'), Tree('c', 'y'), Tree('d', 'z')]
+        expected_botton_up= [Tree('b', 'x'), Tree('c', 'y'), Tree('d', 'z'),
+                    Tree('a', [Tree('b', 'x'), Tree('c', 'y'), Tree('d', 'z')])]
+
+        visitor1.visit(self.tree1)
+        self.assertEqual(visitor1.nodes,expected_botton_up)
+
+        visitor1_recursive.visit(self.tree1)
+        self.assertEqual(visitor1_recursive.nodes,expected_botton_up)
+
+        visitor1.nodes=[]
+        visitor1_recursive.nodes=[]
+
+        visitor1.visit_topdown(self.tree1)
+        self.assertEqual(visitor1.nodes,expected_top_down)
+
+        visitor1_recursive.visit_topdown(self.tree1)
+        self.assertEqual(visitor1_recursive.nodes,expected_top_down)
+
     def test_interp(self):
         t = Tree('a', [Tree('b', []), Tree('c', []), 'd'])