Lichen

Annotated referencing.py

413:1fc56d3a9ac8
2016-12-15 Paul Boddie Implemented the find, index, startswith and endswith methods.
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@338 128
    def instance_of(self, alias=None):
paul@0 129
paul@338 130
        """
paul@338 131
        Return a reference to an instance of the referenced class, indicating an
paul@338 132
        'alias' for the instance if specified.
paul@338 133
        """
paul@0 134
paul@338 135
        return self.has_kind("<class>") and Reference("<instance>", self.origin, alias) or None
paul@0 136
paul@0 137
    def as_var(self):
paul@0 138
paul@0 139
        """
paul@0 140
        Return a variable version of this reference. Any origin information is
paul@0 141
        discarded since variable references are deliberately ambiguous.
paul@0 142
        """
paul@0 143
paul@0 144
        return Reference("<var>", None, self.name)
paul@0 145
paul@391 146
    def copy(self):
paul@391 147
paul@391 148
        "Copy this reference."
paul@391 149
paul@391 150
        return Reference(self.get_kind(), self.get_origin(), self.get_name())
paul@391 151
paul@0 152
    def alias(self, name):
paul@0 153
paul@0 154
        "Alias this reference employing 'name'."
paul@0 155
paul@0 156
        return Reference(self.get_kind(), self.get_origin(), name)
paul@0 157
paul@246 158
    def unaliased(self):
paul@246 159
paul@246 160
        "Return this reference without any alias."
paul@246 161
paul@246 162
        return Reference(self.get_kind(), self.get_origin())
paul@246 163
paul@35 164
    def mutate(self, ref):
paul@35 165
paul@35 166
        "Mutate this reference to have the same details as 'ref'."
paul@35 167
paul@35 168
        self.kind = ref.kind
paul@35 169
        self.origin = ref.origin
paul@35 170
        self.name = ref.name
paul@35 171
paul@86 172
    def parent(self):
paul@86 173
paul@86 174
        "Return the parent of this reference's origin."
paul@86 175
paul@86 176
        if not self.get_origin():
paul@86 177
            return None
paul@86 178
paul@86 179
        return self.get_origin().rsplit(".", 1)[0]
paul@86 180
paul@101 181
    def name_parent(self):
paul@101 182
paul@101 183
        "Return the parent of this reference's aliased name."
paul@101 184
paul@101 185
        if not self.get_name():
paul@101 186
            return None
paul@101 187
paul@101 188
        return self.get_name().rsplit(".", 1)[0]
paul@101 189
paul@16 190
    def ancestors(self):
paul@16 191
paul@16 192
        """
paul@16 193
        Return ancestors of this reference's origin in order of decreasing
paul@16 194
        depth.
paul@16 195
        """
paul@16 196
paul@86 197
        if not self.get_origin():
paul@16 198
            return None
paul@16 199
paul@16 200
        parts = self.get_origin().split(".")
paul@16 201
        ancestors = []
paul@16 202
paul@16 203
        for i in range(len(parts) - 1, 0, -1):
paul@16 204
            ancestors.append(".".join(parts[:i]))
paul@16 205
paul@16 206
        return ancestors
paul@16 207
paul@338 208
    def is_constant_alias(self):
paul@338 209
paul@338 210
        "Return whether this reference is an alias for a constant."
paul@338 211
paul@338 212
        name = self.get_name()
paul@338 213
        return name and name.rsplit(".")[-1].startswith("$c")
paul@338 214
paul@57 215
    def get_types(self):
paul@57 216
paul@57 217
        "Return class, instance-only and module types for this reference."
paul@57 218
paul@57 219
        class_types = self.has_kind("<class>") and [self.get_origin()] or []
paul@57 220
        instance_types = []
paul@57 221
        module_types = self.has_kind("<module>") and [self.get_origin()] or []
paul@57 222
        return class_types, instance_types, module_types
paul@57 223
paul@0 224
def decode_reference(s, name=None):
paul@0 225
paul@0 226
    "Decode 's', making a reference."
paul@0 227
paul@0 228
    if isinstance(s, Reference):
paul@0 229
        return s.alias(name)
paul@0 230
paul@0 231
    # Null value.
paul@0 232
paul@0 233
    elif not s:
paul@0 234
        return Reference("<var>", None, name)
paul@0 235
paul@0 236
    # Kind and origin.
paul@0 237
paul@0 238
    elif ":" in s:
paul@0 239
        kind, origin = s.split(":")
paul@203 240
        if ";" in origin:
paul@203 241
            origin, name = origin.split(";")
paul@0 242
        return Reference(kind, origin, name)
paul@0 243
paul@249 244
    # Kind and name.
paul@249 245
paul@249 246
    elif ";" in s:
paul@249 247
        kind, name = s.split(";")
paul@249 248
        return Reference(kind, None, name)
paul@249 249
paul@0 250
    # Kind-only, origin is indicated name.
paul@0 251
paul@0 252
    elif s[0] == "<":
paul@0 253
        return Reference(s, name, name)
paul@0 254
paul@0 255
    # Module-only.
paul@0 256
paul@0 257
    else:
paul@0 258
        return Reference("<module>", s, name)
paul@0 259
paul@57 260
paul@57 261
paul@57 262
# Type/reference collection functions.
paul@57 263
paul@57 264
def is_single_class_type(all_types):
paul@57 265
paul@57 266
    """
paul@57 267
    Return whether 'all_types' is a mixture of class and instance kinds for
paul@57 268
    a single class type.
paul@57 269
    """
paul@57 270
paul@57 271
    kinds = set()
paul@57 272
    types = set()
paul@57 273
paul@57 274
    for type in all_types:
paul@57 275
        kinds.add(type.get_kind())
paul@57 276
        types.add(type.get_origin())
paul@57 277
paul@57 278
    return len(types) == 1 and kinds == set(["<class>", "<instance>"])
paul@57 279
paul@57 280
def combine_types(class_types, instance_types, module_types):
paul@57 281
paul@57 282
    """
paul@57 283
    Combine 'class_types', 'instance_types', 'module_types' into a single
paul@57 284
    list of references.
paul@57 285
    """
paul@57 286
paul@57 287
    all_types = []
paul@57 288
    for kind, l in [("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types)]:
paul@57 289
        for t in l:
paul@57 290
            all_types.append(Reference(kind, t))
paul@57 291
    return all_types
paul@57 292
paul@57 293
def separate_types(refs):
paul@57 294
paul@57 295
    """
paul@57 296
    Separate 'refs' into type-specific lists, returning a tuple containing
paul@57 297
    lists of class types, instance types, module types, function types and
paul@57 298
    unknown "var" types.
paul@57 299
    """
paul@57 300
paul@57 301
    class_types = []
paul@57 302
    instance_types = []
paul@57 303
    module_types = []
paul@57 304
    function_types = []
paul@57 305
    var_types = []
paul@57 306
paul@57 307
    for kind, l in [
paul@57 308
        ("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types),
paul@57 309
        ("<function>", function_types), ("<var>", var_types)
paul@57 310
        ]:
paul@57 311
paul@57 312
        for ref in refs:
paul@57 313
            if ref.get_kind() == kind:
paul@57 314
                l.append(ref.get_origin())
paul@57 315
paul@57 316
    return class_types, instance_types, module_types, function_types, var_types
paul@57 317
paul@0 318
# vim: tabstop=4 expandtab shiftwidth=4