Lichen

Annotated resolving.py

95:a0f513d3a7b1
2016-10-13 Paul Boddie Fixed instruction plan test operations, optimised the initial accessor to avoid redundant assignments and to use the context where appropriate, introduced accessor and attribute name parameterisation in the generated instructions, introduced a generic expression placeholder in place of any local name.
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@12 22
from common import init_item, predefined_constants
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
        self.remove_redundant_accessors()
paul@12 45
paul@12 46
    def resolve_class_bases(self):
paul@12 47
paul@12 48
        "Resolve all class bases since some of them may have been deferred."
paul@12 49
paul@12 50
        for name, bases in self.classes.items():
paul@12 51
            resolved = []
paul@12 52
            bad = []
paul@12 53
paul@12 54
            for base in bases:
paul@27 55
                ref = self.importer.identify(base.get_origin())
paul@12 56
paul@12 57
                # Obtain the origin of the base class reference.
paul@12 58
paul@12 59
                if not ref or not ref.has_kind("<class>"):
paul@12 60
                    bad.append(base)
paul@12 61
                    break
paul@12 62
paul@12 63
                resolved.append(ref)
paul@12 64
paul@12 65
            if bad:
paul@72 66
                print >>sys.stderr, "Bases of class %s were not classes: %s" % (name, ", ".join(map(str, bad)))
paul@12 67
            else:
paul@12 68
                self.importer.classes[name] = self.classes[name] = resolved
paul@12 69
paul@12 70
    def check_special(self):
paul@12 71
paul@12 72
        "Check special names."
paul@12 73
paul@12 74
        for name, value in self.special.items():
paul@28 75
            self.special[name] = self.importer.identify(value.get_origin())
paul@12 76
paul@12 77
    def check_names_used(self):
paul@12 78
paul@27 79
        "Check the external names used by each scope."
paul@12 80
paul@27 81
        for key, ref in self.name_references.items():
paul@27 82
            path, name = key.rsplit(".", 1)
paul@27 83
            self.resolve_accesses(path, name, ref)
paul@12 84
paul@27 85
    def check_invocations(self):
paul@12 86
paul@27 87
        "Find invocations amongst module data and replace their results."
paul@12 88
paul@27 89
        # Find members and replace invocation results with values. This is
paul@27 90
        # effectively the same as is done for initialised names, but refers to
paul@27 91
        # any unchanging value after initialisation.
paul@12 92
paul@27 93
        for key, ref in self.objects.items():
paul@27 94
            if ref.has_kind("<invoke>"):
paul@27 95
                ref = self.convert_invocation(ref)
paul@27 96
                self.importer.objects[key] = self.objects[key] = ref
paul@12 97
paul@31 98
        # Convert function defaults, which are effectively extra members of the
paul@31 99
        # module, and function locals.
paul@12 100
paul@31 101
        for fname, parameters in self.function_defaults.items():
paul@27 102
            l = []
paul@27 103
            for pname, ref in parameters:
paul@27 104
                if ref.has_kind("<invoke>"):
paul@27 105
                    ref = self.convert_invocation(ref)
paul@27 106
                l.append((pname, ref))
paul@27 107
            self.function_defaults[fname] = l
paul@12 108
paul@31 109
        # Convert function locals referencing invocations.
paul@31 110
paul@31 111
        for fname, names in self.function_locals.items():
paul@31 112
            for name, ref in names.items():
paul@31 113
                if ref.has_kind("<invoke>"):
paul@31 114
                    ref = self.convert_invocation(ref)
paul@31 115
                    names[name] = ref
paul@31 116
paul@27 117
    def convert_invocation(self, ref):
paul@27 118
paul@27 119
        "Convert the given invocation 'ref', handling instantiation."
paul@27 120
paul@27 121
        ref = self.importer.identify(ref.get_origin())
paul@27 122
        return ref and ref.has_kind("<class>") and ref.instance_of() or Reference("<var>")
paul@12 123
paul@12 124
    def resolve_accesses(self, path, name, ref):
paul@12 125
paul@12 126
        """
paul@12 127
        Resolve any unresolved accesses in the function at the given 'path'
paul@12 128
        for the given 'name' corresponding to the indicated 'ref'. Note that
paul@12 129
        this mechanism cannot resolve things like inherited methods because
paul@12 130
        they are not recorded as program objects in their inherited locations.
paul@12 131
        """
paul@12 132
paul@12 133
        attr_accesses = self.global_attr_accesses.get(path)
paul@12 134
        all_attrnames = attr_accesses and attr_accesses.get(name)
paul@12 135
paul@12 136
        if not all_attrnames:
paul@12 137
            return
paul@12 138
paul@12 139
        # Insist on constant accessors.
paul@12 140
paul@12 141
        if not ref.has_kind(["<class>", "<module>"]):
paul@12 142
            return
paul@12 143
paul@12 144
        found_attrnames = set()
paul@12 145
paul@12 146
        for attrnames in all_attrnames:
paul@12 147
paul@12 148
            # Start with the resolved name, adding attributes.
paul@12 149
paul@12 150
            attrs = ref.get_path()
paul@12 151
            remaining = attrnames.split(".")
paul@12 152
            last_ref = ref
paul@12 153
paul@12 154
            # Add each component, testing for a constant object.
paul@12 155
paul@12 156
            while remaining:
paul@12 157
                attrname = remaining[0]
paul@12 158
                attrs.append(attrname)
paul@12 159
                del remaining[0]
paul@12 160
paul@12 161
                # Find any constant object reference.
paul@12 162
paul@12 163
                attr_ref = self.get_resolved_object(".".join(attrs))
paul@12 164
paul@12 165
                # Non-constant accessors terminate the traversal.
paul@12 166
paul@12 167
                if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]):
paul@12 168
paul@12 169
                    # Provide the furthest constant accessor unless the final
paul@12 170
                    # access can be resolved.
paul@12 171
paul@12 172
                    if remaining:
paul@12 173
                        remaining.insert(0, attrs.pop())
paul@12 174
                    else:
paul@12 175
                        last_ref = attr_ref
paul@12 176
                    break
paul@12 177
paul@12 178
                # Follow any reference to a constant object.
paul@12 179
                # Where the given name refers to an object in another location,
paul@12 180
                # switch to the other location in order to be able to test its
paul@12 181
                # attributes.
paul@12 182
paul@12 183
                last_ref = attr_ref
paul@12 184
                attrs = attr_ref.get_path()
paul@12 185
paul@12 186
            # Record a constant accessor only if an object was found
paul@12 187
            # that is different from the namespace involved.
paul@12 188
paul@12 189
            if last_ref:
paul@12 190
                objpath = ".".join(attrs)
paul@12 191
                if objpath != path:
paul@12 192
paul@27 193
                    if last_ref.has_kind("<invoke>"):
paul@27 194
                        last_ref = self.convert_invocation(last_ref)
paul@27 195
paul@12 196
                    # Establish a constant access.
paul@12 197
paul@12 198
                    init_item(self.const_accesses, path, dict)
paul@12 199
                    self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining))
paul@12 200
paul@12 201
                    if len(attrs) > 1:
paul@12 202
                        found_attrnames.add(attrs[1])
paul@12 203
paul@12 204
        # Remove any usage records for the name.
paul@12 205
paul@12 206
        if found_attrnames:
paul@12 207
paul@12 208
            # NOTE: Should be only one name version.
paul@12 209
paul@12 210
            versions = []
paul@12 211
            for version in self.attr_usage[path][name]:
paul@12 212
                new_usage = set()
paul@12 213
                for usage in version:
paul@12 214
                    if found_attrnames.intersection(usage):
paul@12 215
                        new_usage.add(tuple(set(usage).difference(found_attrnames)))
paul@12 216
                    else:
paul@12 217
                        new_usage.add(usage)
paul@12 218
                versions.append(new_usage)
paul@12 219
paul@12 220
            self.attr_usage[path][name] = versions
paul@12 221
paul@12 222
    def resolve_initialisers(self):
paul@12 223
paul@12 224
        "Resolve initialiser values for names."
paul@12 225
paul@12 226
        # Get the initialisers in each namespace.
paul@12 227
paul@12 228
        for path, name_initialisers in self.name_initialisers.items():
paul@12 229
            const_accesses = self.const_accesses.get(path)
paul@12 230
paul@12 231
            # Resolve values for each name in a scope.
paul@12 232
paul@12 233
            for name, values in name_initialisers.items():
paul@12 234
                if path == self.name:
paul@12 235
                    assigned_path = name
paul@12 236
                else:
paul@12 237
                    assigned_path = "%s.%s" % (path, name)
paul@12 238
paul@12 239
                initialised_names = {}
paul@12 240
                aliased_names = {}
paul@12 241
paul@12 242
                for i, name_ref in enumerate(values):
paul@12 243
paul@12 244
                    # Unwrap invocations.
paul@12 245
paul@12 246
                    if isinstance(name_ref, InvocationRef):
paul@12 247
                        invocation = True
paul@12 248
                        name_ref = name_ref.name_ref
paul@12 249
                    else:
paul@12 250
                        invocation = False
paul@12 251
paul@12 252
                    # Obtain a usable reference from names or constants.
paul@12 253
paul@12 254
                    if isinstance(name_ref, ResolvedNameRef):
paul@12 255
                        if not name_ref.reference():
paul@12 256
                            continue
paul@12 257
                        ref = name_ref.reference()
paul@12 258
paul@12 259
                    # Obtain a reference from instances.
paul@12 260
paul@12 261
                    elif isinstance(name_ref, InstanceRef):
paul@12 262
                        if not name_ref.reference():
paul@12 263
                            continue
paul@12 264
                        ref = name_ref.reference()
paul@12 265
paul@12 266
                    # Resolve accesses that employ constants.
paul@12 267
paul@12 268
                    elif isinstance(name_ref, AccessRef):
paul@12 269
                        ref = None
paul@12 270
paul@12 271
                        if const_accesses:
paul@12 272
                            resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
paul@12 273
                            if resolved_access:
paul@12 274
                                objpath, ref, remaining_attrnames = resolved_access
paul@12 275
                                if remaining_attrnames:
paul@12 276
                                    ref = None
paul@12 277
paul@12 278
                        # Accesses that do not employ constants cannot be resolved,
paul@12 279
                        # but they may be resolvable later.
paul@12 280
paul@12 281
                        if not ref:
paul@12 282
                            if not invocation:
paul@12 283
                                aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number
paul@12 284
                            continue
paul@12 285
paul@12 286
                    # Attempt to resolve a plain name reference.
paul@12 287
paul@12 288
                    elif isinstance(name_ref, LocalNameRef):
paul@12 289
                        key = "%s.%s" % (path, name_ref.name)
paul@12 290
                        origin = self.name_references.get(key)
paul@12 291
paul@12 292
                        # Accesses that do not refer to known static objects
paul@12 293
                        # cannot be resolved, but they may be resolvable later.
paul@12 294
paul@12 295
                        if not origin:
paul@12 296
                            if not invocation:
paul@12 297
                                aliased_names[i] = name_ref.name, None, name_ref.number
paul@12 298
                            continue
paul@12 299
paul@12 300
                        ref = self.get_resolved_object(origin)
paul@12 301
                        if not ref:
paul@12 302
                            continue
paul@12 303
paul@12 304
                    elif isinstance(name_ref, NameRef):
paul@12 305
                        key = "%s.%s" % (path, name_ref.name)
paul@12 306
                        origin = self.name_references.get(key)
paul@12 307
                        if not origin:
paul@12 308
                            continue
paul@12 309
paul@12 310
                        ref = self.get_resolved_object(origin)
paul@12 311
                        if not ref:
paul@12 312
                            continue
paul@12 313
paul@12 314
                    else:
paul@12 315
                        continue
paul@12 316
paul@22 317
                    # Resolve any hidden dependencies involving external objects
paul@22 318
                    # or unresolved names referring to globals or built-ins.
paul@22 319
paul@22 320
                    if ref.has_kind("<depends>"):
paul@27 321
                        ref = self.importer.identify(ref.get_origin())
paul@22 322
paul@12 323
                    # Convert class invocations to instances.
paul@12 324
paul@58 325
                    if ref and invocation:
paul@27 326
                        ref = self.convert_invocation(ref)
paul@12 327
paul@27 328
                    if ref and not ref.has_kind("<var>"):
paul@12 329
                        initialised_names[i] = ref
paul@12 330
paul@12 331
                if initialised_names:
paul@12 332
                    self.initialised_names[assigned_path] = initialised_names
paul@12 333
                if aliased_names:
paul@12 334
                    self.aliased_names[assigned_path] = aliased_names
paul@12 335
paul@12 336
    def resolve_literals(self):
paul@12 337
paul@12 338
        "Resolve constant value types."
paul@12 339
paul@12 340
        # Get the constants defined in each namespace.
paul@12 341
paul@12 342
        for path, constants in self.constants.items():
paul@12 343
            for constant, n in constants.items():
paul@12 344
                objpath = "%s.$c%d" % (path, n)
paul@12 345
                _constant, value_type = self.constant_values[objpath]
paul@12 346
                self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
paul@12 347
paul@12 348
        # Get the literals defined in each namespace.
paul@12 349
paul@12 350
        for path, literals in self.literals.items():
paul@12 351
            for n in range(0, literals):
paul@12 352
                objpath = "%s.$C%d" % (path, n)
paul@12 353
                value_type = self.literal_types[objpath]
paul@12 354
                self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
paul@12 355
paul@12 356
    def remove_redundant_accessors(self):
paul@12 357
paul@12 358
        "Remove now-redundant modifier and accessor information."
paul@12 359
paul@12 360
        for path, const_accesses in self.const_accesses.items():
paul@12 361
            accesses = self.attr_accessors.get(path)
paul@12 362
            modifiers = self.attr_access_modifiers.get(path)
paul@12 363
            if not accesses:
paul@12 364
                continue
paul@12 365
            for access in const_accesses.keys():
paul@12 366
                if accesses.has_key(access):
paul@12 367
                    del accesses[access]
paul@12 368
                if modifiers and modifiers.has_key(access):
paul@12 369
                    del modifiers[access]
paul@12 370
paul@29 371
    # Object resolution.
paul@29 372
paul@29 373
    def get_resolved_object(self, path):
paul@29 374
paul@29 375
        """
paul@29 376
        Get the details of an object with the given 'path' within this module.
paul@29 377
        Where the object has not been resolved, None is returned. This differs
paul@29 378
        from the get_object method used elsewhere in that it does not return an
paul@29 379
        unresolved object reference.
paul@29 380
        """
paul@29 381
paul@29 382
        if self.objects.has_key(path):
paul@29 383
            ref = self.objects[path]
paul@29 384
            if ref.has_kind("<depends>"):
paul@29 385
                return None
paul@29 386
            else:
paul@29 387
                return ref
paul@29 388
        else:
paul@29 389
            return None
paul@29 390
paul@12 391
# vim: tabstop=4 expandtab shiftwidth=4