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.
This commit is contained in:
Mostafa Razavi 2019-05-12 19:32:50 +02:00
parent 0f9dfdd623
commit e5868415eb
2 changed files with 38 additions and 1 deletions

View File

@ -2,6 +2,7 @@ from .exceptions import GrammarError
from .lexer import Token from .lexer import Token
from .tree import Tree from .tree import Tree
from .visitors import InlineTransformer # XXX Deprecated from .visitors import InlineTransformer # XXX Deprecated
from .visitors import Transformer_InPlace
###{standalone ###{standalone
from functools import partial, wraps from functools import partial, wraps
@ -193,6 +194,15 @@ def ptb_inline_args(func):
return func(*children) return func(*children)
return f 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: class ParseTreeBuilder:
def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False, maybe_placeholders=False): def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False, maybe_placeholders=False):
self.tree_class = tree_class self.tree_class = tree_class
@ -231,6 +241,8 @@ class ParseTreeBuilder:
# XXX InlineTransformer is deprecated! # XXX InlineTransformer is deprecated!
if getattr(f, 'inline', False) or isinstance(transformer, InlineTransformer): if getattr(f, 'inline', False) or isinstance(transformer, InlineTransformer):
f = ptb_inline_args(f) f = ptb_inline_args(f)
elif hasattr(f, 'whole_tree') or isinstance(transformer, Transformer_InPlace):
f = inplace_transformer(f)
except AttributeError: except AttributeError:
f = partial(self.tree_class, user_callback_name) f = partial(self.tree_class, user_callback_name)

View File

@ -20,7 +20,7 @@ logging.basicConfig(level=logging.INFO)
from lark.lark import Lark from lark.lark import Lark
from lark.exceptions import GrammarError, ParseError, UnexpectedToken, UnexpectedInput, UnexpectedCharacters from lark.exceptions import GrammarError, ParseError, UnexpectedToken, UnexpectedInput, UnexpectedCharacters
from lark.tree import Tree 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.grammar import Rule
from lark.lexer import TerminalDef from lark.lexer import TerminalDef
@ -150,6 +150,31 @@ class TestParsers(unittest.TestCase):
r = g.parse("xx") r = g.parse("xx")
self.assertEqual( r.children, ["<c>"] ) 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): def test_alias(self):
Lark("""start: ["a"] "b" ["c"] "e" ["f"] ["g"] ["h"] "x" -> d """) Lark("""start: ["a"] "b" ["c"] "e" ["f"] ["g"] ["h"] "x" -> d """)