1# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
3#
4# This file is part of astroid.
5#
6# astroid is free software: you can redistribute it and/or modify it
7# under the terms of the GNU Lesser General Public License as published by the
8# Free Software Foundation, either version 2.1 of the License, or (at your
9# option) any later version.
10#
11# astroid is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14# for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License along
17# with astroid. If not, see <http://www.gnu.org/licenses/>.
18"""This module renders Astroid nodes as string:
19
20* :func:`to_code` function return equivalent (hopefuly valid) python string
21
22* :func:`dump` function return an internal representation of nodes found
23  in the tree, useful for debugging or understanding the tree structure
24"""
25
26import sys
27
28INDENT = '    ' # 4 spaces ; keep indentation variable
29
30
31def dump(node, ids=False):
32    """print a nice astroid tree representation.
33
34    :param ids: if true, we also print the ids (usefull for debugging)
35    """
36    result = []
37    _repr_tree(node, result, ids=ids)
38    return "\n".join(result)
39
40def _repr_tree(node, result, indent='', _done=None, ids=False):
41    """built a tree representation of a node as a list of lines"""
42    if _done is None:
43        _done = set()
44    if not hasattr(node, '_astroid_fields'): # not a astroid node
45        return
46    if node in _done:
47        result.append(indent + 'loop in tree: %s' % node)
48        return
49    _done.add(node)
50    node_str = str(node)
51    if ids:
52        node_str += '  . \t%x' % id(node)
53    result.append(indent + node_str)
54    indent += INDENT
55    for field in node._astroid_fields:
56        value = getattr(node, field)
57        if isinstance(value, (list, tuple)):
58            result.append(indent + field + " = [")
59            for child in value:
60                if isinstance(child, (list, tuple)):
61                    # special case for Dict # FIXME
62                    _repr_tree(child[0], result, indent, _done, ids)
63                    _repr_tree(child[1], result, indent, _done, ids)
64                    result.append(indent + ',')
65                else:
66                    _repr_tree(child, result, indent, _done, ids)
67            result.append(indent + "]")
68        else:
69            result.append(indent + field + " = ")
70            _repr_tree(value, result, indent, _done, ids)
71
72
73class AsStringVisitor(object):
74    """Visitor to render an Astroid node as a valid python code string"""
75
76    def __call__(self, node):
77        """Makes this visitor behave as a simple function"""
78        return node.accept(self)
79
80    def _stmt_list(self, stmts):
81        """return a list of nodes to string"""
82        stmts = '\n'.join([nstr for nstr in [n.accept(self) for n in stmts] if nstr])
83        return INDENT + stmts.replace('\n', '\n'+INDENT)
84
85
86    ## visit_<node> methods ###########################################
87
88    def visit_arguments(self, node):
89        """return an astroid.Function node as string"""
90        return node.format_args()
91
92    def visit_assattr(self, node):
93        """return an astroid.AssAttr node as string"""
94        return self.visit_getattr(node)
95
96    def visit_assert(self, node):
97        """return an astroid.Assert node as string"""
98        if node.fail:
99            return 'assert %s, %s' % (node.test.accept(self),
100                                      node.fail.accept(self))
101        return 'assert %s' % node.test.accept(self)
102
103    def visit_assname(self, node):
104        """return an astroid.AssName node as string"""
105        return node.name
106
107    def visit_assign(self, node):
108        """return an astroid.Assign node as string"""
109        lhs = ' = '.join([n.accept(self) for n in node.targets])
110        return '%s = %s' % (lhs, node.value.accept(self))
111
112    def visit_augassign(self, node):
113        """return an astroid.AugAssign node as string"""
114        return '%s %s %s' % (node.target.accept(self), node.op, node.value.accept(self))
115
116    def visit_backquote(self, node):
117        """return an astroid.Backquote node as string"""
118        return '`%s`' % node.value.accept(self)
119
120    def visit_binop(self, node):
121        """return an astroid.BinOp node as string"""
122        return '(%s) %s (%s)' % (node.left.accept(self), node.op, node.right.accept(self))
123
124    def visit_boolop(self, node):
125        """return an astroid.BoolOp node as string"""
126        return (' %s ' % node.op).join(['(%s)' % n.accept(self)
127                                        for n in node.values])
128
129    def visit_break(self, node):
130        """return an astroid.Break node as string"""
131        return 'break'
132
133    def visit_callfunc(self, node):
134        """return an astroid.CallFunc node as string"""
135        expr_str = node.func.accept(self)
136        args = [arg.accept(self) for arg in node.args]
137        if node.starargs:
138            args.append('*' + node.starargs.accept(self))
139        if node.kwargs:
140            args.append('**' + node.kwargs.accept(self))
141        return '%s(%s)' % (expr_str, ', '.join(args))
142
143    def visit_class(self, node):
144        """return an astroid.Class node as string"""
145        decorate = node.decorators and node.decorators.accept(self)  or ''
146        bases = ', '.join([n.accept(self) for n in node.bases])
147        if sys.version_info[0] == 2:
148            bases = bases and '(%s)' % bases or ''
149        else:
150            metaclass = node.metaclass()
151            if metaclass and not node.has_metaclass_hack():
152                if bases:
153                    bases = '(%s, metaclass=%s)' % (bases, metaclass.name)
154                else:
155                    bases = '(metaclass=%s)' % metaclass.name
156            else:
157                bases = bases and '(%s)' % bases or ''
158        docs = node.doc and '\n%s"""%s"""' % (INDENT, node.doc) or ''
159        return '\n\n%sclass %s%s:%s\n%s\n' % (decorate, node.name, bases, docs,
160                                              self._stmt_list(node.body))
161
162    def visit_compare(self, node):
163        """return an astroid.Compare node as string"""
164        rhs_str = ' '.join(['%s %s' % (op, expr.accept(self))
165                            for op, expr in node.ops])
166        return '%s %s' % (node.left.accept(self), rhs_str)
167
168    def visit_comprehension(self, node):
169        """return an astroid.Comprehension node as string"""
170        ifs = ''.join([' if %s' % n.accept(self) for n in node.ifs])
171        return 'for %s in %s%s' % (node.target.accept(self),
172                                   node.iter.accept(self), ifs)
173
174    def visit_const(self, node):
175        """return an astroid.Const node as string"""
176        return repr(node.value)
177
178    def visit_continue(self, node):
179        """return an astroid.Continue node as string"""
180        return 'continue'
181
182    def visit_delete(self, node): # XXX check if correct
183        """return an astroid.Delete node as string"""
184        return 'del %s' % ', '.join([child.accept(self)
185                                     for child in node.targets])
186
187    def visit_delattr(self, node):
188        """return an astroid.DelAttr node as string"""
189        return self.visit_getattr(node)
190
191    def visit_delname(self, node):
192        """return an astroid.DelName node as string"""
193        return node.name
194
195    def visit_decorators(self, node):
196        """return an astroid.Decorators node as string"""
197        return '@%s\n' % '\n@'.join([item.accept(self) for item in node.nodes])
198
199    def visit_dict(self, node):
200        """return an astroid.Dict node as string"""
201        return '{%s}' % ', '.join(['%s: %s' % (key.accept(self),
202                                               value.accept(self))
203                                   for key, value in node.items])
204
205    def visit_dictcomp(self, node):
206        """return an astroid.DictComp node as string"""
207        return '{%s: %s %s}' % (node.key.accept(self), node.value.accept(self),
208                                ' '.join([n.accept(self) for n in node.generators]))
209
210    def visit_discard(self, node):
211        """return an astroid.Discard node as string"""
212        return node.value.accept(self)
213
214    def visit_emptynode(self, node):
215        """dummy method for visiting an Empty node"""
216        return ''
217
218    def visit_excepthandler(self, node):
219        if node.type:
220            if node.name:
221                excs = 'except %s, %s' % (node.type.accept(self),
222                                          node.name.accept(self))
223            else:
224                excs = 'except %s' % node.type.accept(self)
225        else:
226            excs = 'except'
227        return '%s:\n%s' % (excs, self._stmt_list(node.body))
228
229    def visit_ellipsis(self, node):
230        """return an astroid.Ellipsis node as string"""
231        return '...'
232
233    def visit_empty(self, node):
234        """return an Empty node as string"""
235        return ''
236
237    def visit_exec(self, node):
238        """return an astroid.Exec node as string"""
239        if node.locals:
240            return 'exec %s in %s, %s' % (node.expr.accept(self),
241                                          node.locals.accept(self),
242                                          node.globals.accept(self))
243        if node.globals:
244            return 'exec %s in %s' % (node.expr.accept(self),
245                                      node.globals.accept(self))
246        return 'exec %s' % node.expr.accept(self)
247
248    def visit_extslice(self, node):
249        """return an astroid.ExtSlice node as string"""
250        return ','.join([dim.accept(self) for dim in node.dims])
251
252    def visit_for(self, node):
253        """return an astroid.For node as string"""
254        fors = 'for %s in %s:\n%s' % (node.target.accept(self),
255                                      node.iter.accept(self),
256                                      self._stmt_list(node.body))
257        if node.orelse:
258            fors = '%s\nelse:\n%s' % (fors, self._stmt_list(node.orelse))
259        return fors
260
261    def visit_from(self, node):
262        """return an astroid.From node as string"""
263        return 'from %s import %s' % ('.' * (node.level or 0) + node.modname,
264                                      _import_string(node.names))
265
266    def visit_function(self, node):
267        """return an astroid.Function node as string"""
268        decorate = node.decorators and node.decorators.accept(self)  or ''
269        docs = node.doc and '\n%s"""%s"""' % (INDENT, node.doc) or ''
270        return '\n%sdef %s(%s):%s\n%s' % (decorate, node.name, node.args.accept(self),
271                                          docs, self._stmt_list(node.body))
272
273    def visit_genexpr(self, node):
274        """return an astroid.GenExpr node as string"""
275        return '(%s %s)' % (node.elt.accept(self),
276                            ' '.join([n.accept(self) for n in node.generators]))
277
278    def visit_getattr(self, node):
279        """return an astroid.Getattr node as string"""
280        return '%s.%s' % (node.expr.accept(self), node.attrname)
281
282    def visit_global(self, node):
283        """return an astroid.Global node as string"""
284        return 'global %s' % ', '.join(node.names)
285
286    def visit_if(self, node):
287        """return an astroid.If node as string"""
288        ifs = ['if %s:\n%s' % (node.test.accept(self), self._stmt_list(node.body))]
289        if node.orelse:# XXX use elif ???
290            ifs.append('else:\n%s' % self._stmt_list(node.orelse))
291        return '\n'.join(ifs)
292
293    def visit_ifexp(self, node):
294        """return an astroid.IfExp node as string"""
295        return '%s if %s else %s' % (node.body.accept(self),
296                                     node.test.accept(self),
297                                     node.orelse.accept(self))
298
299    def visit_import(self, node):
300        """return an astroid.Import node as string"""
301        return 'import %s' % _import_string(node.names)
302
303    def visit_keyword(self, node):
304        """return an astroid.Keyword node as string"""
305        return '%s=%s' % (node.arg, node.value.accept(self))
306
307    def visit_lambda(self, node):
308        """return an astroid.Lambda node as string"""
309        return 'lambda %s: %s' % (node.args.accept(self),
310                                  node.body.accept(self))
311
312    def visit_list(self, node):
313        """return an astroid.List node as string"""
314        return '[%s]' % ', '.join([child.accept(self) for child in node.elts])
315
316    def visit_listcomp(self, node):
317        """return an astroid.ListComp node as string"""
318        return '[%s %s]' % (node.elt.accept(self),
319                            ' '.join([n.accept(self) for n in node.generators]))
320
321    def visit_module(self, node):
322        """return an astroid.Module node as string"""
323        docs = node.doc and '"""%s"""\n\n' % node.doc or ''
324        return docs + '\n'.join([n.accept(self) for n in node.body]) + '\n\n'
325
326    def visit_name(self, node):
327        """return an astroid.Name node as string"""
328        return node.name
329
330    def visit_pass(self, node):
331        """return an astroid.Pass node as string"""
332        return 'pass'
333
334    def visit_print(self, node):
335        """return an astroid.Print node as string"""
336        nodes = ', '.join([n.accept(self) for n in node.values])
337        if not node.nl:
338            nodes = '%s,' % nodes
339        if node.dest:
340            return 'print >> %s, %s' % (node.dest.accept(self), nodes)
341        return 'print %s' % nodes
342
343    def visit_raise(self, node):
344        """return an astroid.Raise node as string"""
345        if node.exc:
346            if node.inst:
347                if node.tback:
348                    return 'raise %s, %s, %s' % (node.exc.accept(self),
349                                                 node.inst.accept(self),
350                                                 node.tback.accept(self))
351                return 'raise %s, %s' % (node.exc.accept(self),
352                                         node.inst.accept(self))
353            return 'raise %s' % node.exc.accept(self)
354        return 'raise'
355
356    def visit_return(self, node):
357        """return an astroid.Return node as string"""
358        if node.value:
359            return 'return %s' % node.value.accept(self)
360        else:
361            return 'return'
362
363    def visit_index(self, node):
364        """return a astroid.Index node as string"""
365        return node.value.accept(self)
366
367    def visit_set(self, node):
368        """return an astroid.Set node as string"""
369        return '{%s}' % ', '.join([child.accept(self) for child in node.elts])
370
371    def visit_setcomp(self, node):
372        """return an astroid.SetComp node as string"""
373        return '{%s %s}' % (node.elt.accept(self),
374                            ' '.join([n.accept(self) for n in node.generators]))
375
376    def visit_slice(self, node):
377        """return a astroid.Slice node as string"""
378        lower = node.lower and node.lower.accept(self) or ''
379        upper = node.upper and node.upper.accept(self) or ''
380        step = node.step and node.step.accept(self) or ''
381        if step:
382            return '%s:%s:%s' % (lower, upper, step)
383        return  '%s:%s' % (lower, upper)
384
385    def visit_subscript(self, node):
386        """return an astroid.Subscript node as string"""
387        return '%s[%s]' % (node.value.accept(self), node.slice.accept(self))
388
389    def visit_tryexcept(self, node):
390        """return an astroid.TryExcept node as string"""
391        trys = ['try:\n%s' % self._stmt_list(node.body)]
392        for handler in node.handlers:
393            trys.append(handler.accept(self))
394        if node.orelse:
395            trys.append('else:\n%s' % self._stmt_list(node.orelse))
396        return '\n'.join(trys)
397
398    def visit_tryfinally(self, node):
399        """return an astroid.TryFinally node as string"""
400        return 'try:\n%s\nfinally:\n%s' % (self._stmt_list(node.body),
401                                           self._stmt_list(node.finalbody))
402
403    def visit_tuple(self, node):
404        """return an astroid.Tuple node as string"""
405        if len(node.elts) == 1:
406            return '(%s, )' % node.elts[0].accept(self)
407        return '(%s)' % ', '.join([child.accept(self) for child in node.elts])
408
409    def visit_unaryop(self, node):
410        """return an astroid.UnaryOp node as string"""
411        if node.op == 'not':
412            operator = 'not '
413        else:
414            operator = node.op
415        return '%s%s' % (operator, node.operand.accept(self))
416
417    def visit_while(self, node):
418        """return an astroid.While node as string"""
419        whiles = 'while %s:\n%s' % (node.test.accept(self),
420                                    self._stmt_list(node.body))
421        if node.orelse:
422            whiles = '%s\nelse:\n%s' % (whiles, self._stmt_list(node.orelse))
423        return whiles
424
425    def visit_with(self, node): # 'with' without 'as' is possible
426        """return an astroid.With node as string"""
427        items = ', '.join(('(%s)' % expr.accept(self)) +
428                          (vars and ' as (%s)' % (vars.accept(self)) or '')
429                          for expr, vars in node.items)
430        return 'with %s:\n%s' % (items, self._stmt_list(node.body))
431
432    def visit_yield(self, node):
433        """yield an ast.Yield node as string"""
434        yi_val = node.value and (" " + node.value.accept(self)) or ""
435        expr = 'yield' + yi_val
436        if node.parent.is_statement:
437            return expr
438        else:
439            return "(%s)" % (expr,)
440
441
442class AsStringVisitor3k(AsStringVisitor):
443    """AsStringVisitor3k overwrites some AsStringVisitor methods"""
444
445    def visit_excepthandler(self, node):
446        if node.type:
447            if node.name:
448                excs = 'except %s as %s' % (node.type.accept(self),
449                                            node.name.accept(self))
450            else:
451                excs = 'except %s' % node.type.accept(self)
452        else:
453            excs = 'except'
454        return '%s:\n%s' % (excs, self._stmt_list(node.body))
455
456    def visit_nonlocal(self, node):
457        """return an astroid.Nonlocal node as string"""
458        return 'nonlocal %s' % ', '.join(node.names)
459
460    def visit_raise(self, node):
461        """return an astroid.Raise node as string"""
462        if node.exc:
463            if node.cause:
464                return 'raise %s from %s' % (node.exc.accept(self),
465                                             node.cause.accept(self))
466            return 'raise %s' % node.exc.accept(self)
467        return 'raise'
468
469    def visit_starred(self, node):
470        """return Starred node as string"""
471        return "*" + node.value.accept(self)
472
473    def visit_yieldfrom(self, node):
474        """ Return an astroid.YieldFrom node as string. """
475        yi_val = node.value and (" " + node.value.accept(self)) or ""
476        expr = 'yield from' + yi_val
477        if node.parent.is_statement:
478            return expr
479        else:
480            return "(%s)" % (expr,)
481
482
483def _import_string(names):
484    """return a list of (name, asname) formatted as a string"""
485    _names = []
486    for name, asname in names:
487        if asname is not None:
488            _names.append('%s as %s' % (name, asname))
489        else:
490            _names.append(name)
491    return  ', '.join(_names)
492
493
494if sys.version_info >= (3, 0):
495    AsStringVisitor = AsStringVisitor3k
496
497# this visitor is stateless, thus it can be reused
498to_code = AsStringVisitor()
499
500