Lichen

Annotated referencing.py

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