| @@ -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): | ||||
| @@ -14,7 +14,8 @@ from .parsers.lalr_parser import UnexpectedToken | |||||
| from .common import is_terminal, GrammarError, LexerConf, ParserConf, PatternStr, PatternRE, TokenDef | from .common import is_terminal, GrammarError, LexerConf, ParserConf, PatternStr, PatternRE, TokenDef | ||||
| from .grammar import RuleOptions, Rule | from .grammar import RuleOptions, Rule | ||||
| from .tree import Tree, Transformer, InlineTransformer, Visitor, SlottedTree as ST | |||||
| from .tree import Tree, Visitor, SlottedTree as ST | |||||
| from .transformers import Transformer_Children, Transformer_ChildrenInline | |||||
| __path__ = os.path.dirname(__file__) | __path__ = os.path.dirname(__file__) | ||||
| IMPORT_PATHS = [os.path.join(__path__, 'grammars')] | IMPORT_PATHS = [os.path.join(__path__, 'grammars')] | ||||
| @@ -130,7 +131,7 @@ RULES = { | |||||
| } | } | ||||
| class EBNF_to_BNF(InlineTransformer): | |||||
| class EBNF_to_BNF(Transformer_ChildrenInline): | |||||
| def __init__(self): | def __init__(self): | ||||
| self.new_rules = [] | self.new_rules = [] | ||||
| self.rules_by_expr = {} | self.rules_by_expr = {} | ||||
| @@ -226,7 +227,7 @@ class SimplifyRule_Visitor(Visitor): | |||||
| tree.children = list(set(tree.children)) | tree.children = list(set(tree.children)) | ||||
| class RuleTreeToText(Transformer): | |||||
| class RuleTreeToText(Transformer_Children): | |||||
| def expansions(self, x): | def expansions(self, x): | ||||
| return x | return x | ||||
| def expansion(self, symbols): | def expansion(self, symbols): | ||||
| @@ -237,7 +238,7 @@ class RuleTreeToText(Transformer): | |||||
| return expansion, alias.value | return expansion, alias.value | ||||
| class CanonizeTree(InlineTransformer): | |||||
| class CanonizeTree(Transformer_ChildrenInline): | |||||
| def maybe(self, expr): | def maybe(self, expr): | ||||
| return ST('expr', [expr, Token('OP', '?', -1)]) | return ST('expr', [expr, Token('OP', '?', -1)]) | ||||
| @@ -247,7 +248,7 @@ class CanonizeTree(InlineTransformer): | |||||
| tokenmods, value = args | tokenmods, value = args | ||||
| return tokenmods + [value] | return tokenmods + [value] | ||||
| class ExtractAnonTokens(InlineTransformer): | |||||
| class ExtractAnonTokens(Transformer_ChildrenInline): | |||||
| "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): | ||||
| @@ -351,7 +352,7 @@ def _literal_to_pattern(literal): | |||||
| 'REGEXP': PatternRE }[literal.type](s, flags) | 'REGEXP': PatternRE }[literal.type](s, flags) | ||||
| class PrepareLiterals(InlineTransformer): | |||||
| class PrepareLiterals(Transformer_ChildrenInline): | |||||
| def literal(self, literal): | def literal(self, literal): | ||||
| return ST('pattern', [_literal_to_pattern(literal)]) | return ST('pattern', [_literal_to_pattern(literal)]) | ||||
| @@ -363,13 +364,13 @@ class PrepareLiterals(InlineTransformer): | |||||
| regexp = '[%s-%s]' % (start, end) | regexp = '[%s-%s]' % (start, end) | ||||
| return ST('pattern', [PatternRE(regexp)]) | return ST('pattern', [PatternRE(regexp)]) | ||||
| class SplitLiterals(InlineTransformer): | |||||
| class SplitLiterals(Transformer_ChildrenInline): | |||||
| def pattern(self, p): | def pattern(self, p): | ||||
| if isinstance(p, PatternStr) and len(p.value)>1: | if isinstance(p, PatternStr) and len(p.value)>1: | ||||
| return ST('expansion', [ST('pattern', [PatternStr(ch, flags=p.flags)]) for ch in p.value]) | return ST('expansion', [ST('pattern', [PatternStr(ch, flags=p.flags)]) for ch in p.value]) | ||||
| return ST('pattern', [p]) | return ST('pattern', [p]) | ||||
| class TokenTreeToPattern(Transformer): | |||||
| class TokenTreeToPattern(Transformer_Children): | |||||
| def pattern(self, ps): | def pattern(self, ps): | ||||
| p ,= ps | p ,= ps | ||||
| return p | return p | ||||
| @@ -2,6 +2,7 @@ from .common import is_terminal, 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 | |||||
| ###{standalone | ###{standalone | ||||
| from functools import partial | from functools import partial | ||||
| @@ -38,15 +39,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 | ||||
| @@ -0,0 +1,93 @@ | |||||
| from functools import wraps | |||||
| from .tree import Tree | |||||
| class Discard(Exception): | |||||
| pass | |||||
| class Transformer: | |||||
| def _get_userfunc(self, name): | |||||
| return getattr(self, name) | |||||
| def _call_userfunc(self, tree): | |||||
| # Assumes tree is already transformed | |||||
| try: | |||||
| f = self._get_userfunc(tree.data) | |||||
| except AttributeError: | |||||
| return self.__default__(tree) | |||||
| else: | |||||
| return f(tree) | |||||
| def _transform(self, tree): | |||||
| children = [] | |||||
| for c in tree.children: | |||||
| try: | |||||
| children.append(self._transform(c) if isinstance(c, Tree) else c) | |||||
| except Discard: | |||||
| pass | |||||
| tree = Tree(tree.data, children) | |||||
| return self._call_userfunc(tree) | |||||
| def __default__(self, tree): | |||||
| return tree | |||||
| def transform(self, tree): | |||||
| return self._transform(tree) | |||||
| def __mul__(self, other): | |||||
| return TransformerChain(self, other) | |||||
| class Transformer_Children(Transformer): | |||||
| def _call_userfunc(self, tree): | |||||
| # Assumes tree is already transformed | |||||
| try: | |||||
| f = self._get_userfunc(tree.data) | |||||
| except AttributeError: | |||||
| return self.__default__(tree) | |||||
| else: | |||||
| return f(tree.children) | |||||
| class Transformer_ChildrenInline(Transformer): | |||||
| def _call_userfunc(self, tree): | |||||
| # Assumes tree is already transformed | |||||
| try: | |||||
| f = self._get_userfunc(tree.data) | |||||
| except AttributeError: | |||||
| return self.__default__(tree) | |||||
| else: | |||||
| return f(*tree.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,)) | |||||
| #### XXX PSEUDOCODE TODO | |||||
| # def items(obj): | |||||
| # if isinstance(obj, Transformer): | |||||
| # def new_get_userfunc(self, name): | |||||
| # uf = self._get_userfunc(name) | |||||
| # def _f(tree): | |||||
| # return uf(tree.children) | |||||
| # return _f | |||||
| # obj._get_userfunc = new_get_userfunc | |||||
| # else: | |||||
| # assert callable(obj) | |||||
| # # apply decorator | |||||
| # def _f(tree): | |||||
| # return obj(tree.children) | |||||
| # return _f | |||||
| @@ -7,11 +7,23 @@ from copy import deepcopy | |||||
| from .utils import inline_args | from .utils import inline_args | ||||
| class Meta: | |||||
| pass | |||||
| ###{standalone | ###{standalone | ||||
| class Tree(object): | class Tree(object): | ||||
| __slots__ = ('data', 'children', '_meta', 'rule') | |||||
| def __init__(self, data, children): | def __init__(self, data, children): | ||||
| self.data = data | self.data = data | ||||
| self.children = children | self.children = children | ||||
| self._meta = None | |||||
| @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) | ||||
| @@ -60,7 +60,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): | ||||