1 #!/usr/bin/env python 2 3 """ 4 View annotated sources. 5 6 Copyright (C) 2006, 2007, 2010, 2011, 2012 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from micropython.common import * 23 from micropython.data import * 24 from micropython.errors import * 25 from os.path import exists, extsep, join 26 import compiler.ast 27 import sys 28 import os 29 import textwrap 30 31 # Classes. 32 33 # HTML-related output production. 34 35 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 36 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 37 <html xmlns="http://www.w3.org/1999/xhtml"> 38 <head> 39 <title>%(title)s</title> 40 <style type="text/css"> 41 html { 42 background-color: black; color: white; 43 } 44 45 body { 46 padding-bottom: 4em; 47 font-size: 14pt; font-family: monospace; 48 background-color: black; color: white; 49 margin-top: 2em; 50 } 51 52 a { 53 text-decoration: none; 54 } 55 56 .nowrap { white-space: nowrap; } 57 .label { font-size: smaller; } 58 59 .class { margin-top: 1em; margin-bottom: 1em; } 60 .function { margin-top: 1em; margin-bottom: 1em; } 61 .body { padding-left: 2em; } 62 .for, .if, .tryexcept, .tryfinally, .while { margin-bottom: 1em; } 63 .keyword { color: yellow; } 64 .comment { color: blue; } 65 .class-name { color: cyan; } 66 .function-name { color: cyan; } 67 .specific-ref { color: #07F; } 68 .str { color: #FF00FF; } 69 .doc { color: #FF00FF; margin-top: 1em; margin-bottom: 1em; } 70 71 .popup { 72 display: none; 73 position: absolute; 74 top: 3ex; left: 0; 75 color: white; 76 border-bottom: 0.5ex solid #000; 77 border-right: 0.5ex solid #000; 78 z-index: 3; 79 } 80 81 .attributes-popup, 82 .types-popup { 83 display: none; 84 position: absolute; 85 bottom: 3ex; left: 0; 86 color: white; 87 border-bottom: 0.5ex solid #000; 88 border-right: 0.5ex solid #000; 89 z-index: 3; 90 } 91 92 .no-targets { 93 background-color: #d00; 94 color: white; 95 } 96 97 .attr, 98 .accessor, 99 .name, 100 .operation { 101 position: relative; 102 background-color: #300; 103 color: white; 104 } 105 106 .attr:hover, 107 .accessor:hover, 108 .name:hover, 109 .operation:hover { 110 background-color: #500; 111 padding-top: 0.5ex; 112 padding-bottom: 0.5ex; 113 z-index: 2; 114 } 115 116 .attr:hover .attributes-popup, 117 .accessor:hover .types-popup, 118 .name:hover .popup, 119 .operation:hover .popup { 120 display: block; 121 } 122 123 .attrnames, 124 .opnames, 125 .scope, 126 .typenames { 127 padding: 0.5em; 128 background-color: #700; 129 } 130 131 .attrnames a, 132 .opnames a { 133 color: white; 134 } 135 136 .summary-class { 137 vertical-align: top; 138 } 139 140 th.summary-class { 141 font-weight: normal; 142 } 143 144 .summary-attr { 145 background-color: #070; 146 } 147 148 .summary-interface, 149 .summary-attr { 150 font-size: smaller; 151 } 152 153 .summary-interface.complete { 154 background-color: #050; 155 } 156 157 .summary-interface.partial { 158 background-color: #005; 159 } 160 161 .summary-attr-absent { 162 border-left: 0.2em solid #070; 163 font-size: small; 164 } 165 166 .summary-class-attr { 167 background-color: #007; 168 font-size: smaller; 169 } 170 171 .summary-class-attr-absent { 172 border-left: 0.2em solid #007; 173 font-size: small; 174 } 175 176 .summary-ref { 177 color: white; 178 } 179 180 </style> 181 </head> 182 <body> 183 """ 184 185 html_footer = """</body> 186 </html> 187 """ 188 189 # Utility classes. 190 191 class Writer: 192 193 "A utility class providing useful HTML output methods." 194 195 # Methods which return strings. 196 197 def _text(self, text): 198 return text.replace("&", "&").replace("<", "<").replace(">", ">") 199 200 def _attr(self, attr): 201 return self._text(attr).replace("'", "'").replace('"', """) 202 203 def _url(self, url): 204 return self._attr(url).replace("#", "%23").replace("-", "%2d") 205 206 # Methods which write to the stream. 207 208 def _span_start(self, classes=None): 209 self.stream.write("<span class='%s'>" % (classes or "")) 210 211 def _span_end(self): 212 self.stream.write("</span>") 213 214 def _span(self, value, classes=None): 215 self._span_start(classes) 216 self.stream.write(self._text(value)) 217 self._span_end() 218 219 def _name_start(self, classes=None): 220 self._span_start(classes or "name") 221 222 _name_end = _span_end 223 224 def _name(self, name, classes=None): 225 self._name_start(classes) 226 self.stream.write(self._text(name)) 227 self._name_end() 228 229 def _popup_start(self, classes=None): 230 self._span_start(classes or "popup") 231 232 _popup_end = _span_end 233 234 def _comment(self, comment): 235 self._span("# %s" % comment, "comment") 236 self.stream.write("\n") 237 238 def _reserved(self, token, classes, leading=0, trailing=1): 239 if leading: 240 self.stream.write(" ") 241 self._span(token, classes) 242 if trailing: 243 self.stream.write(" ") 244 245 def _keyword(self, kw, leading=0, trailing=1): 246 self._reserved(kw, "keyword", leading, trailing) 247 248 def _doc(self, node): 249 if node.doc is not None: 250 self._docstring(node.doc) 251 252 def _docstring(self, s): 253 self.stream.write("<pre class='doc'>") 254 self.stream.write('"""') 255 output = textwrap.dedent(s.replace('"""', '\\"\\"\\"')) 256 self.stream.write(self._text(output)) 257 self.stream.write('"""') 258 self.stream.write("</pre>\n") 259 260 def _object_name_def(self, module, obj, classes=None): 261 262 """ 263 Link to the summary for 'module' using 'obj'. The optional 'classes' 264 can be used to customise the CSS classes employed. 265 """ 266 267 if isinstance(obj, Class) or (isinstance(obj, Function) and obj.is_method()): 268 self._summary_link(module.full_name(), obj.full_name(), obj.name, classes) 269 else: 270 self._span(obj.name, classes) 271 272 def _object_name_ref(self, module, obj, name=None, classes=None): 273 274 """ 275 Link to the definition for 'module' using 'obj' with the optional 'name' 276 used as the label (instead of the name of 'obj'). The optional 'classes' 277 can be used to customise the CSS classes employed. 278 """ 279 280 self._name_link(module.full_name(), obj.full_name(), name or obj.name, classes) 281 282 def _summary_link(self, module_name, full_name, name, classes=None): 283 self._name_link("%s-summary" % module_name, full_name, name, classes) 284 285 def _name_link(self, module_name, full_name, name, classes=None): 286 self.stream.write("<a class='%s' href='%s%sxhtml#%s'>%s</a>" % ( 287 classes or "specific-ref", module_name, os.path.extsep, 288 self._attr(full_name), self._text(name))) 289 290 def _scope(self, scope): 291 self.stream.write("<div class='scope'><span class='label'>scope</span><br />%s</div>\n" % scope) 292 293 def _assname(self, name, node): 294 self._span_start("assname") 295 if not self._attrcombined(name, node): 296 self._span(name) 297 self._span_end() 298 299 def _op(self, symbol, name=None, leading=0, trailing=1): 300 if leading: 301 self.stream.write(" ") 302 self._span_start(name and "operation" or None) 303 self._span(symbol, "operator") 304 if name is not None: 305 self._popup_start() 306 self.stream.write("<div class='opnames'>") 307 self._name_link("operator", "operator.%s" % name, name) 308 self.stream.write("</div>\n") 309 self._popup_end() 310 # NOTE: Handle "is" and "in". 311 self._span_end() 312 if trailing: 313 self.stream.write(" ") 314 315 def _names_list_start(self, label, classes): 316 self.stream.write("<div class='%s'><span class='label'>%s</span><br />" % (classes, label)) 317 318 def _names_list_end(self): 319 self.stream.write("</div>\n") 320 321 def _names_list(self, names, label, classes): 322 if not names: 323 return 324 names = list(names) 325 names.sort() 326 327 self._names_list_start(label, classes) 328 first = 1 329 for name in names: 330 if not first: 331 self.stream.write("<br />") 332 self.stream.write(name) 333 first = 0 334 self._names_list_end() 335 336 def _attrcombined(self, name, node): 337 attrcombined = hasattr(node, "_attrcombined") and node._attrcombined.get(name) or [] 338 339 for attrnames in attrcombined: 340 if attrnames: 341 break 342 else: 343 return 0 344 345 self._name_start() 346 self.stream.write(name) 347 self._popup_start() 348 for attrnames in attrcombined: 349 self._attrnames(attrnames) 350 self._popup_end() 351 self._name_end() 352 return 1 353 354 def _attrnames(self, attrnames): 355 self._names_list(attrnames, "attributes", "attrnames") 356 357 def _typenames(self, typenames): 358 self._names_list(typenames, "types", "typenames") 359 360 def _accessor_start(self, target_names): 361 if target_names: 362 self._span_start("accessor") 363 self._popup_start("types-popup") 364 self._typenames(target_names) 365 self._popup_end() 366 367 def _accessor_end(self, target_names): 368 if target_names: 369 self._span_end() 370 371 def _attribute_start(self, attrname, attributes): 372 if attributes: 373 374 # Get the output form of the attributes. 375 376 output = [] 377 for value, target, target_name in attributes: 378 if value and not isinstance(value, Instance): 379 fullname = value.full_name() 380 else: 381 fullname = target_name + "." + attrname 382 output.append((fullname, value)) 383 384 output.sort() 385 386 self._span_start("attr") 387 self._popup_start("attributes-popup") 388 self._names_list_start("attributes", "attrnames") 389 390 # Mix links to attributes with labels indicating undetermined 391 # attributes. 392 393 last = None 394 for fullname, value in output: 395 if fullname != last: 396 if last is not None: 397 self.stream.write("<br />") 398 if value is not None and not isinstance(value, Instance): 399 self._object_name_ref(value.module, value, fullname, classes="attribute-name") 400 else: 401 self.stream.write(fullname) 402 last = fullname 403 404 self._names_list_end() 405 self._popup_end() 406 407 def _attribute_end(self, attributes): 408 if attributes: 409 self._span_end() 410 411 def _get_attributes(self, possible_types, attrname): 412 objtable = self.program.get_object_table() 413 attributes = [] 414 for target_name, is_static in possible_types: 415 target = objtable.get_object(target_name) 416 try: 417 attr = objtable.access(target_name, attrname) 418 except TableError: 419 continue 420 if attr.is_static_attribute(): 421 for v in attr.get_values(): 422 attributes.append((v, target, target_name)) 423 else: 424 attributes.append((None, target, target_name)) 425 426 return attributes 427 428 # Summary classes. 429 430 class Summary(Writer): 431 432 "Summarise classes and attributes in modules." 433 434 def __init__(self, module, program): 435 self.module = module 436 self.program = program 437 438 def to_stream(self, stream): 439 440 "Write the summary to the given 'stream'." 441 442 self.stream = stream 443 self.stream.write(html_header % { 444 "title" : "Module: %s" % self.module.full_name() 445 }) 446 self._write_classes(self.module) 447 self.stream.write(html_footer) 448 449 def _write_classes(self, module): 450 451 all_classes = {} 452 453 for obj in self.module.all_objects: 454 if isinstance(obj, Class): 455 all_classes[obj.name] = obj 456 457 if all_classes: 458 459 all_class_names = all_classes.keys() 460 all_class_names.sort() 461 462 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 463 self.stream.write("<thead>\n") 464 self.stream.write("<tr>\n") 465 self.stream.write("<th>Classes</th><th>Attributes</th>\n") 466 self.stream.write("</tr>\n") 467 self.stream.write("</thead>\n") 468 469 for name in all_class_names: 470 self._write_class(all_classes[name]) 471 472 self.stream.write("</table>\n") 473 474 def _write_class(self, obj): 475 476 # Write the class... 477 478 self.stream.write("<tbody class='class'>\n") 479 self.stream.write("<tr>\n") 480 self.stream.write("<th class='summary-class' id='%s' rowspan='2'>" % self._attr(obj.full_name())) 481 self._object_name_ref(self.module, obj, classes="class-name") 482 self.stream.write("</th>\n") 483 484 # ...and instance attribute names in order... 485 486 attrs = obj.instance_attributes().values() 487 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 488 489 if attrs: 490 for attr in attrs: 491 self.stream.write("<td class='summary-attr'>%s</td>\n" % self._text(attr.name)) 492 else: 493 self.stream.write("<td class='summary-attr-absent'>None</td>\n") 494 495 self.stream.write("</tr>\n") 496 self.stream.write("<tr>\n") 497 498 # ...and class attribute names in order. 499 500 attrs = obj.class_attributes().values() 501 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 502 503 if attrs: 504 for attr in attrs: 505 if attr.is_strict_constant(): 506 value = attr.get_value() 507 if not isinstance(value, Const): 508 self.stream.write("<td class='summary-class-attr' id='%s'>" % self._attr(value.full_name())) 509 self._object_name_ref(self.module, value, attr.name, classes="summary-ref") 510 self.stream.write("</td>\n") 511 else: 512 self.stream.write("<td class='summary-class-attr'>%s</td>\n" % self._text(attr.name)) 513 else: 514 self.stream.write("<td class='summary-class-attr'>%s</td>\n" % self._text(attr.name)) 515 else: 516 self.stream.write("<td class='summary-class-attr-absent'>None</td>\n") 517 518 self.stream.write("</tr>\n") 519 self.stream.write("</tbody>\n") 520 521 class Interfaces(Writer): 522 523 "Summarise the interfaces used by reading the object table cache." 524 525 def __init__(self, program): 526 self.program = program 527 528 def to_stream(self, stream): 529 530 "Write the summary to the given 'stream'." 531 532 self.stream = stream 533 self.stream.write(html_header % { 534 "title" : "Interfaces" 535 }) 536 self._write_interfaces() 537 self.stream.write(html_footer) 538 539 def _write_interfaces(self): 540 objtable = self.program.get_object_table() 541 all_interfaces = objtable.all_cache.items() 542 any_interfaces = objtable.any_cache.items() 543 all_interfaces.sort() 544 any_interfaces.sort() 545 546 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 547 self.stream.write("<thead>\n") 548 self.stream.write("<tr>\n") 549 self.stream.write("<th>Complete Interfaces</th>\n") 550 self.stream.write("</tr>\n") 551 self.stream.write("</thead>\n") 552 self._write_interface_type(all_interfaces, "complete") 553 self.stream.write("<thead>\n") 554 self.stream.write("<tr>\n") 555 self.stream.write("<th>Partial Interfaces</th>\n") 556 self.stream.write("</tr>\n") 557 self.stream.write("</thead>\n") 558 self._write_interface_type(any_interfaces, "partial") 559 self.stream.write("</table>\n") 560 561 def _write_interface_type(self, interfaces, classes=""): 562 self.stream.write("<tbody>\n") 563 564 for names, objects in interfaces: 565 if names: 566 names = list(names) 567 names.sort() 568 self.stream.write("<tr>\n") 569 self.stream.write("<td class='summary-interface %s'>%s</td>" % (classes, ", ".join(names))) 570 self.stream.write("</tr>\n") 571 572 self.stream.write("</tbody>\n") 573 574 # Source code classes. 575 576 class AnnotatedSource(ASTVisitor, Writer): 577 578 "A module source code browser." 579 580 def __init__(self, module, program): 581 self.visitor = self 582 self.module = module 583 self.program = program 584 585 def to_stream(self, stream): 586 587 "Write the annotated code to the given 'stream'." 588 589 self.stream = stream 590 self.stream.write(html_header % { 591 "title" : "Module: %s" % self.module.full_name() 592 }) 593 self.dispatch(self.module.astnode) 594 self.stream.write(html_footer) 595 596 def visitModule(self, node): 597 self.default(node) 598 599 # Statements. 600 601 def visitAssert(self, node): 602 self.stream.write("<div class='assert nowrap'>\n") 603 self._keyword("assert") 604 self.dispatch(node.test) 605 if node.fail: 606 self.stream.write(", ") 607 self.dispatch(node.fail) 608 self.stream.write("</div>\n") 609 610 def visitAssign(self, node): 611 self.stream.write("<div class='assign nowrap'>\n") 612 for lvalue in node.nodes: 613 self.dispatch(lvalue) 614 self.stream.write(" = ") 615 self.dispatch(node.expr) 616 self.stream.write("</div>\n") 617 618 def visitAugAssign(self, node): 619 self.stream.write("<div class='augassign nowrap'>\n") 620 self.dispatch(node.node) 621 self._op(node.op, operator_functions[node.op], 1) 622 self.dispatch(node.expr) 623 self.stream.write("</div>\n") 624 625 def visitBreak(self, node): 626 self.stream.write("<div class='break nowrap'>\n") 627 self._keyword("break") 628 self.stream.write("</div>\n") 629 630 def visitClass(self, node): 631 if not used_by_unit(node): 632 self._docstring('"Class %s not generated."' % node.name) 633 return 634 635 # Use inspected details where possible. 636 637 if hasattr(node, "unit"): 638 cls = node.unit 639 bases = cls.bases 640 self.stream.write("<div class='class nowrap' id='%s'>\n" % cls.full_name()) 641 else: 642 print >>sys.stderr, "Warning: class %s not recognised!" % node.name 643 return 644 645 # Write the declaration line. 646 647 self.stream.write("<div>\n") 648 self._keyword("class") 649 self._object_name_def(self.module, cls, "class-name") 650 651 # Suppress the "object" class appearing alone. 652 653 if bases and not (len(bases) == 1 and bases[0].name == "object"): 654 self.stream.write("(") 655 first = 1 656 for base in bases: 657 if not first: 658 self.stream.write(", ") 659 660 self._object_name_ref(base.module, base) 661 662 first = 0 663 self.stream.write(")") 664 665 self.stream.write(":\n") 666 self.stream.write("</div>\n") 667 668 # Write the docstring and class body. 669 670 self.stream.write("<div class='body nowrap'>\n") 671 self._doc(node) 672 673 # NOTE: Some streams may not support tell. 674 675 x = self.stream.tell() 676 677 self.default(node.code) 678 679 # Check for no output. 680 681 if x == self.stream.tell(): 682 self.visitPass(None) 683 684 self.stream.write("</div>\n") 685 self.stream.write("</div>\n") 686 687 def visitContinue(self, node): 688 self.stream.write("<div class='continue nowrap'>\n") 689 self._keyword("continue") 690 self.stream.write("</div>\n") 691 692 def visitDiscard(self, node): 693 self.stream.write("<div class='discard nowrap'>\n") 694 self.default(node) 695 self.stream.write("</div>\n") 696 697 def visitFor(self, node): 698 self.stream.write("<div class='if nowrap'>\n") 699 self.stream.write("<div>\n") 700 self._keyword("for") 701 self.dispatch(node.assign) 702 self._keyword("in", 1) 703 self.dispatch(node.list) 704 self.stream.write(":\n") 705 self.stream.write("</div>\n") 706 self.stream.write("<div class='body nowrap'>\n") 707 self.dispatch(node.body) 708 self.stream.write("</div>\n") 709 if node.else_ is not None: 710 self.stream.write("<div>\n") 711 self._keyword("else", trailing=0) 712 self.stream.write(":\n") 713 self.stream.write("</div>\n") 714 self.stream.write("<div class='body nowrap'>\n") 715 self.dispatch(node.else_) 716 self.stream.write("</div>\n") 717 self.stream.write("</div>\n") 718 719 def visitFrom(self, node): 720 self.stream.write("<div class='from nowrap'>\n") 721 self._keyword("from") 722 self._name(node.modname) 723 self._keyword("import", 1) 724 first = 1 725 for name, alias in node.names: 726 if not first: 727 self.stream.write(", ") 728 if alias: 729 self.stream.write(name + " ") 730 self._keyword("as", 1) 731 self._name(alias or name) 732 first = 0 733 self.stream.write("</div>\n") 734 735 def visitFunction(self, node): 736 if not used_by_unit(node): 737 self._docstring('"Function %s not generated."' % node.name) 738 return 739 740 if hasattr(node, "unit"): 741 fn = node.unit 742 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 743 else: 744 print >>sys.stderr, "Warning: function %s not recognised!" % node.name 745 return 746 747 # Write the declaration line. 748 749 self.stream.write("<div>\n") 750 self._keyword("def") 751 self._object_name_def(self.module, fn, "function-name") 752 753 self.stream.write("(") 754 self._parameters(fn, node) 755 self.stream.write(")") 756 self.stream.write(":\n") 757 self.stream.write("</div>\n") 758 759 self.stream.write("<div class='body nowrap'>\n") 760 self._doc(node) 761 self.dispatch(node.code) 762 self.stream.write("</div>\n") 763 self.stream.write("</div>\n") 764 765 def visitGlobal(self, node): 766 self.stream.write("<div class='global nowrap'>\n") 767 self._keyword("global") 768 first = 1 769 for name in node.names: 770 if not first: 771 self.stream.write(", ") 772 self.stream.write(name) 773 first = 0 774 self.stream.write("</div>\n") 775 776 def visitIf(self, node): 777 self.stream.write("<div class='if nowrap'>\n") 778 first = 1 779 for compare, stmt in node.tests: 780 self.stream.write("<div>\n") 781 if first: 782 self._keyword("if") 783 else: 784 self._keyword("elif") 785 self.dispatch(compare) 786 self.stream.write(":\n") 787 self.stream.write("</div>\n") 788 self.stream.write("<div class='body nowrap'>\n") 789 self.dispatch(stmt) 790 self.stream.write("</div>\n") 791 first = 0 792 if node.else_ is not None: 793 self.stream.write("<div>\n") 794 self._keyword("else", trailing=0) 795 self.stream.write(":\n") 796 self.stream.write("</div>\n") 797 self.stream.write("<div class='body nowrap'>\n") 798 self.dispatch(node.else_) 799 self.stream.write("</div>\n") 800 self.stream.write("</div>\n") 801 802 def visitImport(self, node): 803 self.stream.write("<div class='import nowrap'>\n") 804 self._keyword("import") 805 first = 1 806 for name, alias in node.names: 807 if not first: 808 self.stream.write(",\n") 809 if alias: 810 self.stream.write(name + " ") 811 self._keyword("as", 1) 812 self._name(alias or name) 813 first = 0 814 self.stream.write("</div>\n") 815 816 def visitPass(self, node): 817 self.stream.write("<div class='pass nowrap'>\n") 818 self._keyword("pass") 819 self.stream.write("</div>\n") 820 821 def visitPrint(self, node): 822 self.stream.write("<div class='print nowrap'>\n") 823 self._keyword("print") 824 if node.dest is not None: 825 self.stream.write(">>\n") 826 self.dispatch(node.dest) 827 self.stream.write(",\n") 828 for n in node.nodes: 829 self.dispatch(n) 830 self.stream.write(",\n") 831 self.stream.write("</div>\n") 832 833 def visitPrintnl(self, node): 834 self.stream.write("<div class='printnl nowrap'>\n") 835 self._keyword("print") 836 if node.dest is not None: 837 self.stream.write(">>\n") 838 self.dispatch(node.dest) 839 first = 0 840 else: 841 first = 1 842 for n in node.nodes: 843 if not first: 844 self.stream.write(",\n") 845 self.dispatch(n) 846 first = 0 847 self.stream.write("</div>\n") 848 849 def visitRaise(self, node): 850 self.stream.write("<div class='raise nowrap'>\n") 851 self._keyword("raise") 852 if node.expr1 is not None: 853 self.dispatch(node.expr1) 854 if node.expr2 is not None: 855 self.stream.write(",\n") 856 self.dispatch(node.expr2) 857 if node.expr3 is not None: 858 self.stream.write(",\n") 859 self.dispatch(node.expr3) 860 self.stream.write("</div>\n") 861 862 def visitReturn(self, node): 863 self.stream.write("<div class='return nowrap'>\n") 864 self._keyword("return") 865 self.dispatch(node.value) 866 self.stream.write("</div>\n") 867 868 def visitStmt(self, node): 869 self.stream.write("<div class='stmt nowrap'>\n") 870 self.default(node) 871 self.stream.write("</div>\n") 872 873 def visitTryExcept(self, node): 874 self.stream.write("<div class='tryexcept nowrap'>\n") 875 self.stream.write("<div>\n") 876 self._keyword("try", trailing=0) 877 self.stream.write(":\n") 878 self.stream.write("</div>\n") 879 self.stream.write("<div class='body nowrap'>\n") 880 self.dispatch(node.body) 881 self.stream.write("</div>\n") 882 for spec, assign, statement in node.handlers: 883 self.stream.write("<div>\n") 884 self._keyword("except") 885 if spec is not None: 886 self.dispatch(spec) 887 if assign is not None: 888 self.stream.write(",\n") 889 self.dispatch(assign) 890 self.stream.write(":\n") 891 self.stream.write("</div>\n") 892 self.stream.write("<div class='body nowrap'>\n") 893 self.dispatch(statement) 894 self.stream.write("</div>\n") 895 if node.else_ is not None: 896 self.stream.write("<div>\n") 897 self._keyword("else", trailing=0) 898 self.stream.write(":\n") 899 self.stream.write("</div>\n") 900 self.stream.write("<div class='body nowrap'>\n") 901 self.dispatch(node.else_) 902 self.stream.write("</div>\n") 903 self.stream.write("</div>\n") 904 905 def visitTryFinally(self, node): 906 self.stream.write("<div class='tryfinally nowrap'>\n") 907 self.stream.write("<div>\n") 908 self._keyword("try", trailing=0) 909 self.stream.write(":\n") 910 self.stream.write("</div>\n") 911 self.stream.write("<div class='body nowrap'>\n") 912 self.dispatch(node.body) 913 self.stream.write("</div>\n") 914 self.stream.write("<div>\n") 915 self._keyword("finally", trailing=0) 916 self.stream.write(":\n") 917 self.stream.write("</div>\n") 918 self.stream.write("<div class='body nowrap'>\n") 919 self.dispatch(node.final) 920 self.stream.write("</div>\n") 921 self.stream.write("</div>\n") 922 923 def visitWhile(self, node): 924 self.stream.write("<div class='while nowrap'>\n") 925 self.stream.write("<div>\n") 926 self._keyword("while") 927 self.dispatch(node.test) 928 self.stream.write(":\n") 929 self.stream.write("</div>\n") 930 self.stream.write("<div class='body nowrap'>\n") 931 self.dispatch(node.body) 932 self.stream.write("</div>\n") 933 if node.else_ is not None: 934 self.stream.write("<div>\n") 935 self._keyword("else", trailing=0) 936 self.stream.write(":\n") 937 self.stream.write("</div>\n") 938 self.stream.write("<div class='body nowrap'>\n") 939 self.dispatch(node.else_) 940 self.stream.write("</div>\n") 941 self.stream.write("</div>\n") 942 943 def visitYield(self, node): 944 self.stream.write("<div class='yield nowrap'>\n") 945 self._keyword("yield") 946 self.dispatch(node.value) 947 self.stream.write("</div>\n") 948 949 # Expression-related helper methods. 950 951 def _visitBitBinary(self, node, name, symbol): 952 self._span_start(name) 953 first = 1 954 for node in node.nodes: 955 if not first: 956 self._op(symbol, name, 1) 957 self.dispatch(node) 958 first = 0 959 self._span_end() 960 961 def _visitBinary(self, node, name, symbol): 962 self._span_start(name) 963 self.dispatch(node.left) 964 self._op(symbol, name, 1) 965 self.dispatch(node.right) 966 self._span_end() 967 968 def _visitUnary(self, node, name, symbol): 969 self._span_start(name) 970 self._op(symbol, name, trailing=0) 971 self.dispatch(node.expr) 972 self._span_end() 973 974 # Expressions. 975 976 def visitAdd(self, node): 977 self._visitBinary(node, "add", "+") 978 979 def visitAnd(self, node): 980 self._span_start("and") 981 first = 1 982 for n in node.nodes: 983 if not first: 984 self._keyword("and", 1) 985 self.dispatch(n) 986 first = 0 987 self._span_end() 988 989 def visitAssAttr(self, node): 990 possible_types = self.possible_accessor_types(node, defining_users=0) 991 target_names = set(["%s%s" % (is_static and "static " or "", target_name) 992 for target_name, is_static in possible_types]) 993 attributes = self._get_attributes(possible_types, node.attrname) 994 995 wraps_getattr = isinstance(node.expr, compiler.ast.Getattr) 996 997 if not wraps_getattr: 998 self._span_start("assattr") 999 self._accessor_start(target_names) 1000 self.dispatch(node.expr) 1001 if not wraps_getattr: 1002 self._accessor_end(target_names) 1003 self.stream.write(".") 1004 self._attribute_start(node.attrname, attributes) 1005 self._span(node.attrname, "attrname" + (not target_names and " no-targets" or "")) 1006 self._attribute_end(attributes) 1007 if not wraps_getattr: 1008 self._span_end() 1009 1010 def visitAssList(self, node): 1011 self._span_start("list") 1012 self.stream.write("[") 1013 self._sequence(node) 1014 self.stream.write("]") 1015 self._span_end() 1016 1017 def visitAssName(self, node): 1018 self._assname(node.name, node) 1019 1020 def visitAssTuple(self, node): 1021 self._span_start("tuple") 1022 self.stream.write("(") 1023 self._sequence(node) 1024 self.stream.write(")") 1025 self._span_end() 1026 1027 def visitBitand(self, node): 1028 self._visitBitBinary(node, "bitand", "&") 1029 1030 def visitBitor(self, node): 1031 self._visitBitBinary(node, "bitor", "|") 1032 1033 def visitBitxor(self, node): 1034 self._visitBitBinary(node, "bitxor", "^") 1035 1036 def visitCallFunc(self, node): 1037 self._span_start("callfunc") 1038 self.dispatch(node.node) 1039 self._span_start("call") 1040 self.stream.write("(") 1041 first = 1 1042 for arg in node.args: 1043 if not first: 1044 self.stream.write(", ") 1045 self.dispatch(arg) 1046 first = 0 1047 if node.star_args is not None: 1048 if not first: 1049 self.stream.write(", *") 1050 self.dispatch(node.star_args) 1051 first = 0 1052 if node.dstar_args is not None: 1053 if not first: 1054 self.stream.write(", **") 1055 self.dispatch(node.dstar_args) 1056 first = 0 1057 self.stream.write(")") 1058 self._span_end() 1059 self._span_end() 1060 1061 def visitCompare(self, node): 1062 self._span_start("compare") 1063 self.dispatch(node.expr) 1064 for op_name, expr in node.ops: 1065 self._op(op_name, operator_functions.get(op_name), 1) 1066 self.dispatch(expr) 1067 self._span_end() 1068 1069 def visitConst(self, node): 1070 if isinstance(node.value, (str, unicode)): 1071 self._span_start("str") 1072 self.stream.write(self._text(repr(node.value))) 1073 if isinstance(node.value, (str, unicode)): 1074 self._span_end() 1075 1076 def visitDict(self, node): 1077 self._span_start("dict") 1078 self.stream.write("{") 1079 self._mapping(node) 1080 self.stream.write("}") 1081 self._span_end() 1082 1083 def visitDiv(self, node): 1084 self._visitBinary(node, "div", "/") 1085 1086 def visitFloorDiv(self, node): 1087 self._visitBinary(node, "floordiv", "//") 1088 1089 def visitGetattr(self, node): 1090 possible_types = self.possible_accessor_types(node, defining_users=0) 1091 target_names = set(["%s%s" % (is_static and "static " or "", target_name) 1092 for target_name, is_static in possible_types]) 1093 attributes = self._get_attributes(possible_types, node.attrname) 1094 1095 wraps_getattr = isinstance(node.expr, compiler.ast.Getattr) 1096 1097 if not wraps_getattr: 1098 self._span_start("getattr") 1099 self._accessor_start(target_names) 1100 self.dispatch(node.expr) 1101 if not wraps_getattr: 1102 self._accessor_end(target_names) 1103 self.stream.write(".") 1104 self._attribute_start(node.attrname, attributes) 1105 self._span(node.attrname, "attrname" + (not target_names and " no-targets" or "")) 1106 self._attribute_end(attributes) 1107 if not wraps_getattr: 1108 self._span_end() 1109 1110 def visitGenExpr(self, node): 1111 self._span_start("genexpr") 1112 self.stream.write("(") 1113 self.dispatch(node.code) 1114 self.stream.write(")") 1115 self._span_end() 1116 1117 def visitGenExprFor(self, node): 1118 self._span_start("genexprfor") 1119 self._keyword("for", 1) 1120 self._span_start("item") 1121 self.dispatch(node.assign) 1122 self._span_end() 1123 self._keyword("in", 1) 1124 self._span_start("collection") 1125 self.dispatch(node.iter) 1126 self._span_end() 1127 for if_ in node.ifs: 1128 self.dispatch(if_) 1129 self._span_end() 1130 1131 def visitGenExprIf(self, node): 1132 self._span_start("genexprif") 1133 self._span_start("conditional") 1134 self._keyword("if", 1) 1135 self.dispatch(node.test) 1136 self._span_end() 1137 self._span_end() 1138 1139 def visitGenExprInner(self, node): 1140 self._span_start("genexprinner") 1141 self.dispatch(node.expr) 1142 for qual in node.quals: 1143 self.dispatch(qual) 1144 self._span_end() 1145 1146 def visitIfExp(self, node): 1147 self._span_start("ifexp") 1148 self.dispatch(node.then) 1149 self._keyword("if", 1) 1150 self.dispatch(node.test) 1151 self._keyword("else", 1) 1152 self.dispatch(node.else_) 1153 self._span_end() 1154 1155 def visitInvert(self, node): 1156 self._visitUnary(node, "invert", "~") 1157 1158 def visitKeyword(self, node): 1159 self._span_start("keyword-arg") 1160 self.stream.write(node.name) 1161 self.stream.write("=") 1162 self.dispatch(node.expr) 1163 self._span_end() 1164 1165 def visitLambda(self, node): 1166 if hasattr(node, "unit"): 1167 fn = node.unit 1168 else: 1169 print >>sys.stderr, "Warning: function %s not recognised!" % node.name 1170 return 1171 1172 self._span_start("lambda") 1173 self._keyword("lambda") 1174 self._parameters(fn, node) 1175 self.stream.write(": ") 1176 self._span_start("code") 1177 self.dispatch(node.code) 1178 self._span_end() 1179 self._span_end() 1180 1181 def visitLeftShift(self, node): 1182 self._visitBinary(node, "lshift", "<<") 1183 1184 visitList = visitAssList 1185 1186 def visitListComp(self, node): 1187 self._span_start("listcomp") 1188 self.stream.write("[") 1189 self.dispatch(node.expr) 1190 for qual in node.quals: 1191 self.dispatch(qual) 1192 self.stream.write("]") 1193 self._span_end() 1194 1195 def visitListCompFor(self, node): 1196 self._span_start("listcompfor") 1197 self._keyword("for", 1) 1198 self._span_start("item") 1199 self.dispatch(node.assign) 1200 self._span_end() 1201 self._keyword("in", 1) 1202 self._span_start("collection") 1203 self.dispatch(node.list) 1204 self._span_end() 1205 for if_ in node.ifs: 1206 self.dispatch(if_) 1207 self._span_end() 1208 1209 def visitListCompIf(self, node): 1210 self._span_start("listcompif") 1211 self._span_start("conditional") 1212 self._keyword("if", 1) 1213 self.dispatch(node.test) 1214 self._span_end() 1215 self._span_end() 1216 1217 def visitMod(self, node): 1218 self._visitBinary(node, "mod", "%") 1219 1220 def visitMul(self, node): 1221 self._visitBinary(node, "mul", "*") 1222 1223 def visitName(self, node): 1224 if hasattr(node, "_scope"): 1225 scope = node._scope 1226 self._name_start() 1227 self.stream.write(node.name) 1228 self._popup_start() 1229 self._scope(scope) 1230 self._popup_end() 1231 self._name_end() 1232 else: 1233 self._span(node.name) 1234 1235 def visitNot(self, node): 1236 self._span_start("not") 1237 self._keyword("not") 1238 self.dispatch(node.expr) 1239 self._span_end() 1240 1241 def visitOr(self, node): 1242 self._span_start("or") 1243 first = 1 1244 for n in node.nodes: 1245 if not first: 1246 self._keyword("or", 1) 1247 self.dispatch(n) 1248 first = 0 1249 self._span_end() 1250 1251 def visitPower(self, node): 1252 self._visitBinary(node, "pow", "**") 1253 1254 def visitRightShift(self, node): 1255 self._visitBinary(node, "rshift", ">>") 1256 1257 def visitSlice(self, node): 1258 self._span_start("slice") 1259 self.dispatch(node.expr) 1260 self.stream.write("[") 1261 if node.lower: 1262 self.dispatch(node.lower) 1263 self.stream.write(":") 1264 if node.upper: 1265 self.dispatch(node.upper) 1266 # NOTE: Step? 1267 self.stream.write("]") 1268 self._span_end() 1269 1270 def visitSliceobj(self, node): 1271 self._span_start("sliceobj") 1272 first = 1 1273 for n in node.nodes: 1274 if not first: 1275 self.stream.write(":") 1276 self.dispatch(n) 1277 self._span_end() 1278 1279 def visitSub(self, node): 1280 self._visitBinary(node, "sub", "-") 1281 1282 def visitSubscript(self, node): 1283 self._span_start("subscript") 1284 self.dispatch(node.expr) 1285 self.stream.write("[") 1286 first = 1 1287 for sub in node.subs: 1288 if not first: 1289 self.stream.write(", ") 1290 self.dispatch(sub) 1291 first = 0 1292 self.stream.write("]") 1293 self._span_end() 1294 1295 visitTuple = visitAssTuple 1296 1297 def visitUnaryAdd(self, node): 1298 self._visitUnary(node, "add", "+") 1299 1300 def visitUnarySub(self, node): 1301 self._visitUnary(node, "sub", "-") 1302 1303 # Output preparation methods. 1304 1305 def _sequence(self, node): 1306 first = 1 1307 for n in node.nodes: 1308 if not first: 1309 self.stream.write(", ") 1310 self.dispatch(n) 1311 first = 0 1312 1313 def _mapping(self, node): 1314 first = 1 1315 for k, v in node.items: 1316 if not first: 1317 self.stream.write(", ") 1318 self.dispatch(k) 1319 self.stream.write(" : ") 1320 self.dispatch(v) 1321 first = 0 1322 1323 def _parameters(self, fn, node): 1324 nparams = len(fn.positional_names) 1325 ndefaults = len(fn.defaults) 1326 first_with_default = nparams - ndefaults 1327 1328 first = 1 1329 for n, param in enumerate(fn.positional_names): 1330 if not first: 1331 self.stream.write(", ") 1332 1333 # Handle tuple parameters. 1334 1335 if isinstance(param, tuple): 1336 self._tuple_parameter(param, node) 1337 else: 1338 self._assname(param, node) 1339 1340 n_default = n - first_with_default 1341 if n_default >= 0: 1342 self._default(fn.defaults[n_default]) 1343 first = 0 1344 1345 if fn.has_star: 1346 if not first: 1347 self.stream.write(", *") 1348 self._name(fn.star_name) 1349 1350 if fn.has_dstar: 1351 if not first: 1352 self.stream.write(", **") 1353 self._name(fn.dstar_name) 1354 1355 def _tuple_parameter(self, parameters, node): 1356 self.stream.write("(") 1357 1358 first = 1 1359 for param in parameters: 1360 if not first: 1361 self.stream.write(", ") 1362 1363 # Handle tuples. 1364 1365 if isinstance(param, tuple): 1366 self._tuple_parameter(param, node) 1367 else: 1368 self._assname(param, node) 1369 1370 first = 0 1371 1372 self.stream.write(")") 1373 1374 def _default(self, default): 1375 self.stream.write("=") 1376 self.dispatch(default) 1377 1378 # Convenience functions. 1379 1380 def summarise(module, program, filename): 1381 stream = open(filename, "wb") 1382 try: 1383 summary = Summary(module, program) 1384 summary.to_stream(stream) 1385 finally: 1386 stream.close() 1387 1388 def annotate(module, program, filename): 1389 stream = open(filename, "wb") 1390 try: 1391 source = AnnotatedSource(module, program) 1392 source.to_stream(stream) 1393 finally: 1394 stream.close() 1395 1396 def interfaces(program, filename): 1397 stream = open(filename, "wb") 1398 try: 1399 source = Interfaces(program) 1400 source.to_stream(stream) 1401 finally: 1402 stream.close() 1403 1404 def report(program, directory): 1405 if not exists(directory): 1406 os.mkdir(directory) 1407 1408 for module in program.get_importer().get_modules(): 1409 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1410 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1411 1412 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1413 1414 # vim: tabstop=4 expandtab shiftwidth=4