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