Repository: lark-parser/lark
Pull request #379 (2019-05-12 17:38:01)

Implement embedded in-place transformers. See #378.
As discussed in issue #378, when an embedded transformer (that is, one
passed to the Lark class using the transformer argument), is an
inplace transformer (either a subclass of Transformer_InPlace, or with
the @v_args(tree=True) decorator), the in-place transformer was not
working correctly and in-fact Lark used it like a normal non-in-place
transformer, expecting it to return the transformed value.


Patch:
diff --git a/lark/parse_tree_builder.py b/lark/parse_tree_builder.py
index 977c371..550bc17 100644
--- a/lark/parse_tree_builder.py
+++ b/lark/parse_tree_builder.py
@@ -2,6 +2,7 @@ from .exceptions import GrammarError
 from .lexer import Token
 from .tree import Tree
 from .visitors import InlineTransformer # XXX Deprecated
+from .visitors import Transformer_InPlace
 
 ###{standalone
 from functools import partial, wraps
@@ -193,6 +194,15 @@ def ptb_inline_args(func):
         return func(*children)
     return f
 
+def inplace_transformer(func):
+    @wraps(func)
+    def f(children):
+        # function name in a Transformer is a rule name.
+        tree = Tree(func.__name__, children)
+        func(tree)
+        return tree
+    return f
+
 class ParseTreeBuilder:
     def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False, maybe_placeholders=False):
         self.tree_class = tree_class
@@ -231,6 +241,8 @@ class ParseTreeBuilder:
                 # XXX InlineTransformer is deprecated!
                 if getattr(f, 'inline', False) or isinstance(transformer, InlineTransformer):
                     f = ptb_inline_args(f)
+                elif hasattr(f, 'whole_tree') or isinstance(transformer, Transformer_InPlace):
+                    f = inplace_transformer(f)
             except AttributeError:
                 f = partial(self.tree_class, user_callback_name)
 


Test patch:
diff --git a/tests/test_parser.py b/tests/test_parser.py
index ce8b7d6..0fddf14 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -20,7 +20,7 @@ logging.basicConfig(level=logging.INFO)
 from lark.lark import Lark
 from lark.exceptions import GrammarError, ParseError, UnexpectedToken, UnexpectedInput, UnexpectedCharacters
 from lark.tree import Tree
-from lark.visitors import Transformer
+from lark.visitors import Transformer, Transformer_InPlace, v_args
 from lark.grammar import Rule
 from lark.lexer import TerminalDef
 
@@ -150,6 +150,31 @@ class TestParsers(unittest.TestCase):
         r = g.parse("xx")
         self.assertEqual( r.children, ["<c>"] )
 
+    def test_embedded_transformer_inplace(self):
+        class T1(Transformer_InPlace):
+            def a(self, tree):
+                assert isinstance(tree, Tree)
+                tree.children.append("tested")
+
+        @v_args(tree=True)
+        class T2(Transformer):
+            def a(self, tree):
+                assert isinstance(tree, Tree)
+                tree.children.append("tested")
+
+        class T3(Transformer):
+            @v_args(tree=True)
+            def a(self, tree):
+                assert isinstance(tree, Tree)
+                tree.children.append("tested")
+
+        for t in [T1(), T2(), T3()]:
+            g = Lark("""start: a
+                        a : "x"
+                     """, parser='lalr', transformer=t)
+            r = g.parse("x")
+            first, = r.children
+            self.assertEqual(first.children, ["tested"])
 
     def test_alias(self):
         Lark("""start: ["a"] "b" ["c"] "e" ["f"] ["g"] ["h"] "x" -> d """)