Lichen

Annotated referencing.py

583:aed28d04304d
2017-02-13 Paul Boddie Re-added size information to string instances as the __size__ attribute. This fixes problems introduced when using strlen on data likely to contain embedded nulls, which was the reason for having size information explicitly stored in the first place. attr-strvalue-without-size
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Reference abstractions.
paul@0 5
paul@543 6
Copyright (C) 2016, 2017 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@428 225
    def is_predefined_value(self):
paul@428 226
paul@428 227
        "Return whether this reference identifies a predefined value."
paul@428 228
paul@428 229
        # NOTE: Details of built-in types employed.
paul@428 230
paul@428 231
        return self.get_origin() in ("__builtins__.none.NoneType", "__builtins__.boolean.boolean")
paul@428 232
paul@57 233
    def get_types(self):
paul@57 234
paul@57 235
        "Return class, instance-only and module types for this reference."
paul@57 236
paul@57 237
        class_types = self.has_kind("<class>") and [self.get_origin()] or []
paul@57 238
        instance_types = []
paul@57 239
        module_types = self.has_kind("<module>") and [self.get_origin()] or []
paul@57 240
        return class_types, instance_types, module_types
paul@57 241
paul@0 242
def decode_reference(s, name=None):
paul@0 243
paul@0 244
    "Decode 's', making a reference."
paul@0 245
paul@0 246
    if isinstance(s, Reference):
paul@0 247
        return s.alias(name)
paul@0 248
paul@0 249
    # Null value.
paul@0 250
paul@0 251
    elif not s:
paul@0 252
        return Reference("<var>", None, name)
paul@0 253
paul@0 254
    # Kind and origin.
paul@0 255
paul@0 256
    elif ":" in s:
paul@0 257
        kind, origin = s.split(":")
paul@203 258
        if ";" in origin:
paul@203 259
            origin, name = origin.split(";")
paul@0 260
        return Reference(kind, origin, name)
paul@0 261
paul@249 262
    # Kind and name.
paul@249 263
paul@249 264
    elif ";" in s:
paul@249 265
        kind, name = s.split(";")
paul@249 266
        return Reference(kind, None, name)
paul@249 267
paul@0 268
    # Kind-only, origin is indicated name.
paul@0 269
paul@0 270
    elif s[0] == "<":
paul@0 271
        return Reference(s, name, name)
paul@0 272
paul@0 273
    # Module-only.
paul@0 274
paul@0 275
    else:
paul@0 276
        return Reference("<module>", s, name)
paul@0 277
paul@57 278
paul@57 279
paul@57 280
# Type/reference collection functions.
paul@57 281
paul@57 282
def is_single_class_type(all_types):
paul@57 283
paul@57 284
    """
paul@57 285
    Return whether 'all_types' is a mixture of class and instance kinds for
paul@57 286
    a single class type.
paul@57 287
    """
paul@57 288
paul@57 289
    kinds = set()
paul@57 290
    types = set()
paul@57 291
paul@57 292
    for type in all_types:
paul@57 293
        kinds.add(type.get_kind())
paul@57 294
        types.add(type.get_origin())
paul@57 295
paul@57 296
    return len(types) == 1 and kinds == set(["<class>", "<instance>"])
paul@57 297
paul@57 298
def combine_types(class_types, instance_types, module_types):
paul@57 299
paul@57 300
    """
paul@57 301
    Combine 'class_types', 'instance_types', 'module_types' into a single
paul@57 302
    list of references.
paul@57 303
    """
paul@57 304
paul@57 305
    all_types = []
paul@57 306
    for kind, l in [("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types)]:
paul@57 307
        for t in l:
paul@57 308
            all_types.append(Reference(kind, t))
paul@57 309
    return all_types
paul@57 310
paul@57 311
def separate_types(refs):
paul@57 312
paul@57 313
    """
paul@57 314
    Separate 'refs' into type-specific lists, returning a tuple containing
paul@57 315
    lists of class types, instance types, module types, function types and
paul@57 316
    unknown "var" types.
paul@57 317
    """
paul@57 318
paul@57 319
    class_types = []
paul@57 320
    instance_types = []
paul@57 321
    module_types = []
paul@57 322
    function_types = []
paul@57 323
    var_types = []
paul@57 324
paul@57 325
    for kind, l in [
paul@57 326
        ("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types),
paul@57 327
        ("<function>", function_types), ("<var>", var_types)
paul@57 328
        ]:
paul@57 329
paul@57 330
        for ref in refs:
paul@57 331
            if ref.get_kind() == kind:
paul@57 332
                l.append(ref.get_origin())
paul@57 333
paul@57 334
    return class_types, instance_types, module_types, function_types, var_types
paul@57 335
paul@0 336
# vim: tabstop=4 expandtab shiftwidth=4