1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/resolving.py Sat Sep 03 22:43:44 2016 +0200
1.3 @@ -0,0 +1,409 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Name resolution.
1.8 +
1.9 +Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This program is free software; you can redistribute it and/or modify it under
1.12 +the terms of the GNU General Public License as published by the Free Software
1.13 +Foundation; either version 3 of the License, or (at your option) any later
1.14 +version.
1.15 +
1.16 +This program is distributed in the hope that it will be useful, but WITHOUT
1.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 +details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along with
1.22 +this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +"""
1.24 +
1.25 +from common import init_item, predefined_constants
1.26 +from results import AccessRef, InstanceRef, InvocationRef, LocalNameRef, \
1.27 + NameRef, ResolvedNameRef
1.28 +from referencing import Reference
1.29 +import sys
1.30 +
1.31 +class NameResolving:
1.32 +
1.33 + "Resolving names mix-in for inspected modules."
1.34 +
1.35 + def __init__(self):
1.36 + self.name_references = {} # references to globals
1.37 +
1.38 + # Initialisation-related details.
1.39 +
1.40 + self.initialised_names = {}
1.41 + self.aliased_names = {}
1.42 +
1.43 + # Object resolution.
1.44 +
1.45 + def resolve_object(self, ref):
1.46 +
1.47 + """
1.48 + Return the given 'ref' in resolved form, given knowledge of the entire
1.49 + program.
1.50 + """
1.51 +
1.52 + if ref.has_kind("<depends>"):
1.53 + return self.importer.get_object(ref.get_origin())
1.54 + else:
1.55 + return ref
1.56 +
1.57 + def get_resolved_object(self, path):
1.58 +
1.59 + """
1.60 + Get the details of an object with the given 'path'. Where the object
1.61 + has not been resolved, None is returned. This differs from the
1.62 + get_object method used elsewhere in that it does not return an
1.63 + unresolved object reference.
1.64 + """
1.65 +
1.66 + if self.objects.has_key(path):
1.67 + ref = self.objects[path]
1.68 + if ref.has_kind("<depends>"):
1.69 + return None
1.70 + else:
1.71 + return ref
1.72 + else:
1.73 + return None
1.74 +
1.75 + # Post-inspection resolution activities.
1.76 +
1.77 + def resolve(self):
1.78 +
1.79 + "Resolve dependencies and complete definitions."
1.80 +
1.81 + self.resolve_members()
1.82 + self.resolve_class_bases()
1.83 + #self.check_special()
1.84 + self.check_names_used()
1.85 + self.resolve_initialisers()
1.86 + self.resolve_literals()
1.87 + self.remove_redundant_accessors()
1.88 +
1.89 + def resolve_members(self):
1.90 +
1.91 + "Resolve any members referring to deferred references."
1.92 +
1.93 + for name, ref in self.objects.items():
1.94 + if ref.has_kind("<depends>"):
1.95 + ref = self.importer.get_object(name)
1.96 + ref = ref.alias(name)
1.97 + self.importer.objects[name] = self.objects[name] = ref
1.98 +
1.99 + def resolve_class_bases(self):
1.100 +
1.101 + "Resolve all class bases since some of them may have been deferred."
1.102 +
1.103 + for name, bases in self.classes.items():
1.104 + resolved = []
1.105 + bad = []
1.106 +
1.107 + for base in bases:
1.108 + ref = self.resolve_object(base)
1.109 +
1.110 + # Obtain the origin of the base class reference.
1.111 +
1.112 + if not ref or not ref.has_kind("<class>"):
1.113 + bad.append(base)
1.114 + break
1.115 +
1.116 + resolved.append(ref)
1.117 +
1.118 + if bad:
1.119 + print >>sys.stderr, "Bases of class %s were not classes." % (name, ", ".join(map(str, bad)))
1.120 + else:
1.121 + self.importer.classes[name] = self.classes[name] = resolved
1.122 +
1.123 + def check_special(self):
1.124 +
1.125 + "Check special names."
1.126 +
1.127 + for name, value in self.special.items():
1.128 + # NOTE: Needs to handle Ref classes.
1.129 + self.special[name] = self.get_resolved_object(value.get_origin())
1.130 +
1.131 + def check_names_used(self):
1.132 +
1.133 + "Check the names used by each function."
1.134 +
1.135 + for path in self.names_used.keys():
1.136 + self.check_names_used_for_path(path)
1.137 +
1.138 + def check_names_used_for_path(self, path):
1.139 +
1.140 + "Check the names used by the given 'path'."
1.141 +
1.142 + names = self.names_used.get(path)
1.143 + if not names:
1.144 + return
1.145 +
1.146 + in_function = self.function_locals.has_key(path)
1.147 +
1.148 + for name in names:
1.149 + if name in predefined_constants or in_function and name in self.function_locals[path]:
1.150 + continue
1.151 +
1.152 + # Find local definitions (within static namespaces).
1.153 +
1.154 + key = "%s.%s" % (path, name)
1.155 + ref = self.get_resolved_object(key)
1.156 + if ref:
1.157 + self.name_references[key] = ref.final() or key
1.158 + self.resolve_accesses(path, name, ref)
1.159 + continue
1.160 +
1.161 + # Find global or built-in definitions.
1.162 +
1.163 + ref = self.get_global_or_builtin(name)
1.164 + objpath = ref and (ref.final() or ref.get_name())
1.165 + if objpath:
1.166 + self.name_references[key] = objpath
1.167 + self.resolve_accesses(path, name, ref)
1.168 + continue
1.169 +
1.170 + print >>sys.stderr, "Name not recognised: %s in %s" % (name, path)
1.171 + init_item(self.names_missing, path, set)
1.172 + self.names_missing[path].add(name)
1.173 +
1.174 + def resolve_accesses(self, path, name, ref):
1.175 +
1.176 + """
1.177 + Resolve any unresolved accesses in the function at the given 'path'
1.178 + for the given 'name' corresponding to the indicated 'ref'. Note that
1.179 + this mechanism cannot resolve things like inherited methods because
1.180 + they are not recorded as program objects in their inherited locations.
1.181 + """
1.182 +
1.183 + attr_accesses = self.global_attr_accesses.get(path)
1.184 + all_attrnames = attr_accesses and attr_accesses.get(name)
1.185 +
1.186 + if not all_attrnames:
1.187 + return
1.188 +
1.189 + # Insist on constant accessors.
1.190 +
1.191 + if not ref.has_kind(["<class>", "<module>"]):
1.192 + return
1.193 +
1.194 + found_attrnames = set()
1.195 +
1.196 + for attrnames in all_attrnames:
1.197 +
1.198 + # Start with the resolved name, adding attributes.
1.199 +
1.200 + attrs = ref.get_path()
1.201 + remaining = attrnames.split(".")
1.202 + last_ref = ref
1.203 +
1.204 + # Add each component, testing for a constant object.
1.205 +
1.206 + while remaining:
1.207 + attrname = remaining[0]
1.208 + attrs.append(attrname)
1.209 + del remaining[0]
1.210 +
1.211 + # Find any constant object reference.
1.212 +
1.213 + attr_ref = self.get_resolved_object(".".join(attrs))
1.214 +
1.215 + # Non-constant accessors terminate the traversal.
1.216 +
1.217 + if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]):
1.218 +
1.219 + # Provide the furthest constant accessor unless the final
1.220 + # access can be resolved.
1.221 +
1.222 + if remaining:
1.223 + remaining.insert(0, attrs.pop())
1.224 + else:
1.225 + last_ref = attr_ref
1.226 + break
1.227 +
1.228 + # Follow any reference to a constant object.
1.229 + # Where the given name refers to an object in another location,
1.230 + # switch to the other location in order to be able to test its
1.231 + # attributes.
1.232 +
1.233 + last_ref = attr_ref
1.234 + attrs = attr_ref.get_path()
1.235 +
1.236 + # Record a constant accessor only if an object was found
1.237 + # that is different from the namespace involved.
1.238 +
1.239 + if last_ref:
1.240 + objpath = ".".join(attrs)
1.241 + if objpath != path:
1.242 +
1.243 + # Establish a constant access.
1.244 +
1.245 + init_item(self.const_accesses, path, dict)
1.246 + self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining))
1.247 +
1.248 + if len(attrs) > 1:
1.249 + found_attrnames.add(attrs[1])
1.250 +
1.251 + # Remove any usage records for the name.
1.252 +
1.253 + if found_attrnames:
1.254 +
1.255 + # NOTE: Should be only one name version.
1.256 +
1.257 + versions = []
1.258 + for version in self.attr_usage[path][name]:
1.259 + new_usage = set()
1.260 + for usage in version:
1.261 + if found_attrnames.intersection(usage):
1.262 + new_usage.add(tuple(set(usage).difference(found_attrnames)))
1.263 + else:
1.264 + new_usage.add(usage)
1.265 + versions.append(new_usage)
1.266 +
1.267 + self.attr_usage[path][name] = versions
1.268 +
1.269 + def resolve_initialisers(self):
1.270 +
1.271 + "Resolve initialiser values for names."
1.272 +
1.273 + # Get the initialisers in each namespace.
1.274 +
1.275 + for path, name_initialisers in self.name_initialisers.items():
1.276 + const_accesses = self.const_accesses.get(path)
1.277 +
1.278 + # Resolve values for each name in a scope.
1.279 +
1.280 + for name, values in name_initialisers.items():
1.281 + if path == self.name:
1.282 + assigned_path = name
1.283 + else:
1.284 + assigned_path = "%s.%s" % (path, name)
1.285 +
1.286 + initialised_names = {}
1.287 + aliased_names = {}
1.288 +
1.289 + for i, name_ref in enumerate(values):
1.290 +
1.291 + # Unwrap invocations.
1.292 +
1.293 + if isinstance(name_ref, InvocationRef):
1.294 + invocation = True
1.295 + name_ref = name_ref.name_ref
1.296 + else:
1.297 + invocation = False
1.298 +
1.299 + # Obtain a usable reference from names or constants.
1.300 +
1.301 + if isinstance(name_ref, ResolvedNameRef):
1.302 + if not name_ref.reference():
1.303 + continue
1.304 + ref = name_ref.reference()
1.305 +
1.306 + # Obtain a reference from instances.
1.307 +
1.308 + elif isinstance(name_ref, InstanceRef):
1.309 + if not name_ref.reference():
1.310 + continue
1.311 + ref = name_ref.reference()
1.312 +
1.313 + # Resolve accesses that employ constants.
1.314 +
1.315 + elif isinstance(name_ref, AccessRef):
1.316 + ref = None
1.317 +
1.318 + if const_accesses:
1.319 + resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
1.320 + if resolved_access:
1.321 + objpath, ref, remaining_attrnames = resolved_access
1.322 + if remaining_attrnames:
1.323 + ref = None
1.324 +
1.325 + # Accesses that do not employ constants cannot be resolved,
1.326 + # but they may be resolvable later.
1.327 +
1.328 + if not ref:
1.329 + if not invocation:
1.330 + aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number
1.331 + continue
1.332 +
1.333 + # Attempt to resolve a plain name reference.
1.334 +
1.335 + elif isinstance(name_ref, LocalNameRef):
1.336 + key = "%s.%s" % (path, name_ref.name)
1.337 + origin = self.name_references.get(key)
1.338 +
1.339 + # Accesses that do not refer to known static objects
1.340 + # cannot be resolved, but they may be resolvable later.
1.341 +
1.342 + if not origin:
1.343 + if not invocation:
1.344 + aliased_names[i] = name_ref.name, None, name_ref.number
1.345 + continue
1.346 +
1.347 + ref = self.get_resolved_object(origin)
1.348 + if not ref:
1.349 + continue
1.350 +
1.351 + elif isinstance(name_ref, NameRef):
1.352 + key = "%s.%s" % (path, name_ref.name)
1.353 + origin = self.name_references.get(key)
1.354 + if not origin:
1.355 + continue
1.356 +
1.357 + ref = self.get_resolved_object(origin)
1.358 + if not ref:
1.359 + continue
1.360 +
1.361 + else:
1.362 + continue
1.363 +
1.364 + # Convert class invocations to instances.
1.365 +
1.366 + if invocation:
1.367 + ref = ref.has_kind("<class>") and ref.instance_of() or None
1.368 +
1.369 + if ref:
1.370 + initialised_names[i] = ref
1.371 +
1.372 + if initialised_names:
1.373 + self.initialised_names[assigned_path] = initialised_names
1.374 + if aliased_names:
1.375 + self.aliased_names[assigned_path] = aliased_names
1.376 +
1.377 + def resolve_literals(self):
1.378 +
1.379 + "Resolve constant value types."
1.380 +
1.381 + # Get the constants defined in each namespace.
1.382 +
1.383 + for path, constants in self.constants.items():
1.384 + for constant, n in constants.items():
1.385 + objpath = "%s.$c%d" % (path, n)
1.386 + _constant, value_type = self.constant_values[objpath]
1.387 + self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
1.388 +
1.389 + # Get the literals defined in each namespace.
1.390 +
1.391 + for path, literals in self.literals.items():
1.392 + for n in range(0, literals):
1.393 + objpath = "%s.$C%d" % (path, n)
1.394 + value_type = self.literal_types[objpath]
1.395 + self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
1.396 +
1.397 + def remove_redundant_accessors(self):
1.398 +
1.399 + "Remove now-redundant modifier and accessor information."
1.400 +
1.401 + for path, const_accesses in self.const_accesses.items():
1.402 + accesses = self.attr_accessors.get(path)
1.403 + modifiers = self.attr_access_modifiers.get(path)
1.404 + if not accesses:
1.405 + continue
1.406 + for access in const_accesses.keys():
1.407 + if accesses.has_key(access):
1.408 + del accesses[access]
1.409 + if modifiers and modifiers.has_key(access):
1.410 + del modifiers[access]
1.411 +
1.412 +# vim: tabstop=4 expandtab shiftwidth=4