Lichen

Annotated referencing.py

319:e39bf1c565b8
2016-12-05 Paul Boddie Changed multiple assignment detection to distinguish between different objects of the same kind.
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