Lichen

Annotated referencing.py

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