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