1"""Interface to the compiler's internal symbol tables""" 2 3import _symtable 4from _symtable import (USE, DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL, DEF_PARAM, 5 DEF_IMPORT, DEF_BOUND, DEF_ANNOT, SCOPE_OFF, SCOPE_MASK, FREE, 6 LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL) 7 8import weakref 9 10__all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"] 11 12def symtable(code, filename, compile_type): 13 top = _symtable.symtable(code, filename, compile_type) 14 return _newSymbolTable(top, filename) 15 16class SymbolTableFactory: 17 def __init__(self): 18 self.__memo = weakref.WeakValueDictionary() 19 20 def new(self, table, filename): 21 if table.type == _symtable.TYPE_FUNCTION: 22 return Function(table, filename) 23 if table.type == _symtable.TYPE_CLASS: 24 return Class(table, filename) 25 return SymbolTable(table, filename) 26 27 def __call__(self, table, filename): 28 key = table, filename 29 obj = self.__memo.get(key, None) 30 if obj is None: 31 obj = self.__memo[key] = self.new(table, filename) 32 return obj 33 34_newSymbolTable = SymbolTableFactory() 35 36 37class SymbolTable(object): 38 39 def __init__(self, raw_table, filename): 40 self._table = raw_table 41 self._filename = filename 42 self._symbols = {} 43 44 def __repr__(self): 45 if self.__class__ == SymbolTable: 46 kind = "" 47 else: 48 kind = "%s " % self.__class__.__name__ 49 50 if self._table.name == "global": 51 return "<{0}SymbolTable for module {1}>".format(kind, self._filename) 52 else: 53 return "<{0}SymbolTable for {1} in {2}>".format(kind, 54 self._table.name, 55 self._filename) 56 57 def get_type(self): 58 if self._table.type == _symtable.TYPE_MODULE: 59 return "module" 60 if self._table.type == _symtable.TYPE_FUNCTION: 61 return "function" 62 if self._table.type == _symtable.TYPE_CLASS: 63 return "class" 64 assert self._table.type in (1, 2, 3), \ 65 "unexpected type: {0}".format(self._table.type) 66 67 def get_id(self): 68 return self._table.id 69 70 def get_name(self): 71 return self._table.name 72 73 def get_lineno(self): 74 return self._table.lineno 75 76 def is_optimized(self): 77 return bool(self._table.type == _symtable.TYPE_FUNCTION) 78 79 def is_nested(self): 80 return bool(self._table.nested) 81 82 def has_children(self): 83 return bool(self._table.children) 84 85 def has_exec(self): 86 """Return true if the scope uses exec. Deprecated method.""" 87 return False 88 89 def get_identifiers(self): 90 return self._table.symbols.keys() 91 92 def lookup(self, name): 93 sym = self._symbols.get(name) 94 if sym is None: 95 flags = self._table.symbols[name] 96 namespaces = self.__check_children(name) 97 sym = self._symbols[name] = Symbol(name, flags, namespaces) 98 return sym 99 100 def get_symbols(self): 101 return [self.lookup(ident) for ident in self.get_identifiers()] 102 103 def __check_children(self, name): 104 return [_newSymbolTable(st, self._filename) 105 for st in self._table.children 106 if st.name == name] 107 108 def get_children(self): 109 return [_newSymbolTable(st, self._filename) 110 for st in self._table.children] 111 112 113class Function(SymbolTable): 114 115 # Default values for instance variables 116 __params = None 117 __locals = None 118 __frees = None 119 __globals = None 120 __nonlocals = None 121 122 def __idents_matching(self, test_func): 123 return tuple(ident for ident in self.get_identifiers() 124 if test_func(self._table.symbols[ident])) 125 126 def get_parameters(self): 127 if self.__params is None: 128 self.__params = self.__idents_matching(lambda x:x & DEF_PARAM) 129 return self.__params 130 131 def get_locals(self): 132 if self.__locals is None: 133 locs = (LOCAL, CELL) 134 test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs 135 self.__locals = self.__idents_matching(test) 136 return self.__locals 137 138 def get_globals(self): 139 if self.__globals is None: 140 glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT) 141 test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob 142 self.__globals = self.__idents_matching(test) 143 return self.__globals 144 145 def get_nonlocals(self): 146 if self.__nonlocals is None: 147 self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL) 148 return self.__nonlocals 149 150 def get_frees(self): 151 if self.__frees is None: 152 is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE 153 self.__frees = self.__idents_matching(is_free) 154 return self.__frees 155 156 157class Class(SymbolTable): 158 159 __methods = None 160 161 def get_methods(self): 162 if self.__methods is None: 163 d = {} 164 for st in self._table.children: 165 d[st.name] = 1 166 self.__methods = tuple(d) 167 return self.__methods 168 169 170class Symbol(object): 171 172 def __init__(self, name, flags, namespaces=None): 173 self.__name = name 174 self.__flags = flags 175 self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope() 176 self.__namespaces = namespaces or () 177 178 def __repr__(self): 179 return "<symbol {0!r}>".format(self.__name) 180 181 def get_name(self): 182 return self.__name 183 184 def is_referenced(self): 185 return bool(self.__flags & _symtable.USE) 186 187 def is_parameter(self): 188 return bool(self.__flags & DEF_PARAM) 189 190 def is_global(self): 191 return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)) 192 193 def is_nonlocal(self): 194 return bool(self.__flags & DEF_NONLOCAL) 195 196 def is_declared_global(self): 197 return bool(self.__scope == GLOBAL_EXPLICIT) 198 199 def is_local(self): 200 return bool(self.__flags & DEF_BOUND) 201 202 def is_annotated(self): 203 return bool(self.__flags & DEF_ANNOT) 204 205 def is_free(self): 206 return bool(self.__scope == FREE) 207 208 def is_imported(self): 209 return bool(self.__flags & DEF_IMPORT) 210 211 def is_assigned(self): 212 return bool(self.__flags & DEF_LOCAL) 213 214 def is_namespace(self): 215 """Returns true if name binding introduces new namespace. 216 217 If the name is used as the target of a function or class 218 statement, this will be true. 219 220 Note that a single name can be bound to multiple objects. If 221 is_namespace() is true, the name may also be bound to other 222 objects, like an int or list, that does not introduce a new 223 namespace. 224 """ 225 return bool(self.__namespaces) 226 227 def get_namespaces(self): 228 """Return a list of namespaces bound to this name""" 229 return self.__namespaces 230 231 def get_namespace(self): 232 """Returns the single namespace bound to this name. 233 234 Raises ValueError if the name is bound to multiple namespaces. 235 """ 236 if len(self.__namespaces) != 1: 237 raise ValueError("name is bound to multiple namespaces") 238 return self.__namespaces[0] 239 240if __name__ == "__main__": 241 import os, sys 242 with open(sys.argv[0]) as f: 243 src = f.read() 244 mod = symtable(src, os.path.split(sys.argv[0])[1], "exec") 245 for ident in mod.get_identifiers(): 246 info = mod.lookup(ident) 247 print(info, info.is_local(), info.is_namespace()) 248