1 #!/usr/bin/env python 2 3 """ 4 View annotated sources. 5 6 Copyright (C) 2006 Paul Boddie <paul@boddie.org.uk> 7 8 This software is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License as 10 published by the Free Software Foundation; either version 2 of 11 the License, or (at your option) any later version. 12 13 This software is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public 19 License along with this library; see the file LICENCE.txt 20 If not, write to the Free Software Foundation, Inc., 21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 22 """ 23 24 from compiler.visitor import ASTVisitor 25 from simplified import * 26 import sys 27 import textwrap 28 29 # Exceptions. 30 31 class ViewerError(SimplifiedError): 32 33 "An error in viewing." 34 35 pass 36 37 # Classes. 38 39 class Viewer(ASTVisitor): 40 41 """ 42 A viewing visitor for AST nodes. 43 """ 44 45 def __init__(self, stream): 46 ASTVisitor.__init__(self) 47 self.cached_files = {} 48 self.printed_lines = {} 49 self.visitor = self 50 self.stream = stream 51 52 def process(self, module): 53 self.dispatch(module) 54 55 def dispatch(self, node): 56 self.dispatch_only(node) 57 ASTVisitor.dispatch(self, node) 58 59 def dispatch_only(self, node, every_time=0): 60 self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None), every_time) 61 62 def print_line(self, filename, lineno, every_time): 63 last_printed = self.printed_lines.get(filename, 0) 64 if lineno > last_printed or every_time: 65 self.stream.write(self.get_line(filename, lineno)) 66 self.printed_lines[filename] = lineno 67 68 def get_line(self, filename, lineno): 69 if filename is None or lineno is None: 70 return "" 71 72 if self.cached_files.has_key(filename): 73 lines = self.cached_files[filename] 74 else: 75 f = open(filename) 76 try: 77 self.cached_files[filename] = lines = f.readlines() 78 finally: 79 f.close() 80 81 try: 82 return lines[lineno - 1] 83 except IndexError: 84 return "" 85 86 def report(self, exc): 87 self.stream.write("Exception was:\n\n" + str(exc.exc) + "\n\n") 88 self.stream.write("Nodes:\n\n") 89 for node in exc.nodes: 90 self.stream.write(repr(node) + "\n") 91 self.dispatch_only(node.original, every_time=1) 92 self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n") 93 self.stream.write("\nSimplified node was:\n\n") 94 exc.nodes[0].pprint(stream=self.stream) 95 96 # HTML-related output production. 97 98 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 99 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 100 <html xmlns="http://www.w3.org/1999/xhtml"> 101 <head> 102 <title>Module</title> 103 <style type="text/css"> 104 body { 105 padding-top: 4em; padding-bottom: 4em; 106 font-size: 14pt; font-family: monospace; 107 background-color: black; color: white; 108 } 109 110 .class { margin-bottom: 1em; } 111 .function { margin-bottom: 1em; } 112 .body { padding-left: 2em; } 113 .keyword { color: yellow; } 114 .comment { color: blue; } 115 .str { color: #FF00FF; } 116 .doc { color: #FF00FF; margin-bottom: 1em; } 117 .ref { color: cyan; } 118 .ref a { color: cyan; text-decoration: none; } 119 120 .popup { 121 display: none; z-index: 2; 122 position: absolute; top: 1em; left: 0.5em; 123 padding: 0.2em; background-color: #000000; 124 } 125 126 .invocations { 127 padding: 0.5em; background-color: #770000; 128 clear: all; 129 } 130 131 .types { 132 padding: 0.5em; background-color: #0000FF; 133 float: right; 134 } 135 136 .scopes { 137 padding: 0.5em; background-color: #007700; 138 float: left; 139 } 140 141 .op, 142 .name, 143 .attr 144 { 145 position: relative; 146 } 147 148 .op:hover > .popup, 149 .name:hover > .popup, 150 .attr:hover > .popup 151 { 152 display: block; 153 } 154 155 </style> 156 </head> 157 <body> 158 """ 159 160 html_footer = """</body> 161 </html> 162 """ 163 164 # Browser classes. 165 166 class Browser(ASTVisitor): 167 168 """ 169 A browsing visitor for AST nodes. 170 171 Covered: AssAttr, AssList, AssName, AssTuple, Assign, AugAssign, Break, 172 CallFunc, Class, Compare, Const, Continue, Dict, Discard, For, 173 Function, Getattr, If, Keyword, Lambda, List, Module, Name, Pass, Raise, Return, Slice, 174 Stmt, Subscript, Tuple, While. 175 176 Missing: And, Add, Assert, Backquote, Bitand, Bitor, Bitxor, Decorators, Div, 177 Ellipsis, Exec, FloorDiv, From, Global, Import, Invert, LeftShift, ListComp, ListCompFor, 178 ListCompIf, Mod, Mul, Not, Or, Power, Print, Printnl, RightShift, Sliceobj, 179 Sub, TryExcept, TryFinally, UnaryAdd, UnarySub, Yield. 180 """ 181 182 def __init__(self, stream): 183 ASTVisitor.__init__(self) 184 self.visitor = self 185 self.stream = stream 186 187 def process(self, module): 188 self.stream.write(html_header) 189 self.dispatch(module) 190 self.stream.write(html_footer) 191 192 def dispatch(self, node): 193 try: 194 ASTVisitor.dispatch(self, node) 195 except ViewerError, exc: 196 exc.add(node) 197 raise 198 except Exception, exc: 199 raise ViewerError(exc, node) 200 201 def visitModule(self, node): 202 self.default(node) 203 204 # Statements. 205 206 def visitAssign(self, node): 207 self.stream.write("<div class='assign'>\n") 208 for lvalue in node.nodes: 209 self.dispatch(lvalue) 210 self.stream.write("=\n") 211 self.dispatch(node.expr) 212 self.stream.write("</div>\n") 213 214 def visitAugAssign(self, node): 215 self.stream.write("<div class='augassign'>\n") 216 self.dispatch(node.node) 217 self.stream.write("%s\n" % node.op) 218 self.dispatch(node.expr) 219 self.stream.write("</div>\n") 220 221 def visitBreak(self, node): 222 self.stream.write("<div class='break'>\n") 223 self._keyword("break") 224 self.stream.write("</div>\n") 225 226 def visitClass(self, node): 227 definition = node._node 228 structure = definition.expr.ref 229 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 230 self.stream.write("<div>\n") 231 self._keyword("class") 232 self._name_start(structure.name) 233 self._popup_start() 234 self._scopes(definition) 235 self._popup_end() 236 self._name_end() 237 bases = structure.bases 238 if bases: 239 self.stream.write("(") 240 first = 1 241 for base in bases: 242 if not first: 243 self.stream.write(",\n") 244 self._name_start(base.name) 245 self._popup_start() 246 self._types(base) 247 self._scopes(base) 248 self._popup_end() 249 self._name_end() 250 first = 0 251 self.stream.write(")") 252 self.stream.write(":\n") 253 self._comment(self._text(structure.full_name())) 254 self.stream.write("</div>\n") 255 256 self.stream.write("<div class='body'>\n") 257 self._doc(node) 258 self.dispatch(node.code) 259 self.stream.write("</div>\n") 260 self.stream.write("</div>\n") 261 262 def visitContinue(self, node): 263 self.stream.write("<div class='continue'>\n") 264 self._keyword("continue") 265 self.stream.write("</div>\n") 266 267 def visitFor(self, node): 268 self.stream.write("<div class='if'>\n") 269 self.stream.write("<div>\n") 270 self._keyword("for") 271 self.dispatch(node.assign) 272 self._keyword("in") 273 self.dispatch(node.list) 274 self.stream.write(":\n") 275 self.stream.write("</div>\n") 276 self.stream.write("<div class='body'>\n") 277 self.dispatch(node.body) 278 self.stream.write("</div>\n") 279 if node.else_ is not None: 280 self.stream.write("<div>\n") 281 self._keyword("else") 282 self.stream.write(":\n") 283 self.stream.write("</div>\n") 284 self.stream.write("<div class='body'>\n") 285 self.dispatch(node.else_) 286 self.stream.write("</div>\n") 287 self.stream.write("</div>\n") 288 289 def visitFunction(self, node): 290 definition = node._node 291 subprogram = definition.expr.ref 292 self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name())) 293 self.stream.write("<div>\n") 294 self._keyword("def") 295 self._name_start(subprogram.name) 296 self._popup_start() 297 self._scopes(definition) 298 self._popup_end() 299 self._name_end() 300 self.stream.write("(") 301 self._parameters(subprogram) 302 self.stream.write(")") 303 self.stream.write(":\n") 304 self._comment(self._text(subprogram.full_name())) 305 self.stream.write("</div>\n") 306 307 self.stream.write("<div class='body'>\n") 308 self._doc(node) 309 self.dispatch(node.code) 310 self.stream.write("</div>\n") 311 self.stream.write("</div>\n") 312 313 def visitIf(self, node): 314 self.stream.write("<div class='if'>\n") 315 first = 1 316 for compare, stmt in node.tests: 317 self.stream.write("<div>\n") 318 if first: 319 self._keyword("if") 320 else: 321 self._keyword("elif") 322 self.dispatch(compare) 323 self.stream.write(":\n") 324 self.stream.write("</div>\n") 325 self.stream.write("<div class='body'>\n") 326 self.dispatch(stmt) 327 self.stream.write("</div>\n") 328 first = 0 329 if node.else_ is not None: 330 self.stream.write("<div>\n") 331 self._keyword("else") 332 self.stream.write(":\n") 333 self.stream.write("</div>\n") 334 self.stream.write("<div class='body'>\n") 335 self.dispatch(node.else_) 336 self.stream.write("</div>\n") 337 self.stream.write("</div>\n") 338 339 def visitPass(self, node): 340 self.stream.write("<div class='pass'>\n") 341 self._keyword("pass") 342 self.stream.write("</div>\n") 343 344 def visitRaise(self, node): 345 self.stream.write("<div class='raise'>\n") 346 self._keyword("raise") 347 self.dispatch(node.expr1) 348 if node.expr2 is not None: 349 self.stream.write(",\n") 350 self.dispatch(node.expr2) 351 if node.expr3 is not None: 352 self.stream.write(",\n") 353 self.dispatch(node.expr3) 354 self.stream.write("</div>\n") 355 356 def visitReturn(self, node): 357 self.stream.write("<div class='return'>\n") 358 self._keyword("return") 359 self.dispatch(node.value) 360 self.stream.write("</div>\n") 361 362 def visitStmt(self, node): 363 self.stream.write("<div class='stmt'>\n") 364 self.default(node) 365 self.stream.write("</div>\n") 366 367 def visitWhile(self, node): 368 self.stream.write("<div class='while'>\n") 369 self.stream.write("<div>\n") 370 self._keyword("while") 371 self.dispatch(node.test) 372 self.stream.write(":\n") 373 self.stream.write("</div>\n") 374 self.stream.write("<div class='body'>\n") 375 self.dispatch(node.body) 376 self.stream.write("</div>\n") 377 if node.else_ is not None: 378 self.stream.write("<div>\n") 379 self._keyword("else") 380 self.stream.write(":\n") 381 self.stream.write("</div>\n") 382 self.stream.write("<div class='body'>\n") 383 self.dispatch(node.else_) 384 self.stream.write("</div>\n") 385 self.stream.write("</div>\n") 386 387 # Expressions. 388 389 def visitAssAttr(self, node): 390 self.stream.write("<span class='assattr'>\n") 391 self.dispatch(node.expr) 392 self.stream.write("<span class='attr'>\n") 393 self.stream.write(".%s\n" % self._text(node.attrname)) 394 if hasattr(node, "_node"): 395 self._popup_start() 396 self._types(node._node) 397 self._scopes(node._node) 398 self._popup_end() 399 else: 400 raise ValueError, node 401 self.stream.write("</span>\n") 402 self.stream.write("</span>\n") 403 404 def visitAssList(self, node): 405 self.stream.write("<span class='list'>\n") 406 self.stream.write("[") 407 self._sequence(node) 408 self.stream.write("]\n") 409 self.stream.write("</span>\n") 410 411 def visitAssName(self, node): 412 if hasattr(node, "_node"): 413 self._name_start(node._node.name) 414 self._popup_start() 415 self._types(node._node.expr) 416 self._scopes(node._node) 417 self._popup_end() 418 self._name_end() 419 else: 420 raise ValueError, node 421 self._name(node.name) 422 423 def visitAssTuple(self, node): 424 self.stream.write("<span class='tuple'>\n") 425 self.stream.write("(") 426 self._sequence(node) 427 self.stream.write(")\n") 428 self.stream.write("</span>\n") 429 430 def visitCallFunc(self, node): 431 self.stream.write("<span class='callfunc'>\n") 432 self.dispatch(node.node) 433 self.stream.write("(") 434 first = 1 435 for arg in node.args: 436 if not first: 437 self.stream.write(",\n") 438 self.dispatch(arg) 439 first = 0 440 if node.star_args is not None: 441 if not first: 442 self.stream.write(", *\n") 443 self.dispatch(node.star_args) 444 first = 0 445 if node.dstar_args is not None: 446 if not first: 447 self.stream.write(", **\n") 448 self.dispatch(node.dstar_args) 449 first = 0 450 self.stream.write(")\n") 451 self.stream.write("</span>\n") 452 453 def visitCompare(self, node): 454 self.stream.write("<span class='compare'>\n") 455 self.dispatch(node.expr) 456 for (op_name, expr), _op in map(None, node.ops, node._ops): 457 self.stream.write("<span class='op'>\n") 458 self.stream.write(op_name) 459 self._popup_start() 460 self._op(op_name, _op) 461 self._popup_end() 462 self.stream.write("</span>\n") 463 self.dispatch(expr) 464 self.stream.write("</span>\n") 465 466 def visitConst(self, node): 467 self.stream.write(repr(node.value)) 468 469 def visitGetattr(self, node): 470 self.stream.write("<span class='getattr'>\n") 471 self.dispatch(node.expr) 472 self.stream.write("<span class='attr'>\n") 473 self.stream.write(".%s\n" % self._text(node.attrname)) 474 if hasattr(node, "_node"): 475 self._popup_start() 476 self._types(node._node) 477 self._scopes(node._node) 478 self._popup_end() 479 else: 480 raise ValueError, node 481 self.stream.write("</span>\n") 482 self.stream.write("</span>\n") 483 484 def visitKeyword(self, node): 485 self.stream.write("<span class='keyword'>\n") 486 self.stream.write(node.name) 487 self.stream.write("=") 488 self.dispatch(node.expr) 489 self.stream.write("</span>\n") 490 491 def visitLambda(self, node): 492 definition = node._node 493 subprogram = definition.expr.ref 494 self.stream.write("<span class='lambda'>\n") 495 self._keyword("lambda") 496 self._parameters(subprogram) 497 self.dispatch(node.code) 498 self.stream.write("</span>\n") 499 500 visitList = visitAssList 501 502 def visitName(self, node): 503 if hasattr(node, "_node"): 504 self._name_start(node._node.name) 505 self._popup_start() 506 self._types(node._node) 507 self._scopes(node._node) 508 self._popup_end() 509 self._name_end() 510 else: 511 raise ValueError, node 512 self._name(node.name) 513 514 def visitSlice(self, node): 515 self.stream.write("<span class='slice'>\n") 516 self.dispatch(node.expr) 517 self.stream.write("[") 518 if node.lower: 519 self.dispatch(node.lower) 520 self.stream.write(":") 521 if node.upper: 522 self.dispatch(node.upper) 523 # NOTE: Step? 524 self.stream.write("]") 525 self.stream.write("</span>\n") 526 527 def visitSubscript(self, node): 528 self.stream.write("<span class='subscript'>\n") 529 self.dispatch(node.expr) 530 self.stream.write("[") 531 first = 1 532 for sub in node.subs: 533 if not first: 534 self.stream.write(", ") 535 self.dispatch(sub) 536 first = 0 537 self.stream.write("]") 538 self.stream.write("</span>\n") 539 540 visitTuple = visitAssTuple 541 542 # Output preparation methods. 543 544 def _text(self, text): 545 return text.replace("&", "&").replace("<", "<").replace(">", ">") 546 547 def _attr(self, attr): 548 return self._text(attr).replace("'", "'").replace('"', """) 549 550 def _url(self, url): 551 return self._attr(url).replace("#", "%23").replace("-", "%2d") 552 553 def _comment(self, comment): 554 self.stream.write("<span class='comment'># %s</span>\n" % comment) 555 556 def _keyword(self, kw): 557 self.stream.write("<span class='keyword'>%s</span> " % kw) 558 559 def _doc(self, node): 560 if node.doc is not None: 561 self.stream.write("<pre class='doc'>\n") 562 self.stream.write('"""') 563 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 564 self.stream.write(self._text(output)) 565 self.stream.write('"""') 566 self.stream.write("</pre>\n") 567 568 def _sequence(self, node): 569 first = 1 570 for n in node.nodes: 571 if not first: 572 self.stream.write(",\n") 573 self.dispatch(n) 574 first = 0 575 576 def _parameters(self, subprogram): 577 first = 1 578 for param, default in subprogram.params: 579 if not first: 580 self.stream.write(",\n") 581 self._parameter(subprogram, param, default) 582 first = 0 583 if subprogram.star is not None: 584 if not first: 585 self.stream.write(", *\n") 586 param, default = subprogram.star 587 self._parameter(subprogram, param, default) 588 first = 0 589 if subprogram.dstar is not None: 590 if not first: 591 self.stream.write(", **\n") 592 param, default = subprogram.dstar 593 self._parameter(subprogram, param, default) 594 first = 0 595 596 def _parameter(self, subprogram, param, default): 597 self._name_start(param) 598 if hasattr(subprogram, "paramtypes"): 599 self._popup_start() 600 self._types_list(subprogram.paramtypes[param]) 601 self._popup_end() 602 self._name_end() 603 if default is not None and default.original is not None: 604 self.stream.write("=\n") 605 self.dispatch(default.original) 606 607 def _name(self, name): 608 self.stream.write("<span class='name'>%s</span>\n" % name) 609 610 def _name_start(self, name): 611 self.stream.write("<span class='name'>%s\n" % name) 612 613 def _name_end(self): 614 self.stream.write("</span>\n") 615 616 def _popup_start(self): 617 self.stream.write("<span class='popup'>\n") 618 619 def _popup_end(self): 620 self.stream.write("</span>\n") 621 622 def _op(self, op_name, op): 623 if op is not None: 624 self._invocations(op) 625 626 def _invocations(self, node): 627 if hasattr(node, "invocations"): 628 self._invocations_list(node.invocations) 629 630 def _invocations_list(self, invocations): 631 self.stream.write("<div class='invocations'>\n") 632 for invocation in invocations: 633 fn = invocation.full_name() 634 self.stream.write("<div class='invocation'>") 635 self.stream.write(self._text(fn)) 636 self.stream.write("</div>\n") 637 self.stream.write("</div>\n") 638 639 def _types(self, node): 640 if hasattr(node, "types"): 641 self._types_list(node.types) 642 elif hasattr(node, "writes"): 643 self._types_list(flatten(node.writes.values())) 644 elif hasattr(node, "accesses"): 645 self._types_list(flatten(node.accesses.values())) 646 else: 647 self.stream.write("<div class='types'>\n") 648 self.stream.write("unvisited\n") 649 self.stream.write("</div>\n") 650 651 def _types_list(self, types): 652 self.stream.write("<div class='types'>\n") 653 for type in types: 654 fn = type.type.full_name() 655 self.stream.write("<div class='type'>") 656 self.stream.write(self._text(fn)) 657 self.stream.write("</div>\n") 658 self.stream.write("</div>\n") 659 660 def _scopes(self, node): 661 if not isinstance(node, LoadName): 662 if hasattr(node, "writes") or hasattr(node, "accesses"): 663 self.stream.write("<div class='scopes'>\n") 664 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 665 fn = ref.full_name() 666 self.stream.write("<div class='scope'>") 667 self.stream.write(self._text(fn)) 668 self.stream.write("</div>\n") 669 self.stream.write("</div>\n") 670 671 # Utility functions. 672 673 def flatten(lists): 674 result = [] 675 for l in lists: 676 for attr in l: 677 if attr not in result: 678 result.append(attr) 679 return result 680 681 # Convenience functions. 682 683 def view(module, stream=None): 684 viewer = Viewer(stream or sys.stdout) 685 viewer.process(module.original) 686 687 def browse(module, stream=None): 688 browser = Browser(stream or sys.stdout) 689 browser.process(module.original) 690 691 def makedoc(module, filename): 692 stream = open(filename, "wb") 693 try: 694 browser = Browser(stream) 695 browser.process(module.original) 696 finally: 697 stream.close() 698 699 # vim: tabstop=4 expandtab shiftwidth=4