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