#!/usr/bin/env python
# -*- coding: utf-8 -*-
# java2python.lang.selector -> declarative AST node selection
#
# 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)