1 """Interface to the compiler's internal symbol tables""" 2 3 import _symtable 4 from _symtable import (USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM, 5 DEF_IMPORT, DEF_BOUND, DEF_ANNOT, SCOPE_OFF, SCOPE_MASK, FREE, 6 LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL) 7 8 import weakref 9 10 __all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"] 11 12 def symtable(code, filename, compile_type): 13 top = _symtable.symtable(code, filename, compile_type) 14 return _newSymbolTable(top, filename) 15 16 class 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 37 class 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 113 class Function(SymbolTable): 114 115 # Default values for instance variables 116 __params = None 117 __locals = None 118 __frees = None 119 __globals = None 120 121 def __idents_matching(self, test_func): 122 return tuple([ident for ident in self.get_identifiers() 123 if test_func(self._table.symbols[ident])]) 124 125 def get_parameters(self): 126 if self.__params is None: 127 self.__params = self.__idents_matching(lambda x:x & DEF_PARAM) 128 return self.__params 129 130 def get_locals(self): 131 if self.__locals is None: 132 locs = (LOCAL, CELL) 133 test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs 134 self.__locals = self.__idents_matching(test) 135 return self.__locals 136 137 def get_globals(self): 138 if self.__globals is None: 139 glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT) 140 test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob 141 self.__globals = self.__idents_matching(test) 142 return self.__globals 143 144 def get_frees(self): 145 if self.__frees is None: 146 is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE 147 self.__frees = self.__idents_matching(is_free) 148 return self.__frees 149 150 151 class Class(SymbolTable): 152 153 __methods = None 154 155 def get_methods(self): 156 if self.__methods is None: 157 d = {} 158 for st in self._table.children: 159 d[st.name] = 1 160 self.__methods = tuple(d) 161 return self.__methods 162 163 164 class Symbol(object): 165 166 def __init__(self, name, flags, namespaces=None): 167 self.__name = name 168 self.__flags = flags 169 self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope() 170 self.__namespaces = namespaces or () 171 172 def __repr__(self): 173 return "<symbol {0!r}>".format(self.__name) 174 175 def get_name(self): 176 return self.__name 177 178 def is_referenced(self): 179 return bool(self.__flags & _symtable.USE) 180 181 def is_parameter(self): 182 return bool(self.__flags & DEF_PARAM) 183 184 def is_global(self): 185 return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)) 186 187 def is_declared_global(self): 188 return bool(self.__scope == GLOBAL_EXPLICIT) 189 190 def is_local(self): 191 return bool(self.__flags & DEF_BOUND) 192 193 def is_annotated(self): 194 return bool(self.__flags & DEF_ANNOT) 195 196 def is_free(self): 197 return bool(self.__scope == FREE) 198 199 def is_imported(self): 200 return bool(self.__flags & DEF_IMPORT) 201 202 def is_assigned(self): 203 return bool(self.__flags & DEF_LOCAL) 204 205 def is_namespace(self): 206 """Returns true if name binding introduces new namespace. 207 208 If the name is used as the target of a function or class 209 statement, this will be true. 210 211 Note that a single name can be bound to multiple objects. If 212 is_namespace() is true, the name may also be bound to other 213 objects, like an int or list, that does not introduce a new 214 namespace. 215 """ 216 return bool(self.__namespaces) 217 218 def get_namespaces(self): 219 """Return a list of namespaces bound to this name""" 220 return self.__namespaces 221 222 def get_namespace(self): 223 """Returns the single namespace bound to this name. 224 225 Raises ValueError if the name is bound to multiple namespaces. 226 """ 227 if len(self.__namespaces) != 1: 228 raise ValueError("name is bound to multiple namespaces") 229 return self.__namespaces[0] 230 231 if __name__ == "__main__": 232 import os, sys 233 with open(sys.argv[0]) as f: 234 src = f.read() 235 mod = symtable(src, os.path.split(sys.argv[0])[1], "exec") 236 for ident in mod.get_identifiers(): 237 info = mod.lookup(ident) 238 print(info, info.is_local(), info.is_namespace()) 239