1 #!/usr/bin/env python 2 3 """ 4 Inspect source files, obtaining details of classes and attributes. 5 6 Copyright (C) 2007, 2008 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from micropython.common import * 23 from micropython.data import * 24 import compiler.ast 25 from compiler.visitor import ASTVisitor 26 27 # Program visitors. 28 29 class InspectedModule(ASTVisitor, Module): 30 31 """ 32 An inspected module, providing core details via the Module superclass, but 33 capable of being used as an AST visitor. 34 """ 35 36 def __init__(self, name, importer): 37 38 """ 39 Initialise this visitor with a module 'name' and an 'importer' which is 40 used to provide access to other modules when required. 41 """ 42 43 ASTVisitor.__init__(self) 44 Module.__init__(self, name) 45 self.visitor = self 46 47 self.importer = importer 48 self.builtins = self.importer.modules.get("__builtins__") 49 self.loaded = 0 50 51 # Constant records. 52 53 self.constant_values = importer.constant_values 54 55 # Current expression state. 56 57 self.expr = None 58 59 # Namespace state. 60 61 self.in_init = 0 # Find instance attributes in __init__ methods. 62 self.in_loop = 0 # Note loop "membership", affecting assignments. 63 self.namespaces = [] 64 self.module = None 65 66 def parse(self, filename): 67 68 "Parse the file having the given 'filename'." 69 70 module = compiler.parseFile(filename) 71 self.process(module) 72 73 def process(self, module): 74 75 "Process the given 'module'." 76 77 self.node = self.module = module 78 processed = self.dispatch(module) 79 if self.has_key("__all__"): 80 all = self["__all__"] 81 if isinstance(all, compiler.ast.List): 82 for n in all.nodes: 83 self[n.value] = self.importer.add_module(self.name + "." + n.value) 84 return processed 85 86 def vacuum(self): 87 88 "Vacuum the module namespace, removing unloaded module references." 89 90 for name, value in self.items(): 91 if isinstance(value, Module) and not value.loaded: 92 del self[name] 93 94 # Complain about globals not initialised at the module level. 95 96 if isinstance(value, Global): 97 print "Warning: global %r in module %r not initialised at the module level." % (name, self.name) 98 99 # Namespace methods. 100 101 def store(self, name, obj): 102 103 "Record attribute or local 'name', storing 'obj'." 104 105 if not self.namespaces: 106 self.set(name, obj, not self.in_loop) 107 else: 108 self.namespaces[-1].set(name, obj, not self.in_loop) 109 110 # Record all non-local objects. 111 112 if not (self.namespaces and isinstance(self.namespaces[-1], Function)): 113 self.all_objects.add(obj) 114 115 def store_lambda(self, obj): 116 self.all_objects.add(obj) 117 118 def store_instance_attr(self, name): 119 120 "Record instance attribute 'name' in the current class." 121 122 if self.in_init: 123 124 # Current namespace is the function. 125 # Previous namespace is the class. 126 127 self.namespaces[-2].add_instance_attribute(name) 128 129 def get_parent(self): 130 131 "Return the parent (or most recent) namespace currently exposed." 132 133 return (self.namespaces[-1:] or [self])[0] 134 135 # Visitor methods. 136 137 def default(self, node, *args): 138 raise InspectError(self.full_name(), node, "Node class %r is not supported." % node.__class__) 139 140 def dispatch(self, node, *args): 141 return ASTVisitor.dispatch(self, node, *args) 142 143 def NOP(self, node): 144 for n in node.getChildNodes(): 145 self.dispatch(n) 146 return None 147 148 def OP(self, node): 149 for n in node.getChildNodes(): 150 self.dispatch(n) 151 return Instance() 152 153 def _visitFunction(self, node, name): 154 155 """ 156 Return a function object for the function defined by 'node' with the 157 given 'name'. If a lambda expression is being visited, 'name' should be 158 None. 159 """ 160 161 function = Function( 162 name, 163 self.get_parent(), 164 node.argnames, 165 node.defaults, 166 (node.flags & 4 != 0), 167 (node.flags & 8 != 0), 168 self, 169 node 170 ) 171 172 # Make a back reference from the node for code generation. 173 174 node.unit = function 175 176 # Process the defaults. 177 178 for n in node.defaults: 179 self.expr = self.dispatch(n) 180 if isinstance(self.expr, Attr): 181 function.store_default(self.expr.value) 182 else: 183 function.store_default(self.expr) 184 185 # Enter the function. 186 187 self.namespaces.append(function) 188 189 # Current namespace is the function. 190 # Previous namespace is the class. 191 192 if name == "__init__" and isinstance(self.namespaces[-2], Class): 193 self.in_init = 1 194 195 self.dispatch(node.code) 196 self.in_init = 0 197 self.namespaces.pop() 198 199 if name is not None: 200 self.store(name, function) 201 else: 202 self.store_lambda(function) 203 204 return function 205 206 visitAdd = OP 207 208 visitAnd = OP 209 210 visitAssert = NOP 211 212 def visitAssign(self, node): 213 self.expr = self.dispatch(node.expr) 214 for n in node.nodes: 215 self.dispatch(n) 216 return None 217 218 def visitAssAttr(self, node): 219 expr = self.dispatch(node.expr) 220 if isinstance(expr, Attr) and expr.name == "self": 221 self.store_instance_attr(node.attrname) 222 return None 223 224 def visitAssList(self, node): 225 for n in node.nodes: 226 self.dispatch(n) 227 return None 228 229 def visitAssName(self, node): 230 if isinstance(self.expr, Attr): 231 self.store(node.name, self.expr.value) 232 else: 233 self.store(node.name, self.expr) 234 return None 235 236 visitAssTuple = visitAssList 237 238 visitAugAssign = OP 239 240 visitBackquote = OP 241 242 visitBitand = OP 243 244 visitBitor = OP 245 246 visitBitxor = OP 247 248 visitBreak = NOP 249 250 visitCallFunc = OP 251 252 def visitClass(self, node): 253 if self.namespaces: 254 print "Class %r in %r is not global: ignored." % (node.name, self.namespaces[-1].full_name()) 255 return None 256 else: 257 cls = Class(node.name, self.get_parent(), self, node) 258 259 # Visit the base class expressions, attempting to find concrete 260 # definitions of classes. 261 262 for base in node.bases: 263 expr = self.dispatch(base) 264 if isinstance(expr, Attr): 265 if expr.assignments != 1: 266 raise InspectError(self.full_name(), node, 267 "Base class %r for %r is not constant." % (base, cls.full_name())) 268 else: 269 cls.add_base(expr.value) 270 else: # if expr is None: 271 raise InspectError(self.full_name(), node, 272 "Base class %r for %r is not found: it may be hidden in some way." % (base, cls.full_name())) 273 274 # NOTE: Potentially dubious measure to permit __init__ availability. 275 # If no bases exist, adopt the 'object' class. 276 277 if not node.bases and not (self.name == "__builtins__" and node.name == "object") : 278 expr = self.dispatch(compiler.ast.Name("object")) 279 cls.add_base(expr.value) 280 281 # Make a back reference from the node for code generation. 282 283 node.unit = cls 284 285 # Make an entry for the class. 286 287 self.store(node.name, cls) 288 289 # Process the class body. 290 291 self.namespaces.append(cls) 292 self.dispatch(node.code) 293 self.namespaces.pop() 294 295 return cls 296 297 visitCompare = OP 298 299 def visitConst(self, node): 300 const = Const(node.value) 301 self.constant_values[node.value] = const 302 return const 303 304 visitContinue = NOP 305 306 visitDecorators = NOP 307 308 visitDict = OP 309 310 visitDiscard = NOP 311 312 visitDiv = OP 313 314 visitEllipsis = NOP 315 316 visitExec = NOP 317 318 visitExpression = OP 319 320 visitFloorDiv = OP 321 322 def visitFor(self, node): 323 self.in_loop = 1 324 self.NOP(node) 325 self.in_loop = 0 326 return None 327 328 def visitFrom(self, node): 329 module = self.importer.load(node.modname, 1) 330 331 #if module is None: 332 # print "Warning:", node.modname, "not imported." 333 334 for name, alias in node.names: 335 if name != "*": 336 if module is not None and module.namespace.has_key(name): 337 attr = module[name] 338 self.store(alias or name, attr.value) 339 if isinstance(attr, Module) and not attr.loaded: 340 self.importer.load(attr.name) 341 342 # Support the import of names from missing modules. 343 344 else: 345 self.store(alias or name, UnresolvedName(name, node.modname, self)) 346 else: 347 if module is not None: 348 for n in module.namespace.keys(): 349 attr = module[n] 350 self.store(n, attr.value) 351 if isinstance(attr, Module) and not attr.loaded: 352 self.importer.load(attr.name) 353 354 return None 355 356 def visitFunction(self, node): 357 return self._visitFunction(node, node.name) 358 359 visitGenExpr = OP 360 361 visitGenExprFor = NOP 362 363 visitGenExprIf = NOP 364 365 visitGenExprInner = NOP 366 367 def visitGetattr(self, node): 368 expr = self.dispatch(node.expr) 369 if isinstance(expr, Attr): 370 value = expr.value 371 if isinstance(value, Module): 372 return value.namespace.get(node.attrname) 373 elif isinstance(value, UnresolvedName): 374 return UnresolvedName(node.attrname, value.full_name(), self) 375 if self.builtins is not None: 376 return self.builtins.get(node.attrname) 377 else: 378 return UnresolvedName(node.attrname, value.full_name(), self) 379 380 def visitGlobal(self, node): 381 if self.namespaces: 382 for name in node.names: 383 self.namespaces[-1].make_global(name) 384 385 # Record a global entry for the name in the module. 386 387 if not self.has_key(name): 388 self[name] = Global() 389 390 def visitIf(self, node): 391 for test, body in node.tests: 392 self.dispatch(body) 393 if node.else_ is not None: 394 self.dispatch(node.else_) 395 return None 396 397 visitIfExp = NOP 398 399 def visitImport(self, node): 400 for name, alias in node.names: 401 if alias is not None: 402 self.store(alias, self.importer.load(name, 1) or UnresolvedName(None, name, self)) 403 else: 404 self.store(name.split(".")[0], self.importer.load(name) or UnresolvedName(None, name.split(".")[0], self)) 405 406 return None 407 408 visitInvert = OP 409 410 def visitKeyword(self, node): 411 self.dispatch(node.expr) 412 const = Const(node.name) 413 self.constant_values[node.name] = const 414 self.keyword_names.add(node.name) 415 return None 416 417 def visitLambda(self, node): 418 return self._visitFunction(node, None) 419 420 visitLeftShift = OP 421 422 visitList = OP 423 424 visitListComp = OP 425 426 visitListCompFor = NOP 427 428 visitListCompIf = NOP 429 430 visitMod = OP 431 432 def visitModule(self, node): 433 return self.dispatch(node.node) 434 435 visitMul = OP 436 437 def visitName(self, node): 438 name = node.name 439 if name == "None": 440 const = Const(None) 441 self.constant_values[None] = const 442 return const 443 elif self.namespaces and self.namespaces[-1].has_key(name): 444 return self.namespaces[-1][name] 445 elif self.has_key(name): 446 return self[name] 447 elif self.builtins is not None and self.builtins.has_key(name): 448 return self.builtins[name] 449 else: 450 return None 451 452 visitNot = OP 453 454 visitOr = OP 455 456 visitPass = NOP 457 458 visitPower = OP 459 460 visitPrint = NOP 461 462 visitPrintnl = NOP 463 464 visitRaise = NOP 465 466 visitReturn = NOP 467 468 visitRightShift = OP 469 470 visitSlice = OP 471 472 visitSliceobj = OP 473 474 def visitStmt(self, node): 475 for n in node.nodes: 476 self.dispatch(n) 477 return None 478 479 visitSub = OP 480 481 visitSubscript = OP 482 483 def visitTryExcept(self, node): 484 self.dispatch(node.body) 485 for name, var, n in node.handlers: 486 self.dispatch(n) 487 if node.else_ is not None: 488 self.dispatch(node.else_) 489 return None 490 491 visitTryFinally = NOP 492 493 visitTuple = OP 494 495 visitUnaryAdd = OP 496 497 visitUnarySub = OP 498 499 def visitWhile(self, node): 500 self.in_loop = 1 501 self.NOP(node) 502 self.in_loop = 0 503 return None 504 505 visitWith = NOP 506 507 visitYield = NOP 508 509 class Global: 510 511 """ 512 A reference to an object assigned to a global from outside the module 513 top-level. 514 """ 515 516 pass 517 518 # vim: tabstop=4 expandtab shiftwidth=4