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