Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / third_party / cython / src / Cython / Compiler / Visitor.py
blob21e8b12552d0dcace9f93556eb680620d97c52e7
1 # cython: infer_types=True
4 # Tree visitor and transform framework
6 import inspect
8 from Cython.Compiler import TypeSlots
9 from Cython.Compiler import Builtin
10 from Cython.Compiler import Nodes
11 from Cython.Compiler import ExprNodes
12 from Cython.Compiler import Errors
13 from Cython.Compiler import DebugFlags
15 import cython
18 class TreeVisitor(object):
19 """
20 Base class for writing visitors for a Cython tree, contains utilities for
21 recursing such trees using visitors. Each node is
22 expected to have a child_attrs iterable containing the names of attributes
23 containing child nodes or lists of child nodes. Lists are not considered
24 part of the tree structure (i.e. contained nodes are considered direct
25 children of the parent node).
27 visit_children visits each of the children of a given node (see the visit_children
28 documentation). When recursing the tree using visit_children, an attribute
29 access_path is maintained which gives information about the current location
30 in the tree as a stack of tuples: (parent_node, attrname, index), representing
31 the node, attribute and optional list index that was taken in each step in the path to
32 the current node.
34 Example:
36 >>> class SampleNode(object):
37 ... child_attrs = ["head", "body"]
38 ... def __init__(self, value, head=None, body=None):
39 ... self.value = value
40 ... self.head = head
41 ... self.body = body
42 ... def __repr__(self): return "SampleNode(%s)" % self.value
43 ...
44 >>> tree = SampleNode(0, SampleNode(1), [SampleNode(2), SampleNode(3)])
45 >>> class MyVisitor(TreeVisitor):
46 ... def visit_SampleNode(self, node):
47 ... print "in", node.value, self.access_path
48 ... self.visitchildren(node)
49 ... print "out", node.value
50 ...
51 >>> MyVisitor().visit(tree)
52 in 0 []
53 in 1 [(SampleNode(0), 'head', None)]
54 out 1
55 in 2 [(SampleNode(0), 'body', 0)]
56 out 2
57 in 3 [(SampleNode(0), 'body', 1)]
58 out 3
59 out 0
60 """
61 def __init__(self):
62 super(TreeVisitor, self).__init__()
63 self.dispatch_table = {}
64 self.access_path = []
66 def dump_node(self, node, indent=0):
67 ignored = list(node.child_attrs or []) + [u'child_attrs', u'pos',
68 u'gil_message', u'cpp_message',
69 u'subexprs']
70 values = []
71 pos = getattr(node, 'pos', None)
72 if pos:
73 source = pos[0]
74 if source:
75 import os.path
76 source = os.path.basename(source.get_description())
77 values.append(u'%s:%s:%s' % (source, pos[1], pos[2]))
78 attribute_names = dir(node)
79 attribute_names.sort()
80 for attr in attribute_names:
81 if attr in ignored:
82 continue
83 if attr.startswith(u'_') or attr.endswith(u'_'):
84 continue
85 try:
86 value = getattr(node, attr)
87 except AttributeError:
88 continue
89 if value is None or value == 0:
90 continue
91 elif isinstance(value, list):
92 value = u'[...]/%d' % len(value)
93 elif not isinstance(value, (str, unicode, long, int, float)):
94 continue
95 else:
96 value = repr(value)
97 values.append(u'%s = %s' % (attr, value))
98 return u'%s(%s)' % (node.__class__.__name__,
99 u',\n '.join(values))
101 def _find_node_path(self, stacktrace):
102 import os.path
103 last_traceback = stacktrace
104 nodes = []
105 while hasattr(stacktrace, 'tb_frame'):
106 frame = stacktrace.tb_frame
107 node = frame.f_locals.get(u'self')
108 if isinstance(node, Nodes.Node):
109 code = frame.f_code
110 method_name = code.co_name
111 pos = (os.path.basename(code.co_filename),
112 frame.f_lineno)
113 nodes.append((node, method_name, pos))
114 last_traceback = stacktrace
115 stacktrace = stacktrace.tb_next
116 return (last_traceback, nodes)
118 def _raise_compiler_error(self, child, e):
119 import sys
120 trace = ['']
121 for parent, attribute, index in self.access_path:
122 node = getattr(parent, attribute)
123 if index is None:
124 index = ''
125 else:
126 node = node[index]
127 index = u'[%d]' % index
128 trace.append(u'%s.%s%s = %s' % (
129 parent.__class__.__name__, attribute, index,
130 self.dump_node(node)))
131 stacktrace, called_nodes = self._find_node_path(sys.exc_info()[2])
132 last_node = child
133 for node, method_name, pos in called_nodes:
134 last_node = node
135 trace.append(u"File '%s', line %d, in %s: %s" % (
136 pos[0], pos[1], method_name, self.dump_node(node)))
137 raise Errors.CompilerCrash(
138 getattr(last_node, 'pos', None), self.__class__.__name__,
139 u'\n'.join(trace), e, stacktrace)
141 @cython.final
142 def find_handler(self, obj):
143 # to resolve, try entire hierarchy
144 cls = type(obj)
145 pattern = "visit_%s"
146 mro = inspect.getmro(cls)
147 handler_method = None
148 for mro_cls in mro:
149 handler_method = getattr(self, pattern % mro_cls.__name__, None)
150 if handler_method is not None:
151 return handler_method
152 print type(self), cls
153 if self.access_path:
154 print self.access_path
155 print self.access_path[-1][0].pos
156 print self.access_path[-1][0].__dict__
157 raise RuntimeError("Visitor %r does not accept object: %s" % (self, obj))
159 def visit(self, obj):
160 return self._visit(obj)
162 @cython.final
163 def _visit(self, obj):
164 try:
165 try:
166 handler_method = self.dispatch_table[type(obj)]
167 except KeyError:
168 handler_method = self.find_handler(obj)
169 self.dispatch_table[type(obj)] = handler_method
170 return handler_method(obj)
171 except Errors.CompileError:
172 raise
173 except Errors.AbortError:
174 raise
175 except Exception, e:
176 if DebugFlags.debug_no_exception_intercept:
177 raise
178 self._raise_compiler_error(obj, e)
180 @cython.final
181 def _visitchild(self, child, parent, attrname, idx):
182 self.access_path.append((parent, attrname, idx))
183 result = self._visit(child)
184 self.access_path.pop()
185 return result
187 def visitchildren(self, parent, attrs=None):
188 return self._visitchildren(parent, attrs)
190 @cython.final
191 @cython.locals(idx=int)
192 def _visitchildren(self, parent, attrs):
194 Visits the children of the given parent. If parent is None, returns
195 immediately (returning None).
197 The return value is a dictionary giving the results for each
198 child (mapping the attribute name to either the return value
199 or a list of return values (in the case of multiple children
200 in an attribute)).
202 if parent is None: return None
203 result = {}
204 for attr in parent.child_attrs:
205 if attrs is not None and attr not in attrs: continue
206 child = getattr(parent, attr)
207 if child is not None:
208 if type(child) is list:
209 childretval = [self._visitchild(x, parent, attr, idx) for idx, x in enumerate(child)]
210 else:
211 childretval = self._visitchild(child, parent, attr, None)
212 assert not isinstance(childretval, list), 'Cannot insert list here: %s in %r' % (attr, parent)
213 result[attr] = childretval
214 return result
217 class VisitorTransform(TreeVisitor):
219 A tree transform is a base class for visitors that wants to do stream
220 processing of the structure (rather than attributes etc.) of a tree.
222 It implements __call__ to simply visit the argument node.
224 It requires the visitor methods to return the nodes which should take
225 the place of the visited node in the result tree (which can be the same
226 or one or more replacement). Specifically, if the return value from
227 a visitor method is:
229 - [] or None; the visited node will be removed (set to None if an attribute and
230 removed if in a list)
231 - A single node; the visited node will be replaced by the returned node.
232 - A list of nodes; the visited nodes will be replaced by all the nodes in the
233 list. This will only work if the node was already a member of a list; if it
234 was not, an exception will be raised. (Typically you want to ensure that you
235 are within a StatListNode or similar before doing this.)
237 def visitchildren(self, parent, attrs=None):
238 result = self._visitchildren(parent, attrs)
239 for attr, newnode in result.iteritems():
240 if type(newnode) is not list:
241 setattr(parent, attr, newnode)
242 else:
243 # Flatten the list one level and remove any None
244 newlist = []
245 for x in newnode:
246 if x is not None:
247 if type(x) is list:
248 newlist += x
249 else:
250 newlist.append(x)
251 setattr(parent, attr, newlist)
252 return result
254 def recurse_to_children(self, node):
255 self.visitchildren(node)
256 return node
258 def __call__(self, root):
259 return self._visit(root)
261 class CythonTransform(VisitorTransform):
263 Certain common conventions and utilities for Cython transforms.
265 - Sets up the context of the pipeline in self.context
266 - Tracks directives in effect in self.current_directives
268 def __init__(self, context):
269 super(CythonTransform, self).__init__()
270 self.context = context
272 def __call__(self, node):
273 import ModuleNode
274 if isinstance(node, ModuleNode.ModuleNode):
275 self.current_directives = node.directives
276 return super(CythonTransform, self).__call__(node)
278 def visit_CompilerDirectivesNode(self, node):
279 old = self.current_directives
280 self.current_directives = node.directives
281 self.visitchildren(node)
282 self.current_directives = old
283 return node
285 def visit_Node(self, node):
286 self.visitchildren(node)
287 return node
289 class ScopeTrackingTransform(CythonTransform):
290 # Keeps track of type of scopes
291 #scope_type: can be either of 'module', 'function', 'cclass', 'pyclass', 'struct'
292 #scope_node: the node that owns the current scope
294 def visit_ModuleNode(self, node):
295 self.scope_type = 'module'
296 self.scope_node = node
297 self.visitchildren(node)
298 return node
300 def visit_scope(self, node, scope_type):
301 prev = self.scope_type, self.scope_node
302 self.scope_type = scope_type
303 self.scope_node = node
304 self.visitchildren(node)
305 self.scope_type, self.scope_node = prev
306 return node
308 def visit_CClassDefNode(self, node):
309 return self.visit_scope(node, 'cclass')
311 def visit_PyClassDefNode(self, node):
312 return self.visit_scope(node, 'pyclass')
314 def visit_FuncDefNode(self, node):
315 return self.visit_scope(node, 'function')
317 def visit_CStructOrUnionDefNode(self, node):
318 return self.visit_scope(node, 'struct')
321 class EnvTransform(CythonTransform):
323 This transformation keeps a stack of the environments.
325 def __call__(self, root):
326 self.env_stack = []
327 self.enter_scope(root, root.scope)
328 return super(EnvTransform, self).__call__(root)
330 def current_env(self):
331 return self.env_stack[-1][1]
333 def current_scope_node(self):
334 return self.env_stack[-1][0]
336 def global_scope(self):
337 return self.current_env().global_scope()
339 def enter_scope(self, node, scope):
340 self.env_stack.append((node, scope))
342 def exit_scope(self):
343 self.env_stack.pop()
345 def visit_FuncDefNode(self, node):
346 self.enter_scope(node, node.local_scope)
347 self.visitchildren(node)
348 self.exit_scope()
349 return node
351 def visit_GeneratorBodyDefNode(self, node):
352 self.visitchildren(node)
353 return node
355 def visit_ClassDefNode(self, node):
356 self.enter_scope(node, node.scope)
357 self.visitchildren(node)
358 self.exit_scope()
359 return node
361 def visit_CStructOrUnionDefNode(self, node):
362 self.enter_scope(node, node.scope)
363 self.visitchildren(node)
364 self.exit_scope()
365 return node
367 def visit_ScopedExprNode(self, node):
368 if node.expr_scope:
369 self.enter_scope(node, node.expr_scope)
370 self.visitchildren(node)
371 self.exit_scope()
372 else:
373 self.visitchildren(node)
374 return node
376 def visit_CArgDeclNode(self, node):
377 # default arguments are evaluated in the outer scope
378 if node.default:
379 attrs = [ attr for attr in node.child_attrs if attr != 'default' ]
380 self.visitchildren(node, attrs)
381 self.enter_scope(node, self.current_env().outer_scope)
382 self.visitchildren(node, ('default',))
383 self.exit_scope()
384 else:
385 self.visitchildren(node)
386 return node
389 class NodeRefCleanupMixin(object):
391 Clean up references to nodes that were replaced.
393 NOTE: this implementation assumes that the replacement is
394 done first, before hitting any further references during
395 normal tree traversal. This needs to be arranged by calling
396 "self.visitchildren()" at a proper place in the transform
397 and by ordering the "child_attrs" of nodes appropriately.
399 def __init__(self, *args):
400 super(NodeRefCleanupMixin, self).__init__(*args)
401 self._replacements = {}
403 def visit_CloneNode(self, node):
404 arg = node.arg
405 if arg not in self._replacements:
406 self.visitchildren(node)
407 arg = node.arg
408 node.arg = self._replacements.get(arg, arg)
409 return node
411 def visit_ResultRefNode(self, node):
412 expr = node.expression
413 if expr is None or expr not in self._replacements:
414 self.visitchildren(node)
415 expr = node.expression
416 if expr is not None:
417 node.expression = self._replacements.get(expr, expr)
418 return node
420 def replace(self, node, replacement):
421 self._replacements[node] = replacement
422 return replacement
425 find_special_method_for_binary_operator = {
426 '<': '__lt__',
427 '<=': '__le__',
428 '==': '__eq__',
429 '!=': '__ne__',
430 '>=': '__ge__',
431 '>': '__gt__',
432 '+': '__add__',
433 '&': '__and__',
434 '/': '__truediv__',
435 '//': '__floordiv__',
436 '<<': '__lshift__',
437 '%': '__mod__',
438 '*': '__mul__',
439 '|': '__or__',
440 '**': '__pow__',
441 '>>': '__rshift__',
442 '-': '__sub__',
443 '^': '__xor__',
444 'in': '__contains__',
445 }.get
448 find_special_method_for_unary_operator = {
449 'not': '__not__',
450 '~': '__inv__',
451 '-': '__neg__',
452 '+': '__pos__',
453 }.get
456 class MethodDispatcherTransform(EnvTransform):
458 Base class for transformations that want to intercept on specific
459 builtin functions or methods of builtin types, including special
460 methods triggered by Python operators. Must run after declaration
461 analysis when entries were assigned.
463 Naming pattern for handler methods is as follows:
465 * builtin functions: _handle_(general|simple|any)_function_NAME
467 * builtin methods: _handle_(general|simple|any)_method_TYPENAME_METHODNAME
469 # only visit call nodes and Python operations
470 def visit_GeneralCallNode(self, node):
471 self.visitchildren(node)
472 function = node.function
473 if not function.type.is_pyobject:
474 return node
475 arg_tuple = node.positional_args
476 if not isinstance(arg_tuple, ExprNodes.TupleNode):
477 return node
478 keyword_args = node.keyword_args
479 if keyword_args and not isinstance(keyword_args, ExprNodes.DictNode):
480 # can't handle **kwargs
481 return node
482 args = arg_tuple.args
483 return self._dispatch_to_handler(node, function, args, keyword_args)
485 def visit_SimpleCallNode(self, node):
486 self.visitchildren(node)
487 function = node.function
488 if function.type.is_pyobject:
489 arg_tuple = node.arg_tuple
490 if not isinstance(arg_tuple, ExprNodes.TupleNode):
491 return node
492 args = arg_tuple.args
493 else:
494 args = node.args
495 return self._dispatch_to_handler(node, function, args, None)
497 def visit_PrimaryCmpNode(self, node):
498 if node.cascade:
499 # not currently handled below
500 self.visitchildren(node)
501 return node
502 return self._visit_binop_node(node)
504 def visit_BinopNode(self, node):
505 return self._visit_binop_node(node)
507 def _visit_binop_node(self, node):
508 self.visitchildren(node)
509 # FIXME: could special case 'not_in'
510 special_method_name = find_special_method_for_binary_operator(node.operator)
511 if special_method_name:
512 operand1, operand2 = node.operand1, node.operand2
513 if special_method_name == '__contains__':
514 operand1, operand2 = operand2, operand1
515 obj_type = operand1.type
516 if obj_type.is_builtin_type:
517 type_name = obj_type.name
518 else:
519 type_name = "object" # safety measure
520 node = self._dispatch_to_method_handler(
521 special_method_name, None, False, type_name,
522 node, None, [operand1, operand2], None)
523 return node
525 def visit_UnopNode(self, node):
526 self.visitchildren(node)
527 special_method_name = find_special_method_for_unary_operator(node.operator)
528 if special_method_name:
529 operand = node.operand
530 obj_type = operand.type
531 if obj_type.is_builtin_type:
532 type_name = obj_type.name
533 else:
534 type_name = "object" # safety measure
535 node = self._dispatch_to_method_handler(
536 special_method_name, None, False, type_name,
537 node, None, [operand], None)
538 return node
540 ### dispatch to specific handlers
542 def _find_handler(self, match_name, has_kwargs):
543 call_type = has_kwargs and 'general' or 'simple'
544 handler = getattr(self, '_handle_%s_%s' % (call_type, match_name), None)
545 if handler is None:
546 handler = getattr(self, '_handle_any_%s' % match_name, None)
547 return handler
549 def _delegate_to_assigned_value(self, node, function, arg_list, kwargs):
550 assignment = function.cf_state[0]
551 value = assignment.rhs
552 if value.is_name:
553 if not value.entry or len(value.entry.cf_assignments) > 1:
554 # the variable might have been reassigned => play safe
555 return node
556 elif value.is_attribute and value.obj.is_name:
557 if not value.obj.entry or len(value.obj.entry.cf_assignments) > 1:
558 # the underlying variable might have been reassigned => play safe
559 return node
560 else:
561 return node
562 return self._dispatch_to_handler(
563 node, value, arg_list, kwargs)
565 def _dispatch_to_handler(self, node, function, arg_list, kwargs):
566 if function.is_name:
567 # we only consider functions that are either builtin
568 # Python functions or builtins that were already replaced
569 # into a C function call (defined in the builtin scope)
570 if not function.entry:
571 return node
572 is_builtin = (
573 function.entry.is_builtin or
574 function.entry is self.current_env().builtin_scope().lookup_here(function.name))
575 if not is_builtin:
576 if function.cf_state and function.cf_state.is_single:
577 # we know the value of the variable
578 # => see if it's usable instead
579 return self._delegate_to_assigned_value(
580 node, function, arg_list, kwargs)
581 return node
582 function_handler = self._find_handler(
583 "function_%s" % function.name, kwargs)
584 if function_handler is None:
585 return self._handle_function(node, function.name, function, arg_list, kwargs)
586 if kwargs:
587 return function_handler(node, function, arg_list, kwargs)
588 else:
589 return function_handler(node, function, arg_list)
590 elif function.is_attribute and function.type.is_pyobject:
591 attr_name = function.attribute
592 self_arg = function.obj
593 obj_type = self_arg.type
594 is_unbound_method = False
595 if obj_type.is_builtin_type:
596 if (obj_type is Builtin.type_type and self_arg.is_name and
597 arg_list and arg_list[0].type.is_pyobject):
598 # calling an unbound method like 'list.append(L,x)'
599 # (ignoring 'type.mro()' here ...)
600 type_name = self_arg.name
601 self_arg = None
602 is_unbound_method = True
603 else:
604 type_name = obj_type.name
605 else:
606 type_name = "object" # safety measure
607 return self._dispatch_to_method_handler(
608 attr_name, self_arg, is_unbound_method, type_name,
609 node, function, arg_list, kwargs)
610 else:
611 return node
613 def _dispatch_to_method_handler(self, attr_name, self_arg,
614 is_unbound_method, type_name,
615 node, function, arg_list, kwargs):
616 method_handler = self._find_handler(
617 "method_%s_%s" % (type_name, attr_name), kwargs)
618 if method_handler is None:
619 if (attr_name in TypeSlots.method_name_to_slot
620 or attr_name == '__new__'):
621 method_handler = self._find_handler(
622 "slot%s" % attr_name, kwargs)
623 if method_handler is None:
624 return self._handle_method(
625 node, type_name, attr_name, function,
626 arg_list, is_unbound_method, kwargs)
627 if self_arg is not None:
628 arg_list = [self_arg] + list(arg_list)
629 if kwargs:
630 return method_handler(
631 node, function, arg_list, is_unbound_method, kwargs)
632 else:
633 return method_handler(
634 node, function, arg_list, is_unbound_method)
636 def _handle_function(self, node, function_name, function, arg_list, kwargs):
637 """Fallback handler"""
638 return node
640 def _handle_method(self, node, type_name, attr_name, function,
641 arg_list, is_unbound_method, kwargs):
642 """Fallback handler"""
643 return node
646 class RecursiveNodeReplacer(VisitorTransform):
648 Recursively replace all occurrences of a node in a subtree by
649 another node.
651 def __init__(self, orig_node, new_node):
652 super(RecursiveNodeReplacer, self).__init__()
653 self.orig_node, self.new_node = orig_node, new_node
655 def visit_Node(self, node):
656 self.visitchildren(node)
657 if node is self.orig_node:
658 return self.new_node
659 else:
660 return node
662 def recursively_replace_node(tree, old_node, new_node):
663 replace_in = RecursiveNodeReplacer(old_node, new_node)
664 replace_in(tree)
667 class NodeFinder(TreeVisitor):
669 Find out if a node appears in a subtree.
671 def __init__(self, node):
672 super(NodeFinder, self).__init__()
673 self.node = node
674 self.found = False
676 def visit_Node(self, node):
677 if self.found:
678 pass # short-circuit
679 elif node is self.node:
680 self.found = True
681 else:
682 self._visitchildren(node, None)
684 def tree_contains(tree, node):
685 finder = NodeFinder(node)
686 finder.visit(tree)
687 return finder.found
690 # Utils
691 def replace_node(ptr, value):
692 """Replaces a node. ptr is of the form used on the access path stack
693 (parent, attrname, listidx|None)
695 parent, attrname, listidx = ptr
696 if listidx is None:
697 setattr(parent, attrname, value)
698 else:
699 getattr(parent, attrname)[listidx] = value
701 class PrintTree(TreeVisitor):
702 """Prints a representation of the tree to standard output.
703 Subclass and override repr_of to provide more information
704 about nodes. """
705 def __init__(self):
706 TreeVisitor.__init__(self)
707 self._indent = ""
709 def indent(self):
710 self._indent += " "
711 def unindent(self):
712 self._indent = self._indent[:-2]
714 def __call__(self, tree, phase=None):
715 print("Parse tree dump at phase '%s'" % phase)
716 self.visit(tree)
717 return tree
719 # Don't do anything about process_list, the defaults gives
720 # nice-looking name[idx] nodes which will visually appear
721 # under the parent-node, not displaying the list itself in
722 # the hierarchy.
723 def visit_Node(self, node):
724 if len(self.access_path) == 0:
725 name = "(root)"
726 else:
727 parent, attr, idx = self.access_path[-1]
728 if idx is not None:
729 name = "%s[%d]" % (attr, idx)
730 else:
731 name = attr
732 print("%s- %s: %s" % (self._indent, name, self.repr_of(node)))
733 self.indent()
734 self.visitchildren(node)
735 self.unindent()
736 return node
738 def repr_of(self, node):
739 if node is None:
740 return "(none)"
741 else:
742 result = node.__class__.__name__
743 if isinstance(node, ExprNodes.NameNode):
744 result += "(type=%s, name=\"%s\")" % (repr(node.type), node.name)
745 elif isinstance(node, Nodes.DefNode):
746 result += "(name=\"%s\")" % node.name
747 elif isinstance(node, ExprNodes.ExprNode):
748 t = node.type
749 result += "(type=%s)" % repr(t)
750 elif node.pos:
751 pos = node.pos
752 path = pos[0].get_description()
753 if '/' in path:
754 path = path.split('/')[-1]
755 if '\\' in path:
756 path = path.split('\\')[-1]
757 result += "(pos=(%s:%s:%s))" % (path, pos[1], pos[2])
759 return result
761 if __name__ == "__main__":
762 import doctest
763 doctest.testmod()