#!/usr/bin/env python # -*- coding: utf-8 -*- """ :mod:`java2python.lang.selector` -- Declarative AST node selection ======================================================================= .. module:: java2python.lang.selector :synopsis: Declarative AST node selection. .. moduleauthor:: Troy Melhase This module provides classes for simple AST node selection that can be easily combined to form complex, declarative rules for retrieving AST nodes. The classes are similar to CSS selectors, with a nod to Python parsing libraries like LEPL and PyParsing. At the moment, only a few very basic selector types are implemented, but those that are here already provide all of the functionality necessary for use within the package. Projects using java2python should regard this subpackage as experimental. While the interfaces are not expected to change, the semantics may. Use with caution. """ from java2python.lang import tokens class Selector(object): """ Base class for concrete selectors; provides operator methods. """ def __add__(self, other): """ E + F Like CSS "E + F": an F element immediately preceded by an E element """ return AdjacentSibling(self, other) def __and__(self, other): """ E & F Like CSS "E F": an F element descendant of an E element """ return Descendant(self, other) def __call__(self, *args, **kwds): """ Subclasses must implement. """ raise NotImplementedError('Selector class cannot be called.') def __getitem__(self, key): """ E[n] Like CSS "E:nth-child(n)": an E element, the n-th child of its parent """ return Nth(self, key) def __gt__(self, other): """ E > F Like CSS: "E > F": an F element child of an E element """ return Child(self, other) def __div__(self, other): """ E / F Produces a AnySibling. """ return AnySibling(self, other) def walk(self, tree): """ Select items from the tree and from the tree children. """ for item in self(tree): yield item for child in tree.children: for item in self.walk(child): yield item class Token(Selector): """ Token(...) -> select tokens by matching attributes. Token is the most generic and flexible Selector; using it, arbitrary nodes of any type, line number, position, and/or text can be selected. Calling Token() without any keywords is equivalent to: Token(channel=None, index=None, input=None, line=None, start=None, stop=None, text=None, type=None) Note that the value of each keyword can be a constant or a callable. When callables are specified, they are passed a the token and should return a bool. """ def __init__(self, **attrs): self.attrs = attrs ## we support strings so that the client can refer to the ## token name that way instead of via lookup or worse, integer. if isinstance(attrs.get('type'), (basestring, )): self.attrs['type'] = getattr(tokens, attrs.get('type')) def __call__(self, tree): items = self.attrs.items() token = tree.token def match_or_call(k, v): if callable(v): return v(token) return getattr(token, k)==v if all(match_or_call(k, v) for k, v in items if v is not None): yield tree def __str__(self): items = self.attrs.items() keys = ('{}={}'.format(k, v) for k, v in items if v is not None) return 'Token({})'.format(', '.join(keys)) class Nth(Selector): """ E[n] -> match any slice n of E Similar to the :nth-child pseudo selector in CSS, but without the support for keywords like 'odd', 'even', etc. """ def __init__(self, e, key): self.e, self.key = e, key def __call__(self, tree): for etree in self.e(tree): try: matches = tree.children[self.key] except (IndexError, ): return if not isinstance(matches, (list, )): matches = [matches] for child in matches: yield child def __str__(self): return 'Nth({0})[{1}]'.format(self.e, self.key) class Child(Selector): """ E > F select any F that is a child of E """ def __init__(self, e, f): self.e, self.f = e, f def __call__(self, tree): for ftree in self.f(tree): for etree in self.e(tree.parent): yield ftree def __str__(self): return 'Child({0} > {1})'.format(self.e, self.f) class Type(Selector): """ Type(T) select any token of type T Similar to the type selector in CSS. """ def __init__(self, key, value=None): self.key = key if isinstance(key, int) else getattr(tokens, key) self.value = value def __call__(self, tree): if tree.token.type == self.key: if self.value is None or self.value == tree.token.text: yield tree def __str__(self): val = '' if self.value is None else '={0}'.format(self.value) return 'Type({0}{1}:{2})'.format(tokens.map[self.key], val, self.key) class Star(Selector): """ * select any Similar to the * selector in CSS. """ def __call__(self, tree): yield tree def __str__(self): return 'Star(*)' class Descendant(Selector): """ E & F select any F that is a descendant of E """ def __init__(self, e, f): self.e, self.f = e, f def __call__(self, tree): for ftree in self.f(tree): root, ftree = ftree, ftree.parent while ftree: for etree in self.e(ftree): yield root ftree = ftree.parent def __str__(self): return 'Descendant({0} & {1})'.format(self.e, self.f) class AdjacentSibling(Selector): """ E + F select any F immediately preceded by a sibling E """ def __init__(self, e, f): self.e, self.f = e, f def __call__(self, node): if not node.parent: return for ftree in self.f(node): index = node.parent.children.index(ftree) if not index: return previous = node.parent.children[index-1] for child in self.e(previous): yield ftree def __str__(self): return 'AdjacentSibling({} + {})'.format(self.e, self.f) class AnySibling(Selector): """ E / F select any F preceded by a sibling E """ def __init__(self, e, f): self.e, self.f = e, f def __call__(self, node): if not node.parent: return for ftree in self.f(node): index = node.parent.children.index(ftree) if not index: return for prev in node.parent.children[:index]: for child in self.e(prev): yield ftree def __str__(self): return 'AnySibling({} / {})'.format(self.e, self.f)