1.1 --- a/annotate.py Wed Dec 06 23:54:48 2006 +0100
1.2 +++ b/annotate.py Fri Dec 08 23:41:20 2006 +0100
1.3 @@ -24,11 +24,23 @@
1.4
1.5 --------
1.6
1.7 -To use this module, the easiest approach is to use the annotate function:
1.8 +To use this module, the easiest approach is to use the load function:
1.9 +
1.10 +load(filename, builtins)
1.11 +
1.12 +To control module importing, an importer should be constructed and employed.
1.13 +Here, the standard path for module searching is used:
1.14 +
1.15 +importer = Importer(sys.path)
1.16 +load(filename, builtins, importer)
1.17 +
1.18 +Underneath the load function, the annotate function provides support for
1.19 +annotating modules already processed by simplify and fixnames:
1.20
1.21 annotate(module, builtins)
1.22
1.23 -The more complicated approach involves obtaining an Annotator:
1.24 +And at the most basic level, the most intricate approach involves obtaining an
1.25 +Annotator object:
1.26
1.27 annotator = Annotator()
1.28
1.29 @@ -43,7 +55,9 @@
1.30 """
1.31
1.32 from simplified import *
1.33 +import simplify, fixnames # for the load function
1.34 import compiler
1.35 +import os
1.36
1.37 class System:
1.38
1.39 @@ -117,20 +131,20 @@
1.40 types possible when the means of constructing the namespace may depend on
1.41 run-time behaviour.
1.42
1.43 - Covered: Assign, CheckExc, Conditional, Global, InvokeBlock, InvokeFunction,
1.44 - LoadAttr, LoadExc, LoadName, LoadRef, LoadTemp, Module, Not, Pass,
1.45 - Raise, ReleaseTemp, ReturnFromBlock, ReturnFromFunction, StoreAttr,
1.46 - StoreName, StoreTemp, Subprogram, Try.
1.47 -
1.48 - Missing: Import.
1.49 + Covered: Assign, CheckExc, Conditional, Global, Import, InvokeBlock,
1.50 + InvokeFunction, LoadAttr, LoadExc, LoadName, LoadRef, LoadTemp,
1.51 + Module, Not, Pass, Raise, ReleaseTemp, ReturnFromBlock,
1.52 + ReturnFromFunction, StoreAttr, StoreName, StoreTemp, Subprogram,
1.53 + Try.
1.54 """
1.55
1.56 - def __init__(self):
1.57 + def __init__(self, importer=None):
1.58
1.59 - "Initialise the visitor."
1.60 + "Initialise the visitor with an optional 'importer'."
1.61
1.62 Visitor.__init__(self)
1.63 self.system = system
1.64 + self.importer = importer or Importer()
1.65
1.66 # Satisfy visitor issues.
1.67
1.68 @@ -366,6 +380,20 @@
1.69
1.70 return global_
1.71
1.72 + def visitImport(self, import_):
1.73 +
1.74 + """
1.75 + Return the 'import_' node, importing the module with the stated name
1.76 + and storing details on the node.
1.77 + """
1.78 +
1.79 + module = self.importer.load(import_.name, self.builtins)
1.80 + if module is not None:
1.81 + self.namespace.set_types([module])
1.82 + else:
1.83 + self.namespace.set_types([])
1.84 + return import_
1.85 +
1.86 def _visitInvoke(self, invoke, invocation_types, have_args):
1.87
1.88 """
1.89 @@ -555,7 +583,7 @@
1.90 raised.
1.91 """
1.92
1.93 - self.namespace.types = self.namespace.raises[:]
1.94 + self.namespace.set_types(self.namespace.raises[:])
1.95 self.annotate(loadexc)
1.96 return loadexc
1.97
1.98 @@ -872,14 +900,21 @@
1.99 invoke.syscount = self.system.count
1.100
1.101 # Provide the correct namespace for the invocation.
1.102 + # This may be a "shared" namespace...
1.103
1.104 if getattr(invoke, "share_locals", 0):
1.105 namespace = Namespace()
1.106 namespace.merge_namespace(self.namespace, everything=0)
1.107 using_module_namespace = self.namespace is self.module.namespace
1.108 +
1.109 + # Or it may be a structure...
1.110 +
1.111 elif getattr(target, "structure", None):
1.112 namespace = Namespace()
1.113 using_module_namespace = 0
1.114 +
1.115 + # Or it may be a new namespace populated with the supplied parameters.
1.116 +
1.117 else:
1.118 items = self.make_items(invoke, target, context)
1.119 namespace = Namespace()
1.120 @@ -1157,31 +1192,57 @@
1.121 self.types = []
1.122
1.123 def set_types(self, types):
1.124 +
1.125 + "Set the current collection of 'types'."
1.126 +
1.127 self.types = types
1.128
1.129 def add(self, name, types):
1.130 +
1.131 + "Add to the entry with the given 'name' the specified 'types'."
1.132 +
1.133 if self.names.has_key(name):
1.134 combine(self.names[name], types)
1.135 else:
1.136 self.store(name, types)
1.137
1.138 def store(self, name, types):
1.139 +
1.140 + "Store in (or associate with) the given 'name' the specified 'types'."
1.141 +
1.142 self.names[name] = types
1.143
1.144 __setitem__ = store
1.145
1.146 def load(self, name):
1.147 +
1.148 + "Load the types associated with the given 'name'."
1.149 +
1.150 return self.names[name]
1.151
1.152 __getitem__ = load
1.153
1.154 def revoke(self, name, type):
1.155 +
1.156 + "Revoke from the entry for the given 'name' the specified 'type'."
1.157 +
1.158 self.names[name].remove(type)
1.159
1.160 def revoke_exception_type(self, type):
1.161 +
1.162 + "Revoke the given 'type' from the collection of exception types."
1.163 +
1.164 self.raises.remove(type)
1.165
1.166 def merge_namespace(self, namespace, everything=1):
1.167 +
1.168 + """
1.169 + Merge items from the given 'namespace' with this namespace. When the
1.170 + optional 'everything' parameter is set to a false value (unlike the
1.171 + default), return values and locals snapshots will not be copied to this
1.172 + namespace.
1.173 + """
1.174 +
1.175 self.merge_items(namespace.names.items())
1.176 if everything:
1.177 combine(self.returns, namespace.returns)
1.178 @@ -1194,10 +1255,16 @@
1.179 combine(self.temp[name][-1], values[-1])
1.180
1.181 def merge_items(self, items):
1.182 +
1.183 + "Merge the given 'items' with this namespace."
1.184 +
1.185 for name, types in items:
1.186 self.merge(name, types)
1.187
1.188 def merge(self, name, types):
1.189 +
1.190 + "Merge the entry for the given 'name' and 'types' with this namespace."
1.191 +
1.192 if not self.names.has_key(name):
1.193 self.names[name] = types[:]
1.194 else:
1.195 @@ -1249,6 +1316,126 @@
1.196 def __init__(self, attribute):
1.197 self.types = [attribute]
1.198
1.199 +class Importer:
1.200 +
1.201 + "An import machine, searching for and loading modules."
1.202 +
1.203 + def __init__(self, path=None):
1.204 +
1.205 + """
1.206 + Initialise the importer with the given search 'path' - a list of
1.207 + directories to search for Python modules.
1.208 + """
1.209 +
1.210 + self.path = path or [os.getcwd()]
1.211 + self.modules = []
1.212 +
1.213 + def find_in_path(self, name):
1.214 +
1.215 + """
1.216 + Find the given module 'name' in the search path, returning None where no
1.217 + such module could be found, or a 2-tuple from the 'find' method
1.218 + otherwise.
1.219 + """
1.220 +
1.221 + for d in self.path:
1.222 + m = self.find(d, name)
1.223 + if m: return m
1.224 + return None
1.225 +
1.226 + def find(self, d, name):
1.227 +
1.228 + """
1.229 + In the directory 'd', find the given module 'name', where 'name' can
1.230 + either refer to a single file module or to a package. Return None if the
1.231 + 'name' cannot be associated with either a file or a package directory,
1.232 + or a 2-tuple from '_find_package' or '_find_module' otherwise.
1.233 + """
1.234 +
1.235 + m = self._find_package(d, name)
1.236 + if m: return m
1.237 + m = self._find_module(d, name)
1.238 + if m: return m
1.239 + return None
1.240 +
1.241 + def _find_module(self, d, name):
1.242 +
1.243 + """
1.244 + In the directory 'd', find the given module 'name', returning None where
1.245 + no suitable file exists in the directory, or a 2-tuple consisting of
1.246 + None (indicating that no package directory is involved) and a filename
1.247 + indicating the location of the module.
1.248 + """
1.249 +
1.250 + name_py = name + os.extsep + "py"
1.251 + filename = self._find_file(d, name_py)
1.252 + if filename:
1.253 + return None, filename
1.254 + return None
1.255 +
1.256 + def _find_package(self, d, name):
1.257 +
1.258 + """
1.259 + In the directory 'd', find the given package 'name', returning None
1.260 + where no suitable package directory exists, or a 2-tuple consisting of
1.261 + a directory (indicating the location of the package directory itself)
1.262 + and a filename indicating the location of the __init__.py module which
1.263 + declares the package's top-level contents.
1.264 + """
1.265 +
1.266 + filename = self._find_file(d, name)
1.267 + if filename:
1.268 + init_py = "__init__" + os.path.extsep + "py"
1.269 + init_py_filename = self._find_file(filename, init_py)
1.270 + if init_py_filename:
1.271 + return filename, init_py_filename
1.272 + return None
1.273 +
1.274 + def _find_file(self, d, filename):
1.275 +
1.276 + """
1.277 + Return the filename obtained when searching the directory 'd' for the
1.278 + given 'filename', or None if no actual file exists for the filename.
1.279 + """
1.280 +
1.281 + filename = os.path.join(d, filename)
1.282 + if os.path.exists(filename):
1.283 + return filename
1.284 + else:
1.285 + return None
1.286 +
1.287 + def load(self, name, builtins):
1.288 +
1.289 + """
1.290 + Load the module or package with the given 'name' and using the specified
1.291 + 'builtins'. Return an Attribute object referencing the loaded module or
1.292 + package, or None if no such module or package exists.
1.293 + """
1.294 +
1.295 + path = name.split(".")
1.296 + m = self.find_in_path(path[0])
1.297 + if not m:
1.298 + return None # NOTE: Import error.
1.299 + d, filename = m
1.300 + module = load(filename, builtins)
1.301 + self.modules.append(module)
1.302 +
1.303 + if len(path) > 1:
1.304 + for p in path[1:]:
1.305 + m = self.find(d, p)
1.306 + if not m:
1.307 + return None # NOTE: Import error.
1.308 + d, filename = m
1.309 + submodule = load(filename, builtins)
1.310 + self.modules.append(submodule)
1.311 +
1.312 + # Store the submodule within its parent module.
1.313 +
1.314 + module.namespace[p] = [Attribute(None, submodule)]
1.315 + module = submodule
1.316 +
1.317 + return Attribute(None, module)
1.318 +
1.319 def combine(target, additions):
1.320
1.321 """
1.322 @@ -1341,22 +1528,31 @@
1.323
1.324 # Convenience functions.
1.325
1.326 -def annotate(module, builtins=None):
1.327 +def load(name, builtins=None, importer=None):
1.328 +
1.329 + """
1.330 + Load the module with the given 'name' (which may be a full module path),
1.331 + using the optional 'builtins' to resolve built-in names, and using the
1.332 + optional 'importer' to provide a means of finding and loading modules.
1.333 + """
1.334 +
1.335 + module = simplify.simplify(name, builtins is None)
1.336 + fixnames.fix(module, builtins)
1.337 + annotate(module, builtins, importer)
1.338 + return module
1.339 +
1.340 +def annotate(module, builtins=None, importer=None):
1.341
1.342 """
1.343 Annotate the given 'module', also employing the optional 'builtins' module,
1.344 - if specified.
1.345 + if specified. If the optional 'importer' is given, use that to find and load
1.346 + modules.
1.347 """
1.348
1.349 - annotator = Annotator()
1.350 + annotator = Annotator(importer)
1.351 if builtins is not None:
1.352 annotator.process(module, builtins)
1.353 else:
1.354 annotator.process(module)
1.355
1.356 -def annotate_all(modules, builtins):
1.357 - annotate(builtins)
1.358 - for module in modules:
1.359 - annotate(module, builtins)
1.360 -
1.361 # vim: tabstop=4 expandtab shiftwidth=4