| @@ -2,7 +2,7 @@ | |||||
| # This example shows how to write a basic calculator with variables. | # This example shows how to write a basic calculator with variables. | ||||
| # | # | ||||
| from lark import Lark, InlineTransformer | |||||
| from lark import Lark, Transformer, v_args | |||||
| try: | try: | ||||
| input = raw_input # For Python2 compatibility | input = raw_input # For Python2 compatibility | ||||
| @@ -34,7 +34,8 @@ calc_grammar = """ | |||||
| %ignore WS_INLINE | %ignore WS_INLINE | ||||
| """ | """ | ||||
| class CalculateTree(InlineTransformer): | |||||
| @v_args(inline=True) | |||||
| class CalculateTree(Transformer): | |||||
| from operator import add, sub, mul, truediv as div, neg | from operator import add, sub, mul, truediv as div, neg | ||||
| number = float | number = float | ||||
| @@ -7,7 +7,7 @@ | |||||
| import sys | import sys | ||||
| from lark import Lark, inline_args, Transformer | |||||
| from lark import Lark, Transformer, v_args | |||||
| json_grammar = r""" | json_grammar = r""" | ||||
| ?start: value | ?start: value | ||||
| @@ -34,14 +34,14 @@ json_grammar = r""" | |||||
| """ | """ | ||||
| class TreeToJson(Transformer): | class TreeToJson(Transformer): | ||||
| @inline_args | |||||
| @v_args(inline=True) | |||||
| def string(self, s): | def string(self, s): | ||||
| return s[1:-1].replace('\\"', '"') | return s[1:-1].replace('\\"', '"') | ||||
| array = list | array = list | ||||
| pair = tuple | pair = tuple | ||||
| object = dict | object = dict | ||||
| number = inline_args(float) | |||||
| number = v_args(inline=True)(float) | |||||
| null = lambda self, _: None | null = lambda self, _: None | ||||
| true = lambda self, _: True | true = lambda self, _: True | ||||
| @@ -1,7 +1,8 @@ | |||||
| from .tree import Tree, Transformer, InlineTransformer | |||||
| from .tree import Tree | |||||
| from .visitors import Transformer, Visitor, v_args, Discard | |||||
| from .visitors import InlineTransformer, inline_args # XXX Deprecated | |||||
| from .common import ParseError, GrammarError, UnexpectedToken | from .common import ParseError, GrammarError, UnexpectedToken | ||||
| from .lexer import UnexpectedInput, LexError | from .lexer import UnexpectedInput, LexError | ||||
| from .lark import Lark | from .lark import Lark | ||||
| from .utils import inline_args | |||||
| __version__ = "0.5.6" | __version__ = "0.5.6" | ||||
| @@ -34,6 +34,8 @@ class Token(Str): | |||||
| self.value = value | self.value = value | ||||
| self.line = line | self.line = line | ||||
| self.column = column | self.column = column | ||||
| self.end_line = None | |||||
| self.end_column = None | |||||
| return self | return self | ||||
| @classmethod | @classmethod | ||||
| @@ -112,6 +114,7 @@ class _Lex: | |||||
| if t: | if t: | ||||
| t.end_line = line_ctr.line | t.end_line = line_ctr.line | ||||
| t.end_column = line_ctr.column | t.end_column = line_ctr.column | ||||
| break | break | ||||
| else: | else: | ||||
| if line_ctr.char_pos < len(stream): | if line_ctr.char_pos < len(stream): | ||||
| @@ -15,7 +15,9 @@ from .common import is_terminal, GrammarError, LexerConf, ParserConf, PatternStr | |||||
| from .grammar import RuleOptions, Rule, Terminal, NonTerminal, Symbol | from .grammar import RuleOptions, Rule, Terminal, NonTerminal, Symbol | ||||
| from .utils import classify, suppress | from .utils import classify, suppress | ||||
| from .tree import Tree, Transformer, InlineTransformer, Visitor, SlottedTree as ST | |||||
| from .tree import Tree, SlottedTree as ST | |||||
| from .visitors import Transformer, Visitor, v_args | |||||
| inline_args = v_args(inline=True) | |||||
| __path__ = os.path.dirname(__file__) | __path__ = os.path.dirname(__file__) | ||||
| IMPORT_PATHS = [os.path.join(__path__, 'grammars')] | IMPORT_PATHS = [os.path.join(__path__, 'grammars')] | ||||
| @@ -140,7 +142,8 @@ RULES = { | |||||
| } | } | ||||
| class EBNF_to_BNF(InlineTransformer): | |||||
| @inline_args | |||||
| class EBNF_to_BNF(Transformer): | |||||
| def __init__(self): | def __init__(self): | ||||
| self.new_rules = [] | self.new_rules = [] | ||||
| self.rules_by_expr = {} | self.rules_by_expr = {} | ||||
| @@ -209,17 +212,14 @@ class SimplifyRule_Visitor(Visitor): | |||||
| # --> | # --> | ||||
| # expansions( expansion(b, c, e), expansion(b, d, e) ) | # expansions( expansion(b, c, e), expansion(b, d, e) ) | ||||
| while True: | |||||
| self._flatten(tree) | |||||
| for i, child in enumerate(tree.children): | |||||
| if isinstance(child, Tree) and child.data == 'expansions': | |||||
| tree.data = 'expansions' | |||||
| tree.children = [self.visit(ST('expansion', [option if i==j else other | |||||
| for j, other in enumerate(tree.children)])) | |||||
| for option in set(child.children)] | |||||
| break | |||||
| else: | |||||
| self._flatten(tree) | |||||
| for i, child in enumerate(tree.children): | |||||
| if isinstance(child, Tree) and child.data == 'expansions': | |||||
| tree.data = 'expansions' | |||||
| tree.children = [self.visit(ST('expansion', [option if i==j else other | |||||
| for j, other in enumerate(tree.children)])) | |||||
| for option in set(child.children)] | |||||
| break | break | ||||
| def alias(self, tree): | def alias(self, tree): | ||||
| @@ -243,11 +243,12 @@ class RuleTreeToText(Transformer): | |||||
| return symbols, None | return symbols, None | ||||
| def alias(self, x): | def alias(self, x): | ||||
| (expansion, _alias), alias = x | (expansion, _alias), alias = x | ||||
| assert _alias is None, (alias, expansion, '-', _alias) | |||||
| assert _alias is None, (alias, expansion, '-', _alias) # Double alias not allowed | |||||
| return expansion, alias.value | return expansion, alias.value | ||||
| class CanonizeTree(InlineTransformer): | |||||
| @inline_args | |||||
| class CanonizeTree(Transformer): | |||||
| def maybe(self, expr): | def maybe(self, expr): | ||||
| return ST('expr', [expr, Token('OP', '?', -1)]) | return ST('expr', [expr, Token('OP', '?', -1)]) | ||||
| @@ -257,7 +258,7 @@ class CanonizeTree(InlineTransformer): | |||||
| tokenmods, value = args | tokenmods, value = args | ||||
| return tokenmods + [value] | return tokenmods + [value] | ||||
| class PrepareAnonTerminals(InlineTransformer): | |||||
| class PrepareAnonTerminals(Transformer): | |||||
| "Create a unique list of anonymous tokens. Attempt to give meaningful names to them when we add them" | "Create a unique list of anonymous tokens. Attempt to give meaningful names to them when we add them" | ||||
| def __init__(self, tokens): | def __init__(self, tokens): | ||||
| @@ -267,6 +268,7 @@ class PrepareAnonTerminals(InlineTransformer): | |||||
| self.i = 0 | self.i = 0 | ||||
| @inline_args | |||||
| def pattern(self, p): | def pattern(self, p): | ||||
| value = p.value | value = p.value | ||||
| if p in self.token_reverse and p.flags != self.token_reverse[p].pattern.flags: | if p in self.token_reverse and p.flags != self.token_reverse[p].pattern.flags: | ||||
| @@ -356,7 +358,8 @@ def _literal_to_pattern(literal): | |||||
| 'REGEXP': PatternRE }[literal.type](s, flags) | 'REGEXP': PatternRE }[literal.type](s, flags) | ||||
| class PrepareLiterals(InlineTransformer): | |||||
| @inline_args | |||||
| class PrepareLiterals(Transformer): | |||||
| def literal(self, literal): | def literal(self, literal): | ||||
| return ST('pattern', [_literal_to_pattern(literal)]) | return ST('pattern', [_literal_to_pattern(literal)]) | ||||
| @@ -543,7 +546,8 @@ def options_from_rule(name, *x): | |||||
| def symbols_from_strcase(expansion): | def symbols_from_strcase(expansion): | ||||
| return [Terminal(x, filter_out=x.startswith('_')) if is_terminal(x) else NonTerminal(x) for x in expansion] | return [Terminal(x, filter_out=x.startswith('_')) if is_terminal(x) else NonTerminal(x) for x in expansion] | ||||
| class PrepareGrammar(InlineTransformer): | |||||
| @inline_args | |||||
| class PrepareGrammar(Transformer): | |||||
| def terminal(self, name): | def terminal(self, name): | ||||
| return name | return name | ||||
| def nonterminal(self, name): | def nonterminal(self, name): | ||||
| @@ -2,9 +2,11 @@ from .common import GrammarError | |||||
| from .utils import suppress | from .utils import suppress | ||||
| from .lexer import Token | from .lexer import Token | ||||
| from .grammar import Rule | from .grammar import Rule | ||||
| from .tree import Tree | |||||
| from .visitors import InlineTransformer # XXX Deprecated | |||||
| ###{standalone | ###{standalone | ||||
| from functools import partial | |||||
| from functools import partial, wraps | |||||
| class ExpandSingleChild: | class ExpandSingleChild: | ||||
| @@ -27,15 +29,23 @@ class PropagatePositions: | |||||
| if children: | if children: | ||||
| for a in children: | for a in children: | ||||
| with suppress(AttributeError): | |||||
| res.line = a.line | |||||
| res.column = a.column | |||||
| if isinstance(a, Tree): | |||||
| res.meta.line = a.meta.line | |||||
| res.meta.column = a.meta.column | |||||
| elif isinstance(a, Token): | |||||
| res.meta.line = a.line | |||||
| res.meta.column = a.column | |||||
| break | break | ||||
| for a in reversed(children): | for a in reversed(children): | ||||
| with suppress(AttributeError): | |||||
| res.end_line = a.end_line | |||||
| res.end_column = a.end_column | |||||
| # with suppress(AttributeError): | |||||
| if isinstance(a, Tree): | |||||
| res.meta.end_line = a.meta.end_line | |||||
| res.meta.end_column = a.meta.end_column | |||||
| elif isinstance(a, Token): | |||||
| res.meta.end_line = a.end_line | |||||
| res.meta.end_column = a.end_column | |||||
| break | break | ||||
| return res | return res | ||||
| @@ -86,6 +96,15 @@ def maybe_create_child_filter(expansion, keep_all_tokens, ambiguous): | |||||
| class Callback(object): | class Callback(object): | ||||
| pass | pass | ||||
| def inline_args(func): | |||||
| @wraps(func) | |||||
| def f(children): | |||||
| return func(*children) | |||||
| return f | |||||
| class ParseTreeBuilder: | class ParseTreeBuilder: | ||||
| def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False): | def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False): | ||||
| self.tree_class = tree_class | self.tree_class = tree_class | ||||
| @@ -120,7 +139,11 @@ class ParseTreeBuilder: | |||||
| user_callback_name = rule.alias or rule.origin.name | user_callback_name = rule.alias or rule.origin.name | ||||
| try: | try: | ||||
| f = transformer._get_func(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! | |||||
| if getattr(f, 'inline', False) or isinstance(transformer, InlineTransformer): | |||||
| f = inline_args(f) | |||||
| except AttributeError: | except AttributeError: | ||||
| f = partial(self.tree_class, user_callback_name) | f = partial(self.tree_class, user_callback_name) | ||||
| @@ -13,18 +13,18 @@ | |||||
| # Author: Erez Shinan (2017) | # Author: Erez Shinan (2017) | ||||
| # Email : erezshin@gmail.com | # Email : erezshin@gmail.com | ||||
| from ..tree import Tree | |||||
| from ..visitors import Transformer_InPlace, v_args | |||||
| from ..common import ParseError, UnexpectedToken | from ..common import ParseError, UnexpectedToken | ||||
| from ..tree import Tree, Transformer_NoRecurse | |||||
| from .grammar_analysis import GrammarAnalyzer | from .grammar_analysis import GrammarAnalyzer | ||||
| from ..grammar import NonTerminal | from ..grammar import NonTerminal | ||||
| class Derivation(Tree): | class Derivation(Tree): | ||||
| _hash = None | |||||
| def __init__(self, rule, items=None): | def __init__(self, rule, items=None): | ||||
| Tree.__init__(self, 'drv', items or []) | Tree.__init__(self, 'drv', items or []) | ||||
| self.rule = rule | |||||
| self.meta.rule = rule | |||||
| self._hash = None | |||||
| def _pretty_label(self): # Nicer pretty for debugging the parser | def _pretty_label(self): # Nicer pretty for debugging the parser | ||||
| return self.rule.origin if self.rule else self.data | return self.rule.origin if self.rule else self.data | ||||
| @@ -114,9 +114,9 @@ class Column: | |||||
| if old_tree.data != '_ambig': | if old_tree.data != '_ambig': | ||||
| new_tree = old_tree.copy() | new_tree = old_tree.copy() | ||||
| new_tree.rule = old_tree.rule | |||||
| new_tree.meta.rule = old_tree.meta.rule | |||||
| old_tree.set('_ambig', [new_tree]) | old_tree.set('_ambig', [new_tree]) | ||||
| old_tree.rule = None # No longer a 'drv' node | |||||
| old_tree.meta.rule = None # No longer a 'drv' node | |||||
| if item.tree.children[0] is old_tree: # XXX a little hacky! | if item.tree.children[0] is old_tree: # XXX a little hacky! | ||||
| raise ParseError("Infinite recursion in grammar! (Rule %s)" % item.rule) | raise ParseError("Infinite recursion in grammar! (Rule %s)" % item.rule) | ||||
| @@ -230,9 +230,10 @@ class Parser: | |||||
| return ApplyCallbacks(self.postprocess).transform(tree) | return ApplyCallbacks(self.postprocess).transform(tree) | ||||
| class ApplyCallbacks(Transformer_NoRecurse): | |||||
| class ApplyCallbacks(Transformer_InPlace): | |||||
| def __init__(self, postprocess): | def __init__(self, postprocess): | ||||
| self.postprocess = postprocess | self.postprocess = postprocess | ||||
| def drv(self, tree): | |||||
| return self.postprocess[tree.rule](tree.children) | |||||
| @v_args(meta=True) | |||||
| def drv(self, children, meta): | |||||
| return self.postprocess[meta.rule](children) | |||||
| @@ -1,7 +1,7 @@ | |||||
| from ..utils import compare | from ..utils import compare | ||||
| from functools import cmp_to_key | from functools import cmp_to_key | ||||
| from ..tree import Tree, Visitor_NoRecurse | |||||
| from ..tree import Tree | |||||
| # Standard ambiguity resolver (uses comparison) | # Standard ambiguity resolver (uses comparison) | ||||
| @@ -16,7 +16,7 @@ def _sum_priority(tree): | |||||
| for n in tree.iter_subtrees(): | for n in tree.iter_subtrees(): | ||||
| try: | try: | ||||
| p += n.rule.options.priority or 0 | |||||
| p += n.meta.rule.options.priority or 0 | |||||
| except AttributeError: | except AttributeError: | ||||
| pass | pass | ||||
| @@ -26,8 +26,15 @@ def _compare_priority(tree1, tree2): | |||||
| tree1.iter_subtrees() | tree1.iter_subtrees() | ||||
| def _compare_drv(tree1, tree2): | def _compare_drv(tree1, tree2): | ||||
| rule1 = getattr(tree1, 'rule', None) | |||||
| rule2 = getattr(tree2, 'rule', None) | |||||
| try: | |||||
| rule1 = tree1.meta.rule | |||||
| except AttributeError: | |||||
| rule1 = None | |||||
| try: | |||||
| rule2 = tree2.meta.rule | |||||
| except AttributeError: | |||||
| rule2 = None | |||||
| if None == rule1 == rule2: | if None == rule1 == rule2: | ||||
| return compare(tree1, tree2) | return compare(tree1, tree2) | ||||
| @@ -45,7 +52,7 @@ def _compare_drv(tree1, tree2): | |||||
| if c: | if c: | ||||
| return c | return c | ||||
| c = _compare_rules(tree1.rule, tree2.rule) | |||||
| c = _compare_rules(tree1.meta.rule, tree2.meta.rule) | |||||
| if c: | if c: | ||||
| return c | return c | ||||
| @@ -65,7 +72,7 @@ def _standard_resolve_ambig(tree): | |||||
| best = max(tree.children, key=key_f) | best = max(tree.children, key=key_f) | ||||
| assert best.data == 'drv' | assert best.data == 'drv' | ||||
| tree.set('drv', best.children) | tree.set('drv', best.children) | ||||
| tree.rule = best.rule # needed for applying callbacks | |||||
| tree.meta.rule = best.meta.rule # needed for applying callbacks | |||||
| def standard_resolve_ambig(tree): | def standard_resolve_ambig(tree): | ||||
| for ambig in tree.find_data('_ambig'): | for ambig in tree.find_data('_ambig'): | ||||
| @@ -93,7 +100,7 @@ def _antiscore_sum_resolve_ambig(tree): | |||||
| best = min(tree.children, key=_antiscore_sum_drv) | best = min(tree.children, key=_antiscore_sum_drv) | ||||
| assert best.data == 'drv' | assert best.data == 'drv' | ||||
| tree.set('drv', best.children) | tree.set('drv', best.children) | ||||
| tree.rule = best.rule # needed for applying callbacks | |||||
| tree.meta.rule = best.meta.rule # needed for applying callbacks | |||||
| def antiscore_sum_resolve_ambig(tree): | def antiscore_sum_resolve_ambig(tree): | ||||
| for ambig in tree.find_data('_ambig'): | for ambig in tree.find_data('_ambig'): | ||||
| @@ -160,7 +160,7 @@ def create_code_for_nearley_grammar(g, start, builtin_path, folder_path): | |||||
| emit('class TransformNearley(Transformer):') | emit('class TransformNearley(Transformer):') | ||||
| for alias in n2l.alias_js_code: | for alias in n2l.alias_js_code: | ||||
| emit(" %s = var.get('%s').to_python()" % (alias, alias)) | emit(" %s = var.get('%s').to_python()" % (alias, alias)) | ||||
| emit(" __default__ = lambda self, n, c: c if c else None") | |||||
| emit(" __default__ = lambda self, n, c, m: c if c else None") | |||||
| emit() | emit() | ||||
| emit('parser = Lark(grammar, start="n_%s")' % start) | emit('parser = Lark(grammar, start="n_%s")' % start) | ||||
| @@ -5,13 +5,21 @@ except ImportError: | |||||
| from copy import deepcopy | from copy import deepcopy | ||||
| from .utils import inline_args | |||||
| class Meta: | |||||
| pass | |||||
| ###{standalone | ###{standalone | ||||
| class Tree(object): | class Tree(object): | ||||
| def __init__(self, data, children): | |||||
| def __init__(self, data, children, meta=None): | |||||
| self.data = data | self.data = data | ||||
| self.children = children | self.children = children | ||||
| self._meta = meta | |||||
| @property | |||||
| def meta(self): | |||||
| if self._meta is None: | |||||
| self._meta = Meta() | |||||
| return self._meta | |||||
| def __repr__(self): | def __repr__(self): | ||||
| return 'Tree(%s, %s)' % (self.data, self.children) | return 'Tree(%s, %s)' % (self.data, self.children) | ||||
| @@ -103,130 +111,6 @@ class SlottedTree(Tree): | |||||
| __slots__ = 'data', 'children', 'rule' | __slots__ = 'data', 'children', 'rule' | ||||
| ###{standalone | |||||
| class Transformer(object): | |||||
| def _get_func(self, name): | |||||
| return getattr(self, name) | |||||
| def transform(self, tree): | |||||
| items = [] | |||||
| for c in tree.children: | |||||
| try: | |||||
| items.append(self.transform(c) if isinstance(c, Tree) else c) | |||||
| except Discard: | |||||
| pass | |||||
| try: | |||||
| f = self._get_func(tree.data) | |||||
| except AttributeError: | |||||
| return self.__default__(tree.data, items) | |||||
| else: | |||||
| return f(items) | |||||
| def __default__(self, data, children): | |||||
| return Tree(data, children) | |||||
| def __mul__(self, other): | |||||
| return TransformerChain(self, other) | |||||
| class Discard(Exception): | |||||
| pass | |||||
| class TransformerChain(object): | |||||
| def __init__(self, *transformers): | |||||
| self.transformers = transformers | |||||
| def transform(self, tree): | |||||
| for t in self.transformers: | |||||
| tree = t.transform(tree) | |||||
| return tree | |||||
| def __mul__(self, other): | |||||
| return TransformerChain(*self.transformers + (other,)) | |||||
| class InlineTransformer(Transformer): | |||||
| def _get_func(self, name): # use super()._get_func | |||||
| return inline_args(getattr(self, name)).__get__(self) | |||||
| class Visitor(object): | |||||
| def visit(self, tree): | |||||
| for child in tree.children: | |||||
| if isinstance(child, Tree): | |||||
| self.visit(child) | |||||
| f = getattr(self, tree.data, self.__default__) | |||||
| f(tree) | |||||
| return tree | |||||
| def __default__(self, tree): | |||||
| pass | |||||
| class Visitor_NoRecurse(Visitor): | |||||
| def visit(self, tree): | |||||
| subtrees = list(tree.iter_subtrees()) | |||||
| for subtree in (subtrees): | |||||
| getattr(self, subtree.data, self.__default__)(subtree) | |||||
| return tree | |||||
| from functools import wraps | |||||
| def visit_children_decor(func): | |||||
| @wraps(func) | |||||
| def inner(cls, tree): | |||||
| values = cls.visit_children(tree) | |||||
| return func(cls, values) | |||||
| return inner | |||||
| class Interpreter(object): | |||||
| def visit(self, tree): | |||||
| return getattr(self, tree.data)(tree) | |||||
| def visit_children(self, tree): | |||||
| return [self.visit(child) if isinstance(child, Tree) else child | |||||
| for child in tree.children] | |||||
| def __getattr__(self, name): | |||||
| return self.__default__ | |||||
| def __default__(self, tree): | |||||
| return self.visit_children(tree) | |||||
| class Transformer_NoRecurse(Transformer): | |||||
| def transform(self, tree): | |||||
| subtrees = list(tree.iter_subtrees()) | |||||
| def _t(t): | |||||
| # Assumes t is already transformed | |||||
| try: | |||||
| f = self._get_func(t.data) | |||||
| except AttributeError: | |||||
| return self.__default__(t) | |||||
| else: | |||||
| return f(t) | |||||
| for subtree in subtrees: | |||||
| children = [] | |||||
| for c in subtree.children: | |||||
| try: | |||||
| children.append(_t(c) if isinstance(c, Tree) else c) | |||||
| except Discard: | |||||
| pass | |||||
| subtree.children = children | |||||
| return _t(tree) | |||||
| def __default__(self, t): | |||||
| return t | |||||
| ###} | |||||
| def pydot__tree_to_png(tree, filename): | def pydot__tree_to_png(tree, filename): | ||||
| import pydot | import pydot | ||||
| graph = pydot.Dot(graph_type='digraph', rankdir="LR") | graph = pydot.Dot(graph_type='digraph', rankdir="LR") | ||||
| @@ -50,39 +50,29 @@ except NameError: # Python 3 | |||||
| ###{standalone | ###{standalone | ||||
| import types | import types | ||||
| import functools | |||||
| from functools import wraps, partial | |||||
| from contextlib import contextmanager | from contextlib import contextmanager | ||||
| Str = type(u'') | Str = type(u'') | ||||
| def inline_args(f): | |||||
| # print '@@', f.__name__, type(f), isinstance(f, types.FunctionType), isinstance(f, types.TypeType), isinstance(f, types.BuiltinFunctionType) | |||||
| def smart_decorator(f, create_decorator): | |||||
| if isinstance(f, types.FunctionType): | if isinstance(f, types.FunctionType): | ||||
| @functools.wraps(f) | |||||
| def _f_func(self, args): | |||||
| return f(self, *args) | |||||
| return _f_func | |||||
| return wraps(f)(create_decorator(f, True)) | |||||
| elif isinstance(f, (type, types.BuiltinFunctionType)): | elif isinstance(f, (type, types.BuiltinFunctionType)): | ||||
| @functools.wraps(f) | |||||
| def _f_builtin(_self, args): | |||||
| return f(*args) | |||||
| return _f_builtin | |||||
| return wraps(f)(create_decorator(f, False)) | |||||
| elif isinstance(f, types.MethodType): | elif isinstance(f, types.MethodType): | ||||
| @functools.wraps(f.__func__) | |||||
| def _f(self, args): | |||||
| return f.__func__(self, *args) | |||||
| return _f | |||||
| elif isinstance(f, functools.partial): | |||||
| return wraps(f)(create_decorator(f.__func__, True)) | |||||
| elif isinstance(f, partial): | |||||
| # wraps does not work for partials in 2.7: https://bugs.python.org/issue3445 | # wraps does not work for partials in 2.7: https://bugs.python.org/issue3445 | ||||
| # @functools.wraps(f) | |||||
| def _f(self, args): | |||||
| return f(*args) | |||||
| return _f | |||||
| return create_decorator(f.__func__, True) | |||||
| else: | else: | ||||
| @functools.wraps(f.__call__.__func__) | |||||
| def _f(self, args): | |||||
| return f.__call__.__func__(self, *args) | |||||
| return _f | |||||
| return create_decorator(f.__func__.__call__, True) | |||||
| try: | try: | ||||
| @@ -0,0 +1,216 @@ | |||||
| from inspect import isclass, getmembers, getmro | |||||
| from functools import wraps | |||||
| from .utils import smart_decorator | |||||
| from .tree import Tree | |||||
| class Discard(Exception): | |||||
| pass | |||||
| # Transformers | |||||
| class Transformer: | |||||
| def _call_userfunc(self, data, children, meta): | |||||
| # Assumes tree is already transformed | |||||
| try: | |||||
| f = getattr(self, data) | |||||
| except AttributeError: | |||||
| return self.__default__(data, children, meta) | |||||
| else: | |||||
| if getattr(f, 'meta', False): | |||||
| return f(children, meta) | |||||
| elif getattr(f, 'inline', False): | |||||
| return f(*children) | |||||
| else: | |||||
| return f(children) | |||||
| def _transform_children(self, children): | |||||
| for c in children: | |||||
| try: | |||||
| yield self._transform_tree(c) if isinstance(c, Tree) else c | |||||
| except Discard: | |||||
| pass | |||||
| def _transform_tree(self, tree): | |||||
| children = list(self._transform_children(tree.children)) | |||||
| return self._call_userfunc(tree.data, children, tree.meta) | |||||
| def transform(self, tree): | |||||
| return self._transform_tree(tree) | |||||
| def __mul__(self, other): | |||||
| return TransformerChain(self, other) | |||||
| def __default__(self, data, children, meta): | |||||
| "Default operation on tree (for override)" | |||||
| return Tree(data, children, meta) | |||||
| @classmethod | |||||
| def _apply_decorator(cls, decorator, **kwargs): | |||||
| mro = getmro(cls) | |||||
| assert mro[0] is cls | |||||
| libmembers = {name for _cls in mro[1:] for name, _ in getmembers(_cls)} | |||||
| for name, value in getmembers(cls): | |||||
| if name.startswith('_') or name in libmembers: | |||||
| continue | |||||
| setattr(cls, name, decorator(value, **kwargs)) | |||||
| return cls | |||||
| class InlineTransformer(Transformer): # XXX Deprecated | |||||
| def _call_userfunc(self, data, children, meta): | |||||
| # Assumes tree is already transformed | |||||
| try: | |||||
| f = getattr(self, data) | |||||
| except AttributeError: | |||||
| return self.__default__(data, children, meta) | |||||
| else: | |||||
| return f(*children) | |||||
| class TransformerChain(object): | |||||
| def __init__(self, *transformers): | |||||
| self.transformers = transformers | |||||
| def transform(self, tree): | |||||
| for t in self.transformers: | |||||
| tree = t.transform(tree) | |||||
| return tree | |||||
| def __mul__(self, other): | |||||
| return TransformerChain(*self.transformers + (other,)) | |||||
| class Transformer_InPlace(Transformer): | |||||
| def _transform_tree(self, tree): # Cancel recursion | |||||
| return self._call_userfunc(tree.data, tree.children, tree.meta) | |||||
| def transform(self, tree): | |||||
| for subtree in tree.iter_subtrees(): | |||||
| subtree.children = list(self._transform_children(subtree.children)) | |||||
| return self._transform_tree(tree) | |||||
| class Transformer_InPlaceRecursive(Transformer): | |||||
| def _transform_tree(self, tree): | |||||
| tree.children = list(self._transform_children(tree.children)) | |||||
| return self._call_userfunc(tree.data, tree.children, tree.meta) | |||||
| # Visitors | |||||
| class VisitorBase: | |||||
| def _call_userfunc(self, tree): | |||||
| return getattr(self, tree.data, self.__default__)(tree) | |||||
| def __default__(self, tree): | |||||
| "Default operation on tree (for override)" | |||||
| return tree | |||||
| class Visitor(VisitorBase): | |||||
| "Bottom-up visitor" | |||||
| def visit(self, tree): | |||||
| for subtree in tree.iter_subtrees(): | |||||
| self._call_userfunc(subtree) | |||||
| return tree | |||||
| class Visitor_Recursive(VisitorBase): | |||||
| def visit(self, tree): | |||||
| for child in tree.children: | |||||
| if isinstance(child, Tree): | |||||
| self.visit(child) | |||||
| f = getattr(self, tree.data, self.__default__) | |||||
| f(tree) | |||||
| return tree | |||||
| def visit_children_decor(func): | |||||
| @wraps(func) | |||||
| def inner(cls, tree): | |||||
| values = cls.visit_children(tree) | |||||
| return func(cls, values) | |||||
| return inner | |||||
| class Interpreter: | |||||
| "Top-down visitor" | |||||
| def visit(self, tree): | |||||
| return getattr(self, tree.data)(tree) | |||||
| def visit_children(self, tree): | |||||
| return [self.visit(child) if isinstance(child, Tree) else child | |||||
| for child in tree.children] | |||||
| def __getattr__(self, name): | |||||
| return self.__default__ | |||||
| def __default__(self, tree): | |||||
| return self.visit_children(tree) | |||||
| # Decorators | |||||
| def _apply_decorator(obj, decorator, **kwargs): | |||||
| try: | |||||
| _apply = obj._apply_decorator | |||||
| except AttributeError: | |||||
| return decorator(obj, **kwargs) | |||||
| else: | |||||
| return _apply(decorator, **kwargs) | |||||
| def _inline_args__func(func): | |||||
| @wraps(func) | |||||
| def create_decorator(_f, with_self): | |||||
| if with_self: | |||||
| def f(self, children): | |||||
| return _f(self, *children) | |||||
| else: | |||||
| def f(self, children): | |||||
| return _f(*children) | |||||
| return f | |||||
| return smart_decorator(func, create_decorator) | |||||
| def inline_args(obj): # XXX Deprecated | |||||
| return _apply_decorator(obj, _inline_args__func) | |||||
| def _visitor_args_func_dec(func, inline=False, meta=False): | |||||
| assert not (inline and meta) | |||||
| def create_decorator(_f, with_self): | |||||
| if with_self: | |||||
| def f(self, *args, **kwargs): | |||||
| return _f(self, *args, **kwargs) | |||||
| else: | |||||
| def f(self, *args, **kwargs): | |||||
| return _f(*args, **kwargs) | |||||
| return f | |||||
| f = smart_decorator(func, create_decorator) | |||||
| f.inline = inline | |||||
| f.meta = meta | |||||
| return f | |||||
| def v_args(inline=False, meta=False): | |||||
| if inline and meta: | |||||
| raise ValueError("Visitor functions can either accept meta, or be inlined. Not both.") | |||||
| def _visitor_args_dec(obj): | |||||
| return _apply_decorator(obj, _visitor_args_func_dec, inline=inline, meta=meta) | |||||
| return _visitor_args_dec | |||||
| @@ -20,7 +20,8 @@ logging.basicConfig(level=logging.INFO) | |||||
| from lark.lark import Lark | from lark.lark import Lark | ||||
| from lark.common import GrammarError, ParseError, UnexpectedToken | from lark.common import GrammarError, ParseError, UnexpectedToken | ||||
| from lark.lexer import LexError, UnexpectedInput | from lark.lexer import LexError, UnexpectedInput | ||||
| from lark.tree import Tree, Transformer | |||||
| from lark.tree import Tree | |||||
| from lark.visitors import Transformer | |||||
| __path__ = os.path.dirname(__file__) | __path__ = os.path.dirname(__file__) | ||||
| def _read(n, *args): | def _read(n, *args): | ||||
| @@ -57,7 +58,7 @@ class TestParsers(unittest.TestCase): | |||||
| """, propagate_positions=True) | """, propagate_positions=True) | ||||
| r = g.parse('a') | r = g.parse('a') | ||||
| self.assertEqual( r.children[0].line, 1 ) | |||||
| self.assertEqual( r.children[0].meta.line, 1 ) | |||||
| def test_expand1(self): | def test_expand1(self): | ||||
| @@ -5,7 +5,8 @@ from unittest import TestCase | |||||
| import copy | import copy | ||||
| import pickle | import pickle | ||||
| from lark.tree import Tree, Interpreter, visit_children_decor | |||||
| from lark.tree import Tree | |||||
| from lark.visitors import Transformer, Interpreter, visit_children_decor, v_args | |||||
| class TestTrees(TestCase): | class TestTrees(TestCase): | ||||
| @@ -58,6 +59,44 @@ class TestTrees(TestCase): | |||||
| self.assertEqual(Interp3().visit(t), list('BCd')) | self.assertEqual(Interp3().visit(t), list('BCd')) | ||||
| def test_transformer(self): | |||||
| t = Tree('add', [Tree('sub', [Tree('i', ['3']), Tree('f', ['1.1'])]), Tree('i', ['1'])]) | |||||
| class T(Transformer): | |||||
| i = v_args(inline=True)(int) | |||||
| f = v_args(inline=True)(float) | |||||
| sub = lambda self, values: values[0] - values[1] | |||||
| def add(self, values): | |||||
| return sum(values) | |||||
| res = T().transform(t) | |||||
| self.assertEqual(res, 2.9) | |||||
| @v_args(inline=True) | |||||
| class T(Transformer): | |||||
| i = int | |||||
| f = float | |||||
| sub = lambda self, a, b: a-b | |||||
| def add(self, a, b): | |||||
| return a + b | |||||
| res = T().transform(t) | |||||
| self.assertEqual(res, 2.9) | |||||
| @v_args(inline=True) | |||||
| class T(Transformer): | |||||
| i = int | |||||
| f = float | |||||
| from operator import sub, add | |||||
| res = T().transform(t) | |||||
| self.assertEqual(res, 2.9) | |||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||