Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
marvel
GitHub Repository: marvel/qnf
Path: blob/master/elisp/emacs-for-python/rope-dist/rope/refactor/patchedast.py
1439 views
1
import collections
2
import re
3
import warnings
4
5
from rope.base import ast, codeanalyze, exceptions
6
7
8
def get_patched_ast(source, sorted_children=False):
9
"""Adds ``region`` and ``sorted_children`` fields to nodes
10
11
Adds ``sorted_children`` field only if `sorted_children` is True.
12
13
"""
14
return patch_ast(ast.parse(source), source, sorted_children)
15
16
17
def patch_ast(node, source, sorted_children=False):
18
"""Patches the given node
19
20
After calling, each node in `node` will have a new field named
21
`region` that is a tuple containing the start and end offsets
22
of the code that generated it.
23
24
If `sorted_children` is true, a `sorted_children` field will
25
be created for each node, too. It is a list containing child
26
nodes as well as whitespaces and comments that occur between
27
them.
28
29
"""
30
if hasattr(node, 'region'):
31
return node
32
walker = _PatchingASTWalker(source, children=sorted_children)
33
ast.call_for_nodes(node, walker)
34
return node
35
36
37
def node_region(patched_ast_node):
38
"""Get the region of a patched ast node"""
39
return patched_ast_node.region
40
41
42
def write_ast(patched_ast_node):
43
"""Extract source form a patched AST node with `sorted_children` field
44
45
If the node is patched with sorted_children turned off you can use
46
`node_region` function for obtaining code using module source code.
47
"""
48
result = []
49
for child in patched_ast_node.sorted_children:
50
if isinstance(child, ast.AST):
51
result.append(write_ast(child))
52
else:
53
result.append(child)
54
return ''.join(result)
55
56
57
class MismatchedTokenError(exceptions.RopeError):
58
pass
59
60
61
class _PatchingASTWalker(object):
62
63
def __init__(self, source, children=False):
64
self.source = _Source(source)
65
self.children = children
66
self.lines = codeanalyze.SourceLinesAdapter(source)
67
self.children_stack = []
68
69
Number = object()
70
String = object()
71
72
def __call__(self, node):
73
method = getattr(self, '_' + node.__class__.__name__, None)
74
if method is not None:
75
return method(node)
76
# ???: Unknown node; what should we do here?
77
warnings.warn('Unknown node type <%s>; please report!'
78
% node.__class__.__name__, RuntimeWarning)
79
node.region = (self.source.offset, self.source.offset)
80
if self.children:
81
node.sorted_children = ast.get_children(node)
82
83
def _handle(self, node, base_children, eat_parens=False, eat_spaces=False):
84
if hasattr(node, 'region'):
85
# ???: The same node was seen twice; what should we do?
86
warnings.warn(
87
'Node <%s> has been already patched; please report!' %
88
node.__class__.__name__, RuntimeWarning)
89
return
90
base_children = collections.deque(base_children)
91
self.children_stack.append(base_children)
92
children = collections.deque()
93
formats = []
94
suspected_start = self.source.offset
95
start = suspected_start
96
first_token = True
97
while base_children:
98
child = base_children.popleft()
99
if child is None:
100
continue
101
offset = self.source.offset
102
if isinstance(child, ast.AST):
103
ast.call_for_nodes(child, self)
104
token_start = child.region[0]
105
else:
106
if child is self.String:
107
region = self.source.consume_string(
108
end=self._find_next_statement_start())
109
elif child is self.Number:
110
region = self.source.consume_number()
111
elif child == '!=':
112
# INFO: This has been added to handle deprecated ``<>``
113
region = self.source.consume_not_equal()
114
else:
115
region = self.source.consume(child)
116
child = self.source[region[0]:region[1]]
117
token_start = region[0]
118
if not first_token:
119
formats.append(self.source[offset:token_start])
120
if self.children:
121
children.append(self.source[offset:token_start])
122
else:
123
first_token = False
124
start = token_start
125
if self.children:
126
children.append(child)
127
start = self._handle_parens(children, start, formats)
128
if eat_parens:
129
start = self._eat_surrounding_parens(
130
children, suspected_start, start)
131
if eat_spaces:
132
if self.children:
133
children.appendleft(self.source[0:start])
134
end_spaces = self.source[self.source.offset:]
135
self.source.consume(end_spaces)
136
if self.children:
137
children.append(end_spaces)
138
start = 0
139
if self.children:
140
node.sorted_children = children
141
node.region = (start, self.source.offset)
142
self.children_stack.pop()
143
144
def _handle_parens(self, children, start, formats):
145
"""Changes `children` and returns new start"""
146
opens, closes = self._count_needed_parens(formats)
147
old_end = self.source.offset
148
new_end = None
149
for i in range(closes):
150
new_end = self.source.consume(')')[1]
151
if new_end is not None:
152
if self.children:
153
children.append(self.source[old_end:new_end])
154
new_start = start
155
for i in range(opens):
156
new_start = self.source.rfind_token('(', 0, new_start)
157
if new_start != start:
158
if self.children:
159
children.appendleft(self.source[new_start:start])
160
start = new_start
161
return start
162
163
def _eat_surrounding_parens(self, children, suspected_start, start):
164
index = self.source.rfind_token('(', suspected_start, start)
165
if index is not None:
166
old_start = start
167
old_offset = self.source.offset
168
start = index
169
if self.children:
170
children.appendleft(self.source[start + 1:old_start])
171
children.appendleft('(')
172
token_start, token_end = self.source.consume(')')
173
if self.children:
174
children.append(self.source[old_offset:token_start])
175
children.append(')')
176
return start
177
178
def _count_needed_parens(self, children):
179
start = 0
180
opens = 0
181
for child in children:
182
if not isinstance(child, basestring):
183
continue
184
if child == '' or child[0] in '\'"':
185
continue
186
index = 0
187
while index < len(child):
188
if child[index] == ')':
189
if opens > 0:
190
opens -= 1
191
else:
192
start += 1
193
if child[index] == '(':
194
opens += 1
195
if child[index] == '#':
196
try:
197
index = child.index('\n', index)
198
except ValueError:
199
break
200
index += 1
201
return start, opens
202
203
def _find_next_statement_start(self):
204
for children in reversed(self.children_stack):
205
for child in children:
206
if isinstance(child, ast.stmt):
207
return self.lines.get_line_start(child.lineno)
208
return len(self.source.source)
209
210
_operators = {'And': 'and', 'Or': 'or', 'Add': '+', 'Sub': '-', 'Mult': '*',
211
'Div': '/', 'Mod': '%', 'Pow': '**', 'LShift': '<<',
212
'RShift': '>>', 'BitOr': '|', 'BitAnd': '&', 'BitXor': '^',
213
'FloorDiv': '//', 'Invert': '~', 'Not': 'not', 'UAdd': '+',
214
'USub': '-', 'Eq': '==', 'NotEq': '!=', 'Lt': '<',
215
'LtE': '<=', 'Gt': '>', 'GtE': '>=', 'Is': 'is',
216
'IsNot': 'is not', 'In': 'in', 'NotIn': 'not in'}
217
218
def _get_op(self, node):
219
return self._operators[node.__class__.__name__].split(' ')
220
221
def _Attribute(self, node):
222
self._handle(node, [node.value, '.', node.attr])
223
224
def _Assert(self, node):
225
children = ['assert', node.test]
226
if node.msg:
227
children.append(',')
228
children.append(node.msg)
229
self._handle(node, children)
230
231
def _Assign(self, node):
232
children = self._child_nodes(node.targets, '=')
233
children.append('=')
234
children.append(node.value)
235
self._handle(node, children)
236
237
def _AugAssign(self, node):
238
children = [node.target]
239
children.extend(self._get_op(node.op))
240
children.extend(['=', node.value])
241
self._handle(node, children)
242
243
def _Repr(self, node):
244
self._handle(node, ['`', node.value, '`'])
245
246
def _BinOp(self, node):
247
children = [node.left] + self._get_op(node.op) + [node.right]
248
self._handle(node, children)
249
250
def _BoolOp(self, node):
251
self._handle(node, self._child_nodes(node.values,
252
self._get_op(node.op)[0]))
253
254
def _Break(self, node):
255
self._handle(node, ['break'])
256
257
def _Call(self, node):
258
children = [node.func, '(']
259
args = list(node.args) + node.keywords
260
children.extend(self._child_nodes(args, ','))
261
if node.starargs is not None:
262
if args:
263
children.append(',')
264
children.extend(['*', node.starargs])
265
if node.kwargs is not None:
266
if args or node.starargs is not None:
267
children.append(',')
268
children.extend(['**', node.kwargs])
269
children.append(')')
270
self._handle(node, children)
271
272
def _ClassDef(self, node):
273
children = []
274
if getattr(node, 'decorator_list', None):
275
for decorator in node.decorator_list:
276
children.append('@')
277
children.append(decorator)
278
children.extend(['class', node.name])
279
if node.bases:
280
children.append('(')
281
children.extend(self._child_nodes(node.bases, ','))
282
children.append(')')
283
children.append(':')
284
children.extend(node.body)
285
self._handle(node, children)
286
287
def _Compare(self, node):
288
children = []
289
children.append(node.left)
290
for op, expr in zip(node.ops, node.comparators):
291
children.extend(self._get_op(op))
292
children.append(expr)
293
self._handle(node, children)
294
295
def _Delete(self, node):
296
self._handle(node, ['del'] + self._child_nodes(node.targets, ','))
297
298
def _Num(self, node):
299
self._handle(node, [self.Number])
300
301
def _Str(self, node):
302
self._handle(node, [self.String])
303
304
def _Continue(self, node):
305
self._handle(node, ['continue'])
306
307
def _Dict(self, node):
308
children = []
309
children.append('{')
310
if node.keys:
311
for index, (key, value) in enumerate(zip(node.keys, node.values)):
312
children.extend([key, ':', value])
313
if index < len(node.keys) - 1:
314
children.append(',')
315
children.append('}')
316
self._handle(node, children)
317
318
def _Ellipsis(self, node):
319
self._handle(node, ['...'])
320
321
def _Expr(self, node):
322
self._handle(node, [node.value])
323
324
def _Exec(self, node):
325
children = []
326
children.extend(['exec', node.body])
327
if node.globals:
328
children.extend(['in', node.globals])
329
if node.locals:
330
children.extend([',', node.locals])
331
self._handle(node, children)
332
333
def _ExtSlice(self, node):
334
children = []
335
for index, dim in enumerate(node.dims):
336
if index > 0:
337
children.append(',')
338
children.append(dim)
339
self._handle(node, children)
340
341
def _For(self, node):
342
children = ['for', node.target, 'in', node.iter, ':']
343
children.extend(node.body)
344
if node.orelse:
345
children.extend(['else', ':'])
346
children.extend(node.orelse)
347
self._handle(node, children)
348
349
def _ImportFrom(self, node):
350
children = ['from']
351
if node.level:
352
children.append('.' * node.level)
353
children.extend([node.module, 'import'])
354
children.extend(self._child_nodes(node.names, ','))
355
self._handle(node, children)
356
357
def _alias(self, node):
358
children = [node.name]
359
if node.asname:
360
children.extend(['as', node.asname])
361
self._handle(node, children)
362
363
def _FunctionDef(self, node):
364
children = []
365
try:
366
decorators = getattr(node, 'decorator_list')
367
except AttributeError:
368
decorators = getattr(node, 'decorators', None)
369
if decorators:
370
for decorator in decorators:
371
children.append('@')
372
children.append(decorator)
373
children.extend(['def', node.name, '(', node.args])
374
children.extend([')', ':'])
375
children.extend(node.body)
376
self._handle(node, children)
377
378
def _arguments(self, node):
379
children = []
380
args = list(node.args)
381
defaults = [None] * (len(args) - len(node.defaults)) + list(node.defaults)
382
for index, (arg, default) in enumerate(zip(args, defaults)):
383
if index > 0:
384
children.append(',')
385
self._add_args_to_children(children, arg, default)
386
if node.vararg is not None:
387
if args:
388
children.append(',')
389
children.extend(['*', node.vararg])
390
if node.kwarg is not None:
391
if args or node.vararg is not None:
392
children.append(',')
393
children.extend(['**', node.kwarg])
394
self._handle(node, children)
395
396
def _add_args_to_children(self, children, arg, default):
397
if isinstance(arg, (list, tuple)):
398
self._add_tuple_parameter(children, arg)
399
else:
400
children.append(arg)
401
if default is not None:
402
children.append('=')
403
children.append(default)
404
405
def _add_tuple_parameter(self, children, arg):
406
children.append('(')
407
for index, token in enumerate(arg):
408
if index > 0:
409
children.append(',')
410
if isinstance(token, (list, tuple)):
411
self._add_tuple_parameter(children, token)
412
else:
413
children.append(token)
414
children.append(')')
415
416
def _GeneratorExp(self, node):
417
children = [node.elt]
418
children.extend(node.generators)
419
self._handle(node, children, eat_parens=True)
420
421
def _comprehension(self, node):
422
children = ['for', node.target, 'in', node.iter]
423
if node.ifs:
424
for if_ in node.ifs:
425
children.append('if')
426
children.append(if_)
427
self._handle(node, children)
428
429
def _Global(self, node):
430
children = self._child_nodes(node.names, ',')
431
children.insert(0, 'global')
432
self._handle(node, children)
433
434
def _If(self, node):
435
if self._is_elif(node):
436
children = ['elif']
437
else:
438
children = ['if']
439
children.extend([node.test, ':'])
440
children.extend(node.body)
441
if node.orelse:
442
if len(node.orelse) == 1 and self._is_elif(node.orelse[0]):
443
pass
444
else:
445
children.extend(['else', ':'])
446
children.extend(node.orelse)
447
self._handle(node, children)
448
449
def _is_elif(self, node):
450
if not isinstance(node, ast.If):
451
return False
452
offset = self.lines.get_line_start(node.lineno) + node.col_offset
453
word = self.source[offset:offset + 4]
454
# XXX: This is a bug; the offset does not point to the first
455
alt_word = self.source[offset - 5:offset - 1]
456
return 'elif' in (word, alt_word)
457
458
def _IfExp(self, node):
459
return self._handle(node, [node.body, 'if', node.test,
460
'else', node.orelse])
461
462
def _Import(self, node):
463
children = ['import']
464
children.extend(self._child_nodes(node.names, ','))
465
self._handle(node, children)
466
467
def _keyword(self, node):
468
self._handle(node, [node.arg, '=', node.value])
469
470
def _Lambda(self, node):
471
self._handle(node, ['lambda', node.args, ':', node.body])
472
473
def _List(self, node):
474
self._handle(node, ['['] + self._child_nodes(node.elts, ',') + [']'])
475
476
def _ListComp(self, node):
477
children = ['[', node.elt]
478
children.extend(node.generators)
479
children.append(']')
480
self._handle(node, children)
481
482
def _Module(self, node):
483
self._handle(node, list(node.body), eat_spaces=True)
484
485
def _Name(self, node):
486
self._handle(node, [node.id])
487
488
def _Pass(self, node):
489
self._handle(node, ['pass'])
490
491
def _Print(self, node):
492
children = ['print']
493
if node.dest:
494
children.extend(['>>', node.dest])
495
if node.values:
496
children.append(',')
497
children.extend(self._child_nodes(node.values, ','))
498
if not node.nl:
499
children.append(',')
500
self._handle(node, children)
501
502
def _Raise(self, node):
503
children = ['raise']
504
if node.type:
505
children.append(node.type)
506
if node.inst:
507
children.append(',')
508
children.append(node.inst)
509
if node.tback:
510
children.append(',')
511
children.append(node.tback)
512
self._handle(node, children)
513
514
def _Return(self, node):
515
children = ['return']
516
if node.value:
517
children.append(node.value)
518
self._handle(node, children)
519
520
def _Sliceobj(self, node):
521
children = []
522
for index, slice in enumerate(node.nodes):
523
if index > 0:
524
children.append(':')
525
if slice:
526
children.append(slice)
527
self._handle(node, children)
528
529
def _Index(self, node):
530
self._handle(node, [node.value])
531
532
def _Subscript(self, node):
533
self._handle(node, [node.value, '[', node.slice, ']'])
534
535
def _Slice(self, node):
536
children = []
537
if node.lower:
538
children.append(node.lower)
539
children.append(':')
540
if node.upper:
541
children.append(node.upper)
542
if node.step:
543
children.append(':')
544
children.append(node.step)
545
self._handle(node, children)
546
547
def _TryFinally(self, node):
548
children = []
549
if len(node.body) != 1 or not isinstance(node.body[0], ast.TryExcept):
550
children.extend(['try', ':'])
551
children.extend(node.body)
552
children.extend(['finally', ':'])
553
children.extend(node.finalbody)
554
self._handle(node, children)
555
556
def _TryExcept(self, node):
557
children = ['try', ':']
558
children.extend(node.body)
559
children.extend(node.handlers)
560
if node.orelse:
561
children.extend(['else', ':'])
562
children.extend(node.orelse)
563
self._handle(node, children)
564
565
def _ExceptHandler(self, node):
566
self._excepthandler(node)
567
568
def _excepthandler(self, node):
569
children = ['except']
570
if node.type:
571
children.append(node.type)
572
if node.name:
573
children.extend([',', node.name])
574
children.append(':')
575
children.extend(node.body)
576
self._handle(node, children)
577
578
def _Tuple(self, node):
579
if node.elts:
580
self._handle(node, self._child_nodes(node.elts, ','),
581
eat_parens=True)
582
else:
583
self._handle(node, ['(', ')'])
584
585
def _UnaryOp(self, node):
586
children = self._get_op(node.op)
587
children.append(node.operand)
588
self._handle(node, children)
589
590
def _Yield(self, node):
591
children = ['yield']
592
if node.value:
593
children.append(node.value)
594
self._handle(node, children)
595
596
def _While(self, node):
597
children = ['while', node.test, ':']
598
children.extend(node.body)
599
if node.orelse:
600
children.extend(['else', ':'])
601
children.extend(node.orelse)
602
self._handle(node, children)
603
604
def _With(self, node):
605
children = ['with', node.context_expr]
606
if node.optional_vars:
607
children.extend(['as', node.optional_vars])
608
children.append(':')
609
children.extend(node.body)
610
self._handle(node, children)
611
612
def _child_nodes(self, nodes, separator):
613
children = []
614
for index, child in enumerate(nodes):
615
children.append(child)
616
if index < len(nodes) - 1:
617
children.append(separator)
618
return children
619
620
621
class _Source(object):
622
623
def __init__(self, source):
624
self.source = source
625
self.offset = 0
626
627
def consume(self, token):
628
try:
629
while True:
630
new_offset = self.source.index(token, self.offset)
631
if self._good_token(token, new_offset):
632
break
633
else:
634
self._skip_comment()
635
except (ValueError, TypeError):
636
raise MismatchedTokenError(
637
'Token <%s> at %s cannot be matched' %
638
(token, self._get_location()))
639
self.offset = new_offset + len(token)
640
return (new_offset, self.offset)
641
642
def consume_string(self, end=None):
643
if _Source._string_pattern is None:
644
original = codeanalyze.get_string_pattern()
645
pattern = r'(%s)((\s|\\\n|#[^\n]*\n)*(%s))*' % \
646
(original, original)
647
_Source._string_pattern = re.compile(pattern)
648
repattern = _Source._string_pattern
649
return self._consume_pattern(repattern, end)
650
651
def consume_number(self):
652
if _Source._number_pattern is None:
653
_Source._number_pattern = re.compile(
654
self._get_number_pattern())
655
repattern = _Source._number_pattern
656
return self._consume_pattern(repattern)
657
658
def consume_not_equal(self):
659
if _Source._not_equals_pattern is None:
660
_Source._not_equals_pattern = re.compile(r'<>|!=')
661
repattern = _Source._not_equals_pattern
662
return self._consume_pattern(repattern)
663
664
def _good_token(self, token, offset, start=None):
665
"""Checks whether consumed token is in comments"""
666
if start is None:
667
start = self.offset
668
try:
669
comment_index = self.source.rindex('#', start, offset)
670
except ValueError:
671
return True
672
try:
673
new_line_index = self.source.rindex('\n', start, offset)
674
except ValueError:
675
return False
676
return comment_index < new_line_index
677
678
def _skip_comment(self):
679
self.offset = self.source.index('\n', self.offset + 1)
680
681
def _get_location(self):
682
lines = self.source[:self.offset].split('\n')
683
return (len(lines), len(lines[-1]))
684
685
def _consume_pattern(self, repattern, end=None):
686
while True:
687
if end is None:
688
end = len(self.source)
689
match = repattern.search(self.source, self.offset, end)
690
if self._good_token(match.group(), match.start()):
691
break
692
else:
693
self._skip_comment()
694
self.offset = match.end()
695
return match.start(), match.end()
696
697
def till_token(self, token):
698
new_offset = self.source.index(token, self.offset)
699
return self[self.offset:new_offset]
700
701
def rfind_token(self, token, start, end):
702
index = start
703
while True:
704
try:
705
index = self.source.rindex(token, start, end)
706
if self._good_token(token, index, start=start):
707
return index
708
else:
709
end = index
710
except ValueError:
711
return None
712
713
def from_offset(self, offset):
714
return self[offset:self.offset]
715
716
def find_backwards(self, pattern, offset):
717
return self.source.rindex(pattern, 0, offset)
718
719
def __getitem__(self, index):
720
return self.source[index]
721
722
def __getslice__(self, i, j):
723
return self.source[i:j]
724
725
def _get_number_pattern(self):
726
# HACK: It is merely an approaximation and does the job
727
integer = r'(0|0x)?[\da-fA-F]+[lL]?'
728
return r'(%s(\.\d*)?|(\.\d+))([eE][-+]?\d*)?[jJ]?' % integer
729
730
_string_pattern = None
731
_number_pattern = None
732
_not_equals_pattern = None
733
734