Skip to content

Commit 3421420

Browse files
nchammaserezsh
andauthored
Miscellaneous improvements to the behavior and documentation of Visitor, Transformer, Interpreter, and friends (#1543)
* tweak docs * add missing docstrings, have decorators error when used incorrectly * add tests for changed behavior * remove implementation of visit_topdown * revert changes to _visitor_args_dec * remove associated tests * remove more bad tests i added also tweak new test for decorator use * factor repeated test * Small phrasing update --------- Co-authored-by: Erez Shinan <erezshin+git@gmail.com>
1 parent 1573ca8 commit 3421420

3 files changed

Lines changed: 60 additions & 20 deletions

File tree

docs/visitors.rst

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Transformers & Visitors
44
Transformers & Visitors provide a convenient interface to process the
55
parse-trees that Lark returns.
66

7-
They are used by inheriting from the correct class (visitor or transformer),
7+
They are used by inheriting from one of the classes described here,
88
and implementing methods corresponding to the rule you wish to process. Each
99
method accepts the children as an argument. That can be modified using the
1010
``v_args`` decorator, which allows one to inline the arguments (akin to ``*args``),
@@ -17,9 +17,8 @@ See: `visitors.py`_
1717
Visitor
1818
-------
1919

20-
Visitors visit each node of the tree, and run the appropriate method on it according to the node's data.
21-
22-
They work bottom-up, starting with the leaves and ending at the root of the tree.
20+
Visitors visit each node of the tree and run the appropriate method on it according to the node's data.
21+
They can traverse top-down or bottom-up, depending on the class and method.
2322

2423
There are two classes that implement the visitor interface:
2524

@@ -45,12 +44,12 @@ Example:
4544
Interpreter
4645
-----------
4746

48-
.. autoclass:: lark.visitors.Interpreter
49-
50-
5147
Example:
5248
::
5349

50+
from lark.visitors import Interpreter
51+
52+
5453
class IncreaseSomeOfTheNumbers(Interpreter):
5554
def number(self, tree):
5655
tree.children[0] += 1
@@ -59,7 +58,12 @@ Example:
5958
# skip this subtree. don't change any number node inside it.
6059
pass
6160

62-
IncreaseSomeOfTheNumbers().visit(parse_tree)
61+
62+
IncreaseSomeOfTheNumbers().visit(parse_tree)
63+
64+
65+
.. autoclass:: lark.visitors.Interpreter
66+
:members: visit, visit_children, __default__
6367

6468
Transformer
6569
-----------
@@ -98,14 +102,11 @@ Example:
98102

99103
.. autoclass:: lark.visitors.Transformer_InPlaceRecursive
100104

101-
v_args
102-
------
105+
Useful Utilities
106+
----------------
103107

104108
.. autofunction:: lark.visitors.v_args
105-
106-
merge_transformers
107-
------------------
108-
109+
.. autofunction:: lark.visitors.visit_children_decor
109110
.. autofunction:: lark.visitors.merge_transformers
110111

111112
Discard

lark/visitors.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ class Interpreter(_Decoratable, ABC, Generic[_Leaf_T, _Return_T]):
417417
"""
418418

419419
def visit(self, tree: Tree[_Leaf_T]) -> _Return_T:
420+
"Visit the tree, starting with the root and finally the leaves (top-down)."
420421
# There are no guarantees on the type of the value produced by calling a user func for a
421422
# child will produce. So only annotate the public method and use an internal method when
422423
# visiting child trees.
@@ -431,22 +432,45 @@ def _visit_tree(self, tree: Tree[_Leaf_T]):
431432
return f(tree)
432433

433434
def visit_children(self, tree: Tree[_Leaf_T]) -> List:
434-
return [self._visit_tree(child) if isinstance(child, Tree) else child
435-
for child in tree.children]
435+
"Visit all the children of this tree and return the results as a list."
436+
return [
437+
self._visit_tree(child)
438+
if isinstance(child, Tree)
439+
else child
440+
for child in tree.children
441+
]
436442

437443
def __getattr__(self, name):
438444
return self.__default__
439445

440446
def __default__(self, tree):
447+
"""
448+
Default function that is called if there is no attribute matching ``tree.data``.
449+
450+
Can be overridden. Defaults to visiting all the tree's children.
451+
"""
441452
return self.visit_children(tree)
442453

443454

444455
_InterMethod = Callable[[Type[Interpreter], _Return_T], _R]
445456

446457
def visit_children_decor(func: _InterMethod) -> _InterMethod:
447-
"See Interpreter"
458+
"""
459+
A wrapper around Interpreter methods. It makes the wrapped node method automatically visit the
460+
node's children before proceeding with the logic you have defined for that node.
461+
462+
Example:
463+
::
464+
465+
class ProcessQuery(Interpreter):
466+
@visit_children_decor
467+
def query(self, tree):
468+
pass
469+
"""
448470
@wraps(func)
449471
def inner(cls, tree):
472+
if not isinstance(cls, Interpreter):
473+
raise TypeError("visit_children_decor can only be applied to Interpreter methods.")
450474
values = cls.visit_children(tree)
451475
return func(cls, values)
452476
return inner
@@ -511,11 +535,12 @@ def _vargs_tree(f, data, children, meta):
511535

512536

513537
def v_args(inline: bool = False, meta: bool = False, tree: bool = False, wrapper: Optional[Callable] = None) -> _DECORATOR:
514-
"""A convenience decorator factory for modifying the behavior of user-supplied callback methods of ``Transformer`` classes.
538+
"""A convenience decorator factory for modifying the behavior of user-supplied callback methods
539+
of ``Transformer`` or ``Interpreter`` classes.
515540
516-
By default, transformer callback methods accept one argument - a list of the node's children.
541+
By default, the callback methods for these classes accept one argument - a list of the node's children.
517542
518-
``v_args`` can modify this behavior. When used on a ``Transformer`` class definition, it applies to
543+
``v_args`` can modify this behavior. When used on the class definition, it applies to
519544
all the callback methods inside it.
520545
521546
``v_args`` can be applied to a single method, or to an entire class. When applied to both,

tests/test_trees.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,20 @@ def INT(self, value):
471471

472472
assert MyTransformer().transform(t) is None
473473

474+
def test_incorrect_use_of_decorators(self):
475+
class TestVisitor1(Visitor):
476+
@visit_children_decor
477+
def a(self, tree):
478+
pass
479+
480+
for visit_method in [
481+
TestVisitor1().visit,
482+
TestVisitor1().visit_topdown,
483+
]:
484+
with self.assertRaises(TypeError) as e:
485+
visit_method(self.tree1)
486+
self.assertIn("visit_children_decor", str(e.exception))
487+
self.assertIn("Interpreter", str(e.exception))
474488

475489
if __name__ == '__main__':
476490
unittest.main()

0 commit comments

Comments
 (0)