Lichen

Annotated modules.py

17:363caea72ee4
2016-09-04 Paul Boddie Moved special name handling into the inspector: cached modules do not use it.
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Module abstractions.
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@16 23
from common import init_item, remove_items, CommonModule
paul@0 24
from encoders import decode_modifier_term, encode_modifiers, encode_usage
paul@0 25
from referencing import decode_reference, Reference
paul@12 26
from results import ResolvedNameRef
paul@0 27
import sys
paul@0 28
paul@0 29
class BasicModule(CommonModule):
paul@0 30
paul@0 31
    "The basic module information."
paul@0 32
paul@0 33
    def __init__(self, name, importer):
paul@0 34
        CommonModule.__init__(self, name, importer)
paul@0 35
paul@0 36
        # Global name information.
paul@0 37
paul@0 38
        self.objects = {}
paul@0 39
        self.special = {}
paul@0 40
paul@0 41
        # Class relationships.
paul@0 42
paul@0 43
        self.classes = {}
paul@0 44
paul@0 45
        # Attributes.
paul@0 46
paul@0 47
        self.class_attrs = {}
paul@0 48
        self.instance_attrs = {}
paul@0 49
        self.instance_attr_constants = {}
paul@0 50
        self.module_attrs = set()
paul@0 51
paul@0 52
        # Names used and missing.
paul@0 53
paul@0 54
        self.names_used = {}
paul@0 55
        self.names_missing = {}
paul@0 56
paul@0 57
        # Function details.
paul@0 58
paul@0 59
        self.function_parameters = {}
paul@0 60
        self.function_defaults = {}
paul@0 61
        self.function_locals = {}
paul@0 62
        self.scope_globals = {}
paul@0 63
paul@0 64
        # Invocation details.
paul@0 65
paul@0 66
        self.function_targets = {}
paul@0 67
        self.function_arguments = {}
paul@0 68
paul@0 69
        # Attribute usage at module and function levels.
paul@0 70
paul@0 71
        self.attr_usage = {}
paul@0 72
        self.name_initialisers = {}
paul@0 73
paul@0 74
        # General attribute access expressions.
paul@0 75
paul@0 76
        self.attr_accesses = {}
paul@0 77
        self.const_accesses = {}
paul@0 78
paul@0 79
        # Attribute accessor definition details.
paul@0 80
paul@0 81
        self.attr_accessors = {}
paul@0 82
paul@0 83
        # Assignment details for accesses.
paul@0 84
paul@0 85
        self.attr_access_modifiers = {}
paul@0 86
paul@13 87
        # Name resolution details.
paul@13 88
paul@13 89
        self.name_references = {} # references to globals
paul@13 90
paul@13 91
        # Initialisation-related details.
paul@13 92
paul@13 93
        self.initialised_names = {}
paul@13 94
        self.aliased_names = {}
paul@13 95
paul@0 96
    def __repr__(self):
paul@0 97
        return "BasicModule(%r, %r)" % (self.name, self.importer)
paul@0 98
paul@0 99
    # Derived information methods.
paul@0 100
paul@0 101
    def propagate(self):
paul@0 102
paul@0 103
        "Finalise and propagate module information."
paul@0 104
paul@0 105
        self.propagate_attrs()
paul@0 106
        self.propagate_name_references()
paul@0 107
        self.propagate_attr_accesses()
paul@0 108
        self.propagate_constants()
paul@0 109
paul@0 110
    def unpropagate(self):
paul@0 111
paul@0 112
        """
paul@0 113
        Retract information from the importer including information about this
paul@0 114
        module derived by the importer.
paul@0 115
        """
paul@0 116
paul@0 117
        del self.importer.all_module_attrs[self.name]
paul@0 118
paul@0 119
        for name in self.classes.keys():
paul@0 120
            del self.importer.all_class_attrs[name]
paul@0 121
            del self.importer.all_instance_attrs[name]
paul@0 122
            del self.importer.all_instance_attr_constants[name]
paul@0 123
paul@0 124
            for name, bases in self.classes.items():
paul@0 125
                for base in bases:
paul@0 126
paul@0 127
                    # Get the identity of the class from the reference.
paul@0 128
paul@0 129
                    base = base.get_origin()
paul@0 130
paul@0 131
                    try:
paul@0 132
                        self.importer.subclasses[base].remove(name)
paul@0 133
                    except (KeyError, ValueError):
paul@0 134
                        pass
paul@0 135
paul@0 136
        remove_items(self.importer.all_name_references, self.name_references)
paul@0 137
        remove_items(self.importer.all_initialised_names, self.initialised_names)
paul@0 138
        remove_items(self.importer.all_aliased_names, self.aliased_names)
paul@0 139
        remove_items(self.importer.all_attr_accesses, self.attr_accesses)
paul@0 140
        remove_items(self.importer.all_const_accesses, self.const_accesses)
paul@0 141
        remove_items(self.importer.all_attr_access_modifiers, self.attr_access_modifiers)
paul@0 142
        remove_items(self.importer.all_constants, self.constants)
paul@0 143
        remove_items(self.importer.all_constant_values, self.constant_values)
paul@0 144
paul@0 145
        # Remove this module's objects from the importer. Objects are
paul@0 146
        # automatically propagated when defined.
paul@0 147
paul@0 148
        for name, ref in self.objects.items():
paul@16 149
            del self.importer.objects[name]
paul@0 150
paul@0 151
    def propagate_attrs(self):
paul@0 152
paul@0 153
        "Derive attributes from the class and module member details."
paul@0 154
paul@0 155
        # Initialise class attribute records for all classes.
paul@0 156
paul@0 157
        for name in self.classes.keys():
paul@0 158
            self.importer.all_class_attrs[name] = self.class_attrs[name] = {}
paul@0 159
paul@0 160
        # Separate the objects into module and class attributes.
paul@0 161
paul@0 162
        for name in self.objects.keys():
paul@0 163
            if "." in name:
paul@0 164
                parent, attrname = name.rsplit(".", 1)
paul@0 165
                if self.classes.has_key(parent):
paul@0 166
                    self.class_attrs[parent][attrname] = name
paul@0 167
                elif parent == self.name:
paul@0 168
                    self.module_attrs.add(attrname)
paul@0 169
paul@0 170
        # Propagate the module attributes.
paul@0 171
paul@0 172
        self.importer.all_module_attrs[self.name] = self.module_attrs
paul@0 173
paul@0 174
    def propagate_name_references(self):
paul@0 175
paul@0 176
        "Propagate name references for the module."
paul@0 177
paul@0 178
        self.importer.all_name_references.update(self.name_references)
paul@0 179
        self.importer.all_initialised_names.update(self.initialised_names)
paul@0 180
        self.importer.all_aliased_names.update(self.aliased_names)
paul@0 181
paul@0 182
    def propagate_attr_accesses(self):
paul@0 183
paul@0 184
        "Propagate attribute accesses for the module."
paul@0 185
paul@0 186
        self.importer.all_attr_accesses.update(self.attr_accesses)
paul@0 187
        self.importer.all_const_accesses.update(self.const_accesses)
paul@0 188
        self.importer.all_attr_access_modifiers.update(self.attr_access_modifiers)
paul@0 189
paul@0 190
    def propagate_constants(self):
paul@0 191
paul@0 192
        "Propagate constant values and aliases for the module."
paul@0 193
paul@0 194
        self.importer.all_constants.update(self.constants)
paul@0 195
        self.importer.all_constant_values.update(self.constant_values)
paul@0 196
paul@0 197
        for name in self.classes.keys():
paul@0 198
            self.importer.all_instance_attrs[name] = self.instance_attrs.get(name) or {}
paul@0 199
            self.importer.all_instance_attr_constants[name] = self.instance_attr_constants.get(name) or {}
paul@0 200
paul@0 201
    # Module-relative naming.
paul@0 202
paul@0 203
    def is_global(self, name):
paul@0 204
paul@0 205
        """
paul@0 206
        Return whether 'name' is registered as a global in the current
paul@0 207
        namespace.
paul@0 208
        """
paul@0 209
paul@0 210
        path = self.get_namespace_path()
paul@0 211
        return name in self.scope_globals.get(path, [])
paul@0 212
paul@0 213
    def get_global(self, name):
paul@0 214
paul@0 215
        """
paul@0 216
        Get the global of the given 'name' from this module, returning a
paul@0 217
        reference incorporating the original definition details.
paul@0 218
        """
paul@0 219
paul@0 220
        path = self.get_global_path(name)
paul@0 221
        return self.objects.get(path)
paul@0 222
paul@0 223
    # Name definition discovery.
paul@0 224
paul@0 225
    def get_global_or_builtin(self, name):
paul@0 226
paul@0 227
        """
paul@12 228
        Return a reference for the given 'name' found in this module or in the
paul@12 229
        __builtins__.
paul@0 230
        """
paul@0 231
paul@0 232
        return self.get_global(name) or self.get_builtin(name)
paul@0 233
paul@0 234
    def get_builtin(self, name):
paul@0 235
paul@12 236
        "Return a reference to the built-in with the given 'name'."
paul@0 237
paul@12 238
        self.importer.queue_module("__builtins__", self)
paul@12 239
        return Reference("<depends>", "__builtins__.%s" % name)
paul@0 240
paul@12 241
    def get_builtin_class(self, name):
paul@0 242
paul@12 243
        "Return a reference to the actual object providing 'name'."
paul@0 244
paul@12 245
        # NOTE: This makes assumptions about the __builtins__ structure.
paul@0 246
paul@12 247
        return Reference("<class>", "__builtins__.%s.%s" % (name, name))
paul@0 248
paul@0 249
    def get_object(self, path):
paul@0 250
paul@0 251
        """
paul@12 252
        Get the details of an object with the given 'path'. Where the object
paul@12 253
        cannot be resolved, an unresolved reference is returned.
paul@0 254
        """
paul@0 255
paul@0 256
        if self.objects.has_key(path):
paul@0 257
            return self.objects[path]
paul@0 258
        else:
paul@12 259
            return Reference("<depends>", path)
paul@0 260
paul@0 261
    def set_object(self, name, value=None):
paul@0 262
paul@0 263
        "Set an object with the given 'name' and the given 'value'."
paul@0 264
paul@0 265
        ref = decode_reference(value, name)
paul@0 266
        multiple = self.objects.has_key(name) and self.objects[name].get_kind() != ref.get_kind()
paul@0 267
        self.importer.objects[name] = self.objects[name] = multiple and ref.as_var() or ref
paul@0 268
paul@12 269
    def import_name_from_module(self, name, module_name):
paul@12 270
paul@12 271
        "Import 'name' from the module having the given 'module_name'."
paul@12 272
paul@12 273
        if module_name != self.name:
paul@12 274
            self.importer.queue_module(module_name, self)
paul@12 275
        return Reference("<depends>", "%s.%s" % (module_name, name))
paul@12 276
paul@0 277
class CachedModule(BasicModule):
paul@0 278
paul@0 279
    "A cached module."
paul@0 280
paul@0 281
    def __repr__(self):
paul@0 282
        return "CachedModule(%r, %r)" % (self.name, self.importer)
paul@0 283
paul@0 284
    def to_cache(self, filename):
paul@0 285
paul@0 286
        "Not actually writing the module back to 'filename'."
paul@0 287
paul@0 288
        pass
paul@0 289
paul@0 290
    def from_cache(self, filename):
paul@0 291
paul@0 292
        """
paul@0 293
        Read a module's details from the file with the given 'filename' as
paul@0 294
        described in the to_cache method of InspectedModule.
paul@0 295
        """
paul@0 296
paul@0 297
        f = open(filename)
paul@0 298
        try:
paul@0 299
            self.filename = f.readline().rstrip()
paul@0 300
paul@0 301
            f.readline() # (empty line)
paul@0 302
paul@0 303
            self._get_members(f)
paul@0 304
            self._get_class_relationships(f)
paul@0 305
            self._get_instance_attrs(f)
paul@0 306
            self._get_instance_attr_constants(f)
paul@0 307
            self.from_lines(f, self.names_used)     # "names used:"
paul@0 308
            self.from_lines(f, self.names_missing)  # "names missing:"
paul@0 309
            self._get_name_references(f)
paul@0 310
            self._get_initialised_names(f)
paul@0 311
            self._get_aliased_names(f)
paul@0 312
            self._get_function_parameters(f)
paul@0 313
            self._get_function_defaults(f)
paul@0 314
            self._get_function_locals(f)
paul@0 315
            self.from_lines(f, self.scope_globals)  # "scope globals:"
paul@0 316
            self._get_function_targets(f)
paul@0 317
            self._get_function_arguments(f)
paul@0 318
            self._get_attribute_usage(f)
paul@0 319
            self._get_attr_accesses(f)
paul@0 320
            self._get_const_accesses(f)
paul@0 321
            self._get_attr_accessors(f)
paul@0 322
            self._get_attr_access_modifiers(f)
paul@0 323
            self._get_constant_literals(f)
paul@0 324
            self._get_constant_values(f)
paul@0 325
paul@0 326
        finally:
paul@0 327
            f.close()
paul@0 328
paul@12 329
    def complete(self):
paul@0 330
        self.propagate()
paul@0 331
paul@0 332
    def _get_members(self, f):
paul@0 333
        f.readline() # "members:"
paul@0 334
        line = f.readline().rstrip()
paul@0 335
        while line:
paul@0 336
            name, ref = line.split(" ", 1)
paul@0 337
            self.set_object(name, ref)
paul@0 338
            line = f.readline().rstrip()
paul@0 339
paul@0 340
    def _get_class_relationships(self, f):
paul@0 341
        f.readline() # "class relationships:"
paul@0 342
        line = f.readline().rstrip()
paul@0 343
        while line:
paul@0 344
            name, value = self._get_fields(line)
paul@0 345
            values = value and value.split(", ") or []
paul@0 346
            self.importer.classes[name] = self.classes[name] = map(decode_reference, values)
paul@0 347
            self.importer.subclasses[name] = set()
paul@0 348
            line = f.readline().rstrip()
paul@0 349
paul@0 350
    def _get_instance_attrs(self, f):
paul@0 351
        f.readline() # "instance attributes:"
paul@0 352
        line = f.readline().rstrip()
paul@0 353
        while line:
paul@0 354
            name, value = self._get_fields(line)
paul@0 355
            self.importer.all_instance_attrs[name] = self.instance_attrs[name] = set(value and value.split(", ") or [])
paul@0 356
            line = f.readline().rstrip()
paul@0 357
paul@0 358
    def _get_instance_attr_constants(self, f):
paul@0 359
        f.readline() # "instance attribute constants:"
paul@0 360
        line = f.readline().rstrip()
paul@0 361
        while line:
paul@0 362
            name, attrname, ref = self._get_fields(line, 3)
paul@0 363
            init_item(self.instance_attr_constants, name, dict)
paul@0 364
            self.instance_attr_constants[name][attrname] = decode_reference(ref)
paul@0 365
            line = f.readline().rstrip()
paul@0 366
paul@0 367
    def _get_name_references(self, f):
paul@0 368
        f.readline() # "name references:"
paul@0 369
        line = f.readline().rstrip()
paul@0 370
        while line:
paul@0 371
            name, value = self._get_fields(line)
paul@0 372
            self.name_references[name] = value
paul@0 373
            line = f.readline().rstrip()
paul@0 374
paul@0 375
    def _get_initialised_names(self, f):
paul@0 376
        f.readline() # "initialised names:"
paul@0 377
        line = f.readline().rstrip()
paul@0 378
        while line:
paul@0 379
            name, version, value = self._get_fields(line, 3)
paul@0 380
            init_item(self.initialised_names, name, dict)
paul@0 381
            self.initialised_names[name][int(version)] = decode_reference(value)
paul@0 382
            line = f.readline().rstrip()
paul@0 383
paul@0 384
    def _get_aliased_names(self, f):
paul@0 385
        f.readline() # "aliased names:"
paul@0 386
        line = f.readline().rstrip()
paul@0 387
        while line:
paul@0 388
            name, version, original_name, attrnames, number = self._get_fields(line, 5)
paul@0 389
            init_item(self.aliased_names, name, dict)
paul@0 390
            if number == "{}": number = None
paul@0 391
            else: number = int(number)
paul@0 392
            self.aliased_names[name][int(version)] = (original_name, attrnames != "{}" and attrnames or None, number)
paul@0 393
            line = f.readline().rstrip()
paul@0 394
paul@0 395
    def _get_function_parameters(self, f):
paul@0 396
        f.readline() # "function parameters:"
paul@0 397
        line = f.readline().rstrip()
paul@0 398
        while line:
paul@0 399
            function, names = self._get_fields(line)
paul@0 400
            self.importer.function_parameters[function] = \
paul@0 401
                self.function_parameters[function] = names and names.split(", ") or []
paul@0 402
            line = f.readline().rstrip()
paul@0 403
paul@0 404
    def _get_function_defaults(self, f):
paul@0 405
        f.readline() # "function default parameters:"
paul@0 406
        line = f.readline().rstrip()
paul@0 407
        while line:
paul@0 408
            function, defaults = self._get_fields(line)
paul@0 409
            self.importer.function_defaults[function] = \
paul@0 410
                self.function_defaults[function] = l = []
paul@0 411
            if defaults != "{}":
paul@0 412
                for value in defaults.split(", "):
paul@0 413
                    name, default = value.split("=")
paul@0 414
                    default = decode_reference(default)
paul@0 415
                    l.append((name, default))
paul@0 416
            line = f.readline().rstrip()
paul@0 417
paul@0 418
    def _get_function_locals(self, f):
paul@0 419
        f.readline() # "function locals:"
paul@0 420
        line = f.readline().rstrip()
paul@0 421
        while line:
paul@0 422
            function, name, value = self._get_fields(line, 3)
paul@0 423
            init_item(self.function_locals, function, dict)
paul@0 424
            if name != "{}":
paul@0 425
                self.function_locals[function][name] = decode_reference(value)
paul@0 426
            line = f.readline().rstrip()
paul@0 427
paul@0 428
    def _get_function_targets(self, f):
paul@0 429
        f.readline() # "function targets:"
paul@0 430
        line = f.readline().rstrip()
paul@0 431
        while line:
paul@0 432
            function, n = self._get_fields(line)
paul@0 433
            self.importer.function_targets[function] = \
paul@0 434
                self.function_targets[function] = int(n)
paul@0 435
            line = f.readline().rstrip()
paul@0 436
paul@0 437
    def _get_function_arguments(self, f):
paul@0 438
        f.readline() # "function arguments:"
paul@0 439
        line = f.readline().rstrip()
paul@0 440
        while line:
paul@0 441
            function, n = self._get_fields(line)
paul@0 442
            self.importer.function_arguments[function] = \
paul@0 443
                self.function_arguments[function] = int(n)
paul@0 444
            line = f.readline().rstrip()
paul@0 445
paul@0 446
    def _get_attribute_usage(self, f):
paul@0 447
        f.readline() # "attribute usage:"
paul@0 448
        line = f.readline().rstrip()
paul@0 449
        while line:
paul@0 450
            unit, value = self._get_fields(line)
paul@0 451
            init_item(self.attr_usage, unit, dict)
paul@0 452
            self.usage_from_cache(value, self.attr_usage[unit])
paul@0 453
            line = f.readline().rstrip()
paul@0 454
paul@0 455
    def _get_attr_accesses(self, f):
paul@0 456
        f.readline() # "attribute accesses:"
paul@0 457
        line = f.readline().rstrip()
paul@0 458
        while line:
paul@0 459
            name, value = self._get_fields(line)
paul@0 460
            self.attr_accesses[name] = set(value.split(", "))
paul@0 461
            line = f.readline().rstrip()
paul@0 462
paul@0 463
    def _get_const_accesses(self, f):
paul@0 464
        f.readline() # "constant accesses:"
paul@0 465
        line = f.readline().rstrip()
paul@0 466
        while line:
paul@0 467
            name, original_name, attrnames, objpath, ref, remaining = self._get_fields(line, 6)
paul@0 468
            if attrnames == "{}": attrnames = None
paul@0 469
            init_item(self.const_accesses, name, dict)
paul@0 470
            self.const_accesses[name][(original_name, attrnames)] = (objpath, decode_reference(ref), remaining != "{}" and remaining or "")
paul@0 471
            line = f.readline().rstrip()
paul@0 472
paul@0 473
    def _get_attr_accessors(self, f):
paul@0 474
        f.readline() # "attribute access usage:"
paul@0 475
        line = f.readline().rstrip()
paul@0 476
        while line:
paul@0 477
            objpath, name, attrname, value = self._get_fields(line, 4)
paul@0 478
            if attrname == "{}": attrname = None
paul@0 479
            access = name, attrname
paul@0 480
            init_item(self.attr_accessors, objpath, dict)
paul@0 481
            init_item(self.attr_accessors[objpath], access, list)
paul@0 482
            positions = map(int, value.split(", "))
paul@0 483
            self.attr_accessors[objpath][access].append(positions)
paul@0 484
            line = f.readline().rstrip()
paul@0 485
paul@0 486
    def _get_attr_access_modifiers(self, f):
paul@0 487
        f.readline() # "attribute access modifiers:"
paul@0 488
        line = f.readline().rstrip()
paul@0 489
        while line:
paul@0 490
            objpath, name, attrnames, value = self._get_fields(line, 4)
paul@0 491
            if name == "{}": name = None
paul@0 492
            if attrnames == "{}": attrnames = None
paul@0 493
            access = name, attrnames
paul@0 494
            init_item(self.attr_access_modifiers, objpath, dict)
paul@0 495
            init_item(self.attr_access_modifiers[objpath], access, list)
paul@0 496
            modifiers = [decode_modifier_term(s) for s in value]
paul@0 497
            self.attr_access_modifiers[objpath][access] = modifiers
paul@0 498
            line = f.readline().rstrip()
paul@0 499
paul@0 500
    def _get_constant_literals(self, f):
paul@0 501
        f.readline() # "constant literals:"
paul@0 502
        line = f.readline().rstrip()
paul@0 503
        last_path = None
paul@0 504
        n = None
paul@0 505
        while line:
paul@0 506
            path, constant = self._get_fields(line)
paul@0 507
            if path != last_path:
paul@0 508
                n = 0
paul@0 509
                last_path = path
paul@0 510
            else:
paul@0 511
                n += 1
paul@0 512
            init_item(self.constants, path, dict)
paul@0 513
            self.constants[path][eval(constant)] = n
paul@0 514
            line = f.readline().rstrip()
paul@0 515
paul@0 516
    def _get_constant_values(self, f):
paul@0 517
        f.readline() # "constant values:"
paul@0 518
        line = f.readline().rstrip()
paul@0 519
        while line:
paul@0 520
            name, value_type, value = self._get_fields(line, 3)
paul@0 521
            self.constant_values[name] = eval(value), value_type
paul@0 522
            line = f.readline().rstrip()
paul@0 523
paul@0 524
    # Generic parsing methods.
paul@0 525
paul@0 526
    def from_lines(self, f, d):
paul@0 527
paul@0 528
        "Read lines from 'f', populating 'd'."
paul@0 529
paul@0 530
        f.readline() # section heading
paul@0 531
        line = f.readline().rstrip()
paul@0 532
        while line:
paul@0 533
            name, value = self._get_fields(line)
paul@0 534
            d[name] = set(value and value.split(", ") or [])
paul@0 535
            line = f.readline().rstrip()
paul@0 536
paul@0 537
    def usage_from_cache(self, value, mapping):
paul@0 538
paul@0 539
        """
paul@0 540
        Interpret the given 'value' containing name and usage information,
paul@0 541
        storing the information in the given 'mapping'.
paul@0 542
        """
paul@0 543
paul@0 544
        local, usage = self._get_fields(value)
paul@0 545
        init_item(mapping, local, list)
paul@0 546
        self._usage_from_cache(mapping[local], usage)
paul@0 547
paul@0 548
    def _usage_from_cache(self, d, usage):
paul@0 549
paul@0 550
        # Interpret descriptions of each version of the name.
paul@0 551
paul@0 552
        all_usages = set()
paul@0 553
        for attrnames in usage.split("; "):
paul@0 554
            if attrnames == "{}":
paul@0 555
                all_attrnames = ()
paul@0 556
            else:
paul@0 557
                # Decode attribute details for each usage description.
paul@0 558
paul@0 559
                all_attrnames = set()
paul@0 560
                for attrname_str in attrnames.split(", "):
paul@0 561
                    all_attrnames.add(attrname_str)
paul@0 562
paul@0 563
                all_attrnames = list(all_attrnames)
paul@0 564
                all_attrnames.sort()
paul@0 565
paul@0 566
            all_usages.add(tuple(all_attrnames))
paul@0 567
paul@0 568
        d.append(all_usages)
paul@0 569
paul@0 570
    def _get_fields(self, s, n=2):
paul@0 571
        result = s.split(" ", n-1)
paul@0 572
        if len(result) == n:
paul@0 573
            return result
paul@0 574
        else:
paul@0 575
            return tuple(result) + tuple([""] * (n - len(result)))
paul@0 576
paul@0 577
class CacheWritingModule:
paul@0 578
paul@0 579
    """
paul@0 580
    A mix-in providing cache-writing support, to be combined with BasicModule.
paul@0 581
    """
paul@0 582
paul@0 583
    def to_cache(self, filename):
paul@0 584
paul@0 585
        """
paul@0 586
        Write a cached representation of the inspected module with the following
paul@0 587
        format to the file having the given 'filename':
paul@0 588
paul@0 589
        filename
paul@0 590
        (empty line)
paul@0 591
        "members:"
paul@0 592
        zero or more: qualified name " " reference
paul@0 593
        (empty line)
paul@0 594
        "class relationships:"
paul@0 595
        zero or more: qualified class name " " base class references
paul@0 596
        (empty line)
paul@0 597
        "instance attributes:"
paul@0 598
        zero or more: qualified class name " " instance attribute names
paul@0 599
        (empty line)
paul@0 600
        "instance attribute constants:"
paul@0 601
        zero or more: qualified class name " " attribute name " " reference
paul@0 602
        (empty line)
paul@0 603
        "names used:"
paul@0 604
        zero or more: qualified class/function/module name " " names
paul@0 605
        (empty line)
paul@0 606
        "names missing:"
paul@0 607
        zero or more: qualified class/function/module name " " names
paul@0 608
        (empty line)
paul@0 609
        "name references:"
paul@0 610
        zero or more: qualified name " " reference
paul@0 611
        (empty line)
paul@0 612
        "initialised names:"
paul@0 613
        zero or more: qualified name " " definition version " " reference
paul@0 614
        (empty line)
paul@0 615
        "aliased names:"
paul@0 616
        zero or more: qualified name " " definition version " " original name " " attribute names " " access number
paul@0 617
        (empty line)
paul@0 618
        "function parameters:"
paul@0 619
        zero or more: qualified function name " " parameter names
paul@0 620
        (empty line)
paul@0 621
        "function default parameters:"
paul@0 622
        zero or more: qualified function name " " parameter names with defaults
paul@0 623
        (empty line)
paul@0 624
        "function locals:"
paul@0 625
        zero or more: qualified function name " " local variable name " " reference
paul@0 626
        (empty line)
paul@0 627
        "scope globals:"
paul@0 628
        zero or more: qualified function name " " global variable names
paul@0 629
        (empty line)
paul@0 630
        "function targets:"
paul@0 631
        zero or more: qualified function name " " maximum number of targets allocated
paul@0 632
        (empty line)
paul@0 633
        "function arguments:"
paul@0 634
        zero or more: qualified function name " " maximum number of arguments allocated
paul@0 635
        (empty line)
paul@0 636
        "attribute usage:"
paul@0 637
        zero or more: qualified scope name " " local/global/qualified variable name " " usages
paul@0 638
        (empty line)
paul@0 639
        "attribute accesses:"
paul@0 640
        zero or more: qualified scope name " " attribute-chains
paul@0 641
        (empty line)
paul@0 642
        "constant accesses:"
paul@0 643
        zero or more: qualified function name " " attribute-chain " " reference " " remaining attribute-chain
paul@0 644
        (empty line)
paul@0 645
        "attribute access usage:"
paul@0 646
        zero or more: qualified function name " " local/global variable name " " attribute name " " definition versions
paul@0 647
        (empty line)
paul@0 648
        "attribute access modifiers:"
paul@0 649
        zero or more: qualified function name " " local/global variable name " " attribute name " " access modifiers
paul@0 650
        "constant literals:"
paul@0 651
        zero or more: qualified scope name " " constant literal
paul@0 652
        "constant values:"
paul@0 653
        zero or more: qualified name " " value type " " constant literal
paul@0 654
paul@0 655
        All collections of names are separated by ", " characters.
paul@0 656
paul@0 657
        References can be "<var>", a module name, or one of "<class>" or
paul@0 658
        "<function>" followed optionally by a ":" character and a qualified
paul@0 659
        name.
paul@0 660
paul@0 661
        Parameter names with defaults are separated by ", " characters, with
paul@0 662
        each name followed by "=" and then followed by a reference. If "{}" is
paul@0 663
        indicated, no defaults are defined for the function. Similarly, function
paul@0 664
        locals may be indicated as "{}" meaning that there are no locals.
paul@0 665
paul@0 666
        All usages (attribute usage sets) are separated by "; " characters, with
paul@0 667
        the special string "{}" representing an empty set.
paul@0 668
paul@0 669
        Each usage is a collection of names separated by ", " characters, with
paul@0 670
        assigned attribute names suffixed with a "*" character.
paul@0 671
paul@0 672
        Each attribute-chain expression is a dot-separated chain of attribute
paul@0 673
        names, with assignments suffixed with a "*" character.
paul@0 674
paul@0 675
        Definition versions are separated by ", " characters and indicate the
paul@0 676
        name definition version associated with the access.
paul@0 677
paul@0 678
        Access modifiers are separated by ", " characters and indicate features
paul@0 679
        of each access, with multiple accesses described on a single line.
paul@0 680
        """
paul@0 681
paul@0 682
        f = open(filename, "w")
paul@0 683
        try:
paul@0 684
            print >>f, self.filename
paul@0 685
paul@0 686
            print >>f
paul@0 687
            print >>f, "members:"
paul@0 688
            objects = self.objects.keys()
paul@0 689
            objects.sort()
paul@0 690
            for name in objects:
paul@0 691
                print >>f, name, self.objects[name]
paul@0 692
paul@0 693
            print >>f
paul@0 694
            print >>f, "class relationships:"
paul@0 695
            classes = self.classes.keys()
paul@0 696
            classes.sort()
paul@0 697
            for class_ in classes:
paul@0 698
                bases = self.classes[class_]
paul@0 699
                if bases:
paul@0 700
                    print >>f, class_, ", ".join(map(str, bases))
paul@0 701
                else:
paul@0 702
                    print >>f, class_
paul@0 703
paul@0 704
            self.to_lines(f, "instance attributes:", self.instance_attrs)
paul@0 705
paul@0 706
            print >>f
paul@0 707
            print >>f, "instance attribute constants:"
paul@0 708
            classes = self.instance_attr_constants.items()
paul@0 709
            classes.sort()
paul@0 710
            for name, attrs in classes:
paul@0 711
                attrs = attrs.items()
paul@0 712
                attrs.sort()
paul@0 713
                for attrname, ref in attrs:
paul@0 714
                    print >>f, name, attrname, ref
paul@0 715
paul@0 716
            self.to_lines(f, "names used:", self.names_used)
paul@0 717
            self.to_lines(f, "names missing:", self.names_missing)
paul@0 718
paul@0 719
            print >>f
paul@0 720
            print >>f, "name references:"
paul@0 721
            refs = self.name_references.items()
paul@0 722
            refs.sort()
paul@0 723
            for name, ref in refs:
paul@0 724
                print >>f, name, ref
paul@0 725
paul@0 726
            print >>f
paul@0 727
            print >>f, "initialised names:"
paul@0 728
            assignments = self.initialised_names.items()
paul@0 729
            assignments.sort()
paul@0 730
            for name, refs in assignments:
paul@0 731
                versions = refs.items()
paul@0 732
                versions.sort()
paul@0 733
                for version, ref in versions:
paul@0 734
                    print >>f, name, version, ref
paul@0 735
paul@0 736
            print >>f
paul@0 737
            print >>f, "aliased names:"
paul@0 738
            assignments = self.aliased_names.items()
paul@0 739
            assignments.sort()
paul@0 740
            for name, aliases in assignments:
paul@0 741
                versions = aliases.items()
paul@0 742
                versions.sort()
paul@0 743
                for version, alias in versions:
paul@0 744
                    original_name, attrnames, number = alias
paul@0 745
                    print >>f, name, version, original_name, attrnames or "{}", number is None and "{}" or number
paul@0 746
paul@0 747
            print >>f
paul@0 748
            print >>f, "function parameters:"
paul@0 749
            functions = self.function_parameters.keys()
paul@0 750
            functions.sort()
paul@0 751
            for function in functions:
paul@0 752
                print >>f, function, ", ".join(self.function_parameters[function])
paul@0 753
paul@0 754
            print >>f
paul@0 755
            print >>f, "function default parameters:"
paul@0 756
            functions = self.function_defaults.keys()
paul@0 757
            functions.sort()
paul@0 758
            for function in functions:
paul@0 759
                parameters = self.function_defaults[function]
paul@0 760
                if parameters:
paul@0 761
                    print >>f, function, ", ".join([("%s=%s" % (name, default)) for (name, default) in parameters])
paul@0 762
                else:
paul@0 763
                    print >>f, function, "{}"
paul@0 764
paul@0 765
            print >>f
paul@0 766
            print >>f, "function locals:"
paul@0 767
            functions = self.function_locals.keys()
paul@0 768
            functions.sort()
paul@0 769
            for function in functions:
paul@0 770
                names = self.function_locals[function].items()
paul@0 771
                if names:
paul@0 772
                    names.sort()
paul@0 773
                    for name, value in names:
paul@0 774
                        print >>f, function, name, value
paul@0 775
                else:
paul@0 776
                    print >>f, function, "{}"
paul@0 777
paul@0 778
            self.to_lines(f, "scope globals:", self.scope_globals)
paul@0 779
paul@0 780
            print >>f
paul@0 781
            print >>f, "function targets:"
paul@0 782
            functions = self.function_targets.keys()
paul@0 783
            functions.sort()
paul@0 784
            for function in functions:
paul@0 785
                print >>f, function, self.function_targets[function]
paul@0 786
paul@0 787
            print >>f
paul@0 788
            print >>f, "function arguments:"
paul@0 789
            functions = self.function_arguments.keys()
paul@0 790
            functions.sort()
paul@0 791
            for function in functions:
paul@0 792
                print >>f, function, self.function_arguments[function]
paul@0 793
paul@0 794
            print >>f
paul@0 795
            print >>f, "attribute usage:"
paul@0 796
            units = self.attr_usage.keys()
paul@0 797
            units.sort()
paul@0 798
            for unit in units:
paul@0 799
                d = self.attr_usage[unit]
paul@0 800
                self.usage_to_cache(d, f, unit)
paul@0 801
paul@0 802
            print >>f
paul@0 803
            print >>f, "attribute accesses:"
paul@0 804
            paths = self.attr_accesses.keys()
paul@0 805
            paths.sort()
paul@0 806
            for path in paths:
paul@0 807
                accesses = list(self.attr_accesses[path])
paul@0 808
                accesses.sort()
paul@0 809
                print >>f, path, ", ".join(accesses)
paul@0 810
paul@0 811
            print >>f
paul@0 812
            print >>f, "constant accesses:"
paul@0 813
            paths = self.const_accesses.keys()
paul@0 814
            paths.sort()
paul@0 815
            for path in paths:
paul@0 816
                accesses = self.const_accesses[path].items()
paul@0 817
                accesses.sort()
paul@0 818
                for (original_name, attrnames), (objpath, ref, remaining_attrnames) in accesses:
paul@0 819
                    print >>f, path, original_name, attrnames, objpath, ref, remaining_attrnames or "{}"
paul@0 820
paul@0 821
            print >>f
paul@0 822
            print >>f, "attribute access usage:"
paul@0 823
            paths = self.attr_accessors.keys()
paul@0 824
            paths.sort()
paul@0 825
            for path in paths:
paul@0 826
                all_accesses = self.attr_accessors[path].items()
paul@0 827
                all_accesses.sort()
paul@0 828
                for (name, attrname), accesses in all_accesses:
paul@0 829
                    for positions in accesses:
paul@0 830
                        positions = map(str, positions)
paul@0 831
                        print >>f, path, name, attrname or "{}", ", ".join(positions)
paul@0 832
paul@0 833
            print >>f
paul@0 834
            print >>f, "attribute access modifiers:"
paul@0 835
            paths = self.attr_access_modifiers.keys()
paul@0 836
            paths.sort()
paul@0 837
            for path in paths:
paul@0 838
                all_accesses = self.attr_access_modifiers[path].items()
paul@0 839
                all_accesses.sort()
paul@0 840
                for (name, attrnames), modifiers in all_accesses:
paul@0 841
                    print >>f, path, name or "{}", attrnames or "{}", encode_modifiers(modifiers)
paul@0 842
paul@0 843
            print >>f
paul@0 844
            print >>f, "constant literals:"
paul@0 845
            paths = self.constants.keys()
paul@0 846
            paths.sort()
paul@0 847
            for path in paths:
paul@0 848
                constants = [(v, k) for (k, v) in self.constants[path].items()]
paul@0 849
                constants.sort()
paul@0 850
                for n, constant in constants:
paul@0 851
                    print >>f, path, repr(constant)
paul@0 852
paul@0 853
            print >>f
paul@0 854
            print >>f, "constant values:"
paul@0 855
            names = self.constant_values.keys()
paul@0 856
            names.sort()
paul@0 857
            for name in names:
paul@0 858
                value, value_type = self.constant_values[name]
paul@0 859
                print >>f, name, value_type, repr(value)
paul@0 860
paul@0 861
        finally:
paul@0 862
            f.close()
paul@0 863
paul@0 864
    def to_lines(self, f, heading, d):
paul@0 865
paul@0 866
        "Write lines to 'f' with the given 'heading', using 'd'."
paul@0 867
paul@0 868
        print >>f
paul@0 869
        print >>f, heading
paul@0 870
        keys = d.keys()
paul@0 871
        keys.sort()
paul@0 872
        for key in keys:
paul@0 873
            attrs = list(d[key])
paul@0 874
            if attrs:
paul@0 875
                attrs.sort()
paul@0 876
                print >>f, key, ", ".join(attrs)
paul@0 877
paul@0 878
    def usage_to_cache(self, details, f, prefix):
paul@0 879
paul@0 880
        "Write the given namespace usage details to the cache."
paul@0 881
paul@0 882
        names = list(details.keys())
paul@0 883
        if names:
paul@0 884
            names.sort()
paul@0 885
            for name in names:
paul@0 886
                if details[name]:
paul@0 887
paul@0 888
                    # Produce descriptions for each version of the name.
paul@0 889
paul@0 890
                    for version in details[name]:
paul@0 891
                        all_usages = []
paul@0 892
                        for usage in version:
paul@0 893
                            all_usages.append(encode_usage(usage))
paul@0 894
paul@0 895
                        print >>f, "%s %s %s" % (prefix, name, "; ".join(all_usages))
paul@0 896
paul@0 897
# vim: tabstop=4 expandtab shiftwidth=4