1 #!/usr/bin/env python 2 3 """ 4 Reference abstractions. 5 6 Copyright (C) 2016 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 return self.kind 52 else: 53 alias = self.name and ";%s" % self.name or "" 54 return "%s:%s%s" % (self.kind, self.origin, alias) 55 56 def __hash__(self): 57 58 "Hash instances using the kind and origin only." 59 60 return hash((self.kind, self.get_origin())) 61 62 def __cmp__(self, other): 63 64 "Compare with 'other' using the kind and origin only." 65 66 if isinstance(other, Reference): 67 return cmp((self.kind, self.get_origin()), (other.kind, other.get_origin())) 68 else: 69 return cmp(str(self), other) 70 71 def get_name(self): 72 73 "Return the name used for this reference." 74 75 return self.name 76 77 def get_origin(self): 78 79 "Return the origin of the reference." 80 81 return self.kind != "<var>" and self.origin or None 82 83 def get_kind(self): 84 85 "Return the kind of object referenced." 86 87 return self.kind 88 89 def has_kind(self, kinds): 90 91 """ 92 Return whether the reference describes an object from the given 'kinds', 93 where such kinds may be "<class>", "<function>", "<instance>", 94 "<module>" or "<var>". Unresolved references may also have kinds of 95 "<depends>" and "<invoke>". 96 """ 97 98 if not isinstance(kinds, (list, tuple)): 99 kinds = [kinds] 100 return self.get_kind() in kinds 101 102 def get_path(self): 103 104 "Return the attribute names comprising the path to the origin." 105 106 return self.get_origin().split(".") 107 108 def unresolved(self): 109 110 "Return whether this reference is unresolved." 111 112 return self.has_kind(["<depends>", "<invoke>"]) 113 114 def static(self): 115 116 "Return this reference if it refers to a static object, None otherwise." 117 118 return self.has_kind(["<class>", "<function>", "<module>"]) and self or None 119 120 def final(self): 121 122 "Return a reference to either a static object or None." 123 124 static = self.static() 125 return static and static.origin or None 126 127 def instance_of(self): 128 129 "Return a reference to an instance of the referenced class." 130 131 return self.has_kind("<class>") and Reference("<instance>", self.origin) or None 132 133 def as_var(self): 134 135 """ 136 Return a variable version of this reference. Any origin information is 137 discarded since variable references are deliberately ambiguous. 138 """ 139 140 return Reference("<var>", None, self.name) 141 142 def alias(self, name): 143 144 "Alias this reference employing 'name'." 145 146 return Reference(self.get_kind(), self.get_origin(), name) 147 148 def mutate(self, ref): 149 150 "Mutate this reference to have the same details as 'ref'." 151 152 self.kind = ref.kind 153 self.origin = ref.origin 154 self.name = ref.name 155 156 def parent(self): 157 158 "Return the parent of this reference's origin." 159 160 if not self.get_origin(): 161 return None 162 163 return self.get_origin().rsplit(".", 1)[0] 164 165 def name_parent(self): 166 167 "Return the parent of this reference's aliased name." 168 169 if not self.get_name(): 170 return None 171 172 return self.get_name().rsplit(".", 1)[0] 173 174 def ancestors(self): 175 176 """ 177 Return ancestors of this reference's origin in order of decreasing 178 depth. 179 """ 180 181 if not self.get_origin(): 182 return None 183 184 parts = self.get_origin().split(".") 185 ancestors = [] 186 187 for i in range(len(parts) - 1, 0, -1): 188 ancestors.append(".".join(parts[:i])) 189 190 return ancestors 191 192 def get_types(self): 193 194 "Return class, instance-only and module types for this reference." 195 196 class_types = self.has_kind("<class>") and [self.get_origin()] or [] 197 instance_types = [] 198 module_types = self.has_kind("<module>") and [self.get_origin()] or [] 199 return class_types, instance_types, module_types 200 201 def decode_reference(s, name=None): 202 203 "Decode 's', making a reference." 204 205 if isinstance(s, Reference): 206 return s.alias(name) 207 208 # Null value. 209 210 elif not s: 211 return Reference("<var>", None, name) 212 213 # Kind and origin. 214 215 elif ":" in s: 216 kind, origin = s.split(":") 217 if ";" in origin: 218 origin, name = origin.split(";") 219 return Reference(kind, origin, name) 220 221 # Kind-only, origin is indicated name. 222 223 elif s[0] == "<": 224 return Reference(s, name, name) 225 226 # Module-only. 227 228 else: 229 return Reference("<module>", s, name) 230 231 232 233 # Type/reference collection functions. 234 235 def is_single_class_type(all_types): 236 237 """ 238 Return whether 'all_types' is a mixture of class and instance kinds for 239 a single class type. 240 """ 241 242 kinds = set() 243 types = set() 244 245 for type in all_types: 246 kinds.add(type.get_kind()) 247 types.add(type.get_origin()) 248 249 return len(types) == 1 and kinds == set(["<class>", "<instance>"]) 250 251 def combine_types(class_types, instance_types, module_types): 252 253 """ 254 Combine 'class_types', 'instance_types', 'module_types' into a single 255 list of references. 256 """ 257 258 all_types = [] 259 for kind, l in [("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types)]: 260 for t in l: 261 all_types.append(Reference(kind, t)) 262 return all_types 263 264 def separate_types(refs): 265 266 """ 267 Separate 'refs' into type-specific lists, returning a tuple containing 268 lists of class types, instance types, module types, function types and 269 unknown "var" types. 270 """ 271 272 class_types = [] 273 instance_types = [] 274 module_types = [] 275 function_types = [] 276 var_types = [] 277 278 for kind, l in [ 279 ("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types), 280 ("<function>", function_types), ("<var>", var_types) 281 ]: 282 283 for ref in refs: 284 if ref.get_kind() == kind: 285 l.append(ref.get_origin()) 286 287 return class_types, instance_types, module_types, function_types, var_types 288 289 # vim: tabstop=4 expandtab shiftwidth=4