1 #!/usr/bin/env python 2 3 """ 4 The micropython package for processing Python source code. The code originates 5 from the simplify package but has had various details related to that package 6 removed. 7 8 Copyright (C) 2006, 2007 Paul Boddie <paul@boddie.org.uk> 9 10 This program is free software; you can redistribute it and/or modify it under 11 the terms of the GNU General Public License as published by the Free Software 12 Foundation; either version 3 of the License, or (at your option) any later 13 version. 14 15 This program is distributed in the hope that it will be useful, but WITHOUT 16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 18 details. 19 20 You should have received a copy of the GNU General Public License along with 21 this program. If not, see <http://www.gnu.org/licenses/>. 22 23 -------- 24 25 To use this module, an importer should be constructed and the load_from_file 26 method used. Here, the standard path for module searching is employed: 27 28 importer = Importer(sys.path) 29 importer.load_from_file(filename) 30 importer.vacuum() 31 32 Such importer objects are the most convenient mechanism through which the 33 functionality of the micropython package may be accessed. 34 """ 35 36 import micropython.ast 37 import micropython.inspect 38 import micropython.table 39 import os 40 try: 41 set 42 except NameError: 43 from sets import Set as set 44 45 class Importer: 46 47 "An import machine, searching for and loading modules." 48 49 def __init__(self, path=None): 50 51 """ 52 Initialise the importer with the given search 'path' - a list of 53 directories to search for Python modules. 54 """ 55 56 self.path = path or [os.getcwd()] 57 self.modules = {} 58 self.loading = set() 59 60 def vacuum(self): 61 62 "Tidy up the modules." 63 64 for name, module in self.modules.items(): 65 if module.loaded: 66 module.vacuum() 67 else: 68 del self.modules[name] 69 70 def get_modules(self): 71 72 "Return all modules known to the importer." 73 74 return self.modules.values() 75 76 def get_image(self, objtable=None, paramtable=None): 77 78 "Return a dictionary mapping modules to structures." 79 80 objtable = objtable or self.get_object_table() 81 paramtable = paramtable or self.get_parameter_table() 82 83 image = [] 84 85 for module_name, module in self.modules.items(): 86 pos = len(image) 87 88 # Position the module in the image and make a translation. 89 90 module.location = pos 91 trans = micropython.ast.Translation(module, objtable, paramtable) 92 93 # Append constants to the image. 94 95 for const in module.constants: 96 const.location = pos 97 image.append(const) 98 pos += 1 99 100 # Append module attributes to the image. 101 102 attributes = module.module_attributes() 103 104 for name in module.module_attribute_names(): 105 attr = attributes[name] 106 image.append(attr) 107 pos += 1 108 109 # Append classes and functions to the image. 110 111 for obj in module.all_objects: 112 if isinstance(obj, micropython.inspect.Class): 113 114 # Position the class in the image. 115 116 obj.code_location = obj.location = pos 117 118 # Append class attributes to the image. 119 120 attributes = obj.class_attributes() 121 122 for name in obj.class_attribute_names(): 123 attr = attributes[name] 124 image.append(attr) 125 pos += 1 126 127 # Append the class-level code to the image. 128 # NOTE: An extra optimisation would involve 129 # NOTE: pre-initialisation of attributes and no code being 130 # NOTE: generated here, or perhaps the pre-initialisation of 131 # NOTE: methods and only other attribute-related code being 132 # NOTE: generated here. 133 134 code = trans.get_code(obj) 135 image += code 136 pos += len(code) 137 138 # NOTE: Generate module and function code here. 139 140 elif isinstance(obj, micropython.inspect.Function): 141 142 # Position the function in the image. 143 144 obj.code_location = obj.location = pos 145 146 # Append the function code to the image. 147 148 code = trans.get_code(obj) 149 image += code 150 pos += len(code) 151 152 # Remember the position of the module code. 153 154 module.code_location = pos 155 156 # Append the module top-level code to the image. 157 158 code = trans.get_module_code() 159 image += code 160 pos += len(code) 161 162 return image 163 164 def get_object_table(self): 165 166 "Return a table with details of attributes for classes and modules." 167 168 t = micropython.table.Table() 169 for module in self.get_modules(): 170 t.add(module.full_name(), module.module_attributes()) 171 for obj in module.all_objects: 172 if isinstance(obj, micropython.inspect.Class): 173 t.add(obj.full_name(), obj.all_attributes()) 174 return t 175 176 def get_parameter_table(self): 177 178 "Return a table with details of parameters for functions and methods." 179 180 t = micropython.table.Table() 181 for module in self.get_modules(): 182 for obj in module.all_objects: 183 if isinstance(obj, micropython.inspect.Function): 184 t.add(obj.full_name(), obj.parameters()) 185 return t 186 187 # Import methods. 188 189 def find_in_path(self, name): 190 191 """ 192 Find the given module 'name' in the search path, returning None where no 193 such module could be found, or a 2-tuple from the 'find' method 194 otherwise. 195 """ 196 197 for d in self.path: 198 m = self.find(d, name) 199 if m: return m 200 return None 201 202 def find(self, d, name): 203 204 """ 205 In the directory 'd', find the given module 'name', where 'name' can 206 either refer to a single file module or to a package. Return None if the 207 'name' cannot be associated with either a file or a package directory, 208 or a 2-tuple from '_find_package' or '_find_module' otherwise. 209 """ 210 211 m = self._find_package(d, name) 212 if m: return m 213 m = self._find_module(d, name) 214 if m: return m 215 return None 216 217 def _find_module(self, d, name): 218 219 """ 220 In the directory 'd', find the given module 'name', returning None where 221 no suitable file exists in the directory, or a 2-tuple consisting of 222 None (indicating that no package directory is involved) and a filename 223 indicating the location of the module. 224 """ 225 226 name_py = name + os.extsep + "py" 227 filename = self._find_file(d, name_py) 228 if filename: 229 return None, filename 230 return None 231 232 def _find_package(self, d, name): 233 234 """ 235 In the directory 'd', find the given package 'name', returning None 236 where no suitable package directory exists, or a 2-tuple consisting of 237 a directory (indicating the location of the package directory itself) 238 and a filename indicating the location of the __init__.py module which 239 declares the package's top-level contents. 240 """ 241 242 filename = self._find_file(d, name) 243 if filename: 244 init_py = "__init__" + os.path.extsep + "py" 245 init_py_filename = self._find_file(filename, init_py) 246 if init_py_filename: 247 return filename, init_py_filename 248 return None 249 250 def _find_file(self, d, filename): 251 252 """ 253 Return the filename obtained when searching the directory 'd' for the 254 given 'filename', or None if no actual file exists for the filename. 255 """ 256 257 filename = os.path.join(d, filename) 258 if os.path.exists(filename): 259 return filename 260 else: 261 return None 262 263 def load(self, name, return_leaf=0): 264 265 """ 266 Load the module or package with the given 'name'. Return an object 267 referencing the loaded module or package, or None if no such module or 268 package exists. 269 """ 270 271 if self.modules.has_key(name) and self.modules[name].loaded: 272 #print "Cached (%s)" % name 273 return self.modules[name] 274 print "Loading", name 275 276 # Split the name into path components, and try to find the uppermost in 277 # the search path. 278 279 path = name.split(".") 280 m = self.find_in_path(path[0]) 281 if not m: 282 print "Not found (%s)" % path[0] 283 return None # NOTE: Import error. 284 d, filename = m 285 286 # Either acquire a reference to an already-imported module, or load the 287 # module from a file. 288 289 top = module = self.load_from_file(filename, path[0]) 290 291 # For hierarchical names, traverse each path component... 292 293 if len(path) > 1: 294 if not d: 295 print "No package (%s)" % filename 296 return None # NOTE: Import error (package not found). 297 else: 298 self.add_submodules(d, module) 299 300 path_so_far = path[:1] 301 for p in path[1:]: 302 path_so_far.append(p) 303 304 # Find the package or module concerned. 305 306 m = self.find(d, p) 307 if not m: 308 print "Not found (%s)" % p 309 return None # NOTE: Import error. 310 d, filename = m 311 module_name = ".".join(path_so_far) 312 313 # Either reference an imported module or load one from a file. 314 315 submodule = self.load_from_file(filename, module_name) 316 317 if d: 318 self.add_submodules(d, module) 319 320 # Store the submodule within its parent module. 321 322 module.namespace[p] = submodule 323 module = submodule 324 325 # Return either the deepest or the uppermost module. 326 327 if return_leaf: 328 return module 329 else: 330 return top 331 332 def load_from_file(self, name, module_name=None): 333 334 """ 335 Load the module with the given 'name' (which may be a full module path). 336 """ 337 338 if module_name is None: 339 module_name = "__main__" 340 341 module = self.add_module(module_name) 342 if not module.loaded and module not in self.loading: 343 self.loading.add(module) 344 #print "Parsing", name 345 module.parse(name) 346 #print "Done", name 347 self.loading.remove(module) 348 module.loaded = 1 349 350 # Record the module. 351 352 #print "Loaded", module_name, "with namespace", module.namespace.keys() 353 return module 354 355 def add_module(self, module_name): 356 357 """ 358 Return the module with the given 'module_name', adding a new module 359 object if one does not already exist. 360 """ 361 362 if not self.modules.has_key(module_name): 363 self.modules[module_name] = module = micropython.inspect.InspectedModule(module_name, self) 364 else: 365 module = self.modules[module_name] 366 return module 367 368 def add_submodules(self, pathname, module): 369 370 """ 371 Work around insufficient __all__ declarations and examine the directory 372 with the given 'pathname', adding submodules to the given 'module'. 373 """ 374 375 for filename in os.listdir(pathname): 376 submodule = os.path.splitext(filename)[0] 377 module.namespace[submodule] = self.add_module(module.name + "." + submodule) 378 379 # vim: tabstop=4 expandtab shiftwidth=4