root / logilab.pylintinstaller / logilab / astng / builder.py

Revision 202:d67e86292521, 22.9 kB (checked in by tziade@…, 9 months ago)

added logilab.pylintinstaller

Line 
1# This program is free software; you can redistribute it and/or modify it under
2# the terms of the GNU General Public License as published by the Free Software
3# Foundation; either version 2 of the License, or (at your option) any later
4# version.
5#
6# This program is distributed in the hope that it will be useful, but WITHOUT
7# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
8# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9#
10# You should have received a copy of the GNU General Public License along with
11# this program; if not, write to the Free Software Foundation, Inc.,
12# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
13"""The ASTNGBuilder makes astng from living object and / or from compiler.ast
14
15The builder is not thread safe and can't be used to parse different sources
16at the same time.
17
18TODO:
19 - more complet representation on inspect build
20   (imported modules ? use dis.dis ?)
21
22
23:author:    Sylvain Thenault
24:copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE)
25:contact:   http://www.logilab.fr/ -- mailto:python-projects@logilab.org
26:copyright: 2003-2007 Sylvain Thenault
27:contact:   mailto:thenault@gmail.com
28"""
29
30__docformat__ = "restructuredtext en"
31
32import sys
33from os.path import splitext, basename, dirname, exists, abspath
34from parser import ParserError
35from compiler import parse
36from inspect import isfunction, ismethod, ismethoddescriptor, isclass, \
37     isbuiltin
38from inspect import isdatadescriptor
39
40from logilab.common.fileutils import norm_read
41from logilab.common.modutils import modpath_from_file
42
43from logilab.astng import nodes, YES, Instance
44from logilab.astng.utils import ASTWalker
45from logilab.astng._exceptions import ASTNGBuildingException, InferenceError
46from logilab.astng.raw_building import *
47from logilab.astng.astutils import cvrtr
48
49import token
50from compiler import transformer, consts
51from types import TupleType
52
53def fromto_lineno(asttuple):
54    """return the minimum and maximum line number of the given ast tuple"""
55    return from_lineno(asttuple), to_lineno(asttuple)
56def from_lineno(asttuple):
57    """return the minimum line number of the given ast tuple"""
58    if type(asttuple[1]) is TupleType:
59        return from_lineno(asttuple[1])
60    return asttuple[2]
61def to_lineno(asttuple):
62    """return the maximum line number of the given ast tuple"""
63    if type(asttuple[-1]) is TupleType:
64        return to_lineno(asttuple[-1])
65    return asttuple[2]
66
67def fix_lineno(node, fromast, toast=None):
68    if 'fromlineno' in node.__dict__:
69        return node   
70    #print 'fixing', id(node), id(node.__dict__), node.__dict__.keys(), repr(node)
71    if isinstance(node, nodes.Stmt):
72        node.fromlineno = from_lineno(fromast)#node.nodes[0].fromlineno
73        node.tolineno = node.nodes[-1].tolineno
74        return node
75    if toast is None:
76        node.fromlineno, node.tolineno = fromto_lineno(fromast)
77    else:
78        node.fromlineno, node.tolineno = from_lineno(fromast), to_lineno(toast)
79    #print 'fixed', id(node)
80    return node
81
82BaseTransformer = transformer.Transformer
83
84COORD_MAP = {
85    # if: test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
86    'if': (0, 0),
87    # 'while' test ':' suite ['else' ':' suite]
88    'while': (0, 1),
89    # 'for' exprlist 'in' exprlist ':' suite ['else' ':' suite]
90    'for': (0, 3),
91    # 'try' ':' suite (except_clause ':' suite)+ ['else' ':' suite]
92    'try': (0, 0),
93    # | 'try' ':' suite 'finally' ':' suite
94   
95    }
96
97def fixlineno_wrap(function, stype):
98    def fixlineno_wrapper(self, nodelist):
99        node = function(self, nodelist)           
100        idx1, idx2 = COORD_MAP.get(stype, (0, -1))
101        return fix_lineno(node, nodelist[idx1], nodelist[idx2])
102    return fixlineno_wrapper
103nodes.Module.fromlineno = 0
104nodes.Module.tolineno = 0
105class ASTNGTransformer(BaseTransformer):
106    """ovverides transformer for a better source line number handling"""
107    def com_NEWLINE(self, *args):
108        # A ';' at the end of a line can make a NEWLINE token appear
109        # here, Render it harmless. (genc discards ('discard',
110        # ('const', xxxx)) Nodes)
111        lineno = args[0][1]
112        # don't put fromlineno/tolineno on Const None to mark it as dynamically
113        # added, without "physical" reference in the source
114        n = nodes.Discard(nodes.Const(None))
115        n.fromlineno = n.tolineno = lineno
116        return n   
117    def com_node(self, node):
118        res = self._dispatch[node[0]](node[1:])
119        return fix_lineno(res, node)
120    def com_assign(self, node, assigning):
121        res = BaseTransformer.com_assign(self, node, assigning)
122        return fix_lineno(res, node)
123    def com_apply_trailer(self, primaryNode, nodelist):
124        node = BaseTransformer.com_apply_trailer(self, primaryNode, nodelist)
125        return fix_lineno(node, nodelist)
126   
127##     def atom(self, nodelist):
128##         node = BaseTransformer.atom(self, nodelist)
129##         return fix_lineno(node, nodelist[0], nodelist[-1])
130   
131    def funcdef(self, nodelist):
132        node = BaseTransformer.funcdef(self, nodelist)
133        # XXX decorators
134        return fix_lineno(node, nodelist[-5], nodelist[-3])
135    def classdef(self, nodelist):
136        node = BaseTransformer.classdef(self, nodelist)
137        return fix_lineno(node, nodelist[0], nodelist[-2])
138           
139# wrap *_stmt methods
140for name in dir(BaseTransformer):
141    if name.endswith('_stmt') and not (name in ('com_stmt',
142                                                'com_append_stmt')
143                                       or name in ASTNGTransformer.__dict__):
144        setattr(BaseTransformer, name,
145                fixlineno_wrap(getattr(BaseTransformer, name), name[:-5]))
146           
147transformer.Transformer = ASTNGTransformer
148
149# ast NG builder ##############################################################
150
151class ASTNGBuilder:
152    """provide astng building methods
153    """
154   
155    def __init__(self, manager=None):
156        if manager is None:
157            from logilab.astng import MANAGER as manager
158        self._manager = manager
159        self._module = None
160        self._file = None
161        self._done = None
162        self._stack, self._par_stack = None, None
163        self._metaclass = None       
164        self._walker = ASTWalker(self)
165        self._dyn_modname_map = {'gtk': 'gtk._gtk'}
166        self._delayed = []
167       
168    def module_build(self, module, modname=None):
169        """build an astng from a living module instance
170        """
171        node = None
172        self._module = module
173        path = getattr(module, '__file__', None)
174        if path is not None:
175            path_, ext = splitext(module.__file__)
176            if ext in ('.py', '.pyc', '.pyo') and exists(path_ + '.py'):
177                node = self.file_build(path_ + '.py', modname)
178        if node is None:
179            # this is a built-in module
180            # get a partial representation by introspection
181            node = self.inspect_build(module, modname=modname, path=path)
182        return node
183
184    def inspect_build(self, module, modname=None, path=None):
185        """build astng from a living module (i.e. using inspect)
186        this is used when there is no python source code available (either
187        because it's a built-in module or because the .py is not available)
188        """
189        self._module = module
190        node = build_module(modname or module.__name__, module.__doc__)
191        node.file = node.path = path and abspath(path) or path
192        if self._manager is not None:
193            self._manager._cache[node.file] = self._manager._cache[node.name] = node
194        node.package = hasattr(module, '__path__')
195        attach___dict__(node)
196        self._done = {}
197        self.object_build(node, module)
198        return node
199   
200    def file_build(self, path, modname=None):
201        """build astng from a source code file (i.e. from an ast)
202
203        path is expected to be a python source file
204        """
205        try:
206            data = norm_read(path)
207        except IOError, ex:
208            msg = 'Unable to load file %r (%s)' % (path, ex)
209            raise ASTNGBuildingException(msg)
210        self._file = path
211        # get module name if necessary, *before modifying sys.path*
212        if modname is None:
213            try:
214                modname = '.'.join(modpath_from_file(path))
215            except ImportError:
216                modname = splitext(basename(path))[0]
217        # build astng representation
218        try:
219            sys.path.insert(0, dirname(path))
220            node = self.string_build(data, modname, path)
221            node.file = abspath(path)
222        finally:
223            self._file = None
224            sys.path.pop(0)
225       
226        return node
227   
228    def string_build(self, data, modname='', path=None):
229        """build astng from a source code stream (i.e. from an ast)"""
230        return self.ast_build(parse(data + '\n'), modname, path)
231       
232    def ast_build(self, node, modname=None, path=None):
233        """recurse on the ast (soon ng) to add some arguments et method
234        """
235        if path is not None:
236            node.file = node.path = abspath(path)
237        else:
238            node.file = node.path = '<?>'
239        if modname.endswith('.__init__'):
240            modname = modname[:-9]
241            node.package = True
242        else:
243            node.package = path and path.find('__init__.py') > -1 or False
244        node.name = modname 
245        node.pure_python = True
246        if self._manager is not None:
247            self._manager._cache[node.file] = node
248            if self._file:
249                self._manager._cache[abspath(self._file)] = node
250        self._walker.walk(node)
251        while self._delayed:
252            dnode = self._delayed.pop(0)
253            getattr(self, 'delayed_visit_%s' % dnode.__class__.__name__.lower())(dnode)
254        return node
255
256    # callbacks to build from an existing compiler.ast tree ###################
257
258    def visit_module(self, node):
259        """visit a stmt.Module node -> init node and push the corresponding
260        object or None on the top of the stack
261        """
262        self._stack = [self._module]
263        self._par_stack = [node]
264        self._metaclass = ['']
265        self._global_names = []
266        node.parent = None
267        node.globals = node.locals = {}
268        for name, value in ( ('__name__', node.name),
269                             ('__file__', node.path),
270                             ('__doc__', node.doc) ):
271            const = nodes.Const(value)
272            const.parent = node
273            node.locals[name] = [const]
274        attach___dict__(node)
275        if node.package:
276            # FIXME: List(Const())
277            const = nodes.Const(dirname(node.path))
278            const.parent = node
279            node.locals['__path__'] = [const]
280           
281
282    def leave_module(self, _):
283        """leave a stmt.Module node -> pop the last item on the stack and check
284        the stack is empty
285        """
286        self._stack.pop()
287        assert not self._stack, 'Stack is not empty : %s' % self._stack
288        self._par_stack.pop()
289        assert not self._par_stack, \
290               'Parent stack is not empty : %s' % self._par_stack
291       
292    def visit_class(self, node):
293        """visit a stmt.Class node -> init node and push the corresponding
294        object or None on the top of the stack
295        """
296        self.visit_default(node)
297        node.instance_attrs = {}
298        node.basenames = [b_node.as_string() for b_node in node.bases]
299        self._push(node)
300        for name, value in ( ('__name__', node.name),
301                             ('__module__', node.root().name),
302                             ('__doc__', node.doc) ):
303            const = nodes.Const(value)
304            const.parent = node
305            node.locals[name] = [const]
306        attach___dict__(node)
307        self._metaclass.append(self._metaclass[-1])
308       
309    def leave_class(self, node):
310        """leave a stmt.Class node -> pop the last item on the stack
311        """
312        self.leave_default(node)
313        self._stack.pop()
314        metaclass = self._metaclass.pop()
315        if not node.bases:
316            # no base classes, detect new / style old style according to
317            # current scope
318            node._newstyle = metaclass == 'type'
319       
320    def visit_function(self, node):
321        """visit a stmt.Function node -> init node and push the corresponding
322        object or None on the top of the stack
323        """
324        self.visit_default(node)
325        self._global_names.append({})
326        node.argnames = list(node.argnames)
327        if isinstance(node.parent.frame(), nodes.Class):
328            node.type = 'method'
329            if node.name == '__new__':
330                node.type = 'classmethod'
331        self._push(node)
332        register_arguments(node, node.argnames)
333       
334    def leave_function(self, node):
335        """leave a stmt.Function node -> pop the last item on the stack
336        """
337        self.leave_default(node)
338        self._stack.pop()
339        self._global_names.pop()
340       
341    def visit_lambda(self, node):
342        """visit a stmt.Lambda node -> init node locals
343        """
344        self.visit_default(node)
345        node.argnames = list(node.argnames)
346        node.locals = {}
347        register_arguments(node, node.argnames)
348       
349    def visit_genexpr(self, node):
350        """visit a stmt.GenExpr node -> init node locals
351        """
352        self.visit_default(node)
353        node.locals = {}
354       
355    def visit_global(self, node):
356        """visit a stmt.Global node -> add declared names to locals
357        """
358        self.visit_default(node)
359        if not self._global_names: # global at the module level, no effect
360            return
361        for name in node.names:
362            self._global_names[-1].setdefault(name, []).append(node)
363#             node.parent.set_local(name, node)
364#         module = node.root()
365#         if module is not node.frame():
366#             for name in node.names:
367#                 module.set_local(name, node)
368           
369    def visit_import(self, node):
370        """visit a stmt.Import node -> add imported names to locals
371        """
372        self.visit_default(node)
373        for (name, asname) in node.names:
374            name = asname or name
375            node.parent.set_local(name.split('.')[0], node)
376           
377    def visit_from(self, node):
378        """visit a stmt.From node -> add imported names to locals
379        """
380        self.visit_default(node)
381        # add names imported by the import to locals
382        for (name, asname) in node.names:
383            if name == '*':
384                try:
385                    imported = node.root().import_module(node.modname)
386                except ASTNGBuildingException:
387                    #import traceback
388                    #traceback.print_exc()
389                    continue
390                    # FIXME: log error
391                    #print >> sys.stderr, \
392                    #      'Unable to get imported names for %r line %s"' % (
393                    #    node.modname, node.lineno)
394                for name in imported.wildcard_import_names():
395                    node.parent.set_local(name, node)
396            else:
397