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 possible_types = self._attributes_to_names(node._access_attrs, True) 1161 attribute_names = self._attributes_to_names(node._access_attrs) 1162 1163 wraps_getattr = self._has_descendant(node.expr, compiler.ast.Getattr) 1164 1165 if not wraps_getattr: 1166 self._span_start(label) 1167 self._accessor_start(possible_types) 1168 self.dispatch(node.expr) 1169 if not wraps_getattr: 1170 self._accessor_end(possible_types) 1171 1172 self.stream.write(".") 1173 self._attribute_start(node.attrname, attribute_names) 1174 self._span(node.attrname, "attrname" + ((not node._access_attrs or node._access_type == "impossible") and " no-attributes" or "")) 1175 self._attribute_end(attribute_names) 1176 1177 if not wraps_getattr: 1178 self._span_end() 1179 1180 def visitAssAttr(self, node): 1181 if node.flags == "OP_DELETE": 1182 self._keyword("del") 1183 self._visitAttr(node, "assattr") 1184 1185 def visitAssList(self, node): 1186 self._span_start("list") 1187 self.stream.write("[") 1188 self._sequence(node) 1189 self.stream.write("]") 1190 self._span_end() 1191 1192 def visitAssName(self, node): 1193 self._assname(node.name, node) 1194 1195 def visitAssTuple(self, node): 1196 self._span_start("tuple") 1197 self.stream.write("(") 1198 self._sequence(node, True) 1199 self.stream.write(")") 1200 self._span_end() 1201 1202 def visitBackquote(self, node): 1203 self._span_start("backquote") 1204 self.stream.write("`") 1205 self.dispatch(node.expr) 1206 self.stream.write("`") 1207 self._span_end() 1208 1209 def visitBitand(self, node): 1210 self._visitBitBinary(node, "&") 1211 1212 def visitBitor(self, node): 1213 self._visitBitBinary(node, "|") 1214 1215 def visitBitxor(self, node): 1216 self._visitBitBinary(node, "^") 1217 1218 def visitCallFunc(self, node): 1219 self._span_start("callfunc") 1220 self.dispatch(node.node) 1221 self._span_start("call") 1222 self.stream.write("(") 1223 first = True 1224 for arg in node.args: 1225 if not first: 1226 self.stream.write(", ") 1227 self.dispatch(arg) 1228 first = False 1229 if node.star_args is not None: 1230 if not first: 1231 self.stream.write(", *") 1232 self.dispatch(node.star_args) 1233 first = False 1234 if node.dstar_args is not None: 1235 if not first: 1236 self.stream.write(", **") 1237 self.dispatch(node.dstar_args) 1238 first = False 1239 self.stream.write(")") 1240 self._span_end() 1241 self._span_end() 1242 1243 def visitCompare(self, node): 1244 self._span_start("compare") 1245 self.dispatch(node.expr) 1246 for op_name, expr in node.ops: 1247 self._op(op_name, operator_functions.get(op_name), 1) 1248 self.dispatch(expr) 1249 self._span_end() 1250 1251 def visitConst(self, node): 1252 if isinstance(node.value, (str, unicode)): 1253 self._span_start("str") 1254 self.stream.write(self._text(repr(node.value))) 1255 if isinstance(node.value, (str, unicode)): 1256 self._span_end() 1257 1258 def visitDict(self, node): 1259 self._span_start("dict") 1260 self.stream.write("{") 1261 self._mapping(node) 1262 self.stream.write("}") 1263 self._span_end() 1264 1265 def visitDiv(self, node): 1266 self._visitBinary(node, "/") 1267 1268 def visitFloorDiv(self, node): 1269 self._visitBinary(node, "//") 1270 1271 def visitGetattr(self, node): 1272 self._visitAttr(node, "getattr") 1273 1274 def visitGenExpr(self, node): 1275 self._span_start("genexpr") 1276 self.stream.write("(") 1277 self.dispatch(node.code) 1278 self.stream.write(")") 1279 self._span_end() 1280 1281 def visitGenExprFor(self, node): 1282 self._span_start("genexprfor") 1283 self._keyword("for", 1) 1284 self._span_start("item") 1285 self.dispatch(node.assign) 1286 self._span_end() 1287 self._keyword("in", 1) 1288 self._span_start("collection") 1289 self.dispatch(node.iter) 1290 self._span_end() 1291 for if_ in node.ifs: 1292 self.dispatch(if_) 1293 self._span_end() 1294 1295 def visitGenExprIf(self, node): 1296 self._span_start("genexprif") 1297 self._span_start("conditional") 1298 self._keyword("if", 1) 1299 self.dispatch(node.test) 1300 self._span_end() 1301 self._span_end() 1302 1303 def visitGenExprInner(self, node): 1304 self._span_start("genexprinner") 1305 self.dispatch(node.expr) 1306 for qual in node.quals: 1307 self.dispatch(qual) 1308 self._span_end() 1309 1310 def visitIfExp(self, node): 1311 self._span_start("ifexp") 1312 self.dispatch(node.then) 1313 self._keyword("if", 1) 1314 self.dispatch(node.test) 1315 self._keyword("else", 1) 1316 self.dispatch(node.else_) 1317 self._span_end() 1318 1319 def visitInvert(self, node): 1320 self._visitUnary(node, "~") 1321 1322 def visitKeyword(self, node): 1323 self._span_start("keyword-arg") 1324 self.stream.write(node.name) 1325 self.stream.write("=") 1326 self.dispatch(node.expr) 1327 self._span_end() 1328 1329 def visitLambda(self, node): 1330 fn = node.unit 1331 self.units.append(fn) 1332 1333 self._span_start("lambda") 1334 self._keyword("lambda") 1335 self._parameters(fn, node) 1336 self.stream.write(": ") 1337 self._span_start("code") 1338 self.dispatch(node.code) 1339 self._span_end() 1340 self._span_end() 1341 1342 self.units.pop() 1343 1344 def visitLeftShift(self, node): 1345 self._visitBinary(node, "<<") 1346 1347 visitList = visitAssList 1348 1349 def visitListComp(self, node): 1350 self._span_start("listcomp") 1351 self.stream.write("[") 1352 self.dispatch(node.expr) 1353 for qual in node.quals: 1354 self.dispatch(qual) 1355 self.stream.write("]") 1356 self._span_end() 1357 1358 def visitListCompFor(self, node): 1359 self._span_start("listcompfor") 1360 self._keyword("for", 1) 1361 self._span_start("item") 1362 self.dispatch(node.assign) 1363 self._span_end() 1364 self._keyword("in", 1) 1365 self._span_start("collection") 1366 self.dispatch(node.list) 1367 self._span_end() 1368 for if_ in node.ifs: 1369 self.dispatch(if_) 1370 self._span_end() 1371 1372 def visitListCompIf(self, node): 1373 self._span_start("listcompif") 1374 self._span_start("conditional") 1375 self._keyword("if", 1) 1376 self.dispatch(node.test) 1377 self._span_end() 1378 self._span_end() 1379 1380 def visitMod(self, node): 1381 self._visitBinary(node, "%") 1382 1383 def visitMul(self, node): 1384 self._visitBinary(node, "*") 1385 1386 def visitName(self, node): 1387 if node._scope: 1388 scope = node._scope 1389 self._name_start() 1390 self.stream.write(node.name) 1391 self._popup_start() 1392 self._scope(node._scope, node._attr) 1393 self._popup_end() 1394 self._name_end() 1395 else: 1396 self._span(node.name) 1397 1398 def visitNot(self, node): 1399 self._span_start("not") 1400 self._keyword("not") 1401 self.dispatch(node.expr) 1402 self._span_end() 1403 1404 def visitOr(self, node): 1405 self._span_start("or") 1406 first = True 1407 for n in node.nodes: 1408 if not first: 1409 self._keyword("or", 1) 1410 self.dispatch(n) 1411 first = False 1412 self._span_end() 1413 1414 def visitPower(self, node): 1415 self._visitBinary(node, "**") 1416 1417 def visitRightShift(self, node): 1418 self._visitBinary(node, ">>") 1419 1420 def visitSet(self, node): 1421 self._span_start("set") 1422 self.stream.write("[") 1423 first = True 1424 for n in node.nodes: 1425 if not first: 1426 self.stream.write(", ") 1427 self.dispatch(n) 1428 first = False 1429 self.stream.write("]") 1430 self._span_end() 1431 1432 def visitSlice(self, node): 1433 self._span_start("slice") 1434 self.dispatch(node.expr) 1435 self.stream.write("[") 1436 if node.lower: 1437 self.dispatch(node.lower) 1438 self.stream.write(":") 1439 if node.upper: 1440 self.dispatch(node.upper) 1441 # NOTE: Step? 1442 self.stream.write("]") 1443 self._span_end() 1444 1445 def visitSliceobj(self, node): 1446 self._span_start("sliceobj") 1447 first = True 1448 for n in node.nodes: 1449 if not first: 1450 self.stream.write(":") 1451 self.dispatch(n) 1452 self._span_end() 1453 1454 def visitSub(self, node): 1455 self._visitBinary(node, "-") 1456 1457 def visitSubscript(self, node): 1458 self._span_start("subscript") 1459 self.dispatch(node.expr) 1460 self.stream.write("[") 1461 first = True 1462 for sub in node.subs: 1463 if not first: 1464 self.stream.write(", ") 1465 self.dispatch(sub) 1466 first = False 1467 self.stream.write("]") 1468 self._span_end() 1469 1470 visitTuple = visitAssTuple 1471 1472 def visitUnaryAdd(self, node): 1473 self._visitUnary(node, "+") 1474 1475 def visitUnarySub(self, node): 1476 self._visitUnary(node, "-") 1477 1478 # Output preparation methods. 1479 1480 def _sequence(self, node, istuple=False): 1481 first = True 1482 for n in node.nodes: 1483 if not first: 1484 self.stream.write(", ") 1485 self.dispatch(n) 1486 first = False 1487 if len(node.nodes) == 1 and istuple: 1488 self.stream.write(", ") 1489 1490 def _mapping(self, node): 1491 first = True 1492 for k, v in node.items: 1493 if not first: 1494 self.stream.write(", ") 1495 self.dispatch(k) 1496 self.stream.write(" : ") 1497 self.dispatch(v) 1498 first = False 1499 1500 def _parameters(self, fn, node): 1501 nparams = len(fn.positional_names) 1502 ndefaults = len(fn.defaults) 1503 first_with_default = nparams - ndefaults 1504 1505 first = True 1506 for n, param in enumerate(fn.positional_names): 1507 if not first: 1508 self.stream.write(", ") 1509 1510 # The special context argument should not be reproduced. 1511 # NOTE: For "dynamic" functions, the context is used to access 1512 # NOTE: things like defaults, but could be extended for closures to 1513 # NOTE: refer to many namespaces. 1514 1515 elif param == "__context__": 1516 continue 1517 1518 # Handle tuple parameters. 1519 1520 if isinstance(param, tuple): 1521 self._tuple_parameter(param, node) 1522 else: 1523 self._assname(param, node) 1524 1525 n_default = n - first_with_default 1526 if n_default >= 0: 1527 self._default(fn.defaults[n_default]) 1528 first = False 1529 1530 if fn.has_star: 1531 if not first: 1532 self.stream.write(", *") 1533 self._name(fn.star_name) 1534 1535 if fn.has_dstar: 1536 if not first: 1537 self.stream.write(", **") 1538 self._name(fn.dstar_name) 1539 1540 def _tuple_parameter(self, parameters, node): 1541 self.stream.write("(") 1542 1543 first = True 1544 for param in parameters: 1545 if not first: 1546 self.stream.write(", ") 1547 1548 # Handle tuples. 1549 1550 if isinstance(param, tuple): 1551 self._tuple_parameter(param, node) 1552 else: 1553 self._assname(param, node) 1554 1555 first = False 1556 1557 self.stream.write(")") 1558 1559 def _default(self, default): 1560 self.stream.write("=") 1561 self.dispatch(default) 1562 1563 # Statistics gathering methods. 1564 1565 def record_unknown_targets(self, node): 1566 if not node._attrs_deduced: 1567 self.program.unknown_target_nodes.append((self.units[-1], node)) 1568 elif not node._attrs_deduced_from_specific_usage: 1569 self.program.independent_target_nodes.append((self.units[-1], node)) 1570 1571 # Utility methods. 1572 1573 def _has_descendant(self, node, nodetype): 1574 if isinstance(node, nodetype): 1575 return True 1576 else: 1577 for n in node.getChildNodes(): 1578 if self._has_descendant(n, nodetype): 1579 return True 1580 return False 1581 1582 # Convenience functions. 1583 1584 def summarise(module, program, filename): 1585 stream = codecs.open(filename, "wb", "utf-8") 1586 try: 1587 summary = Summary(module, program) 1588 summary.to_stream(stream) 1589 finally: 1590 stream.close() 1591 1592 def annotate(module, program, filename): 1593 stream = codecs.open(filename, "wb", "utf-8") 1594 try: 1595 source = AnnotatedSource(module, program) 1596 source.to_stream(stream) 1597 finally: 1598 stream.close() 1599 1600 def interfaces(program, filename): 1601 stream = codecs.open(filename, "wb", "utf-8") 1602 try: 1603 source = Interfaces(program) 1604 source.to_stream(stream) 1605 finally: 1606 stream.close() 1607 1608 def report(program, directory): 1609 if not exists(directory): 1610 os.mkdir(directory) 1611 1612 for module in program.get_importer().get_modules(): 1613 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1614 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1615 1616 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1617 1618 # vim: tabstop=4 expandtab shiftwidth=4