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.program.unknown_target_nodes = [] 636 self.units = [] 637 638 def get_unit(self): 639 return self.units[-1] 640 641 def to_stream(self, stream): 642 643 "Write the annotated code to the given 'stream'." 644 645 self.stream = stream 646 self.stream.write(html_header % { 647 "title" : "Module: %s" % self.module.full_name() 648 }) 649 self.dispatch(self.module.astnode) 650 self.stream.write(html_footer) 651 652 def visitModule(self, node): 653 self.units.append(node.unit) 654 655 self._doc(node, "module") 656 self.default(node) 657 658 self.units.pop() 659 660 # Statements. 661 662 def visitAssert(self, node): 663 self.stream.write("<div class='assert nowrap'>\n") 664 self._keyword("assert") 665 self.dispatch(node.test) 666 if node.fail: 667 self.stream.write(", ") 668 self.dispatch(node.fail) 669 self.stream.write("</div>\n") 670 671 def visitAssign(self, node): 672 self.stream.write("<div class='assign nowrap'>\n") 673 for lvalue in node.nodes: 674 self.dispatch(lvalue) 675 self.stream.write(" = ") 676 self.dispatch(node.expr) 677 self.stream.write("</div>\n") 678 679 def visitAugAssign(self, node): 680 self.stream.write("<div class='augassign nowrap'>\n") 681 self.dispatch(node.node) 682 self._op(node.op, operator_functions[node.op], 1) 683 self.dispatch(node.expr) 684 self.stream.write("</div>\n") 685 686 def visitBreak(self, node): 687 self.stream.write("<div class='break nowrap'>\n") 688 self._keyword("break") 689 self.stream.write("</div>\n") 690 691 def visitClass(self, node): 692 if not used_by_unit(node): 693 self._docstring('"Class %s not generated."' % node.name) 694 return 695 696 # Use inspected details where possible. 697 698 cls = node.unit 699 self.units.append(cls) 700 701 bases = cls.bases 702 self.stream.write("<div class='class nowrap' id='%s'>\n" % cls.full_name()) 703 704 # Write the declaration line. 705 706 self.stream.write("<div>\n") 707 self._keyword("class") 708 self._object_name_def(self.module, cls, "class-name") 709 710 # Suppress the "object" class appearing alone. 711 712 if bases and not (len(bases) == 1 and bases[0].name == "object"): 713 self.stream.write("(") 714 first = True 715 for base in bases: 716 if not first: 717 self.stream.write(", ") 718 719 self._object_name_ref(base.module, base) 720 721 first = False 722 self.stream.write(")") 723 724 self.stream.write(":\n") 725 self.stream.write("</div>\n") 726 727 # Write the docstring and class body. 728 729 self.stream.write("<div class='body nowrap'>\n") 730 self._doc(node) 731 732 # NOTE: Some streams may not support tell. 733 734 x = self.stream.tell() 735 736 self.default(node.code) 737 738 # Check for no output. 739 740 if x == self.stream.tell(): 741 self.visitPass(None) 742 743 self.stream.write("</div>\n") 744 self.stream.write("</div>\n") 745 746 self.units.pop() 747 748 def visitContinue(self, node): 749 self.stream.write("<div class='continue nowrap'>\n") 750 self._keyword("continue") 751 self.stream.write("</div>\n") 752 753 def visitDiscard(self, node): 754 self.stream.write("<div class='discard nowrap'>\n") 755 self.default(node) 756 self.stream.write("</div>\n") 757 758 def visitFor(self, node): 759 self.stream.write("<div class='if nowrap'>\n") 760 self.stream.write("<div>\n") 761 self._keyword("for") 762 self.dispatch(node.assign) 763 self._keyword("in", 1) 764 self.dispatch(node.list) 765 self.stream.write(":\n") 766 self.stream.write("</div>\n") 767 self.stream.write("<div class='body nowrap'>\n") 768 self.dispatch(node.body) 769 self.stream.write("</div>\n") 770 if node.else_ is not None: 771 self.stream.write("<div>\n") 772 self._keyword("else", trailing=0) 773 self.stream.write(":\n") 774 self.stream.write("</div>\n") 775 self.stream.write("<div class='body nowrap'>\n") 776 self.dispatch(node.else_) 777 self.stream.write("</div>\n") 778 self.stream.write("</div>\n") 779 780 def visitFrom(self, node): 781 self.stream.write("<div class='from nowrap'>\n") 782 self._keyword("from") 783 self._module_link(node.modname) 784 self._keyword("import", 1) 785 first = True 786 for name, alias in node.names: 787 if not first: 788 self.stream.write(", ") 789 self._name(name) 790 if alias: 791 self._keyword("as", 1) 792 self._name(alias) 793 first = False 794 self.stream.write("</div>\n") 795 796 def visitFunction(self, node): 797 if not used_by_unit(node): 798 self._docstring('"Function %s not generated."' % node.name) 799 return 800 801 fn = node.unit 802 self.units.append(fn) 803 804 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 805 806 # Write the declaration line. 807 808 self.stream.write("<div>\n") 809 self._keyword("def") 810 self._object_name_def(self.module, fn, "function-name") 811 812 self.stream.write("(") 813 self._parameters(fn, node) 814 self.stream.write(")") 815 self.stream.write(":\n") 816 self.stream.write("</div>\n") 817 818 self.stream.write("<div class='body nowrap'>\n") 819 self._doc(node) 820 self.dispatch(node.code) 821 self.stream.write("</div>\n") 822 self.stream.write("</div>\n") 823 824 self.units.pop() 825 826 def visitGlobal(self, node): 827 self.stream.write("<div class='global nowrap'>\n") 828 self._keyword("global") 829 first = True 830 for name in node.names: 831 if not first: 832 self.stream.write(", ") 833 self.stream.write(name) 834 first = False 835 self.stream.write("</div>\n") 836 837 def visitIf(self, node): 838 self.stream.write("<div class='if nowrap'>\n") 839 first = True 840 for compare, stmt in node.tests: 841 self.stream.write("<div>\n") 842 if first: 843 self._keyword("if") 844 else: 845 self._keyword("elif") 846 self.dispatch(compare) 847 self.stream.write(":\n") 848 self.stream.write("</div>\n") 849 self.stream.write("<div class='body nowrap'>\n") 850 self.dispatch(stmt) 851 self.stream.write("</div>\n") 852 first = False 853 if node.else_ is not None: 854 self.stream.write("<div>\n") 855 self._keyword("else", trailing=0) 856 self.stream.write(":\n") 857 self.stream.write("</div>\n") 858 self.stream.write("<div class='body nowrap'>\n") 859 self.dispatch(node.else_) 860 self.stream.write("</div>\n") 861 self.stream.write("</div>\n") 862 863 def visitImport(self, node): 864 self.stream.write("<div class='import nowrap'>\n") 865 self._keyword("import") 866 first = True 867 for name, alias in node.names: 868 if not first: 869 self.stream.write(",\n") 870 self._module_link(name) 871 if alias: 872 self._keyword("as", 1) 873 self._name(alias) 874 first = False 875 self.stream.write("</div>\n") 876 877 def visitPass(self, node): 878 self.stream.write("<div class='pass nowrap'>\n") 879 self._keyword("pass") 880 self.stream.write("</div>\n") 881 882 def visitPrint(self, node): 883 self.stream.write("<div class='print nowrap'>\n") 884 self._keyword("print") 885 if node.dest is not None: 886 self.stream.write(">>\n") 887 self.dispatch(node.dest) 888 self.stream.write(",\n") 889 for n in node.nodes: 890 self.dispatch(n) 891 self.stream.write(",\n") 892 self.stream.write("</div>\n") 893 894 def visitPrintnl(self, node): 895 self.stream.write("<div class='printnl nowrap'>\n") 896 self._keyword("print") 897 if node.dest is not None: 898 self.stream.write(">>\n") 899 self.dispatch(node.dest) 900 first = False 901 else: 902 first = True 903 for n in node.nodes: 904 if not first: 905 self.stream.write(",\n") 906 self.dispatch(n) 907 first = False 908 self.stream.write("</div>\n") 909 910 def visitRaise(self, node): 911 self.stream.write("<div class='raise nowrap'>\n") 912 self._keyword("raise") 913 if node.expr1 is not None: 914 self.dispatch(node.expr1) 915 if node.expr2 is not None: 916 self.stream.write(",\n") 917 self.dispatch(node.expr2) 918 if node.expr3 is not None: 919 self.stream.write(",\n") 920 self.dispatch(node.expr3) 921 self.stream.write("</div>\n") 922 923 def visitReturn(self, node): 924 self.stream.write("<div class='return nowrap'>\n") 925 self._keyword("return") 926 self.dispatch(node.value) 927 self.stream.write("</div>\n") 928 929 def visitStmt(self, node): 930 self.stream.write("<div class='stmt nowrap'>\n") 931 self.default(node) 932 self.stream.write("</div>\n") 933 934 def visitTryExcept(self, node): 935 self.stream.write("<div class='tryexcept nowrap'>\n") 936 self.stream.write("<div>\n") 937 self._keyword("try", trailing=0) 938 self.stream.write(":\n") 939 self.stream.write("</div>\n") 940 self.stream.write("<div class='body nowrap'>\n") 941 self.dispatch(node.body) 942 self.stream.write("</div>\n") 943 for spec, assign, statement in node.handlers: 944 self.stream.write("<div>\n") 945 self._keyword("except") 946 if spec is not None: 947 self.dispatch(spec) 948 if assign is not None: 949 self.stream.write(",\n") 950 self.dispatch(assign) 951 self.stream.write(":\n") 952 self.stream.write("</div>\n") 953 self.stream.write("<div class='body nowrap'>\n") 954 self.dispatch(statement) 955 self.stream.write("</div>\n") 956 if node.else_ is not None: 957 self.stream.write("<div>\n") 958 self._keyword("else", trailing=0) 959 self.stream.write(":\n") 960 self.stream.write("</div>\n") 961 self.stream.write("<div class='body nowrap'>\n") 962 self.dispatch(node.else_) 963 self.stream.write("</div>\n") 964 self.stream.write("</div>\n") 965 966 def visitTryFinally(self, node): 967 self.stream.write("<div class='tryfinally nowrap'>\n") 968 self.stream.write("<div>\n") 969 self._keyword("try", trailing=0) 970 self.stream.write(":\n") 971 self.stream.write("</div>\n") 972 self.stream.write("<div class='body nowrap'>\n") 973 self.dispatch(node.body) 974 self.stream.write("</div>\n") 975 self.stream.write("<div>\n") 976 self._keyword("finally", trailing=0) 977 self.stream.write(":\n") 978 self.stream.write("</div>\n") 979 self.stream.write("<div class='body nowrap'>\n") 980 self.dispatch(node.final) 981 self.stream.write("</div>\n") 982 self.stream.write("</div>\n") 983 984 def visitWhile(self, node): 985 self.stream.write("<div class='while nowrap'>\n") 986 self.stream.write("<div>\n") 987 self._keyword("while") 988 self.dispatch(node.test) 989 self.stream.write(":\n") 990 self.stream.write("</div>\n") 991 self.stream.write("<div class='body nowrap'>\n") 992 self.dispatch(node.body) 993 self.stream.write("</div>\n") 994 if node.else_ is not None: 995 self.stream.write("<div>\n") 996 self._keyword("else", trailing=0) 997 self.stream.write(":\n") 998 self.stream.write("</div>\n") 999 self.stream.write("<div class='body nowrap'>\n") 1000 self.dispatch(node.else_) 1001 self.stream.write("</div>\n") 1002 self.stream.write("</div>\n") 1003 1004 def visitYield(self, node): 1005 self.stream.write("<div class='yield nowrap'>\n") 1006 self._keyword("yield") 1007 self.dispatch(node.value) 1008 self.stream.write("</div>\n") 1009 1010 # Expression-related helper methods. 1011 1012 def _visitBitBinary(self, node, symbol): 1013 name = operator_functions[node.__class__.__name__] 1014 self._span_start(name) 1015 first = True 1016 for node in node.nodes: 1017 if not first: 1018 self._op(symbol, name, 1) 1019 self.dispatch(node) 1020 first = False 1021 self._span_end() 1022 1023 def _visitBinary(self, node, symbol): 1024 name = operator_functions[node.__class__.__name__] 1025 self._span_start(name) 1026 self.dispatch(node.left) 1027 self._op(symbol, name, 1) 1028 self.dispatch(node.right) 1029 self._span_end() 1030 1031 def _visitUnary(self, node, symbol): 1032 name = operator_functions[node.__class__.__name__] 1033 self._span_start(name) 1034 self._op(symbol, name, trailing=0) 1035 self.dispatch(node.expr) 1036 self._span_end() 1037 1038 # Expressions. 1039 1040 def visitAdd(self, node): 1041 self._visitBinary(node, "+") 1042 1043 def visitAnd(self, node): 1044 self._span_start("and") 1045 first = True 1046 for n in node.nodes: 1047 if not first: 1048 self._keyword("and", 1) 1049 self.dispatch(n) 1050 first = False 1051 self._span_end() 1052 1053 def visitAssAttr(self, node): 1054 possible_types = self.possible_accessor_types(node, defining_users=0) 1055 1056 # Record whether types were already deduced. If not, get types using 1057 # only this attribute. 1058 1059 deduced = possible_types 1060 if not possible_types: 1061 possible_types = self._get_possible_types(node.attrname) 1062 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 attributes = self._get_attributes(possible_types, node.attrname) 1170 1171 wraps_getattr = self._has_descendant(node.expr, compiler.ast.Getattr) 1172 1173 if not wraps_getattr: 1174 self._span_start("getattr") 1175 self._accessor_start(possible_types) 1176 1177 self.dispatch(node.expr) 1178 1179 if not wraps_getattr: 1180 self._accessor_end(possible_types) 1181 1182 self.stream.write(".") 1183 1184 self._attribute_start(node.attrname, self._attributes_to_attribute_names(attributes, node.attrname)) 1185 self._span(node.attrname, "attrname" + (not possible_types and " no-targets" or not deduced and " any-target" or "")) 1186 self._attribute_end(attributes) 1187 1188 if not wraps_getattr: 1189 self._span_end() 1190 1191 def visitGenExpr(self, node): 1192 self._span_start("genexpr") 1193 self.stream.write("(") 1194 self.dispatch(node.code) 1195 self.stream.write(")") 1196 self._span_end() 1197 1198 def visitGenExprFor(self, node): 1199 self._span_start("genexprfor") 1200 self._keyword("for", 1) 1201 self._span_start("item") 1202 self.dispatch(node.assign) 1203 self._span_end() 1204 self._keyword("in", 1) 1205 self._span_start("collection") 1206 self.dispatch(node.iter) 1207 self._span_end() 1208 for if_ in node.ifs: 1209 self.dispatch(if_) 1210 self._span_end() 1211 1212 def visitGenExprIf(self, node): 1213 self._span_start("genexprif") 1214 self._span_start("conditional") 1215 self._keyword("if", 1) 1216 self.dispatch(node.test) 1217 self._span_end() 1218 self._span_end() 1219 1220 def visitGenExprInner(self, node): 1221 self._span_start("genexprinner") 1222 self.dispatch(node.expr) 1223 for qual in node.quals: 1224 self.dispatch(qual) 1225 self._span_end() 1226 1227 def visitIfExp(self, node): 1228 self._span_start("ifexp") 1229 self.dispatch(node.then) 1230 self._keyword("if", 1) 1231 self.dispatch(node.test) 1232 self._keyword("else", 1) 1233 self.dispatch(node.else_) 1234 self._span_end() 1235 1236 def visitInvert(self, node): 1237 self._visitUnary(node, "~") 1238 1239 def visitKeyword(self, node): 1240 self._span_start("keyword-arg") 1241 self.stream.write(node.name) 1242 self.stream.write("=") 1243 self.dispatch(node.expr) 1244 self._span_end() 1245 1246 def visitLambda(self, node): 1247 fn = node.unit 1248 self.units.append(fn) 1249 1250 self._span_start("lambda") 1251 self._keyword("lambda") 1252 self._parameters(fn, node) 1253 self.stream.write(": ") 1254 self._span_start("code") 1255 self.dispatch(node.code) 1256 self._span_end() 1257 self._span_end() 1258 1259 self.units.pop() 1260 1261 def visitLeftShift(self, node): 1262 self._visitBinary(node, "<<") 1263 1264 visitList = visitAssList 1265 1266 def visitListComp(self, node): 1267 self._span_start("listcomp") 1268 self.stream.write("[") 1269 self.dispatch(node.expr) 1270 for qual in node.quals: 1271 self.dispatch(qual) 1272 self.stream.write("]") 1273 self._span_end() 1274 1275 def visitListCompFor(self, node): 1276 self._span_start("listcompfor") 1277 self._keyword("for", 1) 1278 self._span_start("item") 1279 self.dispatch(node.assign) 1280 self._span_end() 1281 self._keyword("in", 1) 1282 self._span_start("collection") 1283 self.dispatch(node.list) 1284 self._span_end() 1285 for if_ in node.ifs: 1286 self.dispatch(if_) 1287 self._span_end() 1288 1289 def visitListCompIf(self, node): 1290 self._span_start("listcompif") 1291 self._span_start("conditional") 1292 self._keyword("if", 1) 1293 self.dispatch(node.test) 1294 self._span_end() 1295 self._span_end() 1296 1297 def visitMod(self, node): 1298 self._visitBinary(node, "%") 1299 1300 def visitMul(self, node): 1301 self._visitBinary(node, "*") 1302 1303 def visitName(self, node): 1304 if node._scope: 1305 scope = node._scope 1306 self._name_start() 1307 self.stream.write(node.name) 1308 self._popup_start() 1309 self._scope(node._scope, node._attr) 1310 self._popup_end() 1311 self._name_end() 1312 else: 1313 self._span(node.name) 1314 1315 def visitNot(self, node): 1316 self._span_start("not") 1317 self._keyword("not") 1318 self.dispatch(node.expr) 1319 self._span_end() 1320 1321 def visitOr(self, node): 1322 self._span_start("or") 1323 first = True 1324 for n in node.nodes: 1325 if not first: 1326 self._keyword("or", 1) 1327 self.dispatch(n) 1328 first = False 1329 self._span_end() 1330 1331 def visitPower(self, node): 1332 self._visitBinary(node, "**") 1333 1334 def visitRightShift(self, node): 1335 self._visitBinary(node, ">>") 1336 1337 def visitSlice(self, node): 1338 self._span_start("slice") 1339 self.dispatch(node.expr) 1340 self.stream.write("[") 1341 if node.lower: 1342 self.dispatch(node.lower) 1343 self.stream.write(":") 1344 if node.upper: 1345 self.dispatch(node.upper) 1346 # NOTE: Step? 1347 self.stream.write("]") 1348 self._span_end() 1349 1350 def visitSliceobj(self, node): 1351 self._span_start("sliceobj") 1352 first = True 1353 for n in node.nodes: 1354 if not first: 1355 self.stream.write(":") 1356 self.dispatch(n) 1357 self._span_end() 1358 1359 def visitSub(self, node): 1360 self._visitBinary(node, "-") 1361 1362 def visitSubscript(self, node): 1363 self._span_start("subscript") 1364 self.dispatch(node.expr) 1365 self.stream.write("[") 1366 first = True 1367 for sub in node.subs: 1368 if not first: 1369 self.stream.write(", ") 1370 self.dispatch(sub) 1371 first = False 1372 self.stream.write("]") 1373 self._span_end() 1374 1375 visitTuple = visitAssTuple 1376 1377 def visitUnaryAdd(self, node): 1378 self._visitUnary(node, "+") 1379 1380 def visitUnarySub(self, node): 1381 self._visitUnary(node, "-") 1382 1383 # Output preparation methods. 1384 1385 def _sequence(self, node): 1386 first = True 1387 for n in node.nodes: 1388 if not first: 1389 self.stream.write(", ") 1390 self.dispatch(n) 1391 first = False 1392 1393 def _mapping(self, node): 1394 first = True 1395 for k, v in node.items: 1396 if not first: 1397 self.stream.write(", ") 1398 self.dispatch(k) 1399 self.stream.write(" : ") 1400 self.dispatch(v) 1401 first = False 1402 1403 def _parameters(self, fn, node): 1404 nparams = len(fn.positional_names) 1405 ndefaults = len(fn.defaults) 1406 first_with_default = nparams - ndefaults 1407 1408 first = True 1409 for n, param in enumerate(fn.positional_names): 1410 if not first: 1411 self.stream.write(", ") 1412 1413 # Handle tuple parameters. 1414 1415 if isinstance(param, tuple): 1416 self._tuple_parameter(param, node) 1417 else: 1418 self._assname(param, node) 1419 1420 n_default = n - first_with_default 1421 if n_default >= 0: 1422 self._default(fn.defaults[n_default]) 1423 first = False 1424 1425 if fn.has_star: 1426 if not first: 1427 self.stream.write(", *") 1428 self._name(fn.star_name) 1429 1430 if fn.has_dstar: 1431 if not first: 1432 self.stream.write(", **") 1433 self._name(fn.dstar_name) 1434 1435 def _tuple_parameter(self, parameters, node): 1436 self.stream.write("(") 1437 1438 first = True 1439 for param in parameters: 1440 if not first: 1441 self.stream.write(", ") 1442 1443 # Handle tuples. 1444 1445 if isinstance(param, tuple): 1446 self._tuple_parameter(param, node) 1447 else: 1448 self._assname(param, node) 1449 1450 first = False 1451 1452 self.stream.write(")") 1453 1454 def _default(self, default): 1455 self.stream.write("=") 1456 self.dispatch(default) 1457 1458 # Statistics gathering methods. 1459 1460 def possible_accessor_types(self, node, defining_users=1): 1461 possible_types = set([tn for (tn, st) in ASTVisitor.possible_accessor_types(self, node, defining_users)]) 1462 if not possible_types: 1463 self.program.unknown_target_nodes.append((self.units[-1], node)) 1464 return possible_types 1465 1466 # Utility methods. 1467 1468 def _has_descendant(self, node, nodetype): 1469 if isinstance(node, nodetype): 1470 return True 1471 else: 1472 for n in node.getChildNodes(): 1473 if self._has_descendant(n, nodetype): 1474 return True 1475 return False 1476 1477 # Convenience functions. 1478 1479 def summarise(module, program, filename): 1480 stream = open(filename, "wb") 1481 try: 1482 summary = Summary(module, program) 1483 summary.to_stream(stream) 1484 finally: 1485 stream.close() 1486 1487 def annotate(module, program, filename): 1488 stream = open(filename, "wb") 1489 try: 1490 source = AnnotatedSource(module, program) 1491 source.to_stream(stream) 1492 finally: 1493 stream.close() 1494 1495 def interfaces(program, filename): 1496 stream = open(filename, "wb") 1497 try: 1498 source = Interfaces(program) 1499 source.to_stream(stream) 1500 finally: 1501 stream.close() 1502 1503 def report(program, directory): 1504 if not exists(directory): 1505 os.mkdir(directory) 1506 1507 for module in program.get_importer().get_modules(): 1508 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1509 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1510 1511 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1512 1513 # vim: tabstop=4 expandtab shiftwidth=4