1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
# -*- coding: utf-8 -*-
"""
Part of the astor library for Python AST manipulation.
License: 3-clause BSD
Copyright 2012 (c) Patrick Maupin
Copyright 2013 (c) Berker Peksag
This file contains a TreeWalk class that views a node tree
as a unified whole and allows several modes of traversal.
"""
from .node_util import iter_node
class MetaFlatten(type):
"""This metaclass is used to flatten classes to remove
class hierarchy.
This makes it easier to manipulate classes (find
attributes in a single dict, etc.)
"""
def __new__(clstype, name, bases, clsdict):
newbases = (object,)
newdict = {}
for base in reversed(bases):
if base not in newbases:
newdict.update(vars(base))
newdict.update(clsdict)
# These are class-bound, we should let Python recreate them.
newdict.pop('__dict__', None)
newdict.pop('__weakref__', None)
# Delegate the real work to type
return type.__new__(clstype, name, newbases, newdict)
MetaFlatten = MetaFlatten('MetaFlatten', (object,), {})
class TreeWalk(MetaFlatten):
"""The TreeWalk class can be used as a superclass in order
to walk an AST or similar tree.
Unlike other treewalkers, this class can walk a tree either
recursively or non-recursively. Subclasses can define
methods with the following signatures::
def pre_xxx(self):
pass
def post_xxx(self):
pass
def init_xxx(self):
pass
Where 'xxx' is one of:
- A class name
- An attribute member name concatenated with '_name'
For example, 'pre_targets_name' will process nodes
that are referenced by the name 'targets' in their
parent's node.
- An attribute member name concatenated with '_item'
For example, 'pre_targets_item' will process nodes
that are in a list that is the targets attribute
of some node.
pre_xxx will process a node before processing any of its subnodes.
if the return value from pre_xxx evalates to true, then walk
will not process any of the subnodes. Those can be manually
processed, if desired, by calling self.walk(node) on the subnodes
before returning True.
post_xxx will process a node after processing all its subnodes.
init_xxx methods can decorate the class instance with subclass-specific
information. A single init_whatever method could be written, but to
make it easy to keep initialization with use, any number of init_xxx
methods can be written. They will be called in alphabetical order.
"""
def __init__(self, node=None):
self.nodestack = []
self.setup()
if node is not None:
self.walk(node)
def setup(self):
"""All the node-specific handlers are setup at
object initialization time.
"""
self.pre_handlers = pre_handlers = {}
self.post_handlers = post_handlers = {}
for name in sorted(vars(type(self))):
if name.startswith('init_'):
getattr(self, name)()
elif name.startswith('pre_'):
pre_handlers[name[4:]] = getattr(self, name)
elif name.startswith('post_'):
post_handlers[name[5:]] = getattr(self, name)
def walk(self, node, name='', list=list, len=len, type=type):
"""Walk the tree starting at a given node.
Maintain a stack of nodes.
"""
pre_handlers = self.pre_handlers.get
post_handlers = self.post_handlers.get
nodestack = self.nodestack
emptystack = len(nodestack)
append, pop = nodestack.append, nodestack.pop
append([node, name, list(iter_node(node, name + '_item')), -1])
while len(nodestack) > emptystack:
node, name, subnodes, index = nodestack[-1]
if index >= len(subnodes):
handler = (post_handlers(type(node).__name__) or
post_handlers(name + '_name'))
if handler is None:
pop()
continue
self.cur_node = node
self.cur_name = name
handler()
current = nodestack and nodestack[-1]
popstack = current and current[0] is node
if popstack and current[-1] >= len(current[-2]):
pop()
continue
nodestack[-1][-1] = index + 1
if index < 0:
handler = (pre_handlers(type(node).__name__) or
pre_handlers(name + '_name'))
if handler is not None:
self.cur_node = node
self.cur_name = name
if handler():
pop()
else:
node, name = subnodes[index]
append([node, name, list(iter_node(node, name + '_item')), -1])
@property
def parent(self):
"""Return the parent node of the current node."""
nodestack = self.nodestack
if len(nodestack) < 2:
return None
return nodestack[-2][0]
@property
def parent_name(self):
"""Return the parent node and name."""
nodestack = self.nodestack
if len(nodestack) < 2:
return None
return nodestack[-2][:2]
def replace(self, new_node):
"""Replace a node after first checking integrity of node stack."""
cur_node = self.cur_node
nodestack = self.nodestack
cur = nodestack.pop()
prev = nodestack[-1]
index = prev[-1] - 1
oldnode, name = prev[-2][index]
assert cur[0] is cur_node is oldnode, (cur[0], cur_node, prev[-2],
index)
parent = prev[0]
if isinstance(parent, list):
parent[index] = new_node
else:
setattr(parent, name, new_node)
|