# This program is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # copyright 2003-2010 Sylvain Thenault, all rights reserved. # contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # # logilab-astng is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # # logilab-astng is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along # with logilab-astng. If not, see . """this module contains utilities for rebuilding a compiler.ast or _ast tree in order to get a single ASTNG representation """ from logilab.astng import ASTNGBuildingException, InferenceError from logilab.astng import nodes from logilab.astng.bases import YES, Instance REDIRECT = {'arguments': 'Arguments', 'Attribute': 'Getattr', 'comprehension': 'Comprehension', 'Call': 'CallFunc', 'ClassDef': 'Class', "ListCompFor": 'Comprehension', "GenExprFor": 'Comprehension', 'excepthandler': 'ExceptHandler', 'Expr': 'Discard', 'FunctionDef': 'Function', 'GeneratorExp': 'GenExpr', 'ImportFrom': 'From', 'keyword': 'Keyword', 'Repr': 'Backquote', 'Add': 'BinOp', 'Bitand': 'BinOp', 'Bitor': 'BinOp', 'Bitxor': 'BinOp', 'Div': 'BinOp', 'FloorDiv': 'BinOp', 'LeftShift': 'BinOp', 'Mod': 'BinOp', 'Mul': 'BinOp', 'Power': 'BinOp', 'RightShift': 'BinOp', 'Sub': 'BinOp', 'And': 'BoolOp', 'Or': 'BoolOp', 'UnaryAdd': 'UnaryOp', 'UnarySub': 'UnaryOp', 'Not': 'UnaryOp', 'Invert': 'UnaryOp' } class RebuildVisitor(object): """Visitor to transform an AST to an ASTNG """ def __init__(self, manager): self._manager = manager self.asscontext = None self._metaclass = None self._global_names = None self._delayed_assattr = [] def visit(self, node, parent): if node is None: # some attributes of some nodes are just None return None cls_name = node.__class__.__name__ visit_name = 'visit_' + REDIRECT.get(cls_name, cls_name).lower() visit_method = getattr(self, visit_name) return visit_method(node, parent) def build(self, node, modname, module_file, package): """rebuild the tree starting with an Module node; return an astng.Module node """ self._metaclass = [''] self._global_names = [] self._from_nodes = [] module = self.visit_module(node, modname, package) module.file = module.path = module_file # init module cache here else we may get some infinite recursion # errors while infering delayed assignments if self._manager is not None: self._manager._cache[module.name] = module # handle delayed assattr nodes for from_node in self._from_nodes: self._add_from_names_to_locals(from_node, delayed=True) delay_assattr = self.delayed_assattr for delayed in self._delayed_assattr: delay_assattr(delayed) return module def _save_argument_name(self, node): """save argument names in locals""" if node.vararg: node.parent.set_local(node.vararg, node) if node.kwarg: node.parent.set_local(node.kwarg, node) # visit_ and delayed_ methods ################################# def _set_assign_infos(self, newnode): """set some function or metaclass infos""" # XXX right ? klass = newnode.parent.frame() if (isinstance(klass, nodes.Class) and isinstance(newnode.value, nodes.CallFunc) and isinstance(newnode.value.func, nodes.Name)): func_name = newnode.value.func.name for ass_node in newnode.targets: try: meth = klass[ass_node.name] if isinstance(meth, nodes.Function): if func_name in ('classmethod', 'staticmethod'): meth.type = func_name try: # XXX use setdefault ? meth.extra_decorators.append(newnode.value) except AttributeError: meth.extra_decorators = [newnode.value] except (AttributeError, KeyError): continue elif getattr(newnode.targets[0], 'name', None) == '__metaclass__': # XXX check more... self._metaclass[-1] = 'type' # XXX get the actual metaclass def visit_class(self, node, parent): """visit a Class node to become astng""" self._metaclass.append(self._metaclass[-1]) newnode = self._visit_class(node, parent) metaclass = self._metaclass.pop() if not newnode.bases: # no base classes, detect new / style old style according to # current scope newnode._newstyle = metaclass == 'type' newnode.parent.frame().set_local(newnode.name, newnode) return newnode def visit_break(self, node, parent): """visit a Break node by returning a fresh instance of it""" newnode = nodes.Break() self._set_infos(node, newnode, parent) return newnode def visit_const(self, node, parent): """visit a Const node by returning a fresh instance of it""" newnode = nodes.Const(node.value) self._set_infos(node, newnode, parent) return newnode def visit_continue(self, node, parent): """visit a Continue node by returning a fresh instance of it""" newnode = nodes.Continue() self._set_infos(node, newnode, parent) return newnode def visit_ellipsis(self, node, parent): """visit an Ellipsis node by returning a fresh instance of it""" newnode = nodes.Ellipsis() self._set_infos(node, newnode, parent) return newnode def visit_emptynode(self, node, parent): """visit an EmptyNode node by returning a fresh instance of it""" newnode = nodes.EmptyNode() self._set_infos(node, newnode, parent) return newnode def _store_from_node(self, node): """handle From names by adding them to locals now or after""" # we can not handle wildcard imports if the source module is not # in the cache since 'import_module' calls the MANAGER and we will # end up with infinite recursions working with unfinished trees if node.modname in self._manager._cache: self._add_from_names_to_locals(node) else: self._from_nodes.append(node) def _add_from_names_to_locals(self, node, delayed=False): """store imported names to the locals; resort the locals if coming from a delayed node """ cmp_nodes = lambda x, y: cmp(x.fromlineno, y.fromlineno) for (name, asname) in node.names: if name == '*': try: imported = node.root().import_module(node.modname) except ASTNGBuildingException: continue for name in imported.wildcard_import_names(): node.parent.set_local(name, node) if delayed: node.parent.scope().locals[name].sort(cmp=cmp_nodes) else: node.parent.set_local(asname or name, node) if delayed: node.parent.scope().locals[asname or name].sort(cmp=cmp_nodes) def visit_function(self, node, parent): """visit an Function node to become astng""" self._global_names.append({}) newnode = self._visit_function(node, parent) self._global_names.pop() frame = newnode.parent.frame() if isinstance(frame, nodes.Class): if newnode.name == '__new__': newnode.type = 'classmethod' else: newnode.type = 'method' if newnode.decorators is not None: for decorator_expr in newnode.decorators.nodes: if isinstance(decorator_expr, nodes.Name) and \ decorator_expr.name in ('classmethod', 'staticmethod'): newnode.type = decorator_expr.name frame.set_local(newnode.name, newnode) return newnode def visit_global(self, node, parent): """visit an Global node to become astng""" newnode = nodes.Global(node.names) self._set_infos(node, newnode, parent) if self._global_names: # global at the module level, no effect for name in node.names: self._global_names[-1].setdefault(name, []).append(newnode) return newnode def _save_import_locals(self, newnode): """save import names in parent's locals""" for (name, asname) in newnode.names: name = asname or name newnode.parent.set_local(name.split('.')[0], newnode) def visit_pass(self, node, parent): """visit a Pass node by returning a fresh instance of it""" newnode = nodes.Pass() self._set_infos(node, newnode, parent) return newnode def _save_assignment(self, node, name=None): """save assignement situation since node.parent is not available yet""" if self._global_names and node.name in self._global_names[-1]: node.root().set_local(node.name, node) else: node.parent.set_local(node.name, node) def delayed_assattr(self, node): """visit a AssAttr node -> add name to locals, handle members definition """ try: frame = node.frame() for infered in node.expr.infer(): if infered is YES: continue try: if infered.__class__ is Instance: infered = infered._proxied iattrs = infered.instance_attrs elif isinstance(infered, Instance): # Const, Tuple, ... we may be wrong, may be not, but # anyway we don't want to pollute builtin's namespace continue else: iattrs = infered.locals except AttributeError: # XXX log error #import traceback #traceback.print_exc() continue values = iattrs.setdefault(node.attrname, []) if node in values: continue # get assign in __init__ first XXX useful ? if frame.name == '__init__' and values and not \ values[0].frame().name == '__init__': values.insert(0, node) else: values.append(node) except InferenceError: pass