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