Lichen

Annotated modules.py

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