Lichen

Annotated referencing.py

1045:e6010ccae0c0
5 months ago Paul Boddie Fixed list element assignment, overlooked in previous value replacement changes. value-replacement
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Reference abstractions.
paul@0 5
paul@979 6
Copyright (C) 2016, 2017, 2023 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@543 190
    def leaf(self):
paul@543 191
paul@543 192
        "Return the leafname of the reference's origin."
paul@543 193
paul@543 194
        if not self.get_origin():
paul@543 195
            return None
paul@543 196
paul@543 197
        return self.get_origin().rsplit(".", 1)[-1]
paul@543 198
paul@16 199
    def ancestors(self):
paul@16 200
paul@16 201
        """
paul@16 202
        Return ancestors of this reference's origin in order of decreasing
paul@16 203
        depth.
paul@16 204
        """
paul@16 205
paul@417 206
        origin = self.get_origin()
paul@417 207
        if not origin:
paul@16 208
            return None
paul@16 209
paul@417 210
        parts = origin.split(".")
paul@16 211
        ancestors = []
paul@16 212
paul@16 213
        for i in range(len(parts) - 1, 0, -1):
paul@16 214
            ancestors.append(".".join(parts[:i]))
paul@16 215
paul@16 216
        return ancestors
paul@16 217
paul@338 218
    def is_constant_alias(self):
paul@338 219
paul@338 220
        "Return whether this reference is an alias for a constant."
paul@338 221
paul@338 222
        name = self.get_name()
paul@338 223
        return name and name.rsplit(".")[-1].startswith("$c")
paul@338 224
paul@979 225
    def is_well_defined_instance(self):
paul@979 226
paul@979 227
        "Return whether this reference involves a well-defined instance."
paul@979 228
paul@979 229
        return self.get_kind() == "<instance>"
paul@979 230
paul@428 231
    def is_predefined_value(self):
paul@428 232
paul@428 233
        "Return whether this reference identifies a predefined value."
paul@428 234
paul@428 235
        # NOTE: Details of built-in types employed.
paul@428 236
paul@428 237
        return self.get_origin() in ("__builtins__.none.NoneType", "__builtins__.boolean.boolean")
paul@428 238
paul@57 239
    def get_types(self):
paul@57 240
paul@57 241
        "Return class, instance-only and module types for this reference."
paul@57 242
paul@57 243
        class_types = self.has_kind("<class>") and [self.get_origin()] or []
paul@57 244
        instance_types = []
paul@57 245
        module_types = self.has_kind("<module>") and [self.get_origin()] or []
paul@57 246
        return class_types, instance_types, module_types
paul@57 247
paul@0 248
def decode_reference(s, name=None):
paul@0 249
paul@0 250
    "Decode 's', making a reference."
paul@0 251
paul@0 252
    if isinstance(s, Reference):
paul@0 253
        return s.alias(name)
paul@0 254
paul@0 255
    # Null value.
paul@0 256
paul@0 257
    elif not s:
paul@0 258
        return Reference("<var>", None, name)
paul@0 259
paul@0 260
    # Kind and origin.
paul@0 261
paul@0 262
    elif ":" in s:
paul@0 263
        kind, origin = s.split(":")
paul@203 264
        if ";" in origin:
paul@203 265
            origin, name = origin.split(";")
paul@0 266
        return Reference(kind, origin, name)
paul@0 267
paul@249 268
    # Kind and name.
paul@249 269
paul@249 270
    elif ";" in s:
paul@249 271
        kind, name = s.split(";")
paul@249 272
        return Reference(kind, None, name)
paul@249 273
paul@0 274
    # Kind-only, origin is indicated name.
paul@0 275
paul@0 276
    elif s[0] == "<":
paul@0 277
        return Reference(s, name, name)
paul@0 278
paul@0 279
    # Module-only.
paul@0 280
paul@0 281
    else:
paul@0 282
        return Reference("<module>", s, name)
paul@0 283
paul@57 284
paul@57 285
paul@57 286
# Type/reference collection functions.
paul@57 287
paul@57 288
def is_single_class_type(all_types):
paul@57 289
paul@57 290
    """
paul@57 291
    Return whether 'all_types' is a mixture of class and instance kinds for
paul@57 292
    a single class type.
paul@57 293
    """
paul@57 294
paul@57 295
    kinds = set()
paul@57 296
    types = set()
paul@57 297
paul@57 298
    for type in all_types:
paul@57 299
        kinds.add(type.get_kind())
paul@57 300
        types.add(type.get_origin())
paul@57 301
paul@57 302
    return len(types) == 1 and kinds == set(["<class>", "<instance>"])
paul@57 303
paul@57 304
def combine_types(class_types, instance_types, module_types):
paul@57 305
paul@57 306
    """
paul@57 307
    Combine 'class_types', 'instance_types', 'module_types' into a single
paul@57 308
    list of references.
paul@57 309
    """
paul@57 310
paul@57 311
    all_types = []
paul@57 312
    for kind, l in [("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types)]:
paul@57 313
        for t in l:
paul@57 314
            all_types.append(Reference(kind, t))
paul@57 315
    return all_types
paul@57 316
paul@57 317
def separate_types(refs):
paul@57 318
paul@57 319
    """
paul@57 320
    Separate 'refs' into type-specific lists, returning a tuple containing
paul@57 321
    lists of class types, instance types, module types, function types and
paul@57 322
    unknown "var" types.
paul@57 323
    """
paul@57 324
paul@57 325
    class_types = []
paul@57 326
    instance_types = []
paul@57 327
    module_types = []
paul@57 328
    function_types = []
paul@57 329
    var_types = []
paul@57 330
paul@57 331
    for kind, l in [
paul@57 332
        ("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types),
paul@57 333
        ("<function>", function_types), ("<var>", var_types)
paul@57 334
        ]:
paul@57 335
paul@57 336
        for ref in refs:
paul@57 337
            if ref.get_kind() == kind:
paul@57 338
                l.append(ref.get_origin())
paul@57 339
paul@57 340
    return class_types, instance_types, module_types, function_types, var_types
paul@57 341
paul@0 342
# vim: tabstop=4 expandtab shiftwidth=4