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 visitTryExcept(self, node): 368 self.stream.write("<div class='tryexcept'>\n") 369 self.stream.write("<div>\n") 370 self._keyword("try") 371 self.stream.write(":\n") 372 self.stream.write("</div>\n") 373 self.stream.write("<div class='body'>\n") 374 self.dispatch(node.body) 375 self.stream.write("</div>\n") 376 for spec, assign, statement in node.handlers: 377 self.stream.write("<div>\n") 378 self._keyword("except") 379 if spec is not None: 380 self.dispatch(spec) 381 if assign is not None: 382 self.stream.write(",\n") 383 self.dispatch(assign) 384 self.stream.write(":\n") 385 self.stream.write("</div>\n") 386 self.stream.write("<div class='body'>\n") 387 self.dispatch(statement) 388 self.stream.write("</div>\n") 389 if node.else_ is not None: 390 self.stream.write("<div>\n") 391 self._keyword("else") 392 self.stream.write(":\n") 393 self.stream.write("</div>\n") 394 self.stream.write("<div class='body'>\n") 395 self.dispatch(node.else_) 396 self.stream.write("</div>\n") 397 self.stream.write("</div>\n") 398 399 def visitTryFinally(self, node): 400 self.stream.write("<div class='tryfinally'>\n") 401 self.stream.write("<div>\n") 402 self._keyword("try") 403 self.stream.write(":\n") 404 self.stream.write("</div>\n") 405 self.stream.write("<div class='body'>\n") 406 self.dispatch(node.body) 407 self.stream.write("</div>\n") 408 self.stream.write("<div>\n") 409 self._keyword("finally") 410 self.stream.write(":\n") 411 self.stream.write("</div>\n") 412 self.stream.write("<div class='body'>\n") 413 self.dispatch(node.final) 414 self.stream.write("</div>\n") 415 self.stream.write("</div>\n") 416 417 def visitWhile(self, node): 418 self.stream.write("<div class='while'>\n") 419 self.stream.write("<div>\n") 420 self._keyword("while") 421 self.dispatch(node.test) 422 self.stream.write(":\n") 423 self.stream.write("</div>\n") 424 self.stream.write("<div class='body'>\n") 425 self.dispatch(node.body) 426 self.stream.write("</div>\n") 427 if node.else_ is not None: 428 self.stream.write("<div>\n") 429 self._keyword("else") 430 self.stream.write(":\n") 431 self.stream.write("</div>\n") 432 self.stream.write("<div class='body'>\n") 433 self.dispatch(node.else_) 434 self.stream.write("</div>\n") 435 self.stream.write("</div>\n") 436 437 # Expressions. 438 439 def visitAssAttr(self, node): 440 self.stream.write("<span class='assattr'>\n") 441 self.dispatch(node.expr) 442 self.stream.write("<span class='attr'>\n") 443 self.stream.write(".%s\n" % self._text(node.attrname)) 444 if hasattr(node, "_node"): 445 self._popup_start() 446 self._types(node._node) 447 self._scopes(node._node) 448 self._popup_end() 449 else: 450 raise ValueError, node 451 self.stream.write("</span>\n") 452 self.stream.write("</span>\n") 453 454 def visitAssList(self, node): 455 self.stream.write("<span class='list'>\n") 456 self.stream.write("[") 457 self._sequence(node) 458 self.stream.write("]\n") 459 self.stream.write("</span>\n") 460 461 def visitAssName(self, node): 462 if hasattr(node, "_node"): 463 self._name_start(node._node.name) 464 self._popup_start() 465 self._types(node._node.expr) 466 self._scopes(node._node) 467 self._popup_end() 468 self._name_end() 469 else: 470 raise ValueError, node 471 self._name(node.name) 472 473 def visitAssTuple(self, node): 474 self.stream.write("<span class='tuple'>\n") 475 self.stream.write("(") 476 self._sequence(node) 477 self.stream.write(")\n") 478 self.stream.write("</span>\n") 479 480 def visitCallFunc(self, node): 481 self.stream.write("<span class='callfunc'>\n") 482 self.dispatch(node.node) 483 self.stream.write("(") 484 first = 1 485 for arg in node.args: 486 if not first: 487 self.stream.write(",\n") 488 self.dispatch(arg) 489 first = 0 490 if node.star_args is not None: 491 if not first: 492 self.stream.write(", *\n") 493 self.dispatch(node.star_args) 494 first = 0 495 if node.dstar_args is not None: 496 if not first: 497 self.stream.write(", **\n") 498 self.dispatch(node.dstar_args) 499 first = 0 500 self.stream.write(")\n") 501 self.stream.write("</span>\n") 502 503 def visitCompare(self, node): 504 self.stream.write("<span class='compare'>\n") 505 self.dispatch(node.expr) 506 for (op_name, expr), _op in map(None, node.ops, node._ops): 507 self.stream.write("<span class='op'>\n") 508 self.stream.write(op_name) 509 self._popup_start() 510 self._op(op_name, _op) 511 self._popup_end() 512 self.stream.write("</span>\n") 513 self.dispatch(expr) 514 self.stream.write("</span>\n") 515 516 def visitConst(self, node): 517 self.stream.write(repr(node.value)) 518 519 def visitGetattr(self, node): 520 self.stream.write("<span class='getattr'>\n") 521 self.dispatch(node.expr) 522 self.stream.write("<span class='attr'>\n") 523 self.stream.write(".%s\n" % self._text(node.attrname)) 524 if hasattr(node, "_node"): 525 self._popup_start() 526 self._types(node._node) 527 self._scopes(node._node) 528 self._popup_end() 529 else: 530 raise ValueError, node 531 self.stream.write("</span>\n") 532 self.stream.write("</span>\n") 533 534 def visitKeyword(self, node): 535 self.stream.write("<span class='keyword'>\n") 536 self.stream.write(node.name) 537 self.stream.write("=") 538 self.dispatch(node.expr) 539 self.stream.write("</span>\n") 540 541 def visitLambda(self, node): 542 definition = node._node 543 subprogram = definition.expr.ref 544 self.stream.write("<span class='lambda'>\n") 545 self._keyword("lambda") 546 self._parameters(subprogram) 547 self.dispatch(node.code) 548 self.stream.write("</span>\n") 549 550 visitList = visitAssList 551 552 def visitName(self, node): 553 if hasattr(node, "_node"): 554 self._name_start(node._node.name) 555 self._popup_start() 556 self._types(node._node) 557 self._scopes(node._node) 558 self._popup_end() 559 self._name_end() 560 else: 561 raise ValueError, node 562 self._name(node.name) 563 564 def visitSlice(self, node): 565 self.stream.write("<span class='slice'>\n") 566 self.dispatch(node.expr) 567 self.stream.write("[") 568 if node.lower: 569 self.dispatch(node.lower) 570 self.stream.write(":") 571 if node.upper: 572 self.dispatch(node.upper) 573 # NOTE: Step? 574 self.stream.write("]") 575 self.stream.write("</span>\n") 576 577 def visitSubscript(self, node): 578 self.stream.write("<span class='subscript'>\n") 579 self.dispatch(node.expr) 580 self.stream.write("[") 581 first = 1 582 for sub in node.subs: 583 if not first: 584 self.stream.write(", ") 585 self.dispatch(sub) 586 first = 0 587 self.stream.write("]") 588 self.stream.write("</span>\n") 589 590 visitTuple = visitAssTuple 591 592 # Output preparation methods. 593 594 def _text(self, text): 595 return text.replace("&", "&").replace("<", "<").replace(">", ">") 596 597 def _attr(self, attr): 598 return self._text(attr).replace("'", "'").replace('"', """) 599 600 def _url(self, url): 601 return self._attr(url).replace("#", "%23").replace("-", "%2d") 602 603 def _comment(self, comment): 604 self.stream.write("<span class='comment'># %s</span>\n" % comment) 605 606 def _keyword(self, kw): 607 self.stream.write("<span class='keyword'>%s</span> " % kw) 608 609 def _doc(self, node): 610 if node.doc is not None: 611 self.stream.write("<pre class='doc'>\n") 612 self.stream.write('"""') 613 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 614 self.stream.write(self._text(output)) 615 self.stream.write('"""') 616 self.stream.write("</pre>\n") 617 618 def _sequence(self, node): 619 first = 1 620 for n in node.nodes: 621 if not first: 622 self.stream.write(",\n") 623 self.dispatch(n) 624 first = 0 625 626 def _parameters(self, subprogram): 627 first = 1 628 for param, default in subprogram.params: 629 if not first: 630 self.stream.write(",\n") 631 self._parameter(subprogram, param, default) 632 first = 0 633 if subprogram.star is not None: 634 if not first: 635 self.stream.write(", *\n") 636 param, default = subprogram.star 637 self._parameter(subprogram, param, default) 638 first = 0 639 if subprogram.dstar is not None: 640 if not first: 641 self.stream.write(", **\n") 642 param, default = subprogram.dstar 643 self._parameter(subprogram, param, default) 644 first = 0 645 646 def _parameter(self, subprogram, param, default): 647 self._name_start(param) 648 if hasattr(subprogram, "paramtypes"): 649 self._popup_start() 650 self._types_list(subprogram.paramtypes[param]) 651 self._popup_end() 652 self._name_end() 653 if default is not None and default.original is not None: 654 self.stream.write("=\n") 655 self.dispatch(default.original) 656 657 def _name(self, name): 658 self.stream.write("<span class='name'>%s</span>\n" % name) 659 660 def _name_start(self, name): 661 self.stream.write("<span class='name'>%s\n" % name) 662 663 def _name_end(self): 664 self.stream.write("</span>\n") 665 666 def _popup_start(self): 667 self.stream.write("<span class='popup'>\n") 668 669 def _popup_end(self): 670 self.stream.write("</span>\n") 671 672 def _op(self, op_name, op): 673 if op is not None: 674 self._invocations(op) 675 676 def _invocations(self, node): 677 if hasattr(node, "invocations"): 678 self._invocations_list(node.invocations) 679 680 def _invocations_list(self, invocations): 681 self.stream.write("<div class='invocations'>\n") 682 for invocation in invocations: 683 fn = invocation.full_name() 684 self.stream.write("<div class='invocation'>") 685 self.stream.write(self._text(fn)) 686 self.stream.write("</div>\n") 687 self.stream.write("</div>\n") 688 689 def _types(self, node): 690 if hasattr(node, "types"): 691 self._types_list(node.types) 692 elif hasattr(node, "writes"): 693 self._types_list(flatten(node.writes.values())) 694 elif hasattr(node, "accesses"): 695 self._types_list(flatten(node.accesses.values())) 696 else: 697 self.stream.write("<div class='types'>\n") 698 self.stream.write("unvisited\n") 699 self.stream.write("</div>\n") 700 701 def _types_list(self, types): 702 self.stream.write("<div class='types'>\n") 703 for type in types: 704 fn = type.type.full_name() 705 self.stream.write("<div class='type'>") 706 self.stream.write(self._text(fn)) 707 self.stream.write("</div>\n") 708 self.stream.write("</div>\n") 709 710 def _scopes(self, node): 711 if not isinstance(node, LoadName): 712 if hasattr(node, "writes") or hasattr(node, "accesses"): 713 self.stream.write("<div class='scopes'>\n") 714 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 715 fn = ref.full_name() 716 self.stream.write("<div class='scope'>") 717 self.stream.write(self._text(fn)) 718 self.stream.write("</div>\n") 719 self.stream.write("</div>\n") 720 721 # Utility functions. 722 723 def flatten(lists): 724 result = [] 725 for l in lists: 726 for attr in l: 727 if attr not in result: 728 result.append(attr) 729 return result 730 731 # Convenience functions. 732 733 def view(module, stream=None): 734 viewer = Viewer(stream or sys.stdout) 735 viewer.process(module.original) 736 737 def browse(module, stream=None): 738 browser = Browser(stream or sys.stdout) 739 browser.process(module.original) 740 741 def makedoc(module, filename): 742 stream = open(filename, "wb") 743 try: 744 browser = Browser(stream) 745 browser.process(module.original) 746 finally: 747 stream.close() 748 749 # vim: tabstop=4 expandtab shiftwidth=4