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