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, self, 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 any default values to the image. 179 180 image += obj.default_attrs 181 pos += len(obj.default_attrs) 182 183 # Append the function code to the image. 184 185 obj.code_location = pos 186 code = trans.get_code(obj) 187 image += code 188 pos += len(code) 189 190 # Remember the position of the module code. 191 192 module.code_location = pos 193 194 # Append the module top-level code to the image. 195 196 code = trans.get_module_code() 197 image += code 198 pos += len(code) 199 200 return image 201 202 def get_object_table(self): 203 204 "Return a table with details of attributes for classes and modules." 205 206 if self.objtable is None: 207 t = self.objtable = micropython.table.Table() 208 for module in self.get_modules(): 209 t.add(module.full_name(), module.module_attributes()) 210 for obj in module.all_objects: 211 if isinstance(obj, micropython.inspect.Class): 212 t.add(obj.full_name(), obj.all_attributes()) 213 214 return self.objtable 215 216 def get_parameter_table(self): 217 218 "Return a table with details of parameters for functions and methods." 219 220 # Need the object table to get at class details. 221 222 objtable = self.get_object_table() 223 224 if self.paramtable is None: 225 t = self.paramtable = micropython.table.Table() 226 227 # Visit each module, getting function and method details. 228 229 for module in self.get_modules(): 230 for obj in module.all_objects: 231 if isinstance(obj, micropython.inspect.Function): 232 t.add(obj.full_name(), obj.parameters()) 233 234 # Classes are callable, too. 235 # Take details of the appropriate __init__ method to make an 236 # entry for an instantiation function for the class. 237 238 elif isinstance(obj, micropython.inspect.Class): 239 t.add(obj.full_name(), obj.get_instantiator().parameters()) 240 241 # Filter out all parameter table entries not referenced by keyword 242 # arguments. 243 244 keyword_names = set() 245 246 for module in self.get_modules(): 247 keyword_names.update(module.keyword_names) 248 249 for function_name, parameters in t.table.items(): 250 for name in parameters.keys(): 251 if name in keyword_names: 252 break 253 else: 254 del t.table[function_name] 255 256 return self.paramtable 257 258 # Import methods. 259 260 def find_in_path(self, name): 261 262 """ 263 Find the given module 'name' in the search path, returning None where no 264 such module could be found, or a 2-tuple from the 'find' method 265 otherwise. 266 """ 267 268 for d in self.path: 269 m = self.find(d, name) 270 if m: return m 271 return None 272 273 def find(self, d, name): 274 275 """ 276 In the directory 'd', find the given module 'name', where 'name' can 277 either refer to a single file module or to a package. Return None if the 278 'name' cannot be associated with either a file or a package directory, 279 or a 2-tuple from '_find_package' or '_find_module' otherwise. 280 """ 281 282 m = self._find_package(d, name) 283 if m: return m 284 m = self._find_module(d, name) 285 if m: return m 286 return None 287 288 def _find_module(self, d, name): 289 290 """ 291 In the directory 'd', find the given module 'name', returning None where 292 no suitable file exists in the directory, or a 2-tuple consisting of 293 None (indicating that no package directory is involved) and a filename 294 indicating the location of the module. 295 """ 296 297 name_py = name + os.extsep + "py" 298 filename = self._find_file(d, name_py) 299 if filename: 300 return None, filename 301 return None 302 303 def _find_package(self, d, name): 304 305 """ 306 In the directory 'd', find the given package 'name', returning None 307 where no suitable package directory exists, or a 2-tuple consisting of 308 a directory (indicating the location of the package directory itself) 309 and a filename indicating the location of the __init__.py module which 310 declares the package's top-level contents. 311 """ 312 313 filename = self._find_file(d, name) 314 if filename: 315 init_py = "__init__" + os.path.extsep + "py" 316 init_py_filename = self._find_file(filename, init_py) 317 if init_py_filename: 318 return filename, init_py_filename 319 return None 320 321 def _find_file(self, d, filename): 322 323 """ 324 Return the filename obtained when searching the directory 'd' for the 325 given 'filename', or None if no actual file exists for the filename. 326 """ 327 328 filename = os.path.join(d, filename) 329 if os.path.exists(filename): 330 return filename 331 else: 332 return None 333 334 def load(self, name, return_leaf=0): 335 336 """ 337 Load the module or package with the given 'name'. Return an object 338 referencing the loaded module or package, or None if no such module or 339 package exists. 340 """ 341 342 if self.modules.has_key(name) and self.modules[name].loaded: 343 #print "Cached (%s)" % name 344 return self.modules[name] 345 if self.verbose: 346 print "Loading", name 347 348 # Split the name into path components, and try to find the uppermost in 349 # the search path. 350 351 path = name.split(".") 352 m = self.find_in_path(path[0]) 353 if not m: 354 if self.verbose: 355 print "Not found (%s)" % path[0] 356 return None # NOTE: Import error. 357 d, filename = m 358 359 # Either acquire a reference to an already-imported module, or load the 360 # module from a file. 361 362 top = module = self.load_from_file(filename, path[0]) 363 364 # For hierarchical names, traverse each path component... 365 366 if len(path) > 1: 367 if not d: 368 if self.verbose: 369 print "No package (%s)" % filename 370 return None # NOTE: Import error (package not found). 371 else: 372 self.add_submodules(d, module) 373 374 path_so_far = path[:1] 375 for p in path[1:]: 376 path_so_far.append(p) 377 378 # Find the package or module concerned. 379 380 m = self.find(d, p) 381 if not m: 382 if self.verbose: 383 print "Not found (%s)" % p 384 return None # NOTE: Import error. 385 d, filename = m 386 module_name = ".".join(path_so_far) 387 388 # Either reference an imported module or load one from a file. 389 390 submodule = self.load_from_file(filename, module_name) 391 392 if d: 393 self.add_submodules(d, module) 394 395 # Store the submodule within its parent module. 396 397 module.set_module(p, submodule) 398 module = submodule 399 400 # Return either the deepest or the uppermost module. 401 402 if return_leaf: 403 return module 404 else: 405 return top 406 407 def load_from_file(self, name, module_name=None): 408 409 """ 410 Load the module with the given 'name' (which may be a full module path). 411 """ 412 413 if module_name is None: 414 module_name = "__main__" 415 416 module = self.add_module(module_name) 417 if not module.loaded and module not in self.loading: 418 self.loading.add(module) 419 #print "Parsing", name 420 module.parse(name) 421 #print "Done", name 422 self.loading.remove(module) 423 module.loaded = 1 424 425 # Record the module. 426 427 #print "Loaded", module_name, "with namespace", module.namespace.keys() 428 return module 429 430 def add_module(self, module_name): 431 432 """ 433 Return the module with the given 'module_name', adding a new module 434 object if one does not already exist. 435 """ 436 437 if not self.modules.has_key(module_name): 438 self.modules[module_name] = module = micropython.inspect.InspectedModule(module_name, self) 439 else: 440 module = self.modules[module_name] 441 return module 442 443 def add_submodules(self, pathname, module): 444 445 """ 446 Work around insufficient __all__ declarations and examine the directory 447 with the given 'pathname', adding submodules to the given 'module'. 448 """ 449 450 for filename in os.listdir(pathname): 451 submodule, ext = os.path.splitext(filename) 452 if ext not in ("", ".py"): 453 continue 454 module.set_module(submodule, self.add_module(module.name + "." + submodule)) 455 456 # vim: tabstop=4 expandtab shiftwidth=4