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, 2008 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 from micropython.common import * 37 import micropython.ast 38 import micropython.inspect 39 import micropython.table 40 import os 41 try: 42 set 43 except NameError: 44 from sets import Set as set 45 46 class Importer: 47 48 """ 49 An import machine, searching for and loading modules. In addition, this 50 class supports the generation of a program image. 51 """ 52 53 def __init__(self, path=None, verbose=0): 54 55 """ 56 Initialise the importer with the given search 'path' - a list of 57 directories to search for Python modules. 58 59 The optional 'verbose' parameter causes output concerning the activities 60 of the object to be produced if set to a true value (not the default). 61 """ 62 63 self.path = path or [os.getcwd()] 64 self.verbose = verbose 65 self.modules = {"__builtins__" : micropython.inspect.builtins} 66 self.loading = set() 67 68 # Remember the tables once generated. 69 70 self.objtable = None 71 self.paramtable = None 72 73 def vacuum(self): 74 75 "Tidy up the modules." 76 77 for name, module in self.modules.items(): 78 if module.loaded: 79 module.vacuum() 80 else: 81 del self.modules[name] 82 83 def get_modules(self): 84 85 "Return all modules known to the importer." 86 87 return self.modules.values() 88 89 def get_image(self, with_builtins=0): 90 91 "Return a dictionary mapping modules to structures." 92 93 objtable = self.get_object_table() 94 paramtable = self.get_parameter_table() 95 96 image = [] 97 98 for module_name, module in self.modules.items(): 99 if not with_builtins and module_name == "__builtins__": 100 continue 101 102 # Fix the attributes. 103 104 module.finalise_attributes() 105 106 pos = len(image) 107 108 # Append constants to the image. 109 110 for const in module.constants(): 111 const.location = pos 112 image.append(const) 113 pos += 1 114 115 # Position the module in the image and make a translation. 116 117 module.location = pos 118 trans = micropython.ast.Translation(module, objtable, paramtable) 119 120 # Add header details. 121 122 image.append(module) 123 pos += 1 124 125 # Append module attributes to the image. 126 127 attributes = module.module_attributes() 128 image += module.attributes_as_list() 129 pos += len(attributes.keys()) 130 131 # Append classes and functions to the image. 132 133 for obj in module.all_objects: 134 if isinstance(obj, micropython.inspect.Class): 135 136 # Fix the attributes. 137 138 obj.finalise_attributes() 139 140 # Position the class in the image. 141 142 obj.location = pos 143 144 # Add header details. 145 146 image.append(obj) 147 pos += 1 148 149 # Append class attributes to the image. 150 151 attributes = obj.class_attributes() 152 image += obj.attributes_as_list() 153 pos += len(attributes.keys()) 154 155 # Class-level code is generated separately. 156 # The code location is set within the code generation 157 # process for the module. 158 159 # NOTE: Generate module and function code here. 160 161 elif isinstance(obj, micropython.inspect.Function): 162 163 # Fix the attributes. 164 165 obj.finalise_attributes() 166 167 # Position the function in the image. 168 169 obj.location = pos 170 171 # Add header details. 172 173 image.append(obj) 174 pos += 1 175 176 # Append the function code to the image. 177 178 obj.code_location = pos 179 code = trans.get_code(obj) 180 image += code 181 pos += len(code) 182 183 # Remember the position of the module code. 184 185 module.code_location = pos 186 187 # Append the module top-level code to the image. 188 189 code = trans.get_module_code() 190 image += code 191 pos += len(code) 192 193 return image 194 195 def get_object_table(self): 196 197 "Return a table with details of attributes for classes and modules." 198 199 if self.objtable is None: 200 t = self.objtable = micropython.table.Table() 201 for module in self.get_modules(): 202 t.add(module.full_name(), module.module_attributes()) 203 for obj in module.all_objects: 204 if isinstance(obj, micropython.inspect.Class): 205 t.add(obj.full_name(), obj.all_attributes()) 206 207 return self.objtable 208 209 def get_parameter_table(self): 210 211 "Return a table with details of parameters for functions and methods." 212 213 # Need the object table to get at class details. 214 215 objtable = self.get_object_table() 216 217 if self.paramtable is None: 218 t = self.paramtable = micropython.table.Table() 219 for module in self.get_modules(): 220 for obj in module.all_objects: 221 if isinstance(obj, micropython.inspect.Function): 222 t.add(obj.full_name(), obj.parameters()) 223 224 # Classes are callable, too. 225 # Copy details of the appropriate __init__ method to make an 226 # entry for the class. 227 228 elif isinstance(obj, micropython.inspect.Class): 229 init_method = objtable.table[obj.full_name()]["__init__"].value 230 t.add(obj.full_name(), init_method.parameters()) 231 232 return self.paramtable 233 234 # Import methods. 235 236 def find_in_path(self, name): 237 238 """ 239 Find the given module 'name' in the search path, returning None where no 240 such module could be found, or a 2-tuple from the 'find' method 241 otherwise. 242 """ 243 244 for d in self.path: 245 m = self.find(d, name) 246 if m: return m 247 return None 248 249 def find(self, d, name): 250 251 """ 252 In the directory 'd', find the given module 'name', where 'name' can 253 either refer to a single file module or to a package. Return None if the 254 'name' cannot be associated with either a file or a package directory, 255 or a 2-tuple from '_find_package' or '_find_module' otherwise. 256 """ 257 258 m = self._find_package(d, name) 259 if m: return m 260 m = self._find_module(d, name) 261 if m: return m 262 return None 263 264 def _find_module(self, d, name): 265 266 """ 267 In the directory 'd', find the given module 'name', returning None where 268 no suitable file exists in the directory, or a 2-tuple consisting of 269 None (indicating that no package directory is involved) and a filename 270 indicating the location of the module. 271 """ 272 273 name_py = name + os.extsep + "py" 274 filename = self._find_file(d, name_py) 275 if filename: 276 return None, filename 277 return None 278 279 def _find_package(self, d, name): 280 281 """ 282 In the directory 'd', find the given package 'name', returning None 283 where no suitable package directory exists, or a 2-tuple consisting of 284 a directory (indicating the location of the package directory itself) 285 and a filename indicating the location of the __init__.py module which 286 declares the package's top-level contents. 287 """ 288 289 filename = self._find_file(d, name) 290 if filename: 291 init_py = "__init__" + os.path.extsep + "py" 292 init_py_filename = self._find_file(filename, init_py) 293 if init_py_filename: 294 return filename, init_py_filename 295 return None 296 297 def _find_file(self, d, filename): 298 299 """ 300 Return the filename obtained when searching the directory 'd' for the 301 given 'filename', or None if no actual file exists for the filename. 302 """ 303 304 filename = os.path.join(d, filename) 305 if os.path.exists(filename): 306 return filename 307 else: 308 return None 309 310 def load(self, name, return_leaf=0): 311 312 """ 313 Load the module or package with the given 'name'. Return an object 314 referencing the loaded module or package, or None if no such module or 315 package exists. 316 """ 317 318 if self.modules.has_key(name) and self.modules[name].loaded: 319 #print "Cached (%s)" % name 320 return self.modules[name] 321 if self.verbose: 322 print "Loading", name 323 324 # Split the name into path components, and try to find the uppermost in 325 # the search path. 326 327 path = name.split(".") 328 m = self.find_in_path(path[0]) 329 if not m: 330 if self.verbose: 331 print "Not found (%s)" % path[0] 332 return None # NOTE: Import error. 333 d, filename = m 334 335 # Either acquire a reference to an already-imported module, or load the 336 # module from a file. 337 338 top = module = self.load_from_file(filename, path[0]) 339 340 # For hierarchical names, traverse each path component... 341 342 if len(path) > 1: 343 if not d: 344 if self.verbose: 345 print "No package (%s)" % filename 346 return None # NOTE: Import error (package not found). 347 else: 348 self.add_submodules(d, module) 349 350 path_so_far = path[:1] 351 for p in path[1:]: 352 path_so_far.append(p) 353 354 # Find the package or module concerned. 355 356 m = self.find(d, p) 357 if not m: 358 if self.verbose: 359 print "Not found (%s)" % p 360 return None # NOTE: Import error. 361 d, filename = m 362 module_name = ".".join(path_so_far) 363 364 # Either reference an imported module or load one from a file. 365 366 submodule = self.load_from_file(filename, module_name) 367 368 if d: 369 self.add_submodules(d, module) 370 371 # Store the submodule within its parent module. 372 373 module.set_module(p, submodule) 374 module = submodule 375 376 # Return either the deepest or the uppermost module. 377 378 if return_leaf: 379 return module 380 else: 381 return top 382 383 def load_from_file(self, name, module_name=None): 384 385 """ 386 Load the module with the given 'name' (which may be a full module path). 387 """ 388 389 if module_name is None: 390 module_name = "__main__" 391 392 module = self.add_module(module_name) 393 if not module.loaded and module not in self.loading: 394 self.loading.add(module) 395 #print "Parsing", name 396 module.parse(name) 397 #print "Done", name 398 self.loading.remove(module) 399 module.loaded = 1 400 401 # Record the module. 402 403 #print "Loaded", module_name, "with namespace", module.namespace.keys() 404 return module 405 406 def add_module(self, module_name): 407 408 """ 409 Return the module with the given 'module_name', adding a new module 410 object if one does not already exist. 411 """ 412 413 if not self.modules.has_key(module_name): 414 self.modules[module_name] = module = micropython.inspect.InspectedModule(module_name, self) 415 else: 416 module = self.modules[module_name] 417 return module 418 419 def add_submodules(self, pathname, module): 420 421 """ 422 Work around insufficient __all__ declarations and examine the directory 423 with the given 'pathname', adding submodules to the given 'module'. 424 """ 425 426 for filename in os.listdir(pathname): 427 submodule, ext = os.path.splitext(filename) 428 if ext not in ("", ".py"): 429 continue 430 module.set_module(submodule, self.add_module(module.name + "." + submodule)) 431 432 # vim: tabstop=4 expandtab shiftwidth=4