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