1 #!/usr/bin/env python 2 3 """ 4 Inspect source files, obtaining details of classes and attributes. 5 6 Copyright (C) 2007 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 import compiler.ast 23 from compiler.visitor import ASTVisitor 24 try: 25 set 26 except NameError: 27 from sets import Set as set 28 29 class InspectError(Exception): 30 31 "An inspection error." 32 33 pass 34 35 class NamespaceDict: 36 37 "A mix-in providing dictionary methods." 38 39 def __getitem__(self, name): 40 return self.namespace[name] 41 42 def keys(self): 43 return self.namespace.keys() 44 45 def values(self): 46 return self.namespace.values() 47 48 def items(self): 49 return self.namespace.items() 50 51 class Naming: 52 53 "A mix-in providing naming conveniences." 54 55 def full_name(self): 56 if self.name is not None: 57 return self.parent_name + "." + self.name 58 else: 59 return self.parent_name 60 61 class Class(NamespaceDict, Naming): 62 63 "An inspected class." 64 65 def __init__(self, name, parent_name): 66 self.name = name 67 self.parent_name = parent_name 68 self.bases = [] 69 self.namespace = {} 70 self.instattr = set() 71 self.instattr_names = [] # from instattr 72 73 def add_base(self, base): 74 self.bases.append(base) 75 76 def __repr__(self): 77 return "Class(%r, %r)" % (self.name, self.parent_name) 78 79 def class_attributes(self): 80 81 "Return all class attributes, indicating the class which provides them." 82 83 attr = {} 84 reversed_bases = self.bases[:] 85 reversed_bases.reverse() 86 for cls in reversed_bases: 87 attr.update(cls.class_attributes()) 88 for name, value in self.namespace.items(): 89 attr[name] = self.full_name() 90 return attr 91 92 def instance_attribute_names(self): 93 94 "Return the instance attribute names provided by the class." 95 96 self.instattr_names = self.instattr_names or list(self.instattr) 97 return self.instattr_names 98 99 def instance_attributes(self): 100 101 """ 102 Return all attributes for an instance, indicating either the class which 103 provides them or that the instance itself provides them. 104 """ 105 106 attr = {} 107 attr.update(self.class_attributes()) 108 for i, name in enumerate(self.instance_attribute_names()): 109 if attr.has_key(name): 110 print "Instance attribute %r in %r overrides class attribute." % (name, self) 111 attr[name] = i 112 return attr 113 114 class Function(Naming): 115 116 "An inspected function." 117 118 def __init__(self, name, parent_name, argnames, has_star, has_dstar): 119 self.name = name 120 self.parent_name = parent_name 121 self.argnames = argnames 122 self.has_star = has_star 123 self.has_dstar = has_dstar 124 125 def __repr__(self): 126 return "Function(%r, %r, %r, %r, %r)" % ( 127 self.name, self.parent_name, self.argnames, self.has_star, self.has_dstar 128 ) 129 130 def parameters(self): 131 132 """ 133 Return a dictionary mapping parameter names to their position in the 134 parameter list. 135 """ 136 137 parameters = {} 138 for i, name in enumerate(self.argnames): 139 parameters[name] = i 140 return parameters 141 142 class UnresolvedName(NamespaceDict, Naming): 143 144 "A module, class or function which was mentioned but could not be imported." 145 146 def __init__(self, name, parent_name): 147 self.name = name 148 self.parent_name = parent_name 149 self.namespace = {} 150 151 def class_attributes(self): 152 return {} 153 154 def __repr__(self): 155 return "UnresolvedName(%r, %r)" % (self.name, self.parent_name) 156 157 class Module(ASTVisitor, NamespaceDict): 158 159 "An inspected module." 160 161 def __init__(self, name, importer=None): 162 ASTVisitor.__init__(self) 163 self.visitor = self 164 165 self.name = name 166 self.importer = importer 167 self.loaded = 0 168 169 # Module namespace. 170 171 self.namespace = {} 172 self.namespace_names = [] # from namespace 173 174 # Complete lists of classes and functions. 175 176 self.all_objects = [] 177 178 # Current expression state. 179 180 self.expr = None 181 182 # Namespace state. 183 184 self.in_global = 1 185 self.in_init = 0 186 self.classes = [] 187 self.module = None 188 189 def parse(self, filename): 190 module = compiler.parseFile(filename) 191 self.process(module) 192 193 def process(self, module): 194 self.module = module 195 processed = self.dispatch(module) 196 if self.namespace.has_key("__all__"): 197 all = self.namespace["__all__"] 198 if isinstance(all, compiler.ast.List): 199 for n in all.nodes: 200 self.namespace[n.value] = self.importer.add_module(self.name + "." + n.value) 201 return processed 202 203 def vacuum(self): 204 for name, value in self.namespace.items(): 205 if isinstance(value, Module) and not value.loaded: 206 del self.namespace[name] 207 208 def full_name(self): 209 return self.name 210 211 def __repr__(self): 212 return "Module(%r)" % self.name 213 214 # Attribute methods. 215 216 def module_attribute_names(self): 217 218 "Return the module attribute names provided by the module." 219 220 self.namespace_names = self.namespace_names or self.namespace.keys() 221 return self.namespace_names 222 223 def module_attributes(self): 224 225 """ 226 Return a dictionary mapping module attributes to a position in the 227 module's namespace. 228 """ 229 230 attributes = {} 231 for i, name in enumerate(self.module_attribute_names()): 232 attributes[name] = i 233 return attributes 234 235 # Namespace methods. 236 237 def store(self, name, obj): 238 if self.in_global: 239 if not self.classes: 240 self.namespace[name] = obj 241 else: 242 self.classes[-1].namespace[name] = obj 243 self.all_objects.append(obj) 244 245 def store_attr(self, name): 246 if self.in_init: 247 self.classes[-1].instattr.add(name) 248 249 def get_parent(self): 250 return (self.classes[-1:] or [self])[0] 251 252 # Visitor methods. 253 254 def default(self, node, *args): 255 raise InspectError, node.__class__ 256 257 def dispatch(self, node, *args): 258 return ASTVisitor.dispatch(self, node, *args) 259 260 def NOP(self, node): 261 return node 262 263 visitAdd = NOP 264 265 visitAnd = NOP 266 267 visitAssert = NOP 268 269 def visitAssign(self, node): 270 self.expr = self.dispatch(node.expr) 271 for n in node.nodes: 272 self.dispatch(n) 273 return None 274 275 def visitAssAttr(self, node): 276 expr = self.dispatch(node.expr) 277 if expr is not None and isinstance(expr, Self): 278 self.store_attr(node.attrname) 279 return None 280 281 def visitAssList(self, node): 282 for n in node.nodes: 283 self.dispatch(n) 284 return None 285 286 def visitAssName(self, node): 287 self.store(node.name, self.expr) 288 return None 289 290 visitAssTuple = visitAssList 291 292 visitAugAssign = NOP 293 294 visitBackquote = NOP 295 296 visitBitand = NOP 297 298 visitBitor = NOP 299 300 visitBitxor = NOP 301 302 visitBreak = NOP 303 304 visitCallFunc = NOP 305 306 def visitClass(self, node): 307 if not self.in_global: 308 print "Class %r in %r is not global: ignored." % (node.name, self) 309 else: 310 cls = Class(node.name, self.get_parent().full_name()) 311 for base in node.bases: 312 base_ref = self.dispatch(base) 313 if base_ref is None: 314 raise InspectError, "Base class %r for class %r in %r is not found: it may be hidden in some way." % (base, self, cls) 315 cls.add_base(base_ref) 316 317 # Make an entry for the class. 318 319 self.store(node.name, cls) 320 321 self.classes.append(cls) 322 self.dispatch(node.code) 323 self.classes.pop() 324 325 return node 326 327 visitCompare = NOP 328 329 visitConst = NOP 330 331 visitContinue = NOP 332 333 visitDecorators = NOP 334 335 visitDict = NOP 336 337 visitDiscard = NOP 338 339 visitDiv = NOP 340 341 visitEllipsis = NOP 342 343 visitExec = NOP 344 345 visitExpression = NOP 346 347 visitFloorDiv = NOP 348 349 visitFor = NOP 350 351 def visitFrom(self, node): 352 if self.importer is None: 353 raise InspectError, "Please use the micropython.Importer class for code which uses the 'from' statement." 354 355 module = self.importer.load(node.modname, 1) 356 357 if module is None: 358 print "Warning:", node.modname, "not imported." 359 360 for name, alias in node.names: 361 if name != "*": 362 if module is not None and module.namespace.has_key(name): 363 self.namespace[alias or name] = attr = module.namespace[name] 364 if isinstance(attr, Module) and not attr.loaded: 365 self.importer.load(attr.name) 366 else: 367 self.namespace[alias or name] = UnresolvedName(name, node.modname) 368 else: 369 if module is not None: 370 for n in module.namespace.keys(): 371 self.namespace[n] = attr = module.namespace[n] 372 if isinstance(attr, Module) and not attr.loaded: 373 self.importer.load(attr.name) 374 375 return None 376 377 def visitFunction(self, node): 378 function = Function( 379 node.name, 380 self.get_parent().full_name(), 381 node.argnames, 382 has_star=(node.flags & 4 != 0), 383 has_dstar=(node.flags & 8 != 0) 384 ) 385 386 in_global = self.in_global 387 self.in_global = 0 388 if node.name == "__init__" and self.classes: 389 self.in_init = 1 390 self.dispatch(node.code) 391 self.in_init = 0 392 self.in_global = in_global 393 394 self.store(node.name, function) 395 return None 396 397 visitGenExpr = NOP 398 399 visitGenExprFor = NOP 400 401 visitGenExprIf = NOP 402 403 visitGenExprInner = NOP 404 405 def visitGetattr(self, node): 406 expr = self.dispatch(node.expr) 407 if expr is not None: 408 if isinstance(expr, Module): 409 return expr.namespace.get(node.attrname) 410 elif isinstance(expr, UnresolvedName): 411 return UnresolvedName(node.attrname, expr.full_name()) 412 return builtins_namespace.get(node.attrname) 413 414 visitGlobal = NOP 415 416 def visitIf(self, node): 417 for test, body in node.tests: 418 self.dispatch(body) 419 if node.else_ is not None: 420 self.dispatch(node.else_) 421 return None 422 423 def visitImport(self, node): 424 if self.importer is None: 425 raise InspectError, "Please use the micropython.Importer class for code which uses the 'import' statement." 426 427 for name, alias in node.names: 428 if alias is not None: 429 self.namespace[alias] = self.importer.load(name, 1) or UnresolvedName(None, name) 430 else: 431 self.namespace[name.split(".")[0]] = self.importer.load(name) or UnresolvedName(None, name.split(".")[0]) 432 433 return None 434 435 visitInvert = NOP 436 437 visitKeyword = NOP 438 439 visitLambda = NOP 440 441 visitLeftShift = NOP 442 443 visitList = NOP 444 445 visitListComp = NOP 446 447 visitListCompFor = NOP 448 449 visitListCompIf = NOP 450 451 visitMod = NOP 452 453 def visitModule(self, node): 454 return self.dispatch(node.node) 455 456 visitMul = NOP 457 458 def visitName(self, node): 459 name = node.name 460 if name == "self": 461 return Self() 462 elif self.namespace.has_key(name): 463 return self.namespace[name] 464 elif builtins_namespace.has_key(name): 465 return builtins_namespace[name] 466 else: 467 return None 468 469 visitNot = NOP 470 471 visitOr = NOP 472 473 visitPass = NOP 474 475 visitPower = NOP 476 477 visitPrint = NOP 478 479 visitPrintnl = NOP 480 481 visitRaise = NOP 482 483 visitReturn = NOP 484 485 visitRightShift = NOP 486 487 visitSlice = NOP 488 489 def visitStmt(self, node): 490 for n in node.nodes: 491 self.dispatch(n) 492 return None 493 494 visitSub = NOP 495 496 visitSubscript = NOP 497 498 def visitTryExcept(self, node): 499 self.dispatch(node.body) 500 for name, var, n in node.handlers: 501 self.dispatch(n) 502 if node.else_ is not None: 503 self.dispatch(node.else_) 504 return None 505 506 visitTryFinally = NOP 507 508 visitTuple = NOP 509 510 visitUnaryAdd = NOP 511 512 visitUnarySub = NOP 513 514 visitWhile = NOP 515 516 visitWith = NOP 517 518 visitYield = NOP 519 520 class Self: 521 522 "A reference to an object within a method." 523 524 pass 525 526 # Built-in types initialisation. 527 528 builtins_namespace = {} 529 for key in ['ArithmeticError', 'AssertionError', 'AttributeError', 530 'BaseException', 'DeprecationWarning', 'EOFError', 'Ellipsis', 531 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 532 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 533 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 534 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 535 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 536 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 537 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 538 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 539 'TabError', 'True', 'TypeError', 'UnboundLocalError', 540 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 541 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 542 'ValueError', 'Warning', 'ZeroDivisionError', 543 'bool', 'buffer', 'complex', 'dict', 'file', 'float', 'int', 'list', 544 'long', 'object', 'slice', 'str', 'tuple', 'type', 'unicode']: 545 builtins_namespace[key] = Class(key, "__builtins__") 546 547 # vim: tabstop=4 expandtab shiftwidth=4