| @@ -3,6 +3,7 @@ from .lexer import Token | |||
| from .tree import Tree | |||
| from .visitors import InlineTransformer # XXX Deprecated | |||
| from .visitors import Transformer_InPlace | |||
| from . import visitors | |||
| ###{standalone | |||
| from functools import partial, wraps | |||
| @@ -202,6 +203,15 @@ def inplace_transformer(func): | |||
| return func(tree) | |||
| 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: | |||
| def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False, maybe_placeholders=False): | |||
| self.tree_class = tree_class | |||
| @@ -236,12 +246,15 @@ class ParseTreeBuilder: | |||
| user_callback_name = rule.alias or rule.origin.name | |||
| try: | |||
| f = getattr(transformer, user_callback_name) | |||
| assert not getattr(f, 'meta', False), "Meta args not supported for internal transformer" | |||
| # 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: | |||
| f = partial(self.tree_class, user_callback_name) | |||
| @@ -35,14 +35,9 @@ class Transformer: | |||
| return self.__default__(tree.data, children, tree.meta) | |||
| else: | |||
| 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: | |||
| return f(children) | |||
| 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): | |||
| if with_self: | |||
| def f(self, *args, **kwargs): | |||
| @@ -298,17 +292,42 @@ def _visitor_args_func_dec(func, inline=False, meta=False, whole_tree=False, sta | |||
| else: | |||
| f = smart_decorator(func, create_decorator) | |||
| f.vargs_applied = True | |||
| f.inline = inline | |||
| f.meta = meta | |||
| f.whole_tree = whole_tree | |||
| f.visit_wrapper = visit_wrapper | |||
| 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" | |||
| 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): | |||
| 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 | |||
| @@ -5,6 +5,7 @@ import unittest | |||
| import logging | |||
| import os | |||
| import sys | |||
| from copy import deepcopy | |||
| try: | |||
| from cStringIO import StringIO as cStringIO | |||
| except ImportError: | |||
| @@ -117,6 +118,61 @@ class TestParsers(unittest.TestCase): | |||
| r = p.parse("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): | |||
| class T(Transformer): | |||
| @@ -188,7 +244,7 @@ class TestParsers(unittest.TestCase): | |||
| @v_args(tree=True) | |||
| class T2(Transformer): | |||
| def a(self, tree): | |||
| assert isinstance(tree, Tree) | |||
| assert isinstance(tree, Tree), tree | |||
| tree.children.append("tested") | |||
| return tree | |||