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