| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734 |
- import collections
- import re
- import warnings
- from rope.base import ast, codeanalyze, exceptions
- def get_patched_ast(source, sorted_children=False):
- """Adds ``region`` and ``sorted_children`` fields to nodes
- Adds ``sorted_children`` field only if `sorted_children` is True.
- """
- return patch_ast(ast.parse(source), source, sorted_children)
- def patch_ast(node, source, sorted_children=False):
- """Patches the given node
- After calling, each node in `node` will have a new field named
- `region` that is a tuple containing the start and end offsets
- of the code that generated it.
- If `sorted_children` is true, a `sorted_children` field will
- be created for each node, too. It is a list containing child
- nodes as well as whitespaces and comments that occur between
- them.
- """
- if hasattr(node, 'region'):
- return node
- walker = _PatchingASTWalker(source, children=sorted_children)
- ast.call_for_nodes(node, walker)
- return node
- def node_region(patched_ast_node):
- """Get the region of a patched ast node"""
- return patched_ast_node.region
- def write_ast(patched_ast_node):
- """Extract source form a patched AST node with `sorted_children` field
- If the node is patched with sorted_children turned off you can use
- `node_region` function for obtaining code using module source code.
- """
- result = []
- for child in patched_ast_node.sorted_children:
- if isinstance(child, ast.AST):
- result.append(write_ast(child))
- else:
- result.append(child)
- return ''.join(result)
- class MismatchedTokenError(exceptions.RopeError):
- pass
- class _PatchingASTWalker(object):
- def __init__(self, source, children=False):
- self.source = _Source(source)
- self.children = children
- self.lines = codeanalyze.SourceLinesAdapter(source)
- self.children_stack = []
- Number = object()
- String = object()
- def __call__(self, node):
- method = getattr(self, '_' + node.__class__.__name__, None)
- if method is not None:
- return method(node)
- # ???: Unknown node; what should we do here?
- warnings.warn('Unknown node type <%s>; please report!'
- % node.__class__.__name__, RuntimeWarning)
- node.region = (self.source.offset, self.source.offset)
- if self.children:
- node.sorted_children = ast.get_children(node)
- def _handle(self, node, base_children, eat_parens=False, eat_spaces=False):
- if hasattr(node, 'region'):
- # ???: The same node was seen twice; what should we do?
- warnings.warn(
- 'Node <%s> has been already patched; please report!' %
- node.__class__.__name__, RuntimeWarning)
- return
- base_children = collections.deque(base_children)
- self.children_stack.append(base_children)
- children = collections.deque()
- formats = []
- suspected_start = self.source.offset
- start = suspected_start
- first_token = True
- while base_children:
- child = base_children.popleft()
- if child is None:
- continue
- offset = self.source.offset
- if isinstance(child, ast.AST):
- ast.call_for_nodes(child, self)
- token_start = child.region[0]
- else:
- if child is self.String:
- region = self.source.consume_string(
- end=self._find_next_statement_start())
- elif child is self.Number:
- region = self.source.consume_number()
- elif child == '!=':
- # INFO: This has been added to handle deprecated ``<>``
- region = self.source.consume_not_equal()
- else:
- region = self.source.consume(child)
- child = self.source[region[0]:region[1]]
- token_start = region[0]
- if not first_token:
- formats.append(self.source[offset:token_start])
- if self.children:
- children.append(self.source[offset:token_start])
- else:
- first_token = False
- start = token_start
- if self.children:
- children.append(child)
- start = self._handle_parens(children, start, formats)
- if eat_parens:
- start = self._eat_surrounding_parens(
- children, suspected_start, start)
- if eat_spaces:
- if self.children:
- children.appendleft(self.source[0:start])
- end_spaces = self.source[self.source.offset:]
- self.source.consume(end_spaces)
- if self.children:
- children.append(end_spaces)
- start = 0
- if self.children:
- node.sorted_children = children
- node.region = (start, self.source.offset)
- self.children_stack.pop()
- def _handle_parens(self, children, start, formats):
- """Changes `children` and returns new start"""
- opens, closes = self._count_needed_parens(formats)
- old_end = self.source.offset
- new_end = None
- for i in range(closes):
- new_end = self.source.consume(')')[1]
- if new_end is not None:
- if self.children:
- children.append(self.source[old_end:new_end])
- new_start = start
- for i in range(opens):
- new_start = self.source.rfind_token('(', 0, new_start)
- if new_start != start:
- if self.children:
- children.appendleft(self.source[new_start:start])
- start = new_start
- return start
- def _eat_surrounding_parens(self, children, suspected_start, start):
- index = self.source.rfind_token('(', suspected_start, start)
- if index is not None:
- old_start = start
- old_offset = self.source.offset
- start = index
- if self.children:
- children.appendleft(self.source[start + 1:old_start])
- children.appendleft('(')
- token_start, token_end = self.source.consume(')')
- if self.children:
- children.append(self.source[old_offset:token_start])
- children.append(')')
- return start
- def _count_needed_parens(self, children):
- start = 0
- opens = 0
- for child in children:
- if not isinstance(child, basestring):
- continue
- if child == '' or child[0] in '\'"':
- continue
- index = 0
- while index < len(child):
- if child[index] == ')':
- if opens > 0:
- opens -= 1
- else:
- start += 1
- if child[index] == '(':
- opens += 1
- if child[index] == '#':
- try:
- index = child.index('\n', index)
- except ValueError:
- break
- index += 1
- return start, opens
- def _find_next_statement_start(self):
- for children in reversed(self.children_stack):
- for child in children:
- if isinstance(child, ast.stmt):
- return child.col_offset \
- + self.lines.get_line_start(child.lineno)
- return len(self.source.source)
- _operators = {'And': 'and', 'Or': 'or', 'Add': '+', 'Sub': '-', 'Mult': '*',
- 'Div': '/', 'Mod': '%', 'Pow': '**', 'LShift': '<<',
- 'RShift': '>>', 'BitOr': '|', 'BitAnd': '&', 'BitXor': '^',
- 'FloorDiv': '//', 'Invert': '~', 'Not': 'not', 'UAdd': '+',
- 'USub': '-', 'Eq': '==', 'NotEq': '!=', 'Lt': '<',
- 'LtE': '<=', 'Gt': '>', 'GtE': '>=', 'Is': 'is',
- 'IsNot': 'is not', 'In': 'in', 'NotIn': 'not in'}
- def _get_op(self, node):
- return self._operators[node.__class__.__name__].split(' ')
- def _Attribute(self, node):
- self._handle(node, [node.value, '.', node.attr])
- def _Assert(self, node):
- children = ['assert', node.test]
- if node.msg:
- children.append(',')
- children.append(node.msg)
- self._handle(node, children)
- def _Assign(self, node):
- children = self._child_nodes(node.targets, '=')
- children.append('=')
- children.append(node.value)
- self._handle(node, children)
- def _AugAssign(self, node):
- children = [node.target]
- children.extend(self._get_op(node.op))
- children.extend(['=', node.value])
- self._handle(node, children)
- def _Repr(self, node):
- self._handle(node, ['`', node.value, '`'])
- def _BinOp(self, node):
- children = [node.left] + self._get_op(node.op) + [node.right]
- self._handle(node, children)
- def _BoolOp(self, node):
- self._handle(node, self._child_nodes(node.values,
- self._get_op(node.op)[0]))
- def _Break(self, node):
- self._handle(node, ['break'])
- def _Call(self, node):
- children = [node.func, '(']
- args = list(node.args) + node.keywords
- children.extend(self._child_nodes(args, ','))
- if node.starargs is not None:
- if args:
- children.append(',')
- children.extend(['*', node.starargs])
- if node.kwargs is not None:
- if args or node.starargs is not None:
- children.append(',')
- children.extend(['**', node.kwargs])
- children.append(')')
- self._handle(node, children)
- def _ClassDef(self, node):
- children = []
- if getattr(node, 'decorator_list', None):
- for decorator in node.decorator_list:
- children.append('@')
- children.append(decorator)
- children.extend(['class', node.name])
- if node.bases:
- children.append('(')
- children.extend(self._child_nodes(node.bases, ','))
- children.append(')')
- children.append(':')
- children.extend(node.body)
- self._handle(node, children)
- def _Compare(self, node):
- children = []
- children.append(node.left)
- for op, expr in zip(node.ops, node.comparators):
- children.extend(self._get_op(op))
- children.append(expr)
- self._handle(node, children)
- def _Delete(self, node):
- self._handle(node, ['del'] + self._child_nodes(node.targets, ','))
- def _Num(self, node):
- self._handle(node, [self.Number])
- def _Str(self, node):
- self._handle(node, [self.String])
- def _Continue(self, node):
- self._handle(node, ['continue'])
- def _Dict(self, node):
- children = []
- children.append('{')
- if node.keys:
- for index, (key, value) in enumerate(zip(node.keys, node.values)):
- children.extend([key, ':', value])
- if index < len(node.keys) - 1:
- children.append(',')
- children.append('}')
- self._handle(node, children)
- def _Ellipsis(self, node):
- self._handle(node, ['...'])
- def _Expr(self, node):
- self._handle(node, [node.value])
- def _Exec(self, node):
- children = []
- children.extend(['exec', node.body])
- if node.globals:
- children.extend(['in', node.globals])
- if node.locals:
- children.extend([',', node.locals])
- self._handle(node, children)
- def _ExtSlice(self, node):
- children = []
- for index, dim in enumerate(node.dims):
- if index > 0:
- children.append(',')
- children.append(dim)
- self._handle(node, children)
- def _For(self, node):
- children = ['for', node.target, 'in', node.iter, ':']
- children.extend(node.body)
- if node.orelse:
- children.extend(['else', ':'])
- children.extend(node.orelse)
- self._handle(node, children)
- def _ImportFrom(self, node):
- children = ['from']
- if node.level:
- children.append('.' * node.level)
- children.extend([node.module or '', # see comment at rope.base.ast.walk
- 'import'])
- children.extend(self._child_nodes(node.names, ','))
- self._handle(node, children)
- def _alias(self, node):
- children = [node.name]
- if node.asname:
- children.extend(['as', node.asname])
- self._handle(node, children)
- def _FunctionDef(self, node):
- children = []
- try:
- decorators = getattr(node, 'decorator_list')
- except AttributeError:
- decorators = getattr(node, 'decorators', None)
- if decorators:
- for decorator in decorators:
- children.append('@')
- children.append(decorator)
- children.extend(['def', node.name, '(', node.args])
- children.extend([')', ':'])
- children.extend(node.body)
- self._handle(node, children)
- def _arguments(self, node):
- children = []
- args = list(node.args)
- defaults = [None] * (len(args) - len(node.defaults)) + list(node.defaults)
- for index, (arg, default) in enumerate(zip(args, defaults)):
- if index > 0:
- children.append(',')
- self._add_args_to_children(children, arg, default)
- if node.vararg is not None:
- if args:
- children.append(',')
- children.extend(['*', node.vararg])
- if node.kwarg is not None:
- if args or node.vararg is not None:
- children.append(',')
- children.extend(['**', node.kwarg])
- self._handle(node, children)
- def _add_args_to_children(self, children, arg, default):
- if isinstance(arg, (list, tuple)):
- self._add_tuple_parameter(children, arg)
- else:
- children.append(arg)
- if default is not None:
- children.append('=')
- children.append(default)
- def _add_tuple_parameter(self, children, arg):
- children.append('(')
- for index, token in enumerate(arg):
- if index > 0:
- children.append(',')
- if isinstance(token, (list, tuple)):
- self._add_tuple_parameter(children, token)
- else:
- children.append(token)
- children.append(')')
- def _GeneratorExp(self, node):
- children = [node.elt]
- children.extend(node.generators)
- self._handle(node, children, eat_parens=True)
- def _comprehension(self, node):
- children = ['for', node.target, 'in', node.iter]
- if node.ifs:
- for if_ in node.ifs:
- children.append('if')
- children.append(if_)
- self._handle(node, children)
- def _Global(self, node):
- children = self._child_nodes(node.names, ',')
- children.insert(0, 'global')
- self._handle(node, children)
- def _If(self, node):
- if self._is_elif(node):
- children = ['elif']
- else:
- children = ['if']
- children.extend([node.test, ':'])
- children.extend(node.body)
- if node.orelse:
- if len(node.orelse) == 1 and self._is_elif(node.orelse[0]):
- pass
- else:
- children.extend(['else', ':'])
- children.extend(node.orelse)
- self._handle(node, children)
- def _is_elif(self, node):
- if not isinstance(node, ast.If):
- return False
- offset = self.lines.get_line_start(node.lineno) + node.col_offset
- word = self.source[offset:offset + 4]
- # XXX: This is a bug; the offset does not point to the first
- alt_word = self.source[offset - 5:offset - 1]
- return 'elif' in (word, alt_word)
- def _IfExp(self, node):
- return self._handle(node, [node.body, 'if', node.test,
- 'else', node.orelse])
- def _Import(self, node):
- children = ['import']
- children.extend(self._child_nodes(node.names, ','))
- self._handle(node, children)
- def _keyword(self, node):
- self._handle(node, [node.arg, '=', node.value])
- def _Lambda(self, node):
- self._handle(node, ['lambda', node.args, ':', node.body])
- def _List(self, node):
- self._handle(node, ['['] + self._child_nodes(node.elts, ',') + [']'])
- def _ListComp(self, node):
- children = ['[', node.elt]
- children.extend(node.generators)
- children.append(']')
- self._handle(node, children)
- def _Module(self, node):
- self._handle(node, list(node.body), eat_spaces=True)
- def _Name(self, node):
- self._handle(node, [node.id])
- def _Pass(self, node):
- self._handle(node, ['pass'])
- def _Print(self, node):
- children = ['print']
- if node.dest:
- children.extend(['>>', node.dest])
- if node.values:
- children.append(',')
- children.extend(self._child_nodes(node.values, ','))
- if not node.nl:
- children.append(',')
- self._handle(node, children)
- def _Raise(self, node):
- children = ['raise']
- if node.type:
- children.append(node.type)
- if node.inst:
- children.append(',')
- children.append(node.inst)
- if node.tback:
- children.append(',')
- children.append(node.tback)
- self._handle(node, children)
- def _Return(self, node):
- children = ['return']
- if node.value:
- children.append(node.value)
- self._handle(node, children)
- def _Sliceobj(self, node):
- children = []
- for index, slice in enumerate(node.nodes):
- if index > 0:
- children.append(':')
- if slice:
- children.append(slice)
- self._handle(node, children)
- def _Index(self, node):
- self._handle(node, [node.value])
- def _Subscript(self, node):
- self._handle(node, [node.value, '[', node.slice, ']'])
- def _Slice(self, node):
- children = []
- if node.lower:
- children.append(node.lower)
- children.append(':')
- if node.upper:
- children.append(node.upper)
- if node.step:
- children.append(':')
- children.append(node.step)
- self._handle(node, children)
- def _TryFinally(self, node):
- children = []
- if len(node.body) != 1 or not isinstance(node.body[0], ast.TryExcept):
- children.extend(['try', ':'])
- children.extend(node.body)
- children.extend(['finally', ':'])
- children.extend(node.finalbody)
- self._handle(node, children)
- def _TryExcept(self, node):
- children = ['try', ':']
- children.extend(node.body)
- children.extend(node.handlers)
- if node.orelse:
- children.extend(['else', ':'])
- children.extend(node.orelse)
- self._handle(node, children)
- def _ExceptHandler(self, node):
- self._excepthandler(node)
- def _excepthandler(self, node):
- children = ['except']
- if node.type:
- children.append(node.type)
- if node.name:
- children.extend([',', node.name])
- children.append(':')
- children.extend(node.body)
- self._handle(node, children)
- def _Tuple(self, node):
- if node.elts:
- self._handle(node, self._child_nodes(node.elts, ','),
- eat_parens=True)
- else:
- self._handle(node, ['(', ')'])
- def _UnaryOp(self, node):
- children = self._get_op(node.op)
- children.append(node.operand)
- self._handle(node, children)
- def _Yield(self, node):
- children = ['yield']
- if node.value:
- children.append(node.value)
- self._handle(node, children)
- def _While(self, node):
- children = ['while', node.test, ':']
- children.extend(node.body)
- if node.orelse:
- children.extend(['else', ':'])
- children.extend(node.orelse)
- self._handle(node, children)
- def _With(self, node):
- children = ['with', node.context_expr]
- if node.optional_vars:
- children.extend(['as', node.optional_vars])
- children.append(':')
- children.extend(node.body)
- self._handle(node, children)
- def _child_nodes(self, nodes, separator):
- children = []
- for index, child in enumerate(nodes):
- children.append(child)
- if index < len(nodes) - 1:
- children.append(separator)
- return children
- class _Source(object):
- def __init__(self, source):
- self.source = source
- self.offset = 0
- def consume(self, token):
- try:
- while True:
- new_offset = self.source.index(token, self.offset)
- if self._good_token(token, new_offset):
- break
- else:
- self._skip_comment()
- except (ValueError, TypeError):
- raise MismatchedTokenError(
- 'Token <%s> at %s cannot be matched' %
- (token, self._get_location()))
- self.offset = new_offset + len(token)
- return (new_offset, self.offset)
- def consume_string(self, end=None):
- if _Source._string_pattern is None:
- original = codeanalyze.get_string_pattern()
- pattern = r'(%s)((\s|\\\n|#[^\n]*\n)*(%s))*' % \
- (original, original)
- _Source._string_pattern = re.compile(pattern)
- repattern = _Source._string_pattern
- return self._consume_pattern(repattern, end)
- def consume_number(self):
- if _Source._number_pattern is None:
- _Source._number_pattern = re.compile(
- self._get_number_pattern())
- repattern = _Source._number_pattern
- return self._consume_pattern(repattern)
- def consume_not_equal(self):
- if _Source._not_equals_pattern is None:
- _Source._not_equals_pattern = re.compile(r'<>|!=')
- repattern = _Source._not_equals_pattern
- return self._consume_pattern(repattern)
- def _good_token(self, token, offset, start=None):
- """Checks whether consumed token is in comments"""
- if start is None:
- start = self.offset
- try:
- comment_index = self.source.rindex('#', start, offset)
- except ValueError:
- return True
- try:
- new_line_index = self.source.rindex('\n', start, offset)
- except ValueError:
- return False
- return comment_index < new_line_index
- def _skip_comment(self):
- self.offset = self.source.index('\n', self.offset + 1)
- def _get_location(self):
- lines = self.source[:self.offset].split('\n')
- return (len(lines), len(lines[-1]))
- def _consume_pattern(self, repattern, end=None):
- while True:
- if end is None:
- end = len(self.source)
- match = repattern.search(self.source, self.offset, end)
- if self._good_token(match.group(), match.start()):
- break
- else:
- self._skip_comment()
- self.offset = match.end()
- return match.start(), match.end()
- def till_token(self, token):
- new_offset = self.source.index(token, self.offset)
- return self[self.offset:new_offset]
- def rfind_token(self, token, start, end):
- index = start
- while True:
- try:
- index = self.source.rindex(token, start, end)
- if self._good_token(token, index, start=start):
- return index
- else:
- end = index
- except ValueError:
- return None
- def from_offset(self, offset):
- return self[offset:self.offset]
- def find_backwards(self, pattern, offset):
- return self.source.rindex(pattern, 0, offset)
- def __getitem__(self, index):
- return self.source[index]
- def __getslice__(self, i, j):
- return self.source[i:j]
- def _get_number_pattern(self):
- # HACK: It is merely an approaximation and does the job
- integer = r'(0|0x)?[\da-fA-F]+[lL]?'
- return r'(%s(\.\d*)?|(\.\d+))([eE][-+]?\d*)?[jJ]?' % integer
- _string_pattern = None
- _number_pattern = None
- _not_equals_pattern = None
|