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 = {} 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, self.modules.get("__builtins__")) 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 # Take details of the appropriate __init__ method to make an 226 # entry for an instantiation function for the class. 227 228 elif isinstance(obj, micropython.inspect.Class): 229 t.add(obj.full_name(), obj.get_instantiator().parameters()) 230 231 return self.paramtable 232 233 # Import methods. 234 235 def find_in_path(self, name): 236 237 """ 238 Find the given module 'name' in the search path, returning None where no 239 such module could be found, or a 2-tuple from the 'find' method 240 otherwise. 241 """ 242 243 for d in self.path: 244 m = self.find(d, name) 245 if m: return m 246 return None 247 248 def find(self, d, name): 249 250 """ 251 In the directory 'd', find the given module 'name', where 'name' can 252 either refer to a single file module or to a package. Return None if the 253 'name' cannot be associated with either a file or a package directory, 254 or a 2-tuple from '_find_package' or '_find_module' otherwise. 255 """ 256 257 m = self._find_package(d, name) 258 if m: return m 259 m = self._find_module(d, name) 260 if m: return m 261 return None 262 263 def _find_module(self, d, name): 264 265 """ 266 In the directory 'd', find the given module 'name', returning None where 267 no suitable file exists in the directory, or a 2-tuple consisting of 268 None (indicating that no package directory is involved) and a filename 269 indicating the location of the module. 270 """ 271 272 name_py = name + os.extsep + "py" 273 filename = self._find_file(d, name_py) 274 if filename: 275 return None, filename 276 return None 277 278 def _find_package(self, d, name): 279 280 """ 281 In the directory 'd', find the given package 'name', returning None 282 where no suitable package directory exists, or a 2-tuple consisting of 283 a directory (indicating the location of the package directory itself) 284 and a filename indicating the location of the __init__.py module which 285 declares the package's top-level contents. 286 """ 287 288 filename = self._find_file(d, name) 289 if filename: 290 init_py = "__init__" + os.path.extsep + "py" 291 init_py_filename = self._find_file(filename, init_py) 292 if init_py_filename: 293 return filename, init_py_filename 294 return None 295 296 def _find_file(self, d, filename): 297 298 """ 299 Return the filename obtained when searching the directory 'd' for the 300 given 'filename', or None if no actual file exists for the filename. 301 """ 302 303 filename = os.path.join(d, filename) 304 if os.path.exists(filename): 305 return filename 306 else: 307 return None 308 309 def load(self, name, return_leaf=0): 310 311 """ 312 Load the module or package with the given 'name'. Return an object 313 referencing the loaded module or package, or None if no such module or 314 package exists. 315 """ 316 317 if self.modules.has_key(name) and self.modules[name].loaded: 318 #print "Cached (%s)" % name 319 return self.modules[name] 320 if self.verbose: 321 print "Loading", name 322 323 # Split the name into path components, and try to find the uppermost in 324 # the search path. 325 326 path = name.split(".") 327 m = self.find_in_path(path[0]) 328 if not m: 329 if self.verbose: 330 print "Not found (%s)" % path[0] 331 return None # NOTE: Import error. 332 d, filename = m 333 334 # Either acquire a reference to an already-imported module, or load the 335 # module from a file. 336 337 top = module = self.load_from_file(filename, path[0]) 338 339 # For hierarchical names, traverse each path component... 340 341 if len(path) > 1: 342 if not d: 343 if self.verbose: 344 print "No package (%s)" % filename 345 return None # NOTE: Import error (package not found). 346 else: 347 self.add_submodules(d, module) 348 349 path_so_far = path[:1] 350 for p in path[1:]: 351 path_so_far.append(p) 352 353 # Find the package or module concerned. 354 355 m = self.find(d, p) 356 if not m: 357 if self.verbose: 358 print "Not found (%s)" % p 359 return None # NOTE: Import error. 360 d, filename = m 361 module_name = ".".join(path_so_far) 362 363 # Either reference an imported module or load one from a file. 364 365 submodule = self.load_from_file(filename, module_name) 366 367 if d: 368 self.add_submodules(d, module) 369 370 # Store the submodule within its parent module. 371 372 module.set_module(p, submodule) 373 module = submodule 374 375 # Return either the deepest or the uppermost module. 376 377 if return_leaf: 378 return module 379 else: 380 return top 381 382 def load_from_file(self, name, module_name=None): 383 384 """ 385 Load the module with the given 'name' (which may be a full module path). 386 """ 387 388 if module_name is None: 389 module_name = "__main__" 390 391 module = self.add_module(module_name) 392 if not module.loaded and module not in self.loading: 393 self.loading.add(module) 394 #print "Parsing", name 395 module.parse(name) 396 #print "Done", name 397 self.loading.remove(module) 398 module.loaded = 1 399 400 # Record the module. 401 402 #print "Loaded", module_name, "with namespace", module.namespace.keys() 403 return module 404 405 def add_module(self, module_name): 406 407 """ 408 Return the module with the given 'module_name', adding a new module 409 object if one does not already exist. 410 """ 411 412 if not self.modules.has_key(module_name): 413 self.modules[module_name] = module = micropython.inspect.InspectedModule(module_name, self) 414 else: 415 module = self.modules[module_name] 416 return module 417 418 def add_submodules(self, pathname, module): 419 420 """ 421 Work around insufficient __all__ declarations and examine the directory 422 with the given 'pathname', adding submodules to the given 'module'. 423 """ 424 425 for filename in os.listdir(pathname): 426 submodule, ext = os.path.splitext(filename) 427 if ext not in ("", ".py"): 428 continue 429 module.set_module(submodule, self.add_module(module.name + "." + submodule)) 430 431 # vim: tabstop=4 expandtab shiftwidth=4