Lichen

modules.py

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