simplify

Annotated annotate.py

60:48b747a69cc6
2006-10-07 paulb Added original node attribute initialisation to structure-related classes. Introduced stream parameterisation to the pprint methods of the Node class.
paulb@19 1
#!/usr/bin/env python
paulb@19 2
paulb@19 3
"""
paulb@34 4
Annotate program node structures. The code in this module operates upon nodes
paulb@24 5
which are produced when simplifying AST node trees originating from the compiler
paulb@24 6
module.
paulb@19 7
paulb@19 8
Copyright (C) 2006 Paul Boddie <paul@boddie.org.uk>
paulb@19 9
paulb@19 10
This software is free software; you can redistribute it and/or
paulb@19 11
modify it under the terms of the GNU General Public License as
paulb@19 12
published by the Free Software Foundation; either version 2 of
paulb@19 13
the License, or (at your option) any later version.
paulb@19 14
paulb@19 15
This software is distributed in the hope that it will be useful,
paulb@19 16
but WITHOUT ANY WARRANTY; without even the implied warranty of
paulb@19 17
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
paulb@19 18
GNU General Public License for more details.
paulb@19 19
paulb@19 20
You should have received a copy of the GNU General Public
paulb@19 21
License along with this library; see the file LICENCE.txt
paulb@19 22
If not, write to the Free Software Foundation, Inc.,
paulb@19 23
51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
paulb@50 24
paulb@50 25
--------
paulb@50 26
paulb@57 27
To use this module, the easiest approach is to use the annotate function:
paulb@57 28
paulb@57 29
annotate(module, builtins)
paulb@57 30
paulb@57 31
The more complicated approach involves obtaining an Annotator:
paulb@50 32
paulb@50 33
annotator = Annotator()
paulb@50 34
paulb@57 35
Then, processing an existing module with it:
paulb@50 36
paulb@57 37
annotator.process(module)
paulb@50 38
paulb@50 39
If a module containing built-in classes and functions has already been
paulb@50 40
annotated, such a module should be passed in as an additional argument:
paulb@50 41
paulb@57 42
annotator.process(module, builtins)
paulb@19 43
"""
paulb@19 44
paulb@19 45
from simplified import *
paulb@19 46
import compiler
paulb@19 47
paulb@24 48
class System:
paulb@24 49
paulb@50 50
    """
paulb@50 51
    A class maintaining the state of the annotation system. When the system
paulb@50 52
    counter can no longer be incremented by any annotation operation, the
paulb@50 53
    system may be considered stable and fully annotated.
paulb@50 54
    """
paulb@24 55
paulb@24 56
    def __init__(self):
paulb@24 57
        self.count = 0
paulb@24 58
    def init(self, node):
paulb@24 59
        if not hasattr(node, "types"):
paulb@24 60
            node.types = []
paulb@24 61
    def annotate(self, node, types):
paulb@24 62
        self.init(node)
paulb@24 63
        for type in types:
paulb@24 64
            if type not in node.types:
paulb@24 65
                node.types.append(type)
paulb@24 66
                self.count += 1
paulb@24 67
paulb@24 68
system = System()
paulb@24 69
paulb@58 70
# Exceptions.
paulb@58 71
paulb@58 72
class FailureError(Exception):
paulb@58 73
    def __init__(self, exc, node, *args):
paulb@58 74
        Exception.__init__(self, *args)
paulb@58 75
        self.nodes = [node]
paulb@58 76
        self.exc = exc
paulb@58 77
    def add(self, node):
paulb@58 78
        self.nodes.append(node)
paulb@58 79
    def __str__(self):
paulb@58 80
        return "%s, %s" % (self.exc, self.nodes)
paulb@58 81
paulb@24 82
# Annotation.
paulb@19 83
paulb@20 84
class Annotator(Visitor):
paulb@24 85
paulb@24 86
    """
paulb@24 87
    The type annotator which traverses the program nodes, typically depth-first,
paulb@24 88
    and maintains a record of the current set of types applying to the currently
paulb@24 89
    considered operation. Such types are also recorded on the nodes, and a
paulb@24 90
    special "system" record is maintained to monitor the level of annotation
paulb@24 91
    activity with a view to recognising when no more annotations are possible.
paulb@50 92
paulb@50 93
    Throughout the annotation activity, type information consists of lists of
paulb@50 94
    Attribute objects where such objects retain information about the context of
paulb@50 95
    the type (since a value in the program may be associated with an object or
paulb@50 96
    class) and the actual type of the value being manipulated. Upon accessing
paulb@50 97
    attribute information on namespaces, additional accessor information is also
paulb@50 98
    exchanged - this provides a means of distinguishing between the different
paulb@50 99
    types possible when the means of constructing the namespace may depend on
paulb@50 100
    run-time behaviour.
paulb@24 101
    """
paulb@24 102
paulb@19 103
    def __init__(self):
paulb@50 104
paulb@50 105
        "Initialise the visitor."
paulb@50 106
paulb@19 107
        Visitor.__init__(self)
paulb@24 108
        self.system = system
paulb@19 109
paulb@30 110
        # Satisfy visitor issues.
paulb@30 111
paulb@30 112
        self.visitor = self
paulb@30 113
paulb@57 114
    def process(self, module, builtins=None):
paulb@45 115
paulb@45 116
        """
paulb@57 117
        Process the given 'module', using the optional 'builtins' to access
paulb@57 118
        built-in classes and functions.
paulb@45 119
        """
paulb@45 120
paulb@45 121
        self.subprograms = []
paulb@45 122
        self.current_subprograms = []
paulb@45 123
        self.current_namespaces = []
paulb@34 124
paulb@34 125
        # Give constants their own namespace.
paulb@34 126
paulb@57 127
        for value, constant in module.simplifier.constants.items():
paulb@34 128
            constant.namespace = Namespace()
paulb@34 129
paulb@34 130
        # Process the module, supplying builtins if possible.
paulb@34 131
paulb@45 132
        self.global_namespace = Namespace()
paulb@45 133
paulb@57 134
        if builtins is not None:
paulb@57 135
            self.builtins_namespace = builtins.namespace
paulb@34 136
        else:
paulb@45 137
            self.builtins_namespace = self.global_namespace
paulb@34 138
paulb@57 139
        return self.process_node(module)
paulb@45 140
paulb@45 141
    def process_node(self, node, locals=None):
paulb@24 142
paulb@24 143
        """
paulb@34 144
        Process a subprogram or module 'node', indicating any initial 'locals'.
paulb@34 145
        Return an annotated subprogram or module. Note that this method may
paulb@34 146
        mutate nodes in the original program.
paulb@24 147
        """
paulb@24 148
paulb@45 149
        if locals:
paulb@45 150
            self.namespace = locals
paulb@45 151
        else:
paulb@45 152
            self.namespace = self.global_namespace
paulb@45 153
paulb@45 154
        # Record the current subprogram and namespace.
paulb@20 155
paulb@45 156
        self.current_subprograms.append(node)
paulb@45 157
        self.current_namespaces.append(self.namespace)
paulb@30 158
paulb@25 159
        # Add namespace details to any structure involved.
paulb@25 160
paulb@34 161
        if getattr(node, "structure", None) is not None:
paulb@34 162
            node.structure.namespace = Namespace()
paulb@25 163
paulb@30 164
            # Initialise bases where appropriate.
paulb@19 165
paulb@30 166
            if hasattr(node.structure, "bases"):
paulb@30 167
                base_refs = []
paulb@30 168
                for base in node.structure.bases:
paulb@30 169
                    self.dispatch(base)
paulb@49 170
                    base_refs.append(self.namespace.types)
paulb@30 171
                node.structure.base_refs = base_refs
paulb@30 172
paulb@30 173
        # Dispatch to the code itself.
paulb@30 174
paulb@49 175
        node.namespace = self.namespace
paulb@20 176
        result = self.dispatch(node)
paulb@49 177
        result.namespace = self.namespace
paulb@49 178
paulb@49 179
        # Obtain the return values.
paulb@49 180
paulb@49 181
        self.last_returns = self.namespace.returns
paulb@49 182
        self.returned_locals = self.namespace.return_locals
paulb@45 183
paulb@45 184
        # Restore the previous subprogram and namespace.
paulb@45 185
paulb@45 186
        self.current_namespaces.pop()
paulb@45 187
        if self.current_namespaces:
paulb@45 188
            self.namespace = self.current_namespaces[-1]
paulb@45 189
paulb@45 190
        self.current_subprograms.pop()
paulb@45 191
paulb@20 192
        return result
paulb@20 193
paulb@24 194
    def annotate(self, node):
paulb@24 195
paulb@24 196
        "Annotate the given 'node' in the system."
paulb@24 197
paulb@49 198
        self.system.annotate(node, self.namespace.types)
paulb@34 199
paulb@25 200
    # Visitor methods.
paulb@25 201
paulb@19 202
    def default(self, node):
paulb@24 203
paulb@24 204
        """
paulb@24 205
        Process the given 'node', given that it does not have a specific
paulb@24 206
        handler.
paulb@24 207
        """
paulb@24 208
paulb@25 209
        for attr in ("expr", "lvalue", "test", "handler"):
paulb@19 210
            value = getattr(node, attr, None)
paulb@19 211
            if value is not None:
paulb@20 212
                setattr(node, attr, self.dispatch(value))
paulb@19 213
        for attr in ("body", "else_", "finally_", "code"):
paulb@19 214
            value = getattr(node, attr, None)
paulb@19 215
            if value is not None:
paulb@20 216
                setattr(node, attr, self.dispatches(value))
paulb@20 217
        return node
paulb@20 218
paulb@20 219
    def dispatch(self, node, *args):
paulb@34 220
        try:
paulb@34 221
            return Visitor.dispatch(self, node, *args)
paulb@58 222
        except FailureError, exc:
paulb@58 223
            exc.add(node)
paulb@34 224
            raise
paulb@58 225
        except Exception, exc:
paulb@58 226
            raise FailureError(exc, node)
paulb@19 227
paulb@19 228
    def visitLoadRef(self, loadref):
paulb@49 229
        self.namespace.set_types([Attribute(None, loadref.ref)])
paulb@24 230
        self.annotate(loadref)
paulb@20 231
        return loadref
paulb@19 232
paulb@19 233
    def visitLoadName(self, loadname):
paulb@49 234
        self.namespace.set_types(self.namespace.load(loadname.name))
paulb@31 235
        result = loadname
paulb@31 236
        self.annotate(result)
paulb@24 237
        return result
paulb@19 238
paulb@19 239
    def visitStoreName(self, storename):
paulb@31 240
        storename.expr = self.dispatch(storename.expr)
paulb@49 241
        self.namespace.store(storename.name, self.namespace.types)
paulb@31 242
        return storename
paulb@19 243
paulb@19 244
    def visitLoadTemp(self, loadtemp):
paulb@19 245
        index = getattr(loadtemp, "index", None)
paulb@49 246
        self.namespace.set_types(self.namespace.temp[index][-1])
paulb@24 247
        self.annotate(loadtemp)
paulb@20 248
        return loadtemp
paulb@19 249
paulb@19 250
    def visitStoreTemp(self, storetemp):
paulb@21 251
        storetemp.expr = self.dispatch(storetemp.expr)
paulb@19 252
        index = getattr(storetemp, "index", None)
paulb@49 253
        if not self.namespace.temp.has_key(index):
paulb@49 254
            self.namespace.temp[index] = []
paulb@49 255
        self.namespace.temp[index].append(self.namespace.types)
paulb@20 256
        return storetemp
paulb@19 257
paulb@19 258
    def visitReleaseTemp(self, releasetemp):
paulb@19 259
        index = getattr(releasetemp, "index", None)
paulb@49 260
        self.namespace.temp[index].pop()
paulb@20 261
        return releasetemp
paulb@20 262
paulb@20 263
    def visitLoadAttr(self, loadattr):
paulb@21 264
        loadattr.expr = self.dispatch(loadattr.expr)
paulb@20 265
        types = []
paulb@39 266
        accesses = {}
paulb@49 267
        for attr in self.namespace.types:
paulb@49 268
            if not accesses.has_key(attr.type):
paulb@49 269
                accesses[attr.type] = []
paulb@49 270
            for attribute, accessor in get_attributes(attr.type, loadattr.name):
paulb@49 271
                if attribute is not None:
paulb@49 272
                    types.append(attribute)
paulb@49 273
                else:
paulb@49 274
                    print "Empty attribute via accessor", accessor
paulb@49 275
                accesses[attr.type].append((attribute, accessor))
paulb@49 276
        self.namespace.set_types(types)
paulb@39 277
        loadattr.accesses = accesses
paulb@24 278
        self.annotate(loadattr)
paulb@20 279
        return loadattr
paulb@20 280
paulb@20 281
    def visitStoreAttr(self, storeattr):
paulb@21 282
        storeattr.expr = self.dispatch(storeattr.expr)
paulb@49 283
        expr = self.namespace.types
paulb@21 284
        storeattr.lvalue = self.dispatch(storeattr.lvalue)
paulb@57 285
        writes = {}
paulb@49 286
        for attr in self.namespace.types:
paulb@49 287
            if attr is None:
paulb@49 288
                print "Empty attribute storage attempt"
paulb@49 289
                continue
paulb@49 290
            attr.type.namespace.store(storeattr.name, expr)
paulb@57 291
            writes[attr.type] = attr.type.namespace.load(storeattr.name)
paulb@57 292
        storeattr.writes = writes
paulb@20 293
        return storeattr
paulb@19 294
paulb@34 295
    def visitConditional(self, conditional):
paulb@34 296
paulb@34 297
        # Conditionals keep local namespace changes isolated.
paulb@34 298
        # With Return nodes inside the body/else sections, the changes are
paulb@34 299
        # communicated to the caller.
paulb@34 300
paulb@34 301
        conditional.test = self.dispatch(conditional.test)
paulb@34 302
        saved_namespace = self.namespace
paulb@34 303
paulb@34 304
        self.namespace = Namespace()
paulb@34 305
        self.namespace.merge_namespace(saved_namespace)
paulb@34 306
        conditional.body = self.dispatches(conditional.body)
paulb@38 307
        body_namespace = self.namespace
paulb@34 308
paulb@34 309
        self.namespace = Namespace()
paulb@34 310
        self.namespace.merge_namespace(saved_namespace)
paulb@34 311
        conditional.else_ = self.dispatches(conditional.else_)
paulb@38 312
        else_namespace = self.namespace
paulb@34 313
paulb@38 314
        self.namespace = Namespace()
paulb@38 315
        self.namespace.merge_namespace(body_namespace)
paulb@38 316
        self.namespace.merge_namespace(else_namespace)
paulb@34 317
        return conditional
paulb@34 318
paulb@30 319
    def visitReturn(self, return_):
paulb@30 320
        if hasattr(return_, "expr"):
paulb@30 321
            return_.expr = self.dispatch(return_.expr)
paulb@49 322
            self.namespace.returns += self.namespace.types
paulb@49 323
        self.namespace.snapshot()
paulb@30 324
        return return_
paulb@30 325
paulb@24 326
    def visitInvoke(self, invoke):
paulb@24 327
        invoke.expr = self.dispatch(invoke.expr)
paulb@49 328
        invocation_types = self.namespace.types
paulb@25 329
paulb@54 330
        self.process_args(invoke)
paulb@24 331
paulb@34 332
        # Now locate and invoke the subprogram. This can be complicated because
paulb@34 333
        # the target may be a class or object, and there may be many different
paulb@34 334
        # related subprograms.
paulb@24 335
paulb@38 336
        invocations = {}
paulb@34 337
paulb@39 338
        # Visit each callable in turn, finding subprograms.
paulb@34 339
paulb@49 340
        for attr in invocation_types:
paulb@30 341
paulb@34 342
            # Deal with class invocations by providing instance objects.
paulb@34 343
            # Here, each class is queried for the __init__ method, which may
paulb@34 344
            # exist for some combinations of classes in a hierarchy but not for
paulb@34 345
            # others.
paulb@34 346
paulb@49 347
            if isinstance(attr.type, Class):
paulb@49 348
                attributes = get_attributes(attr.type, "__init__")
paulb@30 349
paulb@34 350
            # Deal with object invocations by using __call__ methods.
paulb@34 351
paulb@49 352
            elif isinstance(attr.type, Instance):
paulb@49 353
                attributes = get_attributes(attr.type, "__call__")
paulb@34 354
paulb@34 355
            # Normal functions or methods are more straightforward.
paulb@39 356
            # Here, we model them using an attribute with no context and with
paulb@39 357
            # no associated accessor.
paulb@34 358
paulb@27 359
            else:
paulb@49 360
                attributes = [(attr, None)]
paulb@39 361
paulb@39 362
            # Inspect each attribute and extract the subprogram.
paulb@25 363
paulb@39 364
            for attribute, accessor in attributes:
paulb@38 365
paulb@39 366
                # If a class is involved, presume that it must create a new
paulb@39 367
                # object.
paulb@39 368
paulb@49 369
                if isinstance(attr.type, Class):
paulb@53 370
paulb@53 371
                    # Instantiate the class.
paulb@53 372
                    # NOTE: Should probably only allocate a single instance.
paulb@53 373
paulb@53 374
                    instance = Instance()
paulb@53 375
                    instance.namespace = Namespace()
paulb@53 376
                    instance.namespace.store("__class__", [attr.type])
paulb@53 377
paulb@53 378
                    # For instantiations, switch the context.
paulb@53 379
paulb@53 380
                    if attribute is not None:
paulb@53 381
                        attribute = Attribute(instance, attribute.type)
paulb@53 382
paulb@53 383
                # Skip cases where no callable is found.
paulb@53 384
paulb@53 385
                if attribute is not None:
paulb@53 386
paulb@53 387
                    # If a subprogram is defined, invoke it.
paulb@53 388
paulb@53 389
                    self.invoke_subprogram(invoke, attribute)
paulb@53 390
                    invocations[callable] = attribute.type
paulb@53 391
paulb@53 392
                else:
paulb@53 393
                    print "Invocation type is None"
paulb@53 394
paulb@53 395
                if isinstance(attr.type, Class):
paulb@53 396
paulb@53 397
                    # Associate the instance with the result of this invocation.
paulb@53 398
paulb@54 399
                    self.namespace.set_types([Attribute(None, instance)])
paulb@39 400
                    self.annotate(invoke)
paulb@39 401
paulb@38 402
        invoke.invocations = invocations
paulb@24 403
paulb@24 404
        return invoke
paulb@24 405
paulb@25 406
    # Utility methods.
paulb@25 407
paulb@34 408
    def invoke_subprogram(self, invoke, subprogram):
paulb@34 409
paulb@34 410
        """
paulb@34 411
        Invoke using the given 'invoke' node the given 'subprogram'.
paulb@34 412
        """
paulb@34 413
paulb@42 414
        # Test to see if anything has changed.
paulb@42 415
paulb@42 416
        if hasattr(invoke, "syscount") and invoke.syscount == self.system.count:
paulb@42 417
            return
paulb@42 418
paulb@54 419
        # Test for context information, making it into a real attribute.
paulb@34 420
paulb@54 421
        if subprogram.context is not None:
paulb@54 422
            context = Attribute(None, subprogram.context)
paulb@34 423
            target = subprogram.type
paulb@34 424
        else:
paulb@34 425
            context = None
paulb@54 426
            target = subprogram.type
paulb@50 427
paulb@34 428
        # Provide the correct namespace for the invocation.
paulb@34 429
paulb@57 430
        if isinstance(invoke, InvokeBlock):
paulb@34 431
            namespace = Namespace()
paulb@34 432
            namespace.merge_namespace(self.namespace)
paulb@34 433
        else:
paulb@34 434
            items = self.make_items(invoke, target, context)
paulb@34 435
            namespace = self.make_namespace(items)
paulb@34 436
paulb@34 437
        # Process the subprogram.
paulb@34 438
paulb@53 439
        self.process_node(target, namespace)
paulb@34 440
paulb@45 441
        # NOTE: Improve and verify this.
paulb@34 442
paulb@53 443
        if getattr(target, "returns_value", 0):
paulb@49 444
            self.namespace.set_types(self.last_returns)
paulb@34 445
            self.annotate(invoke)
paulb@34 446
paulb@57 447
        if isinstance(invoke, InvokeBlock):
paulb@45 448
            for locals in self.returned_locals:
paulb@34 449
                self.namespace.merge_namespace(locals)
paulb@34 450
paulb@42 451
        # Remember the state of the system.
paulb@42 452
paulb@42 453
        invoke.syscount = self.system.count
paulb@42 454
paulb@54 455
    def process_args(self, invoke):
paulb@54 456
paulb@54 457
        # NOTE: Consider initialiser invocation for classes.
paulb@54 458
paulb@54 459
        types = []
paulb@54 460
        args = []
paulb@54 461
paulb@54 462
        # Get type information for regular arguments.
paulb@54 463
paulb@54 464
        for arg in invoke.args:
paulb@54 465
            args.append(self.dispatch(arg))
paulb@54 466
            types.append(self.namespace.types)
paulb@54 467
paulb@54 468
        # Get type information for star and dstar arguments.
paulb@54 469
paulb@54 470
        if invoke.star is not None:
paulb@54 471
            param, default = invoke.star
paulb@54 472
            default = self.dispatch(default)
paulb@54 473
            invoke.star = param, default
paulb@54 474
            types.append(default.types)
paulb@54 475
paulb@54 476
        if invoke.dstar is not None:
paulb@54 477
            param, default = invoke.dstar
paulb@54 478
            default = self.dispatch(default)
paulb@54 479
            invoke.dstar = param, default
paulb@54 480
            types.append(default.types)
paulb@54 481
paulb@54 482
        invoke.args = args
paulb@54 483
paulb@34 484
    def make_items(self, invocation, subprogram, context):
paulb@34 485
paulb@34 486
        """
paulb@34 487
        Make an items mapping for the 'invocation' of the 'subprogram' using the
paulb@34 488
        given 'context' (which may be None).
paulb@34 489
        """
paulb@34 490
paulb@34 491
        if context is not None:
paulb@53 492
            args = [Self(context)] + invocation.args
paulb@34 493
        else:
paulb@34 494
            args = invocation.args
paulb@34 495
paulb@24 496
        params = subprogram.params
paulb@24 497
        items = []
paulb@24 498
        keywords = {}
paulb@24 499
paulb@24 500
        # Process the specified arguments.
paulb@24 501
paulb@24 502
        for arg in args:
paulb@24 503
            if isinstance(arg, Keyword):
paulb@24 504
                keywords[arg.name] = arg.expr
paulb@24 505
                continue
paulb@24 506
            elif params:
paulb@24 507
                param, default = params[0]
paulb@24 508
                if arg is None:
paulb@54 509
                    arg = default
paulb@24 510
            else:
paulb@24 511
                raise TypeError, "Invocation has too many arguments."
paulb@24 512
            items.append((param, arg.types))
paulb@24 513
            params = params[1:]
paulb@24 514
paulb@24 515
        # Collect the remaining defaults.
paulb@24 516
paulb@24 517
        while params:
paulb@24 518
            param, default = params[0]
paulb@24 519
            if keywords.has_key(param):
paulb@24 520
                arg = keywords[param]
paulb@24 521
            else:
paulb@54 522
                arg = self.dispatch(default) # NOTE: Review reprocessing.
paulb@24 523
            items.append((param, arg.types))
paulb@24 524
            params = params[1:]
paulb@24 525
paulb@24 526
        # Add star and dstar.
paulb@24 527
paulb@24 528
        if invocation.star is not None:
paulb@24 529
            if subprogram.star is not None:
paulb@24 530
                param, default = subprogram.star
paulb@24 531
                items.append((param, invocation.star.types))
paulb@24 532
            else:
paulb@24 533
                raise TypeError, "Invocation provides unwanted *args."
paulb@24 534
        elif subprogram.star is not None:
paulb@24 535
            param, default = subprogram.star
paulb@54 536
            arg = self.dispatch(default) # NOTE: Review reprocessing.
paulb@54 537
            items.append((param, arg.types))
paulb@24 538
paulb@24 539
        if invocation.dstar is not None:
paulb@24 540
            if subprogram.dstar is not None:
paulb@24 541
                param, default = subprogram.dstar
paulb@24 542
                items.append((param, invocation.dstar.types))
paulb@24 543
            else:
paulb@24 544
                raise TypeError, "Invocation provides unwanted **args."
paulb@24 545
        elif subprogram.dstar is not None:
paulb@24 546
            param, default = subprogram.dstar
paulb@54 547
            arg = self.dispatch(default) # NOTE: Review reprocessing.
paulb@54 548
            items.append((param, arg.types))
paulb@24 549
paulb@24 550
        return items
paulb@24 551
paulb@24 552
    def make_namespace(self, items):
paulb@24 553
        namespace = Namespace()
paulb@24 554
        namespace.merge_items(items)
paulb@24 555
        return namespace
paulb@24 556
paulb@48 557
# Namespace-related abstractions.
paulb@48 558
paulb@48 559
class Namespace:
paulb@48 560
paulb@48 561
    """
paulb@48 562
    A local namespace which may either relate to a genuine set of function
paulb@50 563
    locals or the initialisation of a structure or module.
paulb@48 564
    """
paulb@48 565
paulb@48 566
    def __init__(self):
paulb@50 567
paulb@50 568
        """
paulb@50 569
        Initialise the namespace with a mapping of local names to possible
paulb@50 570
        types, a list of return values and of possible returned local
paulb@50 571
        namespaces. The namespace also tracks the "current" types and a mapping
paulb@50 572
        of temporary value names to types.
paulb@50 573
        """
paulb@50 574
paulb@48 575
        self.names = {}
paulb@49 576
        self.returns = []
paulb@49 577
        self.return_locals = []
paulb@49 578
        self.temp = {}
paulb@49 579
        self.types = []
paulb@49 580
paulb@49 581
    def set_types(self, types):
paulb@49 582
        self.types = types
paulb@48 583
paulb@48 584
    def store(self, name, types):
paulb@48 585
        self.names[name] = types
paulb@48 586
paulb@48 587
    def load(self, name):
paulb@48 588
        return self.names[name]
paulb@48 589
paulb@48 590
    def merge(self, name, types):
paulb@48 591
        if not self.names.has_key(name):
paulb@48 592
            self.names[name] = types[:]
paulb@48 593
        else:
paulb@48 594
            existing = self.names[name]
paulb@48 595
            for type in types:
paulb@48 596
                if type not in existing:
paulb@48 597
                    existing.append(type)
paulb@48 598
paulb@48 599
    def merge_namespace(self, namespace):
paulb@48 600
        self.merge_items(namespace.names.items())
paulb@48 601
paulb@48 602
    def merge_items(self, items):
paulb@48 603
        for name, types in items:
paulb@48 604
            self.merge(name, types)
paulb@48 605
paulb@49 606
    def snapshot(self):
paulb@49 607
paulb@49 608
        "Make a snapshot of the locals and remember them."
paulb@49 609
paulb@49 610
        namespace = Namespace()
paulb@49 611
        namespace.merge_namespace(self)
paulb@49 612
        self.return_locals.append(namespace)
paulb@49 613
paulb@48 614
    def __repr__(self):
paulb@48 615
        return repr(self.names)
paulb@48 616
paulb@48 617
class Attribute:
paulb@48 618
paulb@48 619
    """
paulb@48 620
    An attribute abstraction, indicating the type of the attribute along with
paulb@48 621
    its context or origin.
paulb@48 622
    """
paulb@48 623
paulb@48 624
    def __init__(self, context, type):
paulb@48 625
        self.context = context
paulb@48 626
        self.type = type
paulb@48 627
paulb@48 628
    def __eq__(self, other):
paulb@48 629
        return hasattr(other, "type") and other.type == self.type or other == self.type
paulb@48 630
paulb@48 631
    def __repr__(self):
paulb@54 632
        return "Attribute(%s, %s)" % (repr(self.context), repr(self.type))
paulb@48 633
paulb@53 634
class Self:
paulb@53 635
    def __init__(self, attribute):
paulb@53 636
        self.types = [attribute]
paulb@53 637
paulb@48 638
def find_attributes(structure, name):
paulb@48 639
paulb@48 640
    """
paulb@48 641
    Find for the given 'structure' all attributes for the given 'name', visiting
paulb@48 642
    base classes where appropriate and returning the attributes in order of
paulb@48 643
    descending precedence for all possible base classes.
paulb@48 644
paulb@48 645
    The elements in the result list are 2-tuples which contain the attribute and
paulb@48 646
    the structure involved in accessing the attribute.
paulb@48 647
    """
paulb@48 648
paulb@48 649
    # First attempt to search the instance/class namespace.
paulb@48 650
paulb@48 651
    try:
paulb@48 652
        l = structure.namespace.load(name)
paulb@48 653
        attributes = []
paulb@48 654
        for attribute in l:
paulb@48 655
            attributes.append((attribute, structure))
paulb@48 656
paulb@48 657
    # If that does not work, attempt to investigate any class or base classes.
paulb@48 658
paulb@48 659
    except KeyError:
paulb@48 660
        attributes = []
paulb@48 661
paulb@48 662
        # Investigate any instance's implementing class.
paulb@48 663
paulb@48 664
        if isinstance(structure, Instance):
paulb@48 665
            for cls in structure.namespace.load("__class__"):
paulb@50 666
                l = get_attributes(cls, name)
paulb@48 667
                for attribute in l:
paulb@48 668
                    if attribute not in attributes:
paulb@48 669
                        attributes.append(attribute)
paulb@48 670
paulb@48 671
        # Investigate any class's base classes.
paulb@48 672
paulb@48 673
        elif isinstance(structure, Class):
paulb@48 674
paulb@48 675
            # If no base classes exist, return an indicator that no attribute
paulb@48 676
            # exists.
paulb@48 677
paulb@48 678
            if not structure.base_refs:
paulb@48 679
                return [(None, structure)]
paulb@48 680
paulb@48 681
            # Otherwise, find all possible base classes.
paulb@48 682
paulb@48 683
            for base_refs in structure.base_refs:
paulb@48 684
                base_attributes = []
paulb@48 685
paulb@48 686
                # For each base class, find attributes either in the base
paulb@48 687
                # class or its own base classes.
paulb@48 688
paulb@48 689
                for base_ref in base_refs:
paulb@50 690
                    l = get_attributes(base_ref, name)
paulb@48 691
                    for attribute in l:
paulb@48 692
                        if attribute not in base_attributes:
paulb@48 693
                            base_attributes.append(attribute)
paulb@48 694
paulb@48 695
                attributes += base_attributes
paulb@48 696
paulb@49 697
    return attributes
paulb@48 698
paulb@48 699
def get_attributes(structure, name):
paulb@48 700
paulb@48 701
    """
paulb@48 702
    Return all possible attributes for the given 'structure' having the given
paulb@48 703
    'name', wrapping each attribute in an Attribute object which includes
paulb@48 704
    context information for the attribute access.
paulb@48 705
paulb@48 706
    The elements in the result list are 2-tuples which contain the attribute and
paulb@48 707
    the structure involved in accessing the attribute.
paulb@48 708
    """
paulb@48 709
paulb@48 710
    if isinstance(structure, Attribute):
paulb@48 711
        structure = structure.type
paulb@53 712
    results = []
paulb@53 713
    for attribute, accessor in find_attributes(structure, name):
paulb@53 714
        if attribute is not None and isinstance(structure, Structure):
paulb@53 715
            results.append((Attribute(structure, attribute.type), accessor))
paulb@53 716
        else:
paulb@53 717
            results.append((attribute, accessor))
paulb@53 718
    return results
paulb@48 719
paulb@57 720
# Convenience functions.
paulb@57 721
paulb@57 722
def annotate(module, builtins=None):
paulb@57 723
paulb@57 724
    """
paulb@57 725
    Annotate the given 'module', also employing the optional 'builtins' module,
paulb@57 726
    if specified.
paulb@57 727
    """
paulb@57 728
paulb@57 729
    annotator = Annotator()
paulb@57 730
    if builtins is not None:
paulb@57 731
        annotator.process(module, builtins)
paulb@57 732
    else:
paulb@57 733
        annotator.process(module)
paulb@57 734
paulb@57 735
def annotate_all(modules, builtins):
paulb@57 736
    annotate(builtins)
paulb@57 737
    for module in modules:
paulb@57 738
        annotate(module, builtins)
paulb@57 739
paulb@19 740
# vim: tabstop=4 expandtab shiftwidth=4