Lichen

Annotated resolving.py

343:e0879c83a439
2016-12-07 Paul Boddie Added support for reading to the end of a stream's input, fixing EOFError raising in fread by returning shorter amounts of data when EOF occurs, only raising an exception if no data was read before EOF occurred. Made the test input longer to exercise tests of reading remaining data.
paul@12 1
#!/usr/bin/env python
paul@12 2
paul@12 3
"""
paul@12 4
Name resolution.
paul@12 5
paul@12 6
Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk>
paul@12 7
paul@12 8
This program is free software; you can redistribute it and/or modify it under
paul@12 9
the terms of the GNU General Public License as published by the Free Software
paul@12 10
Foundation; either version 3 of the License, or (at your option) any later
paul@12 11
version.
paul@12 12
paul@12 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@12 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@12 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@12 16
details.
paul@12 17
paul@12 18
You should have received a copy of the GNU General Public License along with
paul@12 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@12 20
"""
paul@12 21
paul@135 22
from common import init_item
paul@12 23
from results import AccessRef, InstanceRef, InvocationRef, LocalNameRef, \
paul@12 24
                    NameRef, ResolvedNameRef
paul@12 25
from referencing import Reference
paul@12 26
import sys
paul@12 27
paul@12 28
class NameResolving:
paul@12 29
paul@12 30
    "Resolving names mix-in for inspected modules."
paul@12 31
paul@12 32
    # Post-inspection resolution activities.
paul@12 33
paul@12 34
    def resolve(self):
paul@12 35
paul@12 36
        "Resolve dependencies and complete definitions."
paul@12 37
paul@12 38
        self.resolve_class_bases()
paul@21 39
        self.check_special()
paul@12 40
        self.check_names_used()
paul@27 41
        self.check_invocations()
paul@12 42
        self.resolve_initialisers()
paul@12 43
        self.resolve_literals()
paul@12 44
paul@12 45
    def resolve_class_bases(self):
paul@12 46
paul@12 47
        "Resolve all class bases since some of them may have been deferred."
paul@12 48
paul@12 49
        for name, bases in self.classes.items():
paul@12 50
            resolved = []
paul@12 51
            bad = []
paul@12 52
paul@12 53
            for base in bases:
paul@27 54
                ref = self.importer.identify(base.get_origin())
paul@12 55
paul@12 56
                # Obtain the origin of the base class reference.
paul@12 57
paul@12 58
                if not ref or not ref.has_kind("<class>"):
paul@12 59
                    bad.append(base)
paul@12 60
                    break
paul@12 61
paul@12 62
                resolved.append(ref)
paul@12 63
paul@12 64
            if bad:
paul@72 65
                print >>sys.stderr, "Bases of class %s were not classes: %s" % (name, ", ".join(map(str, bad)))
paul@12 66
            else:
paul@12 67
                self.importer.classes[name] = self.classes[name] = resolved
paul@12 68
paul@12 69
    def check_special(self):
paul@12 70
paul@12 71
        "Check special names."
paul@12 72
paul@12 73
        for name, value in self.special.items():
paul@28 74
            self.special[name] = self.importer.identify(value.get_origin())
paul@12 75
paul@12 76
    def check_names_used(self):
paul@12 77
paul@27 78
        "Check the external names used by each scope."
paul@12 79
paul@27 80
        for key, ref in self.name_references.items():
paul@27 81
            path, name = key.rsplit(".", 1)
paul@27 82
            self.resolve_accesses(path, name, ref)
paul@12 83
paul@27 84
    def check_invocations(self):
paul@12 85
paul@27 86
        "Find invocations amongst module data and replace their results."
paul@12 87
paul@27 88
        # Find members and replace invocation results with values. This is
paul@27 89
        # effectively the same as is done for initialised names, but refers to
paul@27 90
        # any unchanging value after initialisation.
paul@12 91
paul@27 92
        for key, ref in self.objects.items():
paul@27 93
            if ref.has_kind("<invoke>"):
paul@27 94
                ref = self.convert_invocation(ref)
paul@27 95
                self.importer.objects[key] = self.objects[key] = ref
paul@12 96
paul@169 97
        # Convert name references.
paul@169 98
paul@169 99
        for key, ref in self.name_references.items():
paul@169 100
            if ref.has_kind("<invoke>"):
paul@169 101
                ref = self.convert_invocation(ref)
paul@169 102
                self.importer.all_name_references[key] = self.name_references[key] = ref
paul@169 103
paul@31 104
        # Convert function defaults, which are effectively extra members of the
paul@31 105
        # module, and function locals.
paul@12 106
paul@31 107
        for fname, parameters in self.function_defaults.items():
paul@27 108
            l = []
paul@27 109
            for pname, ref in parameters:
paul@27 110
                if ref.has_kind("<invoke>"):
paul@27 111
                    ref = self.convert_invocation(ref)
paul@27 112
                l.append((pname, ref))
paul@129 113
            self.importer.function_defaults[fname] = self.function_defaults[fname] = l
paul@12 114
paul@31 115
        # Convert function locals referencing invocations.
paul@31 116
paul@31 117
        for fname, names in self.function_locals.items():
paul@31 118
            for name, ref in names.items():
paul@31 119
                if ref.has_kind("<invoke>"):
paul@31 120
                    ref = self.convert_invocation(ref)
paul@31 121
                    names[name] = ref
paul@31 122
paul@27 123
    def convert_invocation(self, ref):
paul@27 124
paul@27 125
        "Convert the given invocation 'ref', handling instantiation."
paul@27 126
paul@175 127
        alias = ref.get_name()
paul@27 128
        ref = self.importer.identify(ref.get_origin())
paul@175 129
        ref = ref and ref.has_kind("<class>") and ref.instance_of() or Reference("<var>")
paul@175 130
        return ref and ref.alias(alias) or None
paul@12 131
paul@12 132
    def resolve_accesses(self, path, name, ref):
paul@12 133
paul@12 134
        """
paul@12 135
        Resolve any unresolved accesses in the function at the given 'path'
paul@12 136
        for the given 'name' corresponding to the indicated 'ref'. Note that
paul@12 137
        this mechanism cannot resolve things like inherited methods because
paul@12 138
        they are not recorded as program objects in their inherited locations.
paul@12 139
        """
paul@12 140
paul@12 141
        attr_accesses = self.global_attr_accesses.get(path)
paul@12 142
        all_attrnames = attr_accesses and attr_accesses.get(name)
paul@12 143
paul@12 144
        if not all_attrnames:
paul@12 145
            return
paul@12 146
paul@12 147
        # Insist on constant accessors.
paul@12 148
paul@12 149
        if not ref.has_kind(["<class>", "<module>"]):
paul@12 150
            return
paul@12 151
paul@12 152
        found_attrnames = set()
paul@12 153
paul@12 154
        for attrnames in all_attrnames:
paul@12 155
paul@12 156
            # Start with the resolved name, adding attributes.
paul@12 157
paul@12 158
            attrs = ref.get_path()
paul@12 159
            remaining = attrnames.split(".")
paul@12 160
            last_ref = ref
paul@12 161
paul@12 162
            # Add each component, testing for a constant object.
paul@12 163
paul@12 164
            while remaining:
paul@12 165
                attrname = remaining[0]
paul@12 166
                attrs.append(attrname)
paul@12 167
                del remaining[0]
paul@12 168
paul@12 169
                # Find any constant object reference.
paul@12 170
paul@12 171
                attr_ref = self.get_resolved_object(".".join(attrs))
paul@12 172
paul@12 173
                # Non-constant accessors terminate the traversal.
paul@12 174
paul@12 175
                if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]):
paul@12 176
paul@12 177
                    # Provide the furthest constant accessor unless the final
paul@12 178
                    # access can be resolved.
paul@12 179
paul@12 180
                    if remaining:
paul@12 181
                        remaining.insert(0, attrs.pop())
paul@12 182
                    else:
paul@12 183
                        last_ref = attr_ref
paul@12 184
                    break
paul@12 185
paul@12 186
                # Follow any reference to a constant object.
paul@12 187
                # Where the given name refers to an object in another location,
paul@12 188
                # switch to the other location in order to be able to test its
paul@12 189
                # attributes.
paul@12 190
paul@12 191
                last_ref = attr_ref
paul@12 192
                attrs = attr_ref.get_path()
paul@12 193
paul@12 194
            # Record a constant accessor only if an object was found
paul@12 195
            # that is different from the namespace involved.
paul@12 196
paul@12 197
            if last_ref:
paul@12 198
                objpath = ".".join(attrs)
paul@12 199
                if objpath != path:
paul@12 200
paul@27 201
                    if last_ref.has_kind("<invoke>"):
paul@27 202
                        last_ref = self.convert_invocation(last_ref)
paul@27 203
paul@12 204
                    # Establish a constant access.
paul@12 205
paul@12 206
                    init_item(self.const_accesses, path, dict)
paul@12 207
                    self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining))
paul@12 208
paul@12 209
                    if len(attrs) > 1:
paul@12 210
                        found_attrnames.add(attrs[1])
paul@12 211
paul@12 212
        # Remove any usage records for the name.
paul@12 213
paul@12 214
        if found_attrnames:
paul@12 215
paul@12 216
            # NOTE: Should be only one name version.
paul@12 217
paul@12 218
            versions = []
paul@12 219
            for version in self.attr_usage[path][name]:
paul@12 220
                new_usage = set()
paul@12 221
                for usage in version:
paul@12 222
                    if found_attrnames.intersection(usage):
paul@12 223
                        new_usage.add(tuple(set(usage).difference(found_attrnames)))
paul@12 224
                    else:
paul@12 225
                        new_usage.add(usage)
paul@12 226
                versions.append(new_usage)
paul@12 227
paul@12 228
            self.attr_usage[path][name] = versions
paul@12 229
paul@12 230
    def resolve_initialisers(self):
paul@12 231
paul@12 232
        "Resolve initialiser values for names."
paul@12 233
paul@12 234
        # Get the initialisers in each namespace.
paul@12 235
paul@12 236
        for path, name_initialisers in self.name_initialisers.items():
paul@12 237
            const_accesses = self.const_accesses.get(path)
paul@12 238
paul@12 239
            # Resolve values for each name in a scope.
paul@12 240
paul@12 241
            for name, values in name_initialisers.items():
paul@12 242
                if path == self.name:
paul@12 243
                    assigned_path = name
paul@12 244
                else:
paul@12 245
                    assigned_path = "%s.%s" % (path, name)
paul@12 246
paul@12 247
                initialised_names = {}
paul@12 248
                aliased_names = {}
paul@12 249
paul@12 250
                for i, name_ref in enumerate(values):
paul@12 251
paul@12 252
                    # Unwrap invocations.
paul@12 253
paul@12 254
                    if isinstance(name_ref, InvocationRef):
paul@12 255
                        invocation = True
paul@12 256
                        name_ref = name_ref.name_ref
paul@12 257
                    else:
paul@12 258
                        invocation = False
paul@12 259
paul@12 260
                    # Obtain a usable reference from names or constants.
paul@12 261
paul@12 262
                    if isinstance(name_ref, ResolvedNameRef):
paul@12 263
                        if not name_ref.reference():
paul@12 264
                            continue
paul@12 265
                        ref = name_ref.reference()
paul@12 266
paul@12 267
                    # Obtain a reference from instances.
paul@12 268
paul@12 269
                    elif isinstance(name_ref, InstanceRef):
paul@12 270
                        if not name_ref.reference():
paul@12 271
                            continue
paul@12 272
                        ref = name_ref.reference()
paul@12 273
paul@12 274
                    # Resolve accesses that employ constants.
paul@12 275
paul@12 276
                    elif isinstance(name_ref, AccessRef):
paul@12 277
                        ref = None
paul@12 278
paul@12 279
                        if const_accesses:
paul@12 280
                            resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
paul@12 281
                            if resolved_access:
paul@12 282
                                objpath, ref, remaining_attrnames = resolved_access
paul@12 283
                                if remaining_attrnames:
paul@12 284
                                    ref = None
paul@12 285
paul@12 286
                        # Accesses that do not employ constants cannot be resolved,
paul@12 287
                        # but they may be resolvable later.
paul@12 288
paul@12 289
                        if not ref:
paul@12 290
                            if not invocation:
paul@12 291
                                aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number
paul@12 292
                            continue
paul@12 293
paul@12 294
                    # Attempt to resolve a plain name reference.
paul@12 295
paul@12 296
                    elif isinstance(name_ref, LocalNameRef):
paul@12 297
                        key = "%s.%s" % (path, name_ref.name)
paul@12 298
                        origin = self.name_references.get(key)
paul@12 299
paul@12 300
                        # Accesses that do not refer to known static objects
paul@12 301
                        # cannot be resolved, but they may be resolvable later.
paul@12 302
paul@12 303
                        if not origin:
paul@12 304
                            if not invocation:
paul@12 305
                                aliased_names[i] = name_ref.name, None, name_ref.number
paul@12 306
                            continue
paul@12 307
paul@12 308
                        ref = self.get_resolved_object(origin)
paul@12 309
                        if not ref:
paul@12 310
                            continue
paul@12 311
paul@12 312
                    elif isinstance(name_ref, NameRef):
paul@12 313
                        key = "%s.%s" % (path, name_ref.name)
paul@12 314
                        origin = self.name_references.get(key)
paul@12 315
                        if not origin:
paul@12 316
                            continue
paul@12 317
paul@12 318
                        ref = self.get_resolved_object(origin)
paul@12 319
                        if not ref:
paul@12 320
                            continue
paul@12 321
paul@12 322
                    else:
paul@12 323
                        continue
paul@12 324
paul@22 325
                    # Resolve any hidden dependencies involving external objects
paul@22 326
                    # or unresolved names referring to globals or built-ins.
paul@22 327
paul@22 328
                    if ref.has_kind("<depends>"):
paul@27 329
                        ref = self.importer.identify(ref.get_origin())
paul@22 330
paul@12 331
                    # Convert class invocations to instances.
paul@12 332
paul@58 333
                    if ref and invocation:
paul@27 334
                        ref = self.convert_invocation(ref)
paul@12 335
paul@27 336
                    if ref and not ref.has_kind("<var>"):
paul@12 337
                        initialised_names[i] = ref
paul@12 338
paul@12 339
                if initialised_names:
paul@12 340
                    self.initialised_names[assigned_path] = initialised_names
paul@12 341
                if aliased_names:
paul@12 342
                    self.aliased_names[assigned_path] = aliased_names
paul@12 343
paul@12 344
    def resolve_literals(self):
paul@12 345
paul@12 346
        "Resolve constant value types."
paul@12 347
paul@12 348
        # Get the constants defined in each namespace.
paul@12 349
paul@12 350
        for path, constants in self.constants.items():
paul@12 351
            for constant, n in constants.items():
paul@12 352
                objpath = "%s.$c%d" % (path, n)
paul@12 353
                _constant, value_type = self.constant_values[objpath]
paul@12 354
                self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
paul@12 355
paul@12 356
        # Get the literals defined in each namespace.
paul@12 357
paul@12 358
        for path, literals in self.literals.items():
paul@12 359
            for n in range(0, literals):
paul@12 360
                objpath = "%s.$C%d" % (path, n)
paul@12 361
                value_type = self.literal_types[objpath]
paul@12 362
                self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
paul@12 363
paul@29 364
    # Object resolution.
paul@29 365
paul@29 366
    def get_resolved_object(self, path):
paul@29 367
paul@29 368
        """
paul@29 369
        Get the details of an object with the given 'path' within this module.
paul@29 370
        Where the object has not been resolved, None is returned. This differs
paul@29 371
        from the get_object method used elsewhere in that it does not return an
paul@29 372
        unresolved object reference.
paul@29 373
        """
paul@29 374
paul@29 375
        if self.objects.has_key(path):
paul@29 376
            ref = self.objects[path]
paul@29 377
            if ref.has_kind("<depends>"):
paul@29 378
                return None
paul@29 379
            else:
paul@29 380
                return ref
paul@29 381
        else:
paul@29 382
            return None
paul@29 383
paul@12 384
# vim: tabstop=4 expandtab shiftwidth=4