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