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