Lichen

Annotated inspector.py

90:c7ddfc4525da
2016-10-08 Paul Boddie Added some support for eliminating accessor class types where the provided attributes are invoked and are unbound methods. This uses a more sophisticated method involving usage observations that incorporate invocation information, permitting classes as accessors if paths through the code support them, even if other paths require instances as accessors to invoke methods.
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Inspect and obtain module structure.
paul@0 5
paul@0 6
Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013,
paul@0 7
              2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
paul@0 8
paul@0 9
This program is free software; you can redistribute it and/or modify it under
paul@0 10
the terms of the GNU General Public License as published by the Free Software
paul@0 11
Foundation; either version 3 of the License, or (at your option) any later
paul@0 12
version.
paul@0 13
paul@0 14
This program is distributed in the hope that it will be useful, but WITHOUT
paul@0 15
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@0 16
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@0 17
details.
paul@0 18
paul@0 19
You should have received a copy of the GNU General Public License along with
paul@0 20
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@0 21
"""
paul@0 22
paul@0 23
from branching import BranchTracker
paul@12 24
from common import get_argnames, init_item, predefined_constants
paul@26 25
from modules import BasicModule, CacheWritingModule, InspectionNaming
paul@3 26
from errors import InspectError
paul@0 27
from referencing import Reference
paul@12 28
from resolving import NameResolving
paul@12 29
from results import AccessRef, InstanceRef, InvocationRef, LiteralSequenceRef, \
paul@12 30
                    LocalNameRef, NameRef, ResolvedNameRef
paul@0 31
import compiler
paul@0 32
import sys
paul@0 33
paul@26 34
class InspectedModule(BasicModule, CacheWritingModule, NameResolving, InspectionNaming):
paul@0 35
paul@0 36
    "A module inspector."
paul@0 37
paul@0 38
    def __init__(self, name, importer):
paul@13 39
paul@13 40
        "Initialise the module with basic details."
paul@13 41
paul@0 42
        BasicModule.__init__(self, name, importer)
paul@12 43
paul@0 44
        self.in_class = False
paul@0 45
        self.in_conditional = False
paul@88 46
        self.in_invocation = False
paul@0 47
        self.global_attr_accesses = {}
paul@0 48
paul@0 49
        # Usage tracking.
paul@0 50
paul@0 51
        self.trackers = []
paul@0 52
        self.attr_accessor_branches = {}
paul@0 53
paul@0 54
    def __repr__(self):
paul@0 55
        return "InspectedModule(%r, %r)" % (self.name, self.importer)
paul@0 56
paul@27 57
    # Principal methods.
paul@27 58
paul@0 59
    def parse(self, filename):
paul@0 60
paul@0 61
        "Parse the file having the given 'filename'."
paul@0 62
paul@0 63
        self.parse_file(filename)
paul@0 64
paul@0 65
        # Inspect the module.
paul@0 66
paul@0 67
        self.start_tracking_in_module()
paul@0 68
paul@0 69
        # Detect and record imports and globals declared in the module.
paul@0 70
paul@0 71
        self.assign_general_local("__name__", self.get_constant("str", self.name))
paul@0 72
        self.assign_general_local("__file__", self.get_constant("str", filename))
paul@0 73
        self.process_structure(self.astnode)
paul@0 74
paul@0 75
        # Set the class of the module after the definition has occurred.
paul@0 76
paul@0 77
        ref = self.get_builtin("object")
paul@0 78
        self.set_name("__class__", ref)
paul@0 79
paul@0 80
        # Get module-level attribute usage details.
paul@0 81
paul@0 82
        self.stop_tracking_in_module()
paul@0 83
paul@27 84
        # Collect external name references.
paul@0 85
paul@27 86
        self.collect_names()
paul@0 87
paul@12 88
    def complete(self):
paul@0 89
paul@12 90
        "Complete the module inspection."
paul@0 91
paul@12 92
        # Resolve names not definitively mapped to objects.
paul@0 93
paul@12 94
        self.resolve()
paul@0 95
paul@12 96
        # Define the invocation requirements in each namespace.
paul@0 97
paul@12 98
        self.set_invocation_usage()
paul@0 99
paul@12 100
        # Propagate to the importer information needed in subsequent activities.
paul@0 101
paul@12 102
        self.propagate()
paul@0 103
paul@27 104
    # Accessory methods.
paul@0 105
paul@27 106
    def collect_names(self):
paul@0 107
paul@27 108
        "Collect the names used by each scope."
paul@0 109
paul@0 110
        for path in self.names_used.keys():
paul@27 111
            self.collect_names_for_path(path)
paul@27 112
paul@27 113
    def collect_names_for_path(self, path):
paul@0 114
paul@33 115
        """
paul@33 116
        Collect the names used by the given 'path'. These are propagated to the
paul@33 117
        importer in advance of any dependency resolution.
paul@33 118
        """
paul@0 119
paul@0 120
        names = self.names_used.get(path)
paul@0 121
        if not names:
paul@0 122
            return
paul@0 123
paul@0 124
        in_function = self.function_locals.has_key(path)
paul@0 125
paul@0 126
        for name in names:
paul@0 127
            if name in predefined_constants or in_function and name in self.function_locals[path]:
paul@0 128
                continue
paul@0 129
paul@35 130
            # Find local definitions (within dynamic namespaces).
paul@0 131
paul@0 132
            key = "%s.%s" % (path, name)
paul@27 133
            ref = self.get_resolved_object(key)
paul@0 134
            if ref:
paul@40 135
                self.set_name_reference(key, ref)
paul@0 136
                continue
paul@0 137
paul@40 138
            # Find global or known built-in definitions.
paul@0 139
paul@27 140
            ref = self.get_resolved_global_or_builtin(name)
paul@27 141
            if ref:
paul@40 142
                self.set_name_reference(key, ref)
paul@0 143
                continue
paul@0 144
paul@40 145
            # Find presumed built-in definitions.
paul@0 146
paul@40 147
            ref = self.get_builtin(name)
paul@40 148
            self.set_name_reference(key, ref)
paul@0 149
paul@40 150
    def set_name_reference(self, path, ref):
paul@0 151
paul@40 152
        "Map the given name 'path' to 'ref'."
paul@0 153
paul@40 154
        self.importer.all_name_references[path] = self.name_references[path] = ref
paul@0 155
paul@27 156
    def get_resolved_global_or_builtin(self, name):
paul@0 157
paul@27 158
        "Return the resolved global or built-in object with the given 'name'."
paul@0 159
paul@40 160
        # In some circumstances, the name is neither global nor recognised by
paul@40 161
        # the importer. It is then assumed to be a general built-in.
paul@0 162
paul@40 163
        return self.get_global(name) or \
paul@40 164
               self.importer.get_object("__builtins__.%s" % name)
paul@0 165
paul@0 166
    # Module structure traversal.
paul@0 167
paul@0 168
    def process_structure_node(self, n):
paul@0 169
paul@0 170
        "Process the individual node 'n'."
paul@0 171
paul@0 172
        # Module global detection.
paul@0 173
paul@0 174
        if isinstance(n, compiler.ast.Global):
paul@0 175
            self.process_global_node(n)
paul@0 176
paul@0 177
        # Module import declarations.
paul@0 178
paul@0 179
        elif isinstance(n, compiler.ast.From):
paul@0 180
            self.process_from_node(n)
paul@0 181
paul@0 182
        elif isinstance(n, compiler.ast.Import):
paul@0 183
            self.process_import_node(n)
paul@0 184
paul@0 185
        # Nodes using operator module functions.
paul@0 186
paul@0 187
        elif isinstance(n, compiler.ast.Operator):
paul@0 188
            return self.process_operator_node(n)
paul@0 189
paul@0 190
        elif isinstance(n, compiler.ast.AugAssign):
paul@0 191
            self.process_augassign_node(n)
paul@0 192
paul@0 193
        elif isinstance(n, compiler.ast.Compare):
paul@0 194
            return self.process_compare_node(n)
paul@0 195
paul@0 196
        elif isinstance(n, compiler.ast.Slice):
paul@0 197
            return self.process_slice_node(n)
paul@0 198
paul@0 199
        elif isinstance(n, compiler.ast.Sliceobj):
paul@0 200
            return self.process_sliceobj_node(n)
paul@0 201
paul@0 202
        elif isinstance(n, compiler.ast.Subscript):
paul@0 203
            return self.process_subscript_node(n)
paul@0 204
paul@0 205
        # Namespaces within modules.
paul@0 206
paul@0 207
        elif isinstance(n, compiler.ast.Class):
paul@0 208
            self.process_class_node(n)
paul@0 209
paul@0 210
        elif isinstance(n, compiler.ast.Function):
paul@0 211
            self.process_function_node(n, n.name)
paul@0 212
paul@0 213
        elif isinstance(n, compiler.ast.Lambda):
paul@0 214
            return self.process_lambda_node(n)
paul@0 215
paul@0 216
        # Assignments.
paul@0 217
paul@0 218
        elif isinstance(n, compiler.ast.Assign):
paul@0 219
paul@0 220
            # Handle each assignment node.
paul@0 221
paul@0 222
            for node in n.nodes:
paul@0 223
                self.process_assignment_node(node, n.expr)
paul@0 224
paul@0 225
        # Assignments within non-Assign nodes.
paul@0 226
paul@0 227
        elif isinstance(n, compiler.ast.AssName):
paul@0 228
            self.process_assignment_node(n, None)
paul@0 229
paul@0 230
        elif isinstance(n, compiler.ast.AssAttr):
paul@0 231
            self.process_attribute_access(n)
paul@0 232
paul@0 233
        # Accesses.
paul@0 234
paul@0 235
        elif isinstance(n, compiler.ast.Getattr):
paul@0 236
            return self.process_attribute_access(n)
paul@0 237
paul@0 238
        # Name recording for later testing.
paul@0 239
paul@0 240
        elif isinstance(n, compiler.ast.Name):
paul@0 241
            return self.process_name_node(n)
paul@0 242
paul@0 243
        # Conditional statement tracking.
paul@0 244
paul@0 245
        elif isinstance(n, compiler.ast.For):
paul@0 246
            self.process_for_node(n)
paul@0 247
paul@0 248
        elif isinstance(n, compiler.ast.While):
paul@0 249
            self.process_while_node(n)
paul@0 250
paul@0 251
        elif isinstance(n, compiler.ast.If):
paul@0 252
            self.process_if_node(n)
paul@0 253
paul@0 254
        elif isinstance(n, (compiler.ast.And, compiler.ast.Or)):
paul@0 255
            return self.process_logical_node(n)
paul@0 256
paul@0 257
        # Exception control-flow tracking.
paul@0 258
paul@0 259
        elif isinstance(n, compiler.ast.TryExcept):
paul@0 260
            self.process_try_node(n)
paul@0 261
paul@0 262
        elif isinstance(n, compiler.ast.TryFinally):
paul@0 263
            self.process_try_finally_node(n)
paul@0 264
paul@0 265
        # Control-flow modification statements.
paul@0 266
paul@0 267
        elif isinstance(n, compiler.ast.Break):
paul@0 268
            self.trackers[-1].suspend_broken_branch()
paul@0 269
paul@0 270
        elif isinstance(n, compiler.ast.Continue):
paul@0 271
            self.trackers[-1].suspend_continuing_branch()
paul@0 272
paul@0 273
        elif isinstance(n, compiler.ast.Raise):
paul@0 274
            self.process_structure(n)
paul@0 275
            self.trackers[-1].abandon_branch()
paul@0 276
paul@0 277
        elif isinstance(n, compiler.ast.Return):
paul@0 278
            self.process_structure(n)
paul@0 279
            self.trackers[-1].abandon_returning_branch()
paul@0 280
paul@0 281
        # Invocations.
paul@0 282
paul@0 283
        elif isinstance(n, compiler.ast.CallFunc):
paul@0 284
            return self.process_invocation_node(n)
paul@0 285
paul@0 286
        # Constant usage.
paul@0 287
paul@0 288
        elif isinstance(n, compiler.ast.Const):
paul@0 289
            return self.get_literal_instance(n, n.value.__class__.__name__)
paul@0 290
paul@0 291
        elif isinstance(n, compiler.ast.Dict):
paul@0 292
            return self.get_literal_instance(n, "dict")
paul@0 293
paul@0 294
        elif isinstance(n, compiler.ast.List):
paul@0 295
            return self.get_literal_instance(n, "list")
paul@0 296
paul@0 297
        elif isinstance(n, compiler.ast.Tuple):
paul@0 298
            return self.get_literal_instance(n, "tuple")
paul@0 299
paul@3 300
        # Unsupported nodes.
paul@3 301
paul@3 302
        elif isinstance(n, compiler.ast.GenExpr):
paul@3 303
            raise InspectError("Generator expressions are not supported.", self.get_namespace_path(), n)
paul@3 304
paul@3 305
        elif isinstance(n, compiler.ast.IfExp):
paul@3 306
            raise InspectError("If-else expressions are not supported.", self.get_namespace_path(), n)
paul@0 307
paul@0 308
        elif isinstance(n, compiler.ast.ListComp):
paul@3 309
            raise InspectError("List comprehensions are not supported.", self.get_namespace_path(), n)
paul@0 310
paul@0 311
        # All other nodes are processed depth-first.
paul@0 312
paul@0 313
        else:
paul@0 314
            self.process_structure(n)
paul@0 315
paul@0 316
        # By default, no expression details are returned.
paul@0 317
paul@0 318
        return None
paul@0 319
paul@0 320
    # Specific node handling.
paul@0 321
paul@0 322
    def process_assignment_node(self, n, expr):
paul@0 323
paul@0 324
        "Process the individual node 'n' to be assigned the contents of 'expr'."
paul@0 325
paul@0 326
        # Names and attributes are assigned the entire expression.
paul@0 327
paul@0 328
        if isinstance(n, compiler.ast.AssName):
paul@61 329
            if n.name == "self":
paul@61 330
                raise InspectError("Redefinition of self is not allowed.", self.get_namespace_path(), n)
paul@0 331
paul@0 332
            name_ref = expr and self.process_structure_node(expr)
paul@0 333
paul@0 334
            # Name assignments populate either function namespaces or the
paul@0 335
            # general namespace hierarchy.
paul@0 336
paul@0 337
            self.assign_general_local(n.name, name_ref)
paul@0 338
paul@0 339
            # Record usage of the name.
paul@0 340
paul@0 341
            self.record_name(n.name)
paul@0 342
paul@0 343
        elif isinstance(n, compiler.ast.AssAttr):
paul@0 344
            if expr: self.process_structure_node(expr)
paul@0 345
            self.process_attribute_access(n)
paul@0 346
paul@0 347
        # Lists and tuples are matched against the expression and their
paul@0 348
        # items assigned to expression items.
paul@0 349
paul@0 350
        elif isinstance(n, (compiler.ast.AssList, compiler.ast.AssTuple)):
paul@0 351
            self.process_assignment_node_items(n, expr)
paul@0 352
paul@0 353
        # Slices and subscripts are permitted within assignment nodes.
paul@0 354
paul@0 355
        elif isinstance(n, compiler.ast.Slice):
paul@0 356
            self.process_slice_node(n, expr)
paul@0 357
paul@0 358
        elif isinstance(n, compiler.ast.Subscript):
paul@0 359
            self.process_subscript_node(n, expr)
paul@0 360
paul@0 361
    def process_attribute_access(self, n):
paul@0 362
paul@0 363
        "Process the given attribute access node 'n'."
paul@0 364
paul@0 365
        # Obtain any completed chain and return the reference to it.
paul@0 366
paul@88 367
        in_invocation = self.in_invocation
paul@88 368
        self.in_invocation = False
paul@0 369
        name_ref = self.process_attribute_chain(n)
paul@88 370
        self.in_invocation = in_invocation
paul@88 371
paul@0 372
        if self.have_access_expression(n):
paul@0 373
            return name_ref
paul@0 374
paul@0 375
        # Where the start of the chain of attributes has been reached, determine
paul@0 376
        # the complete access.
paul@0 377
paul@0 378
        # Given a non-access node, this chain can be handled in its entirety,
paul@0 379
        # either being name-based and thus an access rooted on a name, or being
paul@0 380
        # based on some other node and thus an anonymous access of some kind.
paul@0 381
paul@0 382
        path = self.get_namespace_path()
paul@0 383
paul@0 384
        # Start with the the full attribute chain.
paul@0 385
paul@0 386
        remaining = self.attrs
paul@0 387
        attrnames = ".".join(remaining)
paul@0 388
paul@0 389
        # If the accessor cannot be identified, or where attributes
paul@0 390
        # remain in an attribute chain, record the anonymous accesses.
paul@0 391
paul@0 392
        if not isinstance(name_ref, NameRef): # includes ResolvedNameRef
paul@0 393
paul@0 394
            assignment = isinstance(n, compiler.ast.AssAttr)
paul@0 395
paul@0 396
            init_item(self.attr_accesses, path, set)
paul@0 397
            self.attr_accesses[path].add(attrnames)
paul@0 398
paul@0 399
            self.record_access_details(None, attrnames, assignment)
paul@0 400
            del self.attrs[0]
paul@0 401
            return
paul@0 402
paul@0 403
        # Name-based accesses will handle the first attribute in a
paul@0 404
        # chain.
paul@0 405
paul@0 406
        else:
paul@0 407
            attrname = remaining[0]
paul@0 408
paul@0 409
            # Attribute assignments are used to identify instance attributes.
paul@0 410
paul@0 411
            if isinstance(n, compiler.ast.AssAttr) and \
paul@0 412
                self.in_class and self.in_function and n.expr.name == "self":
paul@0 413
paul@0 414
                self.set_instance_attr(attrname)
paul@0 415
paul@0 416
            # Record attribute usage using any name local to this namespace,
paul@0 417
            # if assigned in the namespace, or using an external name
paul@0 418
            # (presently just globals within classes).
paul@0 419
paul@0 420
            name = self.get_name_for_tracking(name_ref.name, name_ref.final())
paul@0 421
            tracker = self.trackers[-1]
paul@0 422
paul@0 423
            immediate_access = len(self.attrs) == 1
paul@0 424
            assignment = immediate_access and isinstance(n, compiler.ast.AssAttr)
paul@0 425
paul@0 426
            del self.attrs[0]
paul@0 427
paul@0 428
            # Record global-based chains for subsequent resolution.
paul@0 429
paul@0 430
            is_global = self.in_function and not self.function_locals[path].has_key(name) or \
paul@0 431
                        not self.in_function
paul@0 432
paul@0 433
            if is_global:
paul@0 434
                self.record_global_access_details(name, attrnames)
paul@0 435
paul@0 436
            # Make sure the name is being tracked: global names will not
paul@0 437
            # already be initialised in a branch and must be added
paul@0 438
            # explicitly.
paul@0 439
paul@0 440
            if not tracker.have_name(name):
paul@0 441
                tracker.assign_names([name])
paul@0 442
                if self.in_function:
paul@0 443
                    self.scope_globals[path].add(name)
paul@0 444
paul@0 445
            # Record attribute usage in the tracker, and record the branch
paul@0 446
            # information for the access.
paul@0 447
paul@88 448
            branches = tracker.use_attribute(name, attrname, self.in_invocation)
paul@0 449
paul@0 450
            if not branches:
paul@84 451
                raise InspectError("Name %s is accessed using %s before an assignment." % (
paul@84 452
                    name, attrname), path, n)
paul@0 453
paul@0 454
            self.record_branches_for_access(branches, name, attrnames)
paul@0 455
            access_number = self.record_access_details(name, attrnames, assignment)
paul@0 456
            return AccessRef(name, attrnames, access_number)
paul@0 457
paul@0 458
    def process_class_node(self, n):
paul@0 459
paul@0 460
        "Process the given class node 'n'."
paul@0 461
paul@0 462
        path = self.get_namespace_path()
paul@0 463
paul@0 464
        # To avoid notions of class "versions" where the same definition
paul@0 465
        # might be parameterised with different state and be referenced
paul@0 466
        # elsewhere (as base classes, for example), classes in functions or
paul@0 467
        # conditions are forbidden.
paul@0 468
paul@0 469
        if self.in_function or self.in_conditional:
paul@0 470
            print >>sys.stderr, "In %s, class %s in function or conditional statement ignored." % (
paul@0 471
                path, n.name)
paul@0 472
            return
paul@0 473
paul@0 474
        # Resolve base classes.
paul@0 475
paul@0 476
        bases = []
paul@0 477
paul@0 478
        for base in n.bases:
paul@0 479
            base_class = self.get_class(base)
paul@0 480
paul@0 481
            if not base_class:
paul@12 482
                print >>sys.stderr, "In %s, class %s has unidentifiable base class: %s" % (
paul@12 483
                    path, n.name, base)
paul@0 484
                return
paul@0 485
            else:
paul@0 486
                bases.append(base_class)
paul@0 487
paul@0 488
        # Record bases for the class and retain the class name.
paul@0 489
paul@0 490
        class_name = self.get_object_path(n.name)
paul@0 491
paul@0 492
        if not bases and class_name != "__builtins__.core.object":
paul@0 493
            ref = self.get_object("__builtins__.object")
paul@0 494
            bases.append(ref)
paul@0 495
paul@0 496
        self.importer.classes[class_name] = self.classes[class_name] = bases
paul@0 497
        self.importer.subclasses[class_name] = set()
paul@0 498
        self.scope_globals[class_name] = set()
paul@0 499
paul@0 500
        # Set the definition before entering the namespace rather than
paul@0 501
        # afterwards because methods may reference it. In normal Python,
paul@0 502
        # a class is not accessible until the definition is complete, but
paul@0 503
        # methods can generally reference it since upon being called the
paul@0 504
        # class will already exist.
paul@0 505
paul@0 506
        self.set_definition(n.name, "<class>")
paul@0 507
paul@0 508
        in_class = self.in_class
paul@0 509
        self.in_class = class_name
paul@0 510
        self.set_instance_attr("__class__", Reference("<class>", class_name))
paul@0 511
        self.enter_namespace(n.name)
paul@0 512
        self.set_name("__fn__") # special instantiator attribute
paul@0 513
        self.set_name("__args__") # special instantiator attribute
paul@0 514
        self.assign_general_local("__name__", self.get_constant("str", class_name))
paul@0 515
        self.process_structure_node(n.code)
paul@0 516
        self.exit_namespace()
paul@0 517
        self.in_class = in_class
paul@0 518
paul@0 519
    def process_from_node(self, n):
paul@0 520
paul@0 521
        "Process the given node 'n', importing from another module."
paul@0 522
paul@0 523
        path = self.get_namespace_path()
paul@0 524
paul@12 525
        module_name, names = self.get_module_name(n)
paul@12 526
        if module_name == self.name:
paul@12 527
            raise InspectError("Cannot import from the current module.", path, n)
paul@0 528
paul@18 529
        self.queue_module(module_name)
paul@0 530
paul@0 531
        # Attempt to obtain the referenced objects.
paul@0 532
paul@0 533
        for name, alias in n.names:
paul@0 534
            if name == "*":
paul@12 535
                raise InspectError("Only explicitly specified names can be imported from modules.", path, n)
paul@0 536
paul@0 537
            # Explicit names.
paul@0 538
paul@12 539
            ref = self.import_name_from_module(name, module_name)
paul@0 540
            value = ResolvedNameRef(alias or name, ref)
paul@0 541
            self.set_general_local(alias or name, value)
paul@0 542
paul@0 543
    def process_function_node(self, n, name):
paul@0 544
paul@0 545
        """
paul@0 546
        Process the given function or lambda node 'n' with the given 'name'.
paul@0 547
        """
paul@0 548
paul@0 549
        is_lambda = isinstance(n, compiler.ast.Lambda)
paul@0 550
paul@0 551
        # Where a function is declared conditionally, use a separate name for
paul@0 552
        # the definition, and assign the definition to the stated name.
paul@0 553
paul@0 554
        if (self.in_conditional or self.in_function) and not is_lambda:
paul@0 555
            original_name = name
paul@0 556
            name = self.get_lambda_name()
paul@0 557
        else:
paul@0 558
            original_name = None
paul@0 559
paul@0 560
        # Initialise argument and local records.
paul@0 561
paul@0 562
        function_name = self.get_object_path(name)
paul@46 563
        argnames = get_argnames(n.argnames)
paul@48 564
        is_method = self.in_class and not self.in_function
paul@0 565
paul@48 566
        # Remove explicit "self" from method parameters.
paul@46 567
paul@48 568
        if is_method and argnames and argnames[0] == "self":
paul@48 569
            del argnames[0]
paul@48 570
paul@48 571
        # Copy and propagate the parameters.
paul@46 572
paul@46 573
        self.importer.function_parameters[function_name] = \
paul@48 574
        self.function_parameters[function_name] = argnames[:]
paul@46 575
paul@46 576
        # Define all arguments/parameters in the local namespace.
paul@46 577
paul@0 578
        locals = self.function_locals[function_name] = {}
paul@0 579
paul@48 580
        # Insert "self" into method locals.
paul@48 581
paul@48 582
        if is_method:
paul@48 583
            argnames.insert(0, "self")
paul@48 584
paul@47 585
        # Define "self" in terms of the class if in a method.
paul@47 586
        # This does not diminish the need for type-narrowing in the deducer.
paul@47 587
paul@47 588
        if argnames:
paul@48 589
            if self.in_class and not self.in_function and argnames[0] == "self":
paul@47 590
                locals[argnames[0]] = Reference("<instance>", self.in_class)
paul@47 591
            else:
paul@47 592
                locals[argnames[0]] = Reference("<var>")
paul@47 593
paul@47 594
        for argname in argnames[1:]:
paul@0 595
            locals[argname] = Reference("<var>")
paul@0 596
paul@0 597
        globals = self.scope_globals[function_name] = set()
paul@0 598
paul@0 599
        # Process the defaults.
paul@0 600
paul@0 601
        defaults = self.importer.function_defaults[function_name] = \
paul@0 602
                   self.function_defaults[function_name] = []
paul@0 603
paul@0 604
        for argname, default in compiler.ast.get_defaults(n):
paul@0 605
            if default:
paul@0 606
paul@0 607
                # Obtain any reference for the default.
paul@0 608
paul@0 609
                name_ref = self.process_structure_node(default)
paul@0 610
                defaults.append((argname, name_ref.is_name() and name_ref.reference() or Reference("<var>")))
paul@0 611
paul@0 612
        # Reset conditional tracking to focus on the function contents.
paul@0 613
paul@0 614
        in_conditional = self.in_conditional
paul@0 615
        self.in_conditional = False
paul@0 616
paul@0 617
        in_function = self.in_function
paul@0 618
        self.in_function = function_name
paul@0 619
paul@0 620
        self.enter_namespace(name)
paul@0 621
paul@0 622
        # Track attribute usage within the namespace.
paul@0 623
paul@0 624
        path = self.get_namespace_path()
paul@0 625
paul@0 626
        self.start_tracking(locals)
paul@0 627
        self.process_structure_node(n.code)
paul@0 628
        self.stop_tracking()
paul@0 629
paul@1 630
        # Exit to the parent.
paul@0 631
paul@0 632
        self.exit_namespace()
paul@0 633
paul@0 634
        # Update flags.
paul@0 635
paul@0 636
        self.in_function = in_function
paul@0 637
        self.in_conditional = in_conditional
paul@0 638
paul@0 639
        # Define the function using the appropriate name.
paul@0 640
paul@0 641
        self.set_definition(name, "<function>")
paul@0 642
paul@0 643
        # Where a function is set conditionally, assign the name.
paul@0 644
paul@0 645
        if original_name:
paul@0 646
            self.process_assignment_for_function(original_name, name)
paul@0 647
paul@0 648
    def process_global_node(self, n):
paul@0 649
paul@0 650
        """
paul@0 651
        Process the given "global" node 'n'.
paul@0 652
        """
paul@0 653
paul@0 654
        path = self.get_namespace_path()
paul@0 655
paul@0 656
        if path != self.name:
paul@0 657
            self.scope_globals[path].update(n.names)
paul@0 658
paul@0 659
    def process_if_node(self, n):
paul@0 660
paul@0 661
        """
paul@0 662
        Process the given "if" node 'n'.
paul@0 663
        """
paul@0 664
paul@0 665
        tracker = self.trackers[-1]
paul@0 666
        tracker.new_branchpoint()
paul@0 667
paul@0 668
        for test, body in n.tests:
paul@0 669
            self.process_structure_node(test)
paul@0 670
paul@0 671
            tracker.new_branch()
paul@0 672
paul@0 673
            in_conditional = self.in_conditional
paul@0 674
            self.in_conditional = True
paul@0 675
            self.process_structure_node(body)
paul@0 676
            self.in_conditional = in_conditional
paul@0 677
paul@0 678
            tracker.shelve_branch()
paul@0 679
paul@0 680
        # Maintain a branch for the else clause.
paul@0 681
paul@0 682
        tracker.new_branch()
paul@0 683
        if n.else_:
paul@0 684
            self.process_structure_node(n.else_)
paul@0 685
        tracker.shelve_branch()
paul@0 686
paul@0 687
        tracker.merge_branches()
paul@0 688
paul@0 689
    def process_import_node(self, n):
paul@0 690
paul@0 691
        "Process the given import node 'n'."
paul@0 692
paul@0 693
        path = self.get_namespace_path()
paul@0 694
paul@0 695
        # Load the mentioned module.
paul@0 696
paul@0 697
        for name, alias in n.names:
paul@12 698
            if name == self.name:
paul@12 699
                raise InspectError("Cannot import the current module.", path, n)
paul@0 700
paul@13 701
            self.set_module(alias or name.split(".")[-1], name)
paul@18 702
            self.queue_module(name, True)
paul@0 703
paul@0 704
    def process_invocation_node(self, n):
paul@0 705
paul@0 706
        "Process the given invocation node 'n'."
paul@0 707
paul@0 708
        path = self.get_namespace_path()
paul@0 709
paul@0 710
        self.allocate_arguments(path, n.args)
paul@0 711
paul@0 712
        try:
paul@0 713
            # Process the expression, obtaining any identified reference.
paul@0 714
paul@88 715
            in_invocation = self.in_invocation
paul@88 716
            self.in_invocation = True
paul@0 717
            name_ref = self.process_structure_node(n.node)
paul@88 718
            self.in_invocation = in_invocation
paul@0 719
paul@0 720
            # Process the arguments.
paul@0 721
paul@0 722
            for arg in n.args:
paul@0 723
                self.process_structure_node(arg)
paul@0 724
paul@0 725
            # Detect class invocations.
paul@0 726
paul@0 727
            if isinstance(name_ref, ResolvedNameRef) and name_ref.has_kind("<class>"):
paul@0 728
                return InstanceRef(name_ref.reference().instance_of())
paul@0 729
paul@0 730
            elif isinstance(name_ref, NameRef):
paul@0 731
                return InvocationRef(name_ref)
paul@0 732
paul@0 733
            return None
paul@0 734
paul@0 735
        finally:
paul@0 736
            self.deallocate_arguments(path, n.args)
paul@0 737
paul@0 738
    def process_lambda_node(self, n):
paul@0 739
paul@0 740
        "Process the given lambda node 'n'."
paul@0 741
paul@0 742
        name = self.get_lambda_name()
paul@0 743
        self.process_function_node(n, name)
paul@0 744
paul@0 745
        origin = self.get_object_path(name)
paul@0 746
        return ResolvedNameRef(name, Reference("<function>", origin))
paul@0 747
paul@0 748
    def process_logical_node(self, n):
paul@0 749
paul@0 750
        "Process the given operator node 'n'."
paul@0 751
paul@0 752
        self.process_operator_chain(n.nodes, self.process_structure_node)
paul@0 753
paul@0 754
    def process_name_node(self, n):
paul@0 755
paul@0 756
        "Process the given name node 'n'."
paul@0 757
paul@0 758
        path = self.get_namespace_path()
paul@0 759
paul@0 760
        # Special names.
paul@0 761
paul@0 762
        if n.name.startswith("$"):
paul@0 763
            value = self.get_special(n.name)
paul@0 764
            if value:
paul@0 765
                return value
paul@0 766
paul@0 767
        # Special case for operator functions introduced through code
paul@0 768
        # transformations.
paul@0 769
paul@0 770
        if n.name.startswith("$op"):
paul@0 771
paul@0 772
            # Obtain the location of the actual function defined in the operator
paul@0 773
            # package.
paul@0 774
paul@0 775
            op = n.name[len("$op"):]
paul@0 776
paul@0 777
            # Attempt to get a reference.
paul@0 778
paul@12 779
            ref = self.import_name_from_module(op, "operator")
paul@35 780
            self.add_deferred(ref)
paul@0 781
paul@0 782
            # Record the imported name and provide the resolved name reference.
paul@0 783
paul@0 784
            value = ResolvedNameRef(n.name, ref)
paul@0 785
            self.set_special(n.name, value)
paul@0 786
            return value
paul@0 787
paul@60 788
        # Test for self usage, which is only allowed in methods.
paul@60 789
paul@60 790
        if n.name == "self" and not (self.in_function and self.in_class):
paul@60 791
            raise InspectError("Use of self is only allowed in methods.", path, n)
paul@60 792
paul@0 793
        # Record usage of the name.
paul@0 794
paul@0 795
        self.record_name(n.name)
paul@0 796
paul@0 797
        # Search for unknown names in non-function scopes immediately.
paul@0 798
        # External names in functions are resolved later.
paul@0 799
paul@0 800
        ref = self.find_name(n.name)
paul@0 801
        if ref:
paul@0 802
            return ResolvedNameRef(n.name, ref)
paul@0 803
paul@40 804
        # Explicitly-declared global names.
paul@0 805
paul@0 806
        elif self.in_function and n.name in self.scope_globals[path]:
paul@0 807
            return NameRef(n.name)
paul@0 808
paul@0 809
        # Examine other names.
paul@0 810
paul@0 811
        else:
paul@0 812
            tracker = self.trackers[-1]
paul@0 813
paul@0 814
            # Check local names.
paul@0 815
paul@0 816
            branches = tracker.tracking_name(n.name)
paul@0 817
paul@1 818
            # Local name.
paul@0 819
paul@0 820
            if branches:
paul@0 821
                self.record_branches_for_access(branches, n.name, None)
paul@0 822
                access_number = self.record_access_details(n.name, None, False)
paul@0 823
                return LocalNameRef(n.name, access_number)
paul@0 824
paul@40 825
            # Possible global or built-in name.
paul@0 826
paul@0 827
            else:
paul@0 828
                return NameRef(n.name)
paul@0 829
paul@0 830
    def process_operator_chain(self, nodes, fn):
paul@0 831
paul@0 832
        """
paul@0 833
        Process the given chain of 'nodes', applying 'fn' to each node or item.
paul@0 834
        Each node starts a new conditional region, effectively making a deeply-
paul@0 835
        nested collection of if-like statements.
paul@0 836
        """
paul@0 837
paul@0 838
        tracker = self.trackers[-1]
paul@0 839
paul@0 840
        for item in nodes:
paul@0 841
            tracker.new_branchpoint()
paul@0 842
            tracker.new_branch()
paul@0 843
            fn(item)
paul@0 844
paul@0 845
        for item in nodes[:-1]:
paul@0 846
            tracker.shelve_branch()
paul@0 847
            tracker.new_branch()
paul@0 848
            tracker.shelve_branch()
paul@0 849
            tracker.merge_branches()
paul@0 850
paul@0 851
        tracker.shelve_branch()
paul@0 852
        tracker.merge_branches()
paul@0 853
paul@0 854
    def process_try_node(self, n):
paul@0 855
paul@0 856
        """
paul@0 857
        Process the given "try...except" node 'n'.
paul@0 858
        """
paul@0 859
paul@0 860
        tracker = self.trackers[-1]
paul@0 861
        tracker.new_branchpoint()
paul@0 862
paul@0 863
        self.process_structure_node(n.body)
paul@0 864
paul@0 865
        for name, var, handler in n.handlers:
paul@0 866
            if name is not None:
paul@0 867
                self.process_structure_node(name)
paul@0 868
paul@0 869
            # Any abandoned branches from the body can now be resumed in a new
paul@0 870
            # branch.
paul@0 871
paul@0 872
            tracker.resume_abandoned_branches()
paul@0 873
paul@0 874
            # Establish the local for the handler.
paul@0 875
paul@0 876
            if var is not None:
paul@0 877
                self.process_structure_node(var)
paul@0 878
            if handler is not None:
paul@0 879
                self.process_structure_node(handler)
paul@0 880
paul@0 881
            tracker.shelve_branch()
paul@0 882
paul@0 883
        # The else clause maintains the usage from the body but without the
paul@0 884
        # abandoned branches since they would never lead to the else clause
paul@0 885
        # being executed.
paul@0 886
paul@0 887
        if n.else_:
paul@0 888
            tracker.new_branch()
paul@0 889
            self.process_structure_node(n.else_)
paul@0 890
            tracker.shelve_branch()
paul@0 891
paul@0 892
        # Without an else clause, a null branch propagates the successful
paul@0 893
        # outcome.
paul@0 894
paul@0 895
        else:
paul@0 896
            tracker.new_branch()
paul@0 897
            tracker.shelve_branch()
paul@0 898
paul@0 899
        tracker.merge_branches()
paul@0 900
paul@0 901
    def process_try_finally_node(self, n):
paul@0 902
paul@0 903
        """
paul@0 904
        Process the given "try...finally" node 'n'.
paul@0 905
        """
paul@0 906
paul@0 907
        tracker = self.trackers[-1]
paul@0 908
        self.process_structure_node(n.body)
paul@0 909
paul@0 910
        # Any abandoned branches from the body can now be resumed.
paul@0 911
paul@0 912
        branches = tracker.resume_all_abandoned_branches()
paul@0 913
        self.process_structure_node(n.final)
paul@0 914
paul@0 915
        # At the end of the finally clause, abandoned branches are discarded.
paul@0 916
paul@0 917
        tracker.restore_active_branches(branches)
paul@0 918
paul@0 919
    def process_while_node(self, n):
paul@0 920
paul@0 921
        "Process the given while node 'n'."
paul@0 922
paul@0 923
        tracker = self.trackers[-1]
paul@0 924
        tracker.new_branchpoint(loop_node=True)
paul@0 925
paul@0 926
        # Evaluate any test or iterator outside the loop.
paul@0 927
paul@0 928
        self.process_structure_node(n.test)
paul@0 929
paul@0 930
        # Propagate attribute usage to branches.
paul@0 931
paul@0 932
        tracker.new_branch(loop_node=True)
paul@0 933
paul@0 934
        # Enter the loop.
paul@0 935
paul@0 936
        in_conditional = self.in_conditional
paul@0 937
        self.in_conditional = True
paul@0 938
        self.process_structure_node(n.body)
paul@0 939
        self.in_conditional = in_conditional
paul@0 940
paul@0 941
        # Continuing branches are resumed before any test.
paul@0 942
paul@0 943
        tracker.resume_continuing_branches()
paul@0 944
paul@0 945
        # Evaluate any continuation test within the body.
paul@0 946
paul@0 947
        self.process_structure_node(n.test)
paul@0 948
paul@0 949
        tracker.shelve_branch(loop_node=True)
paul@0 950
paul@0 951
        # Support the non-looping condition.
paul@0 952
paul@0 953
        tracker.new_branch()
paul@0 954
        tracker.shelve_branch()
paul@0 955
paul@0 956
        tracker.merge_branches()
paul@0 957
paul@0 958
        # Evaluate any else clause outside branches.
paul@0 959
paul@0 960
        if n.else_:
paul@0 961
            self.process_structure_node(n.else_)
paul@0 962
paul@0 963
        # Connect broken branches to the code after any loop.
paul@0 964
paul@0 965
        tracker.resume_broken_branches()
paul@0 966
paul@0 967
    # Branch tracking methods.
paul@0 968
paul@0 969
    def start_tracking(self, names):
paul@0 970
paul@0 971
        """
paul@0 972
        Start tracking attribute usage for names in the current namespace,
paul@0 973
        immediately registering the given 'names'.
paul@0 974
        """
paul@0 975
paul@0 976
        path = self.get_namespace_path()
paul@0 977
        parent = self.trackers[-1]
paul@0 978
        tracker = BranchTracker()
paul@0 979
        self.trackers.append(tracker)
paul@0 980
paul@0 981
        # Record the given names established as new branches.
paul@0 982
paul@0 983
        tracker.assign_names(names)
paul@0 984
paul@0 985
    def assign_name(self, name, name_ref):
paul@0 986
paul@0 987
        "Assign to 'name' the given 'name_ref' in the current namespace."
paul@0 988
paul@0 989
        name = self.get_name_for_tracking(name)
paul@0 990
        self.trackers[-1].assign_names([name], [name_ref])
paul@0 991
paul@0 992
    def stop_tracking(self):
paul@0 993
paul@0 994
        """
paul@0 995
        Stop tracking attribute usage, recording computed usage for the current
paul@0 996
        namespace.
paul@0 997
        """
paul@0 998
paul@0 999
        path = self.get_namespace_path()
paul@0 1000
        tracker = self.trackers.pop()
paul@0 1001
        self.record_assignments_for_access(tracker)
paul@0 1002
paul@0 1003
        self.attr_usage[path] = tracker.get_all_usage()
paul@0 1004
        self.name_initialisers[path] = tracker.get_all_values()
paul@0 1005
paul@0 1006
    def start_tracking_in_module(self):
paul@0 1007
paul@0 1008
        "Start tracking attribute usage in the module."
paul@0 1009
paul@0 1010
        tracker = BranchTracker()
paul@0 1011
        self.trackers.append(tracker)
paul@0 1012
paul@0 1013
    def stop_tracking_in_module(self):
paul@0 1014
paul@0 1015
        "Stop tracking attribute usage in the module."
paul@0 1016
paul@0 1017
        tracker = self.trackers[0]
paul@0 1018
        self.record_assignments_for_access(tracker)
paul@0 1019
        self.attr_usage[self.name] = tracker.get_all_usage()
paul@0 1020
        self.name_initialisers[self.name] = tracker.get_all_values()
paul@0 1021
paul@0 1022
    def record_assignments_for_access(self, tracker):
paul@0 1023
paul@0 1024
        """
paul@0 1025
        For the current path, use the given 'tracker' to record assignment
paul@0 1026
        version information for attribute accesses.
paul@0 1027
        """
paul@0 1028
paul@0 1029
        path = self.get_path_for_access()
paul@0 1030
paul@0 1031
        if not self.attr_accessor_branches.has_key(path):
paul@0 1032
            return
paul@0 1033
paul@0 1034
        init_item(self.attr_accessors, path, dict)
paul@0 1035
        attr_accessors = self.attr_accessors[path]
paul@0 1036
paul@0 1037
        # Obtain the branches applying during each access.
paul@0 1038
paul@0 1039
        for access, all_branches in self.attr_accessor_branches[path].items():
paul@0 1040
            name, attrnames = access
paul@0 1041
            init_item(attr_accessors, access, list)
paul@0 1042
paul@0 1043
            # Obtain the assignments applying to each branch.
paul@0 1044
paul@0 1045
            for branches in all_branches:
paul@0 1046
                positions = tracker.get_assignment_positions_for_branches(name, branches)
paul@0 1047
paul@0 1048
                # Detect missing name information.
paul@0 1049
paul@0 1050
                if None in positions:
paul@0 1051
                    globals = self.global_attr_accesses.get(path)
paul@0 1052
                    accesses = globals and globals.get(name)
paul@0 1053
                    if not accesses:
paul@0 1054
                        print >>sys.stderr, "In %s, %s may not be defined when used." % (
paul@0 1055
                            self.get_namespace_path(), name)
paul@0 1056
                    positions.remove(None)
paul@0 1057
paul@0 1058
                attr_accessors[access].append(positions)
paul@0 1059
paul@0 1060
    def record_branches_for_access(self, branches, name, attrnames):
paul@0 1061
paul@0 1062
        """
paul@0 1063
        Record the given 'branches' for an access involving the given 'name' and
paul@0 1064
        'attrnames'.
paul@0 1065
        """
paul@0 1066
paul@0 1067
        access = name, attrnames
paul@0 1068
        path = self.get_path_for_access()
paul@0 1069
paul@0 1070
        init_item(self.attr_accessor_branches, path, dict)
paul@0 1071
        attr_accessor_branches = self.attr_accessor_branches[path]
paul@0 1072
paul@0 1073
        init_item(attr_accessor_branches, access, list)
paul@0 1074
        attr_accessor_branches[access].append(branches)
paul@0 1075
paul@0 1076
    def record_access_details(self, name, attrnames, assignment):
paul@0 1077
paul@0 1078
        """
paul@0 1079
        For the given 'name' and 'attrnames', record an access indicating
paul@0 1080
        whether 'assignment' is occurring.
paul@0 1081
paul@0 1082
        These details correspond to accesses otherwise recorded by the attribute
paul@0 1083
        accessor and attribute access dictionaries.
paul@0 1084
        """
paul@0 1085
paul@0 1086
        access = name, attrnames
paul@0 1087
        path = self.get_path_for_access()
paul@0 1088
paul@0 1089
        init_item(self.attr_access_modifiers, path, dict)
paul@0 1090
        init_item(self.attr_access_modifiers[path], access, list)
paul@0 1091
paul@0 1092
        access_number = len(self.attr_access_modifiers[path][access])
paul@0 1093
        self.attr_access_modifiers[path][access].append(assignment)
paul@0 1094
        return access_number
paul@0 1095
paul@0 1096
    def record_global_access_details(self, name, attrnames):
paul@0 1097
paul@0 1098
        """
paul@0 1099
        Record details of a global access via the given 'name' involving the
paul@0 1100
        indicated 'attrnames'.
paul@0 1101
        """
paul@0 1102
paul@0 1103
        path = self.get_namespace_path()
paul@0 1104
paul@0 1105
        init_item(self.global_attr_accesses, path, dict)
paul@0 1106
        init_item(self.global_attr_accesses[path], name, set)
paul@0 1107
        self.global_attr_accesses[path][name].add(attrnames)
paul@0 1108
paul@0 1109
    # Namespace modification.
paul@0 1110
paul@0 1111
    def record_name(self, name):
paul@0 1112
paul@0 1113
        "Record the use of 'name' in a namespace."
paul@0 1114
paul@0 1115
        path = self.get_namespace_path()
paul@0 1116
        init_item(self.names_used, path, set)
paul@0 1117
        self.names_used[path].add(name)
paul@0 1118
paul@12 1119
    def set_module(self, name, module_name):
paul@0 1120
paul@0 1121
        """
paul@12 1122
        Set a module in the current namespace using the given 'name' associated
paul@12 1123
        with the corresponding 'module_name'.
paul@0 1124
        """
paul@0 1125
paul@0 1126
        if name:
paul@12 1127
            self.set_general_local(name, Reference("<module>", module_name))
paul@0 1128
paul@0 1129
    def set_definition(self, name, kind):
paul@0 1130
paul@0 1131
        """
paul@0 1132
        Set the definition having the given 'name' and 'kind'.
paul@0 1133
paul@0 1134
        Definitions are set in the static namespace hierarchy, but they can also
paul@0 1135
        be recorded for function locals.
paul@0 1136
        """
paul@0 1137
paul@0 1138
        if self.is_global(name):
paul@0 1139
            print >>sys.stderr, "In %s, %s is defined as being global." % (
paul@0 1140
                self.get_namespace_path(), name)
paul@0 1141
paul@0 1142
        path = self.get_object_path(name)
paul@0 1143
        self.set_object(path, kind)
paul@0 1144
paul@0 1145
        ref = self.get_object(path)
paul@0 1146
        if ref.get_kind() == "<var>":
paul@0 1147
            print >>sys.stderr, "In %s, %s is defined more than once." % (
paul@0 1148
                self.get_namespace_path(), name)
paul@0 1149
paul@0 1150
        if not self.is_global(name) and self.in_function:
paul@0 1151
            self.set_function_local(name, ref)
paul@0 1152
paul@0 1153
    def set_function_local(self, name, ref=None):
paul@0 1154
paul@0 1155
        "Set the local with the given 'name' and optional 'ref'."
paul@0 1156
paul@0 1157
        locals = self.function_locals[self.get_namespace_path()]
paul@0 1158
        multiple = not ref or locals.has_key(name) and locals[name] != ref
paul@0 1159
        locals[name] = multiple and Reference("<var>") or ref
paul@0 1160
paul@0 1161
    def assign_general_local(self, name, name_ref):
paul@0 1162
paul@0 1163
        """
paul@0 1164
        Set for 'name' the given 'name_ref', recording the name for attribute
paul@0 1165
        usage tracking.
paul@0 1166
        """
paul@0 1167
paul@0 1168
        self.set_general_local(name, name_ref)
paul@0 1169
        self.assign_name(name, name_ref)
paul@0 1170
paul@0 1171
    def set_general_local(self, name, value=None):
paul@0 1172
paul@0 1173
        """
paul@0 1174
        Set the 'name' with optional 'value' in any kind of local namespace,
paul@0 1175
        where the 'value' should be a reference if specified.
paul@0 1176
        """
paul@0 1177
paul@0 1178
        init_value = self.get_initialising_value(value)
paul@0 1179
paul@0 1180
        # Module global names.
paul@0 1181
paul@0 1182
        if self.is_global(name):
paul@0 1183
            path = self.get_global_path(name)
paul@0 1184
            self.set_object(path, init_value)
paul@0 1185
paul@0 1186
        # Function local names.
paul@0 1187
paul@0 1188
        elif self.in_function:
paul@0 1189
            path = self.get_object_path(name)
paul@0 1190
            self.set_function_local(name, init_value)
paul@0 1191
paul@0 1192
        # Other namespaces (classes).
paul@0 1193
paul@0 1194
        else:
paul@0 1195
            path = self.get_object_path(name)
paul@0 1196
            self.set_name(name, init_value)
paul@0 1197
paul@0 1198
    def set_name(self, name, ref=None):
paul@0 1199
paul@0 1200
        "Attach the 'name' with optional 'ref' to the current namespace."
paul@0 1201
paul@0 1202
        self.set_object(self.get_object_path(name), ref)
paul@0 1203
paul@0 1204
    def set_instance_attr(self, name, ref=None):
paul@0 1205
paul@0 1206
        """
paul@0 1207
        Add an instance attribute of the given 'name' to the current class,
paul@0 1208
        using the optional 'ref'.
paul@0 1209
        """
paul@0 1210
paul@0 1211
        init_item(self.instance_attrs, self.in_class, set)
paul@0 1212
        self.instance_attrs[self.in_class].add(name)
paul@0 1213
paul@0 1214
        if ref:
paul@0 1215
            init_item(self.instance_attr_constants, self.in_class, dict)
paul@0 1216
            self.instance_attr_constants[self.in_class][name] = ref
paul@0 1217
paul@0 1218
    def get_initialising_value(self, value):
paul@0 1219
paul@0 1220
        "Return a suitable initialiser reference for 'value'."
paul@0 1221
paul@25 1222
        # Includes LiteralSequenceRef, ResolvedNameRef...
paul@25 1223
paul@25 1224
        if isinstance(value, (NameRef, AccessRef, InstanceRef)):
paul@0 1225
            return value.reference()
paul@0 1226
paul@0 1227
        # In general, invocations do not produce known results. However, the
paul@0 1228
        # name initialisers are resolved once a module has been inspected.
paul@0 1229
paul@0 1230
        elif isinstance(value, InvocationRef):
paul@27 1231
            return value.reference()
paul@0 1232
paul@0 1233
        else:
paul@0 1234
            return value
paul@0 1235
paul@0 1236
    # Static, program-relative naming.
paul@0 1237
paul@0 1238
    def find_name(self, name):
paul@0 1239
paul@0 1240
        """
paul@0 1241
        Return the qualified name for the given 'name' used in the current
paul@0 1242
        non-function namespace.
paul@0 1243
        """
paul@0 1244
paul@0 1245
        path = self.get_namespace_path()
paul@0 1246
        ref = None
paul@0 1247
paul@0 1248
        if not self.in_function and name not in predefined_constants:
paul@0 1249
            if self.in_class:
paul@0 1250
                ref = self.get_object(self.get_object_path(name))
paul@0 1251
            if not ref:
paul@0 1252
                ref = self.get_global_or_builtin(name)
paul@0 1253
paul@0 1254
        return ref
paul@0 1255
paul@0 1256
    def get_class(self, node):
paul@0 1257
paul@0 1258
        """
paul@0 1259
        Use the given 'node' to obtain the identity of a class. Return a
paul@0 1260
        reference for the class. Unresolved dependencies are permitted and must
paul@0 1261
        be resolved later.
paul@0 1262
        """
paul@0 1263
paul@0 1264
        ref = self._get_class(node)
paul@0 1265
        return ref.has_kind(["<class>", "<depends>"]) and ref or None
paul@0 1266
paul@0 1267
    def _get_class(self, node):
paul@0 1268
paul@0 1269
        """
paul@0 1270
        Use the given 'node' to find a class definition. Return a reference to
paul@0 1271
        the class.
paul@0 1272
        """
paul@0 1273
paul@0 1274
        if isinstance(node, compiler.ast.Getattr):
paul@0 1275
paul@0 1276
            # Obtain the identity of the access target.
paul@0 1277
paul@0 1278
            ref = self._get_class(node.expr)
paul@0 1279
paul@0 1280
            # Where the target is a class or module, obtain the identity of the
paul@0 1281
            # attribute.
paul@0 1282
paul@0 1283
            if ref.has_kind(["<function>", "<var>"]):
paul@0 1284
                return None
paul@0 1285
            else:
paul@0 1286
                attrname = "%s.%s" % (ref.get_origin(), node.attrname)
paul@0 1287
                return self.get_object(attrname)
paul@0 1288
paul@0 1289
        # Names can be module-level or built-in.
paul@0 1290
paul@0 1291
        elif isinstance(node, compiler.ast.Name):
paul@0 1292
paul@0 1293
            # Record usage of the name and attempt to identify it.
paul@0 1294
paul@0 1295
            self.record_name(node.name)
paul@73 1296
            return self.find_name(node.name)
paul@0 1297
        else:
paul@0 1298
            return None
paul@0 1299
paul@0 1300
    def get_constant(self, name, value):
paul@0 1301
paul@0 1302
        "Return a constant reference for the given type 'name' and 'value'."
paul@0 1303
paul@12 1304
        ref = self.get_builtin_class(name)
paul@0 1305
        return self.get_constant_reference(ref, value)
paul@0 1306
paul@0 1307
    def get_literal_instance(self, n, name):
paul@0 1308
paul@0 1309
        "For node 'n', return a reference to an instance of 'name'."
paul@0 1310
paul@12 1311
        # Get a reference to the built-in class.
paul@0 1312
paul@12 1313
        ref = self.get_builtin_class(name)
paul@0 1314
paul@0 1315
        # Obtain the details of the literal itself.
paul@0 1316
        # An alias to the type is generated for sequences.
paul@0 1317
paul@0 1318
        if name in ("dict", "list", "tuple"):
paul@0 1319
            self.set_special_literal(name, ref)
paul@0 1320
            return self.process_literal_sequence_node(n, name, ref, LiteralSequenceRef)
paul@0 1321
paul@0 1322
        # Constant values are independently recorded.
paul@0 1323
paul@0 1324
        else:
paul@0 1325
            return self.get_constant_reference(ref, n.value)
paul@0 1326
paul@17 1327
    # Special names.
paul@0 1328
paul@17 1329
    def get_special(self, name):
paul@0 1330
paul@17 1331
        "Return any stored value for the given special 'name'."
paul@0 1332
paul@17 1333
        return self.special.get(name)
paul@17 1334
paul@17 1335
    def set_special(self, name, value):
paul@0 1336
paul@17 1337
        """
paul@17 1338
        Set a special 'name' that merely tracks the use of an implicit object
paul@17 1339
        'value'.
paul@17 1340
        """
paul@0 1341
paul@17 1342
        self.special[name] = value
paul@17 1343
paul@17 1344
    def set_special_literal(self, name, ref):
paul@0 1345
paul@17 1346
        """
paul@17 1347
        Set a special name for the literal type 'name' having type 'ref'. Such
paul@17 1348
        special names provide a way of referring to literal object types.
paul@17 1349
        """
paul@0 1350
paul@17 1351
        literal_name = "$L%s" % name
paul@17 1352
        value = ResolvedNameRef(literal_name, ref)
paul@17 1353
        self.set_special(literal_name, value)
paul@0 1354
paul@0 1355
    # Functions and invocations.
paul@0 1356
paul@36 1357
    def set_invocation_usage(self):
paul@36 1358
paul@36 1359
        """
paul@36 1360
        Discard the current invocation storage figures, retaining the maximum
paul@36 1361
        values.
paul@36 1362
        """
paul@36 1363
paul@36 1364
        for path, (current, maximum) in self.function_targets.items():
paul@36 1365
            self.importer.function_targets[path] = self.function_targets[path] = maximum
paul@36 1366
paul@36 1367
        for path, (current, maximum) in self.function_arguments.items():
paul@36 1368
            self.importer.function_arguments[path] = self.function_arguments[path] = maximum
paul@36 1369
paul@0 1370
    def allocate_arguments(self, path, args):
paul@0 1371
paul@0 1372
        """
paul@0 1373
        Allocate temporary argument storage using current and maximum
paul@0 1374
        requirements for the given 'path' and 'args'.
paul@0 1375
        """
paul@0 1376
paul@0 1377
        init_item(self.function_targets, path, lambda: [0, 0])
paul@0 1378
        t = self.function_targets[path]
paul@0 1379
        t[0] += 1
paul@0 1380
        t[1] = max(t[0], t[1])
paul@0 1381
paul@0 1382
        init_item(self.function_arguments, path, lambda: [0, 0])
paul@0 1383
        t = self.function_arguments[path]
paul@0 1384
        t[0] += len(args) + 1
paul@0 1385
        t[1] = max(t[0], t[1])
paul@0 1386
paul@0 1387
    def deallocate_arguments(self, path, args):
paul@0 1388
paul@0 1389
        "Deallocate temporary argument storage for the given 'path' and 'args'."
paul@0 1390
paul@0 1391
        self.function_targets[path][0] -= 1
paul@0 1392
        self.function_arguments[path][0] -= len(args) + 1
paul@0 1393
paul@0 1394
# vim: tabstop=4 expandtab shiftwidth=4