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 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 self._span_start("assname") 311 if not self._attrcombined(name, node): 312 self._span(name) 313 self._span_end() 314 315 def _op(self, symbol, name=None, leading=0, trailing=1): 316 if leading: 317 self.stream.write(" ") 318 self._span_start(name and "operation" or None) 319 self._span(symbol, "operator") 320 if name is not None: 321 self._popup_start() 322 self.stream.write("<div class='opnames'>") 323 self._name_link("operator", "operator.%s" % name, name) 324 self.stream.write("</div>\n") 325 self._popup_end() 326 # NOTE: Handle "is" and "in". 327 self._span_end() 328 if trailing: 329 self.stream.write(" ") 330 331 def _names_list_start(self, label, classes): 332 self.stream.write("<div class='%s'><span class='label'>%s</span><br />" % (classes, label)) 333 334 def _names_list_end(self): 335 self.stream.write("</div>\n") 336 337 def _names_list(self, names, label, classes): 338 if not names: 339 return 340 names = list(names) 341 names.sort() 342 343 self._names_list_start(label, classes) 344 first = True 345 for name in names: 346 if not first: 347 self.stream.write("<br />") 348 self.stream.write(name) 349 first = False 350 self._names_list_end() 351 352 def _attrcombined(self, name, node): 353 attrcombined = node._attrcombined and node._attrcombined.get(name) or [] 354 355 # Since assigned values will not be shown, produce a sorted list of 356 # distinct attribute name lists. 357 358 all_attrnames = set() 359 for usage in attrcombined: 360 if usage: 361 all_attrnames.add(tuple(usage.keys())) 362 363 if not all_attrnames: 364 return False 365 366 all_attrnames = list(all_attrnames) 367 all_attrnames.sort() 368 369 # Write the lists of attribute names. 370 371 self._name_start() 372 self.stream.write(name) 373 self._popup_start() 374 for attrnames in all_attrnames: 375 self._attrnames(attrnames) 376 self._popup_end() 377 self._name_end() 378 return True 379 380 def _attrnames(self, attrnames): 381 self._names_list(attrnames, "attributes", "attrnames") 382 383 def _typenames(self, typenames): 384 self._names_list(typenames, "types", "typenames") 385 386 def _accessor_start(self, target_names): 387 if target_names: 388 self._span_start("accessor") 389 self._popup_start("types-popup") 390 self._typenames(target_names) 391 self._popup_end() 392 393 def _accessor_end(self, target_names): 394 if target_names: 395 self._span_end() 396 397 def _values_to_attribute_names(self, attr): 398 399 "Get the output form of the values referenced by 'attr'." 400 401 if isinstance(attr, Const): 402 return [(repr(attr.get_value()), attr)] 403 elif isinstance(attr, Instance): 404 return [] 405 406 have_instances = False 407 values = [] 408 for v in attr.get_values(): 409 if isinstance(v, Const): 410 values.append((repr(v.get_value()), v)) 411 elif not isinstance(v, Instance): 412 values.append((v.full_name(), v)) 413 else: 414 have_instances = True 415 416 if have_instances: 417 values.append(("...", None)) 418 419 values.sort() 420 return values 421 422 def _attributes_to_attribute_names(self, attributes, attrname): 423 424 "Get the output form of the 'attributes'." 425 426 output = [] 427 for value, target, target_name in attributes: 428 if value and not isinstance(value, Instance): 429 fullname = value.full_name() 430 else: 431 fullname = target_name + "." + attrname 432 output.append((fullname, value)) 433 434 output.sort() 435 return output 436 437 def _attribute_start(self, attrname, attributes): 438 if attributes: 439 self._span_start("attr") 440 self._popup_start("attributes-popup") 441 self._names_list_start("attributes", "attrnames") 442 self._attribute_list(attributes) 443 self._names_list_end() 444 self._popup_end() 445 446 def _attribute_list(self, attributes): 447 448 # Mix links to attributes with labels indicating undetermined 449 # attributes. 450 451 last = None 452 for fullname, value in attributes: 453 if fullname != last: 454 if last is not None: 455 self.stream.write("<br />") 456 if value is not None and not isinstance(value, Instance): 457 self._object_name_ref(value.module, value, fullname, classes="attribute-name") 458 else: 459 self.stream.write(self._text(fullname)) 460 last = fullname 461 462 def _attribute_end(self, attributes): 463 if attributes: 464 self._span_end() 465 466 def _get_possible_types(self, attrname): 467 objtable = self.program.get_object_table() 468 return objtable.any_possible_objects([attrname]) 469 470 def _get_attributes(self, possible_types, attrname): 471 objtable = self.program.get_object_table() 472 attributes = [] 473 for target_name in possible_types: 474 target = objtable.get_object(target_name) 475 try: 476 attr = objtable.access(target_name, attrname) 477 except TableError: 478 continue 479 if attr.is_static_attribute(): 480 for v in attr.get_values(): 481 attributes.append((v, target, target_name)) 482 else: 483 attributes.append((None, target, target_name)) 484 485 return attributes 486 487 # Summary classes. 488 489 class Summary(Writer): 490 491 "Summarise classes and attributes in modules." 492 493 def __init__(self, module, program): 494 self.module = module 495 self.program = program 496 497 def to_stream(self, stream): 498 499 "Write the summary to the given 'stream'." 500 501 self.stream = stream 502 self.stream.write(html_header % { 503 "title" : "Module: %s" % self.module.full_name() 504 }) 505 self._write_classes(self.module) 506 self.stream.write(html_footer) 507 508 def _write_classes(self, module): 509 510 all_classes = {} 511 512 for obj in self.module.all_objects: 513 if isinstance(obj, Class): 514 all_classes[obj.name] = obj 515 516 if all_classes: 517 518 all_class_names = all_classes.keys() 519 all_class_names.sort() 520 521 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 522 self.stream.write("<thead>\n") 523 self.stream.write("<tr>\n") 524 self.stream.write("<th>Classes</th><th>Attributes</th>\n") 525 self.stream.write("</tr>\n") 526 self.stream.write("</thead>\n") 527 528 for name in all_class_names: 529 self._write_class(all_classes[name]) 530 531 self.stream.write("</table>\n") 532 533 def _write_class(self, obj): 534 535 # Write the class... 536 537 self.stream.write("<tbody class='class'>\n") 538 self.stream.write("<tr>\n") 539 self.stream.write("<th class='summary-class' id='%s' rowspan='2'>" % self._attr(obj.full_name())) 540 self._object_name_ref(self.module, obj, classes="class-name") 541 self.stream.write("</th>\n") 542 543 # ...and instance attribute names in order... 544 545 attrs = obj.instance_attributes().values() 546 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 547 548 if attrs: 549 for attr in attrs: 550 self.stream.write("<td class='summary-attr'>%s</td>\n" % self._text(attr.name)) 551 else: 552 self.stream.write("<td class='summary-attr-absent'>None</td>\n") 553 554 self.stream.write("</tr>\n") 555 self.stream.write("<tr>\n") 556 557 # ...and class attribute names in order. 558 559 attrs = obj.class_attributes().values() 560 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 561 562 if attrs: 563 for attr in attrs: 564 if attr.is_strict_constant(): 565 value = attr.get_value() 566 if not isinstance(value, Const): 567 self.stream.write("<td class='summary-class-attr' id='%s'>" % self._attr(value.full_name())) 568 self._object_name_ref(self.module, value, attr.name, classes="summary-ref") 569 self.stream.write("</td>\n") 570 else: 571 self.stream.write("<td class='summary-class-attr'>%s</td>\n" % self._text(attr.name)) 572 else: 573 self.stream.write("<td class='summary-class-attr'>%s</td>\n" % self._text(attr.name)) 574 else: 575 self.stream.write("<td class='summary-class-attr-absent'>None</td>\n") 576 577 self.stream.write("</tr>\n") 578 self.stream.write("</tbody>\n") 579 580 class Interfaces(Writer): 581 582 "Summarise the interfaces used by reading the object table cache." 583 584 def __init__(self, program): 585 self.program = program 586 587 def to_stream(self, stream): 588 589 "Write the summary to the given 'stream'." 590 591 self.stream = stream 592 self.stream.write(html_header % { 593 "title" : "Interfaces" 594 }) 595 self._write_interfaces() 596 self.stream.write(html_footer) 597 598 def _write_interfaces(self): 599 objtable = self.program.get_object_table() 600 all_interfaces = objtable.all_cache.items() 601 all_interfaces.sort() 602 603 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 604 self.stream.write("<thead>\n") 605 self.stream.write("<tr>\n") 606 self.stream.write("<th>Complete Interfaces</th>\n") 607 self.stream.write("</tr>\n") 608 self.stream.write("</thead>\n") 609 self._write_interface_type(all_interfaces, "complete") 610 self.stream.write("</table>\n") 611 612 def _write_interface_type(self, interfaces, classes=""): 613 self.stream.write("<tbody>\n") 614 615 for names, objects in interfaces: 616 if names: 617 names = list(names) 618 names.sort() 619 self.stream.write("<tr>\n") 620 self.stream.write("<td class='summary-interface %s'>%s</td>" % (classes, ", ".join(names))) 621 self.stream.write("</tr>\n") 622 623 self.stream.write("</tbody>\n") 624 625 # Source code classes. 626 627 class AnnotatedSource(ASTVisitor, Writer): 628 629 "A module source code browser." 630 631 def __init__(self, module, program): 632 self.visitor = self 633 self.module = module 634 self.program = program 635 self.units = [] 636 637 def get_unit(self): 638 return self.units[-1] 639 640 def to_stream(self, stream): 641 642 "Write the annotated code to the given 'stream'." 643 644 self.stream = stream 645 self.stream.write(html_header % { 646 "title" : "Module: %s" % self.module.full_name() 647 }) 648 self.dispatch(self.module.astnode) 649 self.stream.write(html_footer) 650 651 def visitModule(self, node): 652 self.units.append(node.unit) 653 654 self._doc(node, "module") 655 self.default(node) 656 657 self.units.pop() 658 659 # Statements. 660 661 def visitAssert(self, node): 662 self.stream.write("<div class='assert nowrap'>\n") 663 self._keyword("assert") 664 self.dispatch(node.test) 665 if node.fail: 666 self.stream.write(", ") 667 self.dispatch(node.fail) 668 self.stream.write("</div>\n") 669 670 def visitAssign(self, node): 671 self.stream.write("<div class='assign nowrap'>\n") 672 for lvalue in node.nodes: 673 self.dispatch(lvalue) 674 self.stream.write(" = ") 675 self.dispatch(node.expr) 676 self.stream.write("</div>\n") 677 678 def visitAugAssign(self, node): 679 self.stream.write("<div class='augassign nowrap'>\n") 680 self.dispatch(node.node) 681 self._op(node.op, operator_functions[node.op], 1) 682 self.dispatch(node.expr) 683 self.stream.write("</div>\n") 684 685 def visitBreak(self, node): 686 self.stream.write("<div class='break nowrap'>\n") 687 self._keyword("break") 688 self.stream.write("</div>\n") 689 690 def visitClass(self, node): 691 if not used_by_unit(node): 692 self._docstring('"Class %s not generated."' % node.name) 693 return 694 695 # Use inspected details where possible. 696 697 cls = node.unit 698 self.units.append(cls) 699 700 bases = cls.bases 701 self.stream.write("<div class='class nowrap' id='%s'>\n" % cls.full_name()) 702 703 # Write the declaration line. 704 705 self.stream.write("<div>\n") 706 self._keyword("class") 707 self._object_name_def(self.module, cls, "class-name") 708 709 # Suppress the "object" class appearing alone. 710 711 if bases and not (len(bases) == 1 and bases[0].name == "object"): 712 self.stream.write("(") 713 first = True 714 for base in bases: 715 if not first: 716 self.stream.write(", ") 717 718 self._object_name_ref(base.module, base) 719 720 first = False 721 self.stream.write(")") 722 723 self.stream.write(":\n") 724 self.stream.write("</div>\n") 725 726 # Write the docstring and class body. 727 728 self.stream.write("<div class='body nowrap'>\n") 729 self._doc(node) 730 731 # NOTE: Some streams may not support tell. 732 733 x = self.stream.tell() 734 735 self.default(node.code) 736 737 # Check for no output. 738 739 if x == self.stream.tell(): 740 self.visitPass(None) 741 742 self.stream.write("</div>\n") 743 self.stream.write("</div>\n") 744 745 self.units.pop() 746 747 def visitContinue(self, node): 748 self.stream.write("<div class='continue nowrap'>\n") 749 self._keyword("continue") 750 self.stream.write("</div>\n") 751 752 def visitDiscard(self, node): 753 self.stream.write("<div class='discard nowrap'>\n") 754 self.default(node) 755 self.stream.write("</div>\n") 756 757 def visitFor(self, node): 758 self.stream.write("<div class='if nowrap'>\n") 759 self.stream.write("<div>\n") 760 self._keyword("for") 761 self.dispatch(node.assign) 762 self._keyword("in", 1) 763 self.dispatch(node.list) 764 self.stream.write(":\n") 765 self.stream.write("</div>\n") 766 self.stream.write("<div class='body nowrap'>\n") 767 self.dispatch(node.body) 768 self.stream.write("</div>\n") 769 if node.else_ is not None: 770 self.stream.write("<div>\n") 771 self._keyword("else", trailing=0) 772 self.stream.write(":\n") 773 self.stream.write("</div>\n") 774 self.stream.write("<div class='body nowrap'>\n") 775 self.dispatch(node.else_) 776 self.stream.write("</div>\n") 777 self.stream.write("</div>\n") 778 779 def visitFrom(self, node): 780 self.stream.write("<div class='from nowrap'>\n") 781 self._keyword("from") 782 self._module_link(node.modname) 783 self._keyword("import", 1) 784 first = True 785 for name, alias in node.names: 786 if not first: 787 self.stream.write(", ") 788 self._name(name) 789 if alias: 790 self._keyword("as", 1) 791 self._name(alias) 792 first = False 793 self.stream.write("</div>\n") 794 795 def visitFunction(self, node): 796 if not used_by_unit(node): 797 self._docstring('"Function %s not generated."' % node.name) 798 return 799 800 fn = node.unit 801 self.units.append(fn) 802 803 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 804 805 # Write the declaration line. 806 807 self.stream.write("<div>\n") 808 self._keyword("def") 809 self._object_name_def(self.module, fn, "function-name") 810 811 self.stream.write("(") 812 self._parameters(fn, node) 813 self.stream.write(")") 814 self.stream.write(":\n") 815 self.stream.write("</div>\n") 816 817 self.stream.write("<div class='body nowrap'>\n") 818 self._doc(node) 819 self.dispatch(node.code) 820 self.stream.write("</div>\n") 821 self.stream.write("</div>\n") 822 823 self.units.pop() 824 825 def visitGlobal(self, node): 826 self.stream.write("<div class='global nowrap'>\n") 827 self._keyword("global") 828 first = True 829 for name in node.names: 830 if not first: 831 self.stream.write(", ") 832 self.stream.write(name) 833 first = False 834 self.stream.write("</div>\n") 835 836 def visitIf(self, node): 837 self.stream.write("<div class='if nowrap'>\n") 838 first = True 839 for compare, stmt in node.tests: 840 self.stream.write("<div>\n") 841 if first: 842 self._keyword("if") 843 else: 844 self._keyword("elif") 845 self.dispatch(compare) 846 self.stream.write(":\n") 847 self.stream.write("</div>\n") 848 self.stream.write("<div class='body nowrap'>\n") 849 self.dispatch(stmt) 850 self.stream.write("</div>\n") 851 first = False 852 if node.else_ is not None: 853 self.stream.write("<div>\n") 854 self._keyword("else", trailing=0) 855 self.stream.write(":\n") 856 self.stream.write("</div>\n") 857 self.stream.write("<div class='body nowrap'>\n") 858 self.dispatch(node.else_) 859 self.stream.write("</div>\n") 860 self.stream.write("</div>\n") 861 862 def visitImport(self, node): 863 self.stream.write("<div class='import nowrap'>\n") 864 self._keyword("import") 865 first = True 866 for name, alias in node.names: 867 if not first: 868 self.stream.write(",\n") 869 self._module_link(name) 870 if alias: 871 self._keyword("as", 1) 872 self._name(alias) 873 first = False 874 self.stream.write("</div>\n") 875 876 def visitPass(self, node): 877 self.stream.write("<div class='pass nowrap'>\n") 878 self._keyword("pass") 879 self.stream.write("</div>\n") 880 881 def visitPrint(self, node): 882 self.stream.write("<div class='print nowrap'>\n") 883 self._keyword("print") 884 if node.dest is not None: 885 self.stream.write(">>\n") 886 self.dispatch(node.dest) 887 self.stream.write(",\n") 888 for n in node.nodes: 889 self.dispatch(n) 890 self.stream.write(",\n") 891 self.stream.write("</div>\n") 892 893 def visitPrintnl(self, node): 894 self.stream.write("<div class='printnl nowrap'>\n") 895 self._keyword("print") 896 if node.dest is not None: 897 self.stream.write(">>\n") 898 self.dispatch(node.dest) 899 first = False 900 else: 901 first = True 902 for n in node.nodes: 903 if not first: 904 self.stream.write(",\n") 905 self.dispatch(n) 906 first = False 907 self.stream.write("</div>\n") 908 909 def visitRaise(self, node): 910 self.stream.write("<div class='raise nowrap'>\n") 911 self._keyword("raise") 912 if node.expr1 is not None: 913 self.dispatch(node.expr1) 914 if node.expr2 is not None: 915 self.stream.write(",\n") 916 self.dispatch(node.expr2) 917 if node.expr3 is not None: 918 self.stream.write(",\n") 919 self.dispatch(node.expr3) 920 self.stream.write("</div>\n") 921 922 def visitReturn(self, node): 923 self.stream.write("<div class='return nowrap'>\n") 924 self._keyword("return") 925 self.dispatch(node.value) 926 self.stream.write("</div>\n") 927 928 def visitStmt(self, node): 929 self.stream.write("<div class='stmt nowrap'>\n") 930 self.default(node) 931 self.stream.write("</div>\n") 932 933 def visitTryExcept(self, node): 934 self.stream.write("<div class='tryexcept nowrap'>\n") 935 self.stream.write("<div>\n") 936 self._keyword("try", trailing=0) 937 self.stream.write(":\n") 938 self.stream.write("</div>\n") 939 self.stream.write("<div class='body nowrap'>\n") 940 self.dispatch(node.body) 941 self.stream.write("</div>\n") 942 for spec, assign, statement in node.handlers: 943 self.stream.write("<div>\n") 944 self._keyword("except") 945 if spec is not None: 946 self.dispatch(spec) 947 if assign is not None: 948 self.stream.write(",\n") 949 self.dispatch(assign) 950 self.stream.write(":\n") 951 self.stream.write("</div>\n") 952 self.stream.write("<div class='body nowrap'>\n") 953 self.dispatch(statement) 954 self.stream.write("</div>\n") 955 if node.else_ is not None: 956 self.stream.write("<div>\n") 957 self._keyword("else", trailing=0) 958 self.stream.write(":\n") 959 self.stream.write("</div>\n") 960 self.stream.write("<div class='body nowrap'>\n") 961 self.dispatch(node.else_) 962 self.stream.write("</div>\n") 963 self.stream.write("</div>\n") 964 965 def visitTryFinally(self, node): 966 self.stream.write("<div class='tryfinally nowrap'>\n") 967 self.stream.write("<div>\n") 968 self._keyword("try", trailing=0) 969 self.stream.write(":\n") 970 self.stream.write("</div>\n") 971 self.stream.write("<div class='body nowrap'>\n") 972 self.dispatch(node.body) 973 self.stream.write("</div>\n") 974 self.stream.write("<div>\n") 975 self._keyword("finally", trailing=0) 976 self.stream.write(":\n") 977 self.stream.write("</div>\n") 978 self.stream.write("<div class='body nowrap'>\n") 979 self.dispatch(node.final) 980 self.stream.write("</div>\n") 981 self.stream.write("</div>\n") 982 983 def visitWhile(self, node): 984 self.stream.write("<div class='while nowrap'>\n") 985 self.stream.write("<div>\n") 986 self._keyword("while") 987 self.dispatch(node.test) 988 self.stream.write(":\n") 989 self.stream.write("</div>\n") 990 self.stream.write("<div class='body nowrap'>\n") 991 self.dispatch(node.body) 992 self.stream.write("</div>\n") 993 if node.else_ is not None: 994 self.stream.write("<div>\n") 995 self._keyword("else", trailing=0) 996 self.stream.write(":\n") 997 self.stream.write("</div>\n") 998 self.stream.write("<div class='body nowrap'>\n") 999 self.dispatch(node.else_) 1000 self.stream.write("</div>\n") 1001 self.stream.write("</div>\n") 1002 1003 def visitYield(self, node): 1004 self.stream.write("<div class='yield nowrap'>\n") 1005 self._keyword("yield") 1006 self.dispatch(node.value) 1007 self.stream.write("</div>\n") 1008 1009 # Expression-related helper methods. 1010 1011 def _visitBitBinary(self, node, symbol): 1012 name = operator_functions[node.__class__.__name__] 1013 self._span_start(name) 1014 first = True 1015 for node in node.nodes: 1016 if not first: 1017 self._op(symbol, name, 1) 1018 self.dispatch(node) 1019 first = False 1020 self._span_end() 1021 1022 def _visitBinary(self, node, symbol): 1023 name = operator_functions[node.__class__.__name__] 1024 self._span_start(name) 1025 self.dispatch(node.left) 1026 self._op(symbol, name, 1) 1027 self.dispatch(node.right) 1028 self._span_end() 1029 1030 def _visitUnary(self, node, symbol): 1031 name = operator_functions[node.__class__.__name__] 1032 self._span_start(name) 1033 self._op(symbol, name, trailing=0) 1034 self.dispatch(node.expr) 1035 self._span_end() 1036 1037 # Expressions. 1038 1039 def visitAdd(self, node): 1040 self._visitBinary(node, "+") 1041 1042 def visitAnd(self, node): 1043 self._span_start("and") 1044 first = True 1045 for n in node.nodes: 1046 if not first: 1047 self._keyword("and", 1) 1048 self.dispatch(n) 1049 first = False 1050 self._span_end() 1051 1052 def visitAssAttr(self, node): 1053 possible_types = self.possible_accessor_types(node, defining_users=0) 1054 1055 # Record whether types were already deduced. If not, get types using 1056 # only this attribute. 1057 1058 deduced = possible_types 1059 if not possible_types: 1060 possible_types = self._get_possible_types(node.attrname) 1061 1062 self.record_unknown_targets(possible_types, deduced, node) 1063 attributes = self._get_attributes(possible_types, node.attrname) 1064 1065 wraps_getattr = self._has_descendant(node.expr, compiler.ast.Getattr) 1066 1067 if not wraps_getattr: 1068 self._span_start("assattr") 1069 self._accessor_start(possible_types) 1070 self.dispatch(node.expr) 1071 if not wraps_getattr: 1072 self._accessor_end(possible_types) 1073 self.stream.write(".") 1074 self._attribute_start(node.attrname, self._attributes_to_attribute_names(attributes, node.attrname)) 1075 self._span(node.attrname, "attrname" + (not possible_types and " no-targets" or not deduced and " any-target" or "")) 1076 self._attribute_end(attributes) 1077 if not wraps_getattr: 1078 self._span_end() 1079 1080 def visitAssList(self, node): 1081 self._span_start("list") 1082 self.stream.write("[") 1083 self._sequence(node) 1084 self.stream.write("]") 1085 self._span_end() 1086 1087 def visitAssName(self, node): 1088 self._assname(node.name, node) 1089 1090 def visitAssTuple(self, node): 1091 self._span_start("tuple") 1092 self.stream.write("(") 1093 self._sequence(node) 1094 self.stream.write(")") 1095 self._span_end() 1096 1097 def visitBitand(self, node): 1098 self._visitBitBinary(node, "&") 1099 1100 def visitBitor(self, node): 1101 self._visitBitBinary(node, "|") 1102 1103 def visitBitxor(self, node): 1104 self._visitBitBinary(node, "^") 1105 1106 def visitCallFunc(self, node): 1107 self._span_start("callfunc") 1108 self.dispatch(node.node) 1109 self._span_start("call") 1110 self.stream.write("(") 1111 first = True 1112 for arg in node.args: 1113 if not first: 1114 self.stream.write(", ") 1115 self.dispatch(arg) 1116 first = False 1117 if node.star_args is not None: 1118 if not first: 1119 self.stream.write(", *") 1120 self.dispatch(node.star_args) 1121 first = False 1122 if node.dstar_args is not None: 1123 if not first: 1124 self.stream.write(", **") 1125 self.dispatch(node.dstar_args) 1126 first = False 1127 self.stream.write(")") 1128 self._span_end() 1129 self._span_end() 1130 1131 def visitCompare(self, node): 1132 self._span_start("compare") 1133 self.dispatch(node.expr) 1134 for op_name, expr in node.ops: 1135 self._op(op_name, operator_functions.get(op_name), 1) 1136 self.dispatch(expr) 1137 self._span_end() 1138 1139 def visitConst(self, node): 1140 if isinstance(node.value, (str, unicode)): 1141 self._span_start("str") 1142 self.stream.write(self._text(repr(node.value))) 1143 if isinstance(node.value, (str, unicode)): 1144 self._span_end() 1145 1146 def visitDict(self, node): 1147 self._span_start("dict") 1148 self.stream.write("{") 1149 self._mapping(node) 1150 self.stream.write("}") 1151 self._span_end() 1152 1153 def visitDiv(self, node): 1154 self._visitBinary(node, "/") 1155 1156 def visitFloorDiv(self, node): 1157 self._visitBinary(node, "//") 1158 1159 def visitGetattr(self, node): 1160 possible_types = self.possible_accessor_types(node, defining_users=0) 1161 1162 # Record whether types were already deduced. If not, get types using 1163 # only this attribute. 1164 1165 deduced = possible_types 1166 if not possible_types: 1167 possible_types = self._get_possible_types(node.attrname) 1168 1169 self.record_unknown_targets(possible_types, deduced, node) 1170 attributes = self._get_attributes(possible_types, node.attrname) 1171 1172 wraps_getattr = self._has_descendant(node.expr, compiler.ast.Getattr) 1173 1174 if not wraps_getattr: 1175 self._span_start("getattr") 1176 self._accessor_start(possible_types) 1177 1178 self.dispatch(node.expr) 1179 1180 if not wraps_getattr: 1181 self._accessor_end(possible_types) 1182 1183 self.stream.write(".") 1184 1185 self._attribute_start(node.attrname, self._attributes_to_attribute_names(attributes, node.attrname)) 1186 self._span(node.attrname, "attrname" + (not possible_types and " no-targets" or not deduced and " any-target" or "")) 1187 self._attribute_end(attributes) 1188 1189 if not wraps_getattr: 1190 self._span_end() 1191 1192 def visitGenExpr(self, node): 1193 self._span_start("genexpr") 1194 self.stream.write("(") 1195 self.dispatch(node.code) 1196 self.stream.write(")") 1197 self._span_end() 1198 1199 def visitGenExprFor(self, node): 1200 self._span_start("genexprfor") 1201 self._keyword("for", 1) 1202 self._span_start("item") 1203 self.dispatch(node.assign) 1204 self._span_end() 1205 self._keyword("in", 1) 1206 self._span_start("collection") 1207 self.dispatch(node.iter) 1208 self._span_end() 1209 for if_ in node.ifs: 1210 self.dispatch(if_) 1211 self._span_end() 1212 1213 def visitGenExprIf(self, node): 1214 self._span_start("genexprif") 1215 self._span_start("conditional") 1216 self._keyword("if", 1) 1217 self.dispatch(node.test) 1218 self._span_end() 1219 self._span_end() 1220 1221 def visitGenExprInner(self, node): 1222 self._span_start("genexprinner") 1223 self.dispatch(node.expr) 1224 for qual in node.quals: 1225 self.dispatch(qual) 1226 self._span_end() 1227 1228 def visitIfExp(self, node): 1229 self._span_start("ifexp") 1230 self.dispatch(node.then) 1231 self._keyword("if", 1) 1232 self.dispatch(node.test) 1233 self._keyword("else", 1) 1234 self.dispatch(node.else_) 1235 self._span_end() 1236 1237 def visitInvert(self, node): 1238 self._visitUnary(node, "~") 1239 1240 def visitKeyword(self, node): 1241 self._span_start("keyword-arg") 1242 self.stream.write(node.name) 1243 self.stream.write("=") 1244 self.dispatch(node.expr) 1245 self._span_end() 1246 1247 def visitLambda(self, node): 1248 fn = node.unit 1249 self.units.append(fn) 1250 1251 self._span_start("lambda") 1252 self._keyword("lambda") 1253 self._parameters(fn, node) 1254 self.stream.write(": ") 1255 self._span_start("code") 1256 self.dispatch(node.code) 1257 self._span_end() 1258 self._span_end() 1259 1260 self.units.pop() 1261 1262 def visitLeftShift(self, node): 1263 self._visitBinary(node, "<<") 1264 1265 visitList = visitAssList 1266 1267 def visitListComp(self, node): 1268 self._span_start("listcomp") 1269 self.stream.write("[") 1270 self.dispatch(node.expr) 1271 for qual in node.quals: 1272 self.dispatch(qual) 1273 self.stream.write("]") 1274 self._span_end() 1275 1276 def visitListCompFor(self, node): 1277 self._span_start("listcompfor") 1278 self._keyword("for", 1) 1279 self._span_start("item") 1280 self.dispatch(node.assign) 1281 self._span_end() 1282 self._keyword("in", 1) 1283 self._span_start("collection") 1284 self.dispatch(node.list) 1285 self._span_end() 1286 for if_ in node.ifs: 1287 self.dispatch(if_) 1288 self._span_end() 1289 1290 def visitListCompIf(self, node): 1291 self._span_start("listcompif") 1292 self._span_start("conditional") 1293 self._keyword("if", 1) 1294 self.dispatch(node.test) 1295 self._span_end() 1296 self._span_end() 1297 1298 def visitMod(self, node): 1299 self._visitBinary(node, "%") 1300 1301 def visitMul(self, node): 1302 self._visitBinary(node, "*") 1303 1304 def visitName(self, node): 1305 if node._scope: 1306 scope = node._scope 1307 self._name_start() 1308 self.stream.write(node.name) 1309 self._popup_start() 1310 self._scope(node._scope, node._attr) 1311 self._popup_end() 1312 self._name_end() 1313 else: 1314 self._span(node.name) 1315 1316 def visitNot(self, node): 1317 self._span_start("not") 1318 self._keyword("not") 1319 self.dispatch(node.expr) 1320 self._span_end() 1321 1322 def visitOr(self, node): 1323 self._span_start("or") 1324 first = True 1325 for n in node.nodes: 1326 if not first: 1327 self._keyword("or", 1) 1328 self.dispatch(n) 1329 first = False 1330 self._span_end() 1331 1332 def visitPower(self, node): 1333 self._visitBinary(node, "**") 1334 1335 def visitRightShift(self, node): 1336 self._visitBinary(node, ">>") 1337 1338 def visitSlice(self, node): 1339 self._span_start("slice") 1340 self.dispatch(node.expr) 1341 self.stream.write("[") 1342 if node.lower: 1343 self.dispatch(node.lower) 1344 self.stream.write(":") 1345 if node.upper: 1346 self.dispatch(node.upper) 1347 # NOTE: Step? 1348 self.stream.write("]") 1349 self._span_end() 1350 1351 def visitSliceobj(self, node): 1352 self._span_start("sliceobj") 1353 first = True 1354 for n in node.nodes: 1355 if not first: 1356 self.stream.write(":") 1357 self.dispatch(n) 1358 self._span_end() 1359 1360 def visitSub(self, node): 1361 self._visitBinary(node, "-") 1362 1363 def visitSubscript(self, node): 1364 self._span_start("subscript") 1365 self.dispatch(node.expr) 1366 self.stream.write("[") 1367 first = True 1368 for sub in node.subs: 1369 if not first: 1370 self.stream.write(", ") 1371 self.dispatch(sub) 1372 first = False 1373 self.stream.write("]") 1374 self._span_end() 1375 1376 visitTuple = visitAssTuple 1377 1378 def visitUnaryAdd(self, node): 1379 self._visitUnary(node, "+") 1380 1381 def visitUnarySub(self, node): 1382 self._visitUnary(node, "-") 1383 1384 # Output preparation methods. 1385 1386 def _sequence(self, node): 1387 first = True 1388 for n in node.nodes: 1389 if not first: 1390 self.stream.write(", ") 1391 self.dispatch(n) 1392 first = False 1393 1394 def _mapping(self, node): 1395 first = True 1396 for k, v in node.items: 1397 if not first: 1398 self.stream.write(", ") 1399 self.dispatch(k) 1400 self.stream.write(" : ") 1401 self.dispatch(v) 1402 first = False 1403 1404 def _parameters(self, fn, node): 1405 nparams = len(fn.positional_names) 1406 ndefaults = len(fn.defaults) 1407 first_with_default = nparams - ndefaults 1408 1409 first = True 1410 for n, param in enumerate(fn.positional_names): 1411 if not first: 1412 self.stream.write(", ") 1413 1414 # Handle tuple parameters. 1415 1416 if isinstance(param, tuple): 1417 self._tuple_parameter(param, node) 1418 else: 1419 self._assname(param, node) 1420 1421 n_default = n - first_with_default 1422 if n_default >= 0: 1423 self._default(fn.defaults[n_default]) 1424 first = False 1425 1426 if fn.has_star: 1427 if not first: 1428 self.stream.write(", *") 1429 self._name(fn.star_name) 1430 1431 if fn.has_dstar: 1432 if not first: 1433 self.stream.write(", **") 1434 self._name(fn.dstar_name) 1435 1436 def _tuple_parameter(self, parameters, node): 1437 self.stream.write("(") 1438 1439 first = True 1440 for param in parameters: 1441 if not first: 1442 self.stream.write(", ") 1443 1444 # Handle tuples. 1445 1446 if isinstance(param, tuple): 1447 self._tuple_parameter(param, node) 1448 else: 1449 self._assname(param, node) 1450 1451 first = False 1452 1453 self.stream.write(")") 1454 1455 def _default(self, default): 1456 self.stream.write("=") 1457 self.dispatch(default) 1458 1459 # Statistics gathering methods. 1460 1461 def possible_accessor_types(self, node, defining_users=1): 1462 return set([tn for (tn, st) in ASTVisitor.possible_accessor_types(self, node, defining_users)]) 1463 1464 def record_unknown_targets(self, possible_types, deduced, node): 1465 if not possible_types: 1466 self.program.unknown_target_nodes.append((self.units[-1], node)) 1467 elif not deduced: 1468 self.program.independent_target_nodes.append((self.units[-1], node)) 1469 1470 # Utility methods. 1471 1472 def _has_descendant(self, node, nodetype): 1473 if isinstance(node, nodetype): 1474 return True 1475 else: 1476 for n in node.getChildNodes(): 1477 if self._has_descendant(n, nodetype): 1478 return True 1479 return False 1480 1481 # Convenience functions. 1482 1483 def summarise(module, program, filename): 1484 stream = open(filename, "wb") 1485 try: 1486 summary = Summary(module, program) 1487 summary.to_stream(stream) 1488 finally: 1489 stream.close() 1490 1491 def annotate(module, program, filename): 1492 stream = open(filename, "wb") 1493 try: 1494 source = AnnotatedSource(module, program) 1495 source.to_stream(stream) 1496 finally: 1497 stream.close() 1498 1499 def interfaces(program, filename): 1500 stream = open(filename, "wb") 1501 try: 1502 source = Interfaces(program) 1503 source.to_stream(stream) 1504 finally: 1505 stream.close() 1506 1507 def report(program, directory): 1508 if not exists(directory): 1509 os.mkdir(directory) 1510 1511 for module in program.get_importer().get_modules(): 1512 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1513 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1514 1515 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1516 1517 # vim: tabstop=4 expandtab shiftwidth=4