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