Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions docs/visitors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Transformers & Visitors
Transformers & Visitors provide a convenient interface to process the
parse-trees that Lark returns.

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

Visitors visit each node of the tree, and run the appropriate method on it according to the node's data.

They work bottom-up, starting with the leaves and ending at the root of the tree.
Visitors visit each node of the tree and run the appropriate method on it according to the node's data.
They can work top-down or bottom-up, depending on what method you use.

There are two classes that implement the visitor interface:

Expand All @@ -45,12 +44,12 @@ Example:
Interpreter
-----------

.. autoclass:: lark.visitors.Interpreter


Example:
::

from lark.visitors import Interpreter


class IncreaseSomeOfTheNumbers(Interpreter):
def number(self, tree):
tree.children[0] += 1
Expand All @@ -59,7 +58,12 @@ Example:
# skip this subtree. don't change any number node inside it.
pass

IncreaseSomeOfTheNumbers().visit(parse_tree)

IncreaseSomeOfTheNumbers().visit(parse_tree)


.. autoclass:: lark.visitors.Interpreter
:members: visit, visit_children, __default__

Transformer
-----------
Expand Down Expand Up @@ -98,14 +102,11 @@ Example:

.. autoclass:: lark.visitors.Transformer_InPlaceRecursive

v_args
------
Useful Utilities
----------------

.. autofunction:: lark.visitors.v_args

merge_transformers
------------------

.. autofunction:: lark.visitors.visit_children_decor
.. autofunction:: lark.visitors.merge_transformers

Discard
Expand Down
49 changes: 43 additions & 6 deletions lark/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ class Interpreter(_Decoratable, ABC, Generic[_Leaf_T, _Return_T]):
"""

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

def visit_children(self, tree: Tree[_Leaf_T]) -> List:
return [self._visit_tree(child) if isinstance(child, Tree) else child
for child in tree.children]
"Visit all the children of this tree and return the results as a list."
return [
self._visit_tree(child)
if isinstance(child, Tree)
else child
for child in tree.children
]

def __getattr__(self, name):
return self.__default__

def __default__(self, tree):
"""
Default function that is called if there is no attribute matching ``tree.data``.

Can be overridden. Defaults to visiting all the tree's children.
"""
return self.visit_children(tree)


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

def visit_children_decor(func: _InterMethod) -> _InterMethod:
"See Interpreter"
"""
A wrapper around Interpreter methods. It makes the wrapped node method automatically visit the
node's children before proceeding with the logic you have defined for that node.

Example:
::

class ProcessQuery(Interpreter):
@visit_children_decor
def query(self, tree):
pass
"""
@wraps(func)
def inner(cls, tree):
if not isinstance(cls, Interpreter):
raise TypeError("visit_children_decor can only be applied to Interpreter methods.")
values = cls.visit_children(tree)
return func(cls, values)
return inner
Expand Down Expand Up @@ -511,11 +535,12 @@ def _vargs_tree(f, data, children, meta):


def v_args(inline: bool = False, meta: bool = False, tree: bool = False, wrapper: Optional[Callable] = None) -> Callable[[_DECORATED], _DECORATED]:
"""A convenience decorator factory for modifying the behavior of user-supplied callback methods of ``Transformer`` classes.
"""A convenience decorator factory for modifying the behavior of user-supplied callback methods
of ``Transformer`` or ``Interpreter`` classes.

By default, transformer callback methods accept one argument - a list of the node's children.
By default, the callback methods for these classes accept one argument - a list of the node's children.

``v_args`` can modify this behavior. When used on a ``Transformer`` class definition, it applies to
``v_args`` can modify this behavior. When used on the class definition, it applies to
all the callback methods inside it.

``v_args`` can be applied to a single method, or to an entire class. When applied to both,
Expand Down Expand Up @@ -567,6 +592,18 @@ def tree_node(self, tree):
func = wrapper

def _visitor_args_dec(obj):
from inspect import isclass
if isclass(obj) and issubclass(obj, Visitor):
raise TypeError("v_args cannot be applied to Visitor classes.")
if callable(obj) and not isclass(obj):
@wraps(obj)
def method_wrapper(*args, **kwargs):
if args:
self_instance = args[0]
if isinstance(self_instance, Visitor):
raise TypeError("v_args cannot be applied to Visitor methods.")
return obj(*args, **kwargs)
return _apply_v_args(method_wrapper, func)
Comment on lines +595 to +606
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect in at least one way: We want this to error out on any classes other than Transformer and Interpreter (or their subclasses), not just Visitor. But I am struggling to fix this without causing other tests to fail; I don't really follow this code well to begin with, and would be OK just stripping it out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erezsh - Would you like me to remove this new logic as well, or do you have a suggestion as to how we could make it better?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erezsh - Checking in again here. Happy to cut this PR back to just documentation changes if you prefer that.

return _apply_v_args(obj, func)
return _visitor_args_dec

Expand Down
28 changes: 28 additions & 0 deletions tests/test_trees.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ def c(self, tree):
return 'C'

self.assertEqual(Interp3().visit(t), list('BCd'))
self.assertEqual(
Interp3().visit(t),
Interp3().visit_topdown(t),
)

def test_transformer(self):
t = Tree('add', [Tree('sub', [Tree('i', ['3']), Tree('f', ['1.1'])]), Tree('i', ['1'])])
Expand Down Expand Up @@ -471,6 +475,30 @@ def INT(self, value):

assert MyTransformer().transform(t) is None

def test_incorrect_use_of_decorators(self):
class TestVisitor1(Visitor):
@visit_children_decor
def a(self, tree):
pass

@v_args(inline=True)
def b(self, tree):
pass

with self.assertRaises(TypeError) as e:
TestVisitor1().visit_topdown(self.tree1)
self.assertIn("visit_children_decor", str(e.exception))
self.assertIn("Interpreter", str(e.exception))
with self.assertRaises(TypeError) as e:
TestVisitor1().visit(self.tree1)
self.assertIn("v_args", str(e.exception))

with self.assertRaises(TypeError) as e:
@v_args(inline=True)
class TestVisitor2(Visitor):
def a(self, tree):
pass
self.assertIn("v_args", str(e.exception))

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