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