Lichen

Annotated referencing.py

118:900d641f42d6
2016-10-20 Paul Boddie Added some more support for generating invocation code, distinguishing between static invocation targets that are identified and whose functions can be obtained directly and other kinds of targets whose functions must be obtained via the special attribute.
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