| |
| """ |
| Part of the astor library for Python AST manipulation. |
| |
| License: 3-clause BSD |
| |
| Copyright 2012-2015 (c) Patrick Maupin |
| Copyright 2013-2015 (c) Berker Peksag |
| |
| Utilities for node (and, by extension, tree) manipulation. |
| For a whole-tree approach, see the treewalk submodule. |
| |
| """ |
|
|
| import ast |
| import itertools |
|
|
| try: |
| zip_longest = itertools.zip_longest |
| except AttributeError: |
| zip_longest = itertools.izip_longest |
|
|
|
|
| class NonExistent(object): |
| """This is not the class you are looking for. |
| """ |
| pass |
|
|
|
|
| def iter_node(node, name='', unknown=None, |
| |
| list=list, getattr=getattr, isinstance=isinstance, |
| enumerate=enumerate, missing=NonExistent): |
| """Iterates over an object: |
| |
| - If the object has a _fields attribute, |
| it gets attributes in the order of this |
| and returns name, value pairs. |
| |
| - Otherwise, if the object is a list instance, |
| it returns name, value pairs for each item |
| in the list, where the name is passed into |
| this function (defaults to blank). |
| |
| - Can update an unknown set with information about |
| attributes that do not exist in fields. |
| """ |
| fields = getattr(node, '_fields', None) |
| if fields is not None: |
| for name in fields: |
| value = getattr(node, name, missing) |
| if value is not missing: |
| yield value, name |
| if unknown is not None: |
| unknown.update(set(vars(node)) - set(fields)) |
| elif isinstance(node, list): |
| for value in node: |
| yield value, name |
|
|
|
|
| def dump_tree(node, name=None, initial_indent='', indentation=' ', |
| maxline=120, maxmerged=80, |
| |
| iter_node=iter_node, special=ast.AST, |
| list=list, isinstance=isinstance, type=type, len=len): |
| """Dumps an AST or similar structure: |
| |
| - Pretty-prints with indentation |
| - Doesn't print line/column/ctx info |
| |
| """ |
| def dump(node, name=None, indent=''): |
| level = indent + indentation |
| name = name and name + '=' or '' |
| values = list(iter_node(node)) |
| if isinstance(node, list): |
| prefix, suffix = '%s[' % name, ']' |
| elif values: |
| prefix, suffix = '%s%s(' % (name, type(node).__name__), ')' |
| elif isinstance(node, special): |
| prefix, suffix = name + type(node).__name__, '' |
| else: |
| return '%s%s' % (name, repr(node)) |
| node = [dump(a, b, level) for a, b in values if b != 'ctx'] |
| oneline = '%s%s%s' % (prefix, ', '.join(node), suffix) |
| if len(oneline) + len(indent) < maxline: |
| return '%s' % oneline |
| if node and len(prefix) + len(node[0]) < maxmerged: |
| prefix = '%s%s,' % (prefix, node.pop(0)) |
| node = (',\n%s' % level).join(node).lstrip() |
| return '%s\n%s%s%s' % (prefix, level, node, suffix) |
| return dump(node, name, initial_indent) |
|
|
|
|
| def strip_tree(node, |
| |
| iter_node=iter_node, special=ast.AST, |
| list=list, isinstance=isinstance, type=type, len=len): |
| """Strips an AST by removing all attributes not in _fields. |
| |
| Returns a set of the names of all attributes stripped. |
| |
| This canonicalizes two trees for comparison purposes. |
| """ |
| stripped = set() |
|
|
| def strip(node, indent): |
| unknown = set() |
| leaf = True |
| for subnode, _ in iter_node(node, unknown=unknown): |
| leaf = False |
| strip(subnode, indent + ' ') |
| if leaf: |
| if isinstance(node, special): |
| unknown = set(vars(node)) |
| stripped.update(unknown) |
| for name in unknown: |
| delattr(node, name) |
| if hasattr(node, 'ctx'): |
| delattr(node, 'ctx') |
| if 'ctx' in node._fields: |
| mylist = list(node._fields) |
| mylist.remove('ctx') |
| node._fields = mylist |
| strip(node, '') |
| return stripped |
|
|
|
|
| class ExplicitNodeVisitor(ast.NodeVisitor): |
| """This expands on the ast module's NodeVisitor class |
| to remove any implicit visits. |
| |
| """ |
|
|
| def abort_visit(node): |
| msg = 'No defined handler for node of type %s' |
| raise AttributeError(msg % node.__class__.__name__) |
|
|
| def visit(self, node, abort=abort_visit): |
| """Visit a node.""" |
| method = 'visit_' + node.__class__.__name__ |
| visitor = getattr(self, method, abort) |
| return visitor(node) |
|
|
|
|
| def allow_ast_comparison(): |
| """This ugly little monkey-patcher adds in a helper class |
| to all the AST node types. This helper class allows |
| eq/ne comparisons to work, so that entire trees can |
| be easily compared by Python's comparison machinery. |
| Used by the anti8 functions to compare old and new ASTs. |
| Could also be used by the test library. |
| |
| |
| """ |
|
|
| class CompareHelper(object): |
| def __eq__(self, other): |
| return type(self) == type(other) and vars(self) == vars(other) |
|
|
| def __ne__(self, other): |
| return type(self) != type(other) or vars(self) != vars(other) |
|
|
| for item in vars(ast).values(): |
| if type(item) != type: |
| continue |
| if issubclass(item, ast.AST): |
| try: |
| item.__bases__ = tuple(list(item.__bases__) + [CompareHelper]) |
| except TypeError: |
| pass |
|
|
|
|
| def fast_compare(tree1, tree2): |
| """ This is optimized to compare two AST trees for equality. |
| It makes several assumptions that are currently true for |
| AST trees used by rtrip, and it doesn't examine the _attributes. |
| """ |
|
|
| geta = ast.AST.__getattribute__ |
|
|
| work = [(tree1, tree2)] |
| pop = work.pop |
| extend = work.extend |
| |
| exception = TypeError, AttributeError |
| zipl = zip_longest |
| type_ = type |
| list_ = list |
| while work: |
| n1, n2 = pop() |
| try: |
| f1 = geta(n1, '_fields') |
| f2 = geta(n2, '_fields') |
| except exception: |
| if type_(n1) is list_: |
| extend(zipl(n1, n2)) |
| continue |
| if n1 == n2: |
| continue |
| return False |
| else: |
| f1 = [x for x in f1 if x != 'ctx'] |
| if f1 != [x for x in f2 if x != 'ctx']: |
| return False |
| extend((geta(n1, fname), geta(n2, fname)) for fname in f1) |
|
|
| return True |
|
|