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