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