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 Class: 36 37 "An inspected class." 38 39 def __init__(self, name): 40 self.name = name 41 self.bases = [] 42 self.namespace = {} 43 self.instattr = set() 44 45 def add_base(self, base): 46 self.bases.append(base) 47 48 class Function: 49 50 "An inspected function." 51 52 def __init__(self, name): 53 self.name = name 54 55 class Module(ASTVisitor): 56 57 "An inspected module." 58 59 def __init__(self, name, importer=None): 60 ASTVisitor.__init__(self) 61 self.visitor = self 62 63 self.name = name 64 self.importer = importer 65 self.loaded = 0 66 67 # Module namespace. 68 69 self.namespace = {} 70 self.namespace.update(builtins_namespace) 71 72 # Current expression state. 73 74 self.expr = None 75 76 # Namespace state. 77 78 self.in_global = 1 79 self.in_init = 0 80 self.classes = [] 81 self.module = None 82 83 def parse(self, filename): 84 module = compiler.parseFile(filename) 85 self.process(module) 86 87 def process(self, module): 88 self.module = module 89 processed = self.dispatch(module) 90 if self.namespace.has_key("__all__"): 91 all = self.namespace["__all__"] 92 if isinstance(all, compiler.ast.List): 93 for n in all.nodes: 94 self.namespace[n.value] = self.importer.add_module(self.name + "." + n.value) 95 return processed 96 97 def default(self, node, *args): 98 raise InspectError, node.__class__ 99 100 def dispatch(self, node, *args): 101 return ASTVisitor.dispatch(self, node, *args) 102 103 def store(self, name, obj): 104 if self.in_global: 105 if not self.classes: 106 self.namespace[name] = obj 107 else: 108 self.classes[-1].namespace[name] = obj 109 110 def store_attr(self, name): 111 if self.in_init: 112 self.classes[-1].instattr.add(name) 113 114 def NOP(self, node): 115 return node 116 117 visitAdd = NOP 118 119 visitAnd = NOP 120 121 def visitAssign(self, node): 122 self.expr = self.dispatch(node.expr) 123 for n in node.nodes: 124 self.dispatch(n) 125 return None 126 127 def visitAssAttr(self, node): 128 expr = self.dispatch(node.expr) 129 if expr is not None and isinstance(expr, Self): 130 self.store_attr(node.attrname) 131 return None 132 133 def visitAssList(self, node): 134 for n in node.nodes: 135 self.dispatch(n) 136 return None 137 138 def visitAssName(self, node): 139 self.store(node.name, self.expr) 140 return None 141 142 visitAssTuple = visitAssList 143 144 visitAugAssign = NOP 145 146 visitBitand = NOP 147 148 visitBitor = NOP 149 150 visitCallFunc = NOP 151 152 def visitClass(self, node): 153 if not self.in_global: 154 raise InspectError, "Class is %s not global: cannot handle this reasonably." % node.name 155 else: 156 cls = Class(node.name) 157 for base in node.bases: 158 base_ref = self.dispatch(base) 159 if base_ref is None: 160 raise InspectError, "Base class %s for class %s is not found: it may be hidden in some way." % (base, node.name) 161 cls.add_base(base_ref) 162 163 # Make an entry for the class. 164 165 self.store(node.name, cls) 166 167 self.classes.append(cls) 168 self.dispatch(node.code) 169 self.classes.pop() 170 171 return node 172 173 visitConst = NOP 174 175 visitDict = NOP 176 177 visitDiscard = NOP 178 179 visitFor = NOP 180 181 def visitFrom(self, node): 182 if self.importer is None: 183 raise InspectError, "Please use the micropython.Importer class for code which uses the 'from' statement." 184 185 module = self.importer.load(node.modname, 1) 186 187 if module is None: 188 print "Warning:", node.modname, "not imported." 189 return None 190 191 for name, alias in node.names: 192 if name != "*": 193 self.namespace[alias or name] = attr = module.namespace[name] 194 if isinstance(attr, Module) and not attr.loaded: 195 self.importer.load(attr.name) 196 else: 197 for n in module.namespace.keys(): 198 self.namespace[n] = attr = module.namespace[n] 199 if isinstance(attr, Module) and not attr.loaded: 200 self.importer.load(attr.name) 201 202 return None 203 204 def visitFunction(self, node): 205 self.store(node.name, Function(node.name)) 206 207 in_global = self.in_global 208 self.in_global = 0 209 if node.name == "__init__" and self.classes: 210 self.in_init = 1 211 self.dispatch(node.code) 212 self.in_init = 0 213 self.in_global = in_global 214 return None 215 216 def visitGetattr(self, node): 217 expr = self.dispatch(node.expr) 218 if expr is not None and isinstance(expr, Module): 219 return expr.namespace.get(node.attrname) 220 else: 221 return None 222 223 def visitIf(self, node): 224 for test, body in node.tests: 225 self.dispatch(body) 226 if node.else_ is not None: 227 self.dispatch(node.else_) 228 return None 229 230 def visitImport(self, node): 231 if self.importer is None: 232 raise InspectError, "Please use the micropython.Importer class for code which uses the 'import' statement." 233 234 for name, alias in node.names: 235 if alias is not None: 236 self.namespace[alias] = self.importer.load(name, 1) 237 else: 238 self.namespace[name.split(".")[0]] = self.importer.load(name) 239 240 return None 241 242 visitLeftShift = NOP 243 244 visitList = NOP 245 246 def visitModule(self, node): 247 return self.dispatch(node.node) 248 249 visitMul = NOP 250 251 def visitName(self, node): 252 name = node.name 253 if name == "self": 254 return Self() 255 elif self.namespace.has_key(name): 256 return self.namespace[name] 257 else: 258 return None 259 260 visitNot = NOP 261 262 visitOr = NOP 263 264 visitPass = NOP 265 266 visitRaise = NOP 267 268 visitRightShift = NOP 269 270 def visitStmt(self, node): 271 for n in node.nodes: 272 self.dispatch(n) 273 return None 274 275 visitSubscript = NOP 276 277 def visitTryExcept(self, node): 278 self.dispatch(node.body) 279 for name, var, n in node.handlers: 280 self.dispatch(n) 281 if node.else_ is not None: 282 self.dispatch(node.else_) 283 return None 284 285 visitTuple = NOP 286 287 class Self: 288 289 "A reference to an object within a method." 290 291 pass 292 293 builtins_namespace = {} 294 for key in ['ArithmeticError', 'AssertionError', 'AttributeError', 295 'BaseException', 'DeprecationWarning', 'EOFError', 'Ellipsis', 296 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 297 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 298 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 299 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 300 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 301 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 302 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 303 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 304 'TabError', 'True', 'TypeError', 'UnboundLocalError', 305 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 306 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 307 'ValueError', 'Warning', 'ZeroDivisionError', 'object']: 308 builtins_namespace[key] = Class(key) 309 310 # vim: tabstop=4 expandtab shiftwidth=4