Lichen

referencing.py

934:2989aab1b4f7
5 months ago Paul Boddie Renamed the utf8string class to unicode, eliminating the unicode function. This means that the simple case of merely returning an object if it is already a Unicode object no longer occurs when using the unicode callable, but such behaviour might be better supported with more general customised instantiation functionality.
     1 #!/usr/bin/env python     2      3 """     4 Reference abstractions.     5      6 Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 class Reference:    23     24     "A reference abstraction."    25     26     def __init__(self, kind, origin=None, name=None):    27     28         """    29         Initialise a reference using 'kind' to indicate the kind of object,    30         'origin' to indicate the actual origin of a referenced object, and a    31         'name' indicating an alias for the object in the program structure.    32         """    33     34         if isinstance(kind, Reference):    35             raise ValueError, (kind, origin)    36         self.kind = kind    37         self.origin = origin    38         self.name = name    39     40     def __repr__(self):    41         return "Reference(%r, %r, %r)" % (self.kind, self.origin, self.name)    42     43     def __str__(self):    44     45         """    46         Serialise the reference as '<var>' or a description incorporating the    47         kind and origin.    48         """    49     50         if self.kind == "<var>":    51             alias = self.name and ";%s" % self.name or ""    52             return "%s%s" % (self.kind, alias)    53         else:    54             alias = self.name and self.name != self.origin and ";%s" % self.name or ""    55             return "%s:%s%s" % (self.kind, self.origin, alias)    56     57     def __hash__(self):    58     59         "Hash instances using the kind and origin only."    60     61         return hash((self.kind, self.get_origin()))    62     63     def __cmp__(self, other):    64     65         "Compare with 'other' using the kind and origin only."    66     67         if isinstance(other, Reference):    68             return cmp((self.kind, self.get_origin()), (other.kind, other.get_origin()))    69         else:    70             return cmp(str(self), other)    71     72     def get_name(self):    73     74         "Return the name used for this reference."    75     76         return self.name    77     78     def get_origin(self):    79     80         "Return the origin of the reference."    81     82         return self.kind != "<var>" and self.origin or None    83     84     def get_kind(self):    85     86         "Return the kind of object referenced."    87     88         return self.kind    89     90     def has_kind(self, kinds):    91     92         """    93         Return whether the reference describes an object from the given 'kinds',    94         where such kinds may be "<class>", "<function>", "<instance>",    95         "<module>" or "<var>". Unresolved references may also have kinds of    96         "<depends>" and "<invoke>".    97         """    98     99         if not isinstance(kinds, (list, tuple)):   100             kinds = [kinds]   101         return self.get_kind() in kinds   102    103     def get_path(self):   104    105         "Return the attribute names comprising the path to the origin."   106    107         return self.get_origin().split(".")   108    109     def unresolved(self):   110    111         "Return whether this reference is unresolved."   112    113         return self.has_kind(["<depends>", "<invoke>", "<var>"])   114    115     def static(self):   116    117         "Return this reference if it refers to a static object, None otherwise."   118    119         return self.has_kind(["<class>", "<function>", "<module>"]) and self or None   120    121     def final(self):   122    123         "Return a reference to either a static object or None."   124    125         static = self.static()   126         return static and static.origin or None   127    128     def instance_of(self, alias=None):   129    130         """   131         Return a reference to an instance of the referenced class, indicating an   132         'alias' for the instance if specified.   133         """   134    135         return self.has_kind("<class>") and Reference("<instance>", self.origin, alias) or None   136    137     def as_var(self):   138    139         """   140         Return a variable version of this reference. Any origin information is   141         discarded since variable references are deliberately ambiguous.   142         """   143    144         return Reference("<var>", None, self.name)   145    146     def copy(self):   147    148         "Copy this reference."   149    150         return Reference(self.get_kind(), self.get_origin(), self.get_name())   151    152     def alias(self, name):   153    154         "Alias this reference employing 'name'."   155    156         return Reference(self.get_kind(), self.get_origin(), name)   157    158     def unaliased(self):   159    160         "Return this reference without any alias."   161    162         return Reference(self.get_kind(), self.get_origin())   163    164     def mutate(self, ref):   165    166         "Mutate this reference to have the same details as 'ref'."   167    168         self.kind = ref.kind   169         self.origin = ref.origin   170         self.name = ref.name   171    172     def parent(self):   173    174         "Return the parent of this reference's origin."   175    176         if not self.get_origin():   177             return None   178    179         return self.get_origin().rsplit(".", 1)[0]   180    181     def name_parent(self):   182    183         "Return the parent of this reference's aliased name."   184    185         if not self.get_name():   186             return None   187    188         return self.get_name().rsplit(".", 1)[0]   189    190     def leaf(self):   191    192         "Return the leafname of the reference's origin."   193    194         if not self.get_origin():   195             return None   196    197         return self.get_origin().rsplit(".", 1)[-1]   198    199     def ancestors(self):   200    201         """   202         Return ancestors of this reference's origin in order of decreasing   203         depth.   204         """   205    206         origin = self.get_origin()   207         if not origin:   208             return None   209    210         parts = origin.split(".")   211         ancestors = []   212    213         for i in range(len(parts) - 1, 0, -1):   214             ancestors.append(".".join(parts[:i]))   215    216         return ancestors   217    218     def is_constant_alias(self):   219    220         "Return whether this reference is an alias for a constant."   221    222         name = self.get_name()   223         return name and name.rsplit(".")[-1].startswith("$c")   224    225     def is_predefined_value(self):   226    227         "Return whether this reference identifies a predefined value."   228    229         # NOTE: Details of built-in types employed.   230    231         return self.get_origin() in ("__builtins__.none.NoneType", "__builtins__.boolean.boolean")   232    233     def get_types(self):   234    235         "Return class, instance-only and module types for this reference."   236    237         class_types = self.has_kind("<class>") and [self.get_origin()] or []   238         instance_types = []   239         module_types = self.has_kind("<module>") and [self.get_origin()] or []   240         return class_types, instance_types, module_types   241    242 def decode_reference(s, name=None):   243    244     "Decode 's', making a reference."   245    246     if isinstance(s, Reference):   247         return s.alias(name)   248    249     # Null value.   250    251     elif not s:   252         return Reference("<var>", None, name)   253    254     # Kind and origin.   255    256     elif ":" in s:   257         kind, origin = s.split(":")   258         if ";" in origin:   259             origin, name = origin.split(";")   260         return Reference(kind, origin, name)   261    262     # Kind and name.   263    264     elif ";" in s:   265         kind, name = s.split(";")   266         return Reference(kind, None, name)   267    268     # Kind-only, origin is indicated name.   269    270     elif s[0] == "<":   271         return Reference(s, name, name)   272    273     # Module-only.   274    275     else:   276         return Reference("<module>", s, name)   277    278    279    280 # Type/reference collection functions.   281    282 def is_single_class_type(all_types):   283    284     """   285     Return whether 'all_types' is a mixture of class and instance kinds for   286     a single class type.   287     """   288    289     kinds = set()   290     types = set()   291    292     for type in all_types:   293         kinds.add(type.get_kind())   294         types.add(type.get_origin())   295    296     return len(types) == 1 and kinds == set(["<class>", "<instance>"])   297    298 def combine_types(class_types, instance_types, module_types):   299    300     """   301     Combine 'class_types', 'instance_types', 'module_types' into a single   302     list of references.   303     """   304    305     all_types = []   306     for kind, l in [("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types)]:   307         for t in l:   308             all_types.append(Reference(kind, t))   309     return all_types   310    311 def separate_types(refs):   312    313     """   314     Separate 'refs' into type-specific lists, returning a tuple containing   315     lists of class types, instance types, module types, function types and   316     unknown "var" types.   317     """   318    319     class_types = []   320     instance_types = []   321     module_types = []   322     function_types = []   323     var_types = []   324    325     for kind, l in [   326         ("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types),   327         ("<function>", function_types), ("<var>", var_types)   328         ]:   329    330         for ref in refs:   331             if ref.get_kind() == kind:   332                 l.append(ref.get_origin())   333    334     return class_types, instance_types, module_types, function_types, var_types   335    336 # vim: tabstop=4 expandtab shiftwidth=4