micropython

Annotated micropython/report.py

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