Lichen

modules.py

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