@@ -3,6 +3,7 @@ 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 | from .visitors import Transformer_InPlace | ||||
from . import visitors | |||||
###{standalone | ###{standalone | ||||
from functools import partial, wraps | from functools import partial, wraps | ||||
@@ -202,6 +203,15 @@ def inplace_transformer(func): | |||||
return func(tree) | return func(tree) | ||||
return f | return f | ||||
def apply_visit_wrapper(func, name, wrapper): | |||||
if wrapper is visitors._vargs_meta or wrapper is visitors._vargs_meta_inline: | |||||
raise NotImplementedError("Meta args not supported for internal transformer") | |||||
@wraps(func) | |||||
def f(children): | |||||
return wrapper(func, name, children, None) | |||||
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 | ||||
@@ -236,12 +246,15 @@ class ParseTreeBuilder: | |||||
user_callback_name = rule.alias or rule.origin.name | user_callback_name = rule.alias or rule.origin.name | ||||
try: | try: | ||||
f = getattr(transformer, user_callback_name) | f = getattr(transformer, user_callback_name) | ||||
assert not getattr(f, 'meta', False), "Meta args not supported for internal transformer" | |||||
# XXX InlineTransformer is deprecated! | # 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) | |||||
wrapper = getattr(f, 'visit_wrapper', None) | |||||
if wrapper is not None: | |||||
f = apply_visit_wrapper(f, user_callback_name, wrapper) | |||||
else: | |||||
if isinstance(transformer, InlineTransformer): | |||||
f = ptb_inline_args(f) | |||||
elif 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) | ||||
@@ -35,14 +35,9 @@ class Transformer: | |||||
return self.__default__(tree.data, children, tree.meta) | return self.__default__(tree.data, children, tree.meta) | ||||
else: | else: | ||||
try: | try: | ||||
if getattr(f, 'meta', False): | |||||
return f(children, tree.meta) | |||||
elif getattr(f, 'inline', False): | |||||
return f(*children) | |||||
elif getattr(f, 'whole_tree', False): | |||||
if new_children is not None: | |||||
tree.children = new_children | |||||
return f(tree) | |||||
wrapper = getattr(f, 'visit_wrapper', None) | |||||
if wrapper is not None: | |||||
return f.visit_wrapper(f, tree.data, children, tree.meta) | |||||
else: | else: | ||||
return f(children) | return f(children) | ||||
except (GrammarError, Discard): | except (GrammarError, Discard): | ||||
@@ -282,8 +277,7 @@ def inline_args(obj): # XXX Deprecated | |||||
def _visitor_args_func_dec(func, inline=False, meta=False, whole_tree=False, static=False): | |||||
assert [whole_tree, meta, inline].count(True) <= 1 | |||||
def _visitor_args_func_dec(func, visit_wrapper=None, static=False): | |||||
def create_decorator(_f, with_self): | def create_decorator(_f, with_self): | ||||
if with_self: | if with_self: | ||||
def f(self, *args, **kwargs): | def f(self, *args, **kwargs): | ||||
@@ -298,17 +292,42 @@ def _visitor_args_func_dec(func, inline=False, meta=False, whole_tree=False, sta | |||||
else: | else: | ||||
f = smart_decorator(func, create_decorator) | f = smart_decorator(func, create_decorator) | ||||
f.vargs_applied = True | f.vargs_applied = True | ||||
f.inline = inline | |||||
f.meta = meta | |||||
f.whole_tree = whole_tree | |||||
f.visit_wrapper = visit_wrapper | |||||
return f | return f | ||||
def v_args(inline=False, meta=False, tree=False): | |||||
def _vargs_inline(f, data, children, meta): | |||||
return f(*children) | |||||
def _vargs_meta_inline(f, data, children, meta): | |||||
return f(meta, *children) | |||||
def _vargs_meta(f, data, children, meta): | |||||
return f(children, meta) # TODO swap these for consistency? Backwards incompatible! | |||||
def _vargs_tree(f, data, children, meta): | |||||
return f(Tree(data, children, meta)) | |||||
def v_args(inline=False, meta=False, tree=False, wrapper=None): | |||||
"A convenience decorator factory, for modifying the behavior of user-supplied visitor methods" | "A convenience decorator factory, for modifying the behavior of user-supplied visitor methods" | ||||
if [tree, meta, inline].count(True) > 1: | |||||
raise ValueError("Visitor functions can either accept tree, or meta, or be inlined. These cannot be combined.") | |||||
if tree and (meta or inline): | |||||
raise ValueError("Visitor functions cannot combine 'tree' with 'meta' or 'inline'.") | |||||
func = None | |||||
if meta: | |||||
if inline: | |||||
func = _vargs_meta_inline | |||||
else: | |||||
func = _vargs_meta | |||||
elif inline: | |||||
func = _vargs_inline | |||||
elif tree: | |||||
func = _vargs_tree | |||||
if wrapper is not None: | |||||
if func is not None: | |||||
raise ValueError("Cannot use 'wrapper' along with 'tree', 'meta' or 'inline'.") | |||||
func = wrapper | |||||
def _visitor_args_dec(obj): | def _visitor_args_dec(obj): | ||||
return _apply_decorator(obj, _visitor_args_func_dec, inline=inline, meta=meta, whole_tree=tree) | |||||
return _apply_decorator(obj, _visitor_args_func_dec, visit_wrapper=func) | |||||
return _visitor_args_dec | return _visitor_args_dec | ||||
@@ -5,6 +5,7 @@ import unittest | |||||
import logging | import logging | ||||
import os | import os | ||||
import sys | import sys | ||||
from copy import deepcopy | |||||
try: | try: | ||||
from cStringIO import StringIO as cStringIO | from cStringIO import StringIO as cStringIO | ||||
except ImportError: | except ImportError: | ||||
@@ -117,6 +118,61 @@ class TestParsers(unittest.TestCase): | |||||
r = p.parse("x") | r = p.parse("x") | ||||
self.assertEqual( r.children, ["X!"] ) | self.assertEqual( r.children, ["X!"] ) | ||||
def test_vargs_meta(self): | |||||
@v_args(meta=True) | |||||
class T1(Transformer): | |||||
def a(self, children, meta): | |||||
assert not children | |||||
return meta.line | |||||
def start(self, children, meta): | |||||
return children | |||||
@v_args(meta=True, inline=True) | |||||
class T2(Transformer): | |||||
def a(self, meta): | |||||
return meta.line | |||||
def start(self, meta, *res): | |||||
return list(res) | |||||
for T in (T1, T2): | |||||
for internal in [False, True]: | |||||
try: | |||||
g = Lark(r"""start: a+ | |||||
a : "x" _NL? | |||||
_NL: /\n/+ | |||||
""", parser='lalr', transformer=T() if internal else None) | |||||
except NotImplementedError: | |||||
assert internal | |||||
continue | |||||
res = g.parse("xx\nx\nxxx\n\n\nxx") | |||||
assert not internal | |||||
res = T().transform(res) | |||||
self.assertEqual(res, [1, 1, 2, 3, 3, 3, 6, 6]) | |||||
def test_vargs_tree(self): | |||||
tree = Lark(''' | |||||
start: a a a | |||||
!a: "A" | |||||
''').parse('AAA') | |||||
tree_copy = deepcopy(tree) | |||||
@v_args(tree=True) | |||||
class T(Transformer): | |||||
def a(self, tree): | |||||
return 1 | |||||
def start(self, tree): | |||||
return tree.children | |||||
res = T().transform(tree) | |||||
self.assertEqual(res, [1, 1, 1]) | |||||
self.assertEqual(tree, tree_copy) | |||||
def test_embedded_transformer(self): | def test_embedded_transformer(self): | ||||
class T(Transformer): | class T(Transformer): | ||||
@@ -188,7 +244,7 @@ class TestParsers(unittest.TestCase): | |||||
@v_args(tree=True) | @v_args(tree=True) | ||||
class T2(Transformer): | class T2(Transformer): | ||||
def a(self, tree): | def a(self, tree): | ||||
assert isinstance(tree, Tree) | |||||
assert isinstance(tree, Tree), tree | |||||
tree.children.append("tested") | tree.children.append("tested") | ||||
return tree | return tree | ||||