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 9from enum import StrEnum 10 11__all__ = ["symtable", "SymbolTableType", "SymbolTable", "Class", "Function", "Symbol"] 12 13def symtable(code, filename, compile_type): 14 """ Return the toplevel *SymbolTable* for the source code. 15 16 *filename* is the name of the file with the code 17 and *compile_type* is the *compile()* mode argument. 18 """ 19 top = _symtable.symtable(code, filename, compile_type) 20 return _newSymbolTable(top, filename) 21 22class SymbolTableFactory: 23 def __init__(self): 24 self.__memo = weakref.WeakValueDictionary() 25 26 def new(self, table, filename): 27 if table.type == _symtable.TYPE_FUNCTION: 28 return Function(table, filename) 29 if table.type == _symtable.TYPE_CLASS: 30 return Class(table, filename) 31 return SymbolTable(table, filename) 32 33 def __call__(self, table, filename): 34 key = table, filename 35 obj = self.__memo.get(key, None) 36 if obj is None: 37 obj = self.__memo[key] = self.new(table, filename) 38 return obj 39 40_newSymbolTable = SymbolTableFactory() 41 42 43class SymbolTableType(StrEnum): 44 MODULE = "module" 45 FUNCTION = "function" 46 CLASS = "class" 47 ANNOTATION = "annotation" 48 TYPE_ALIAS = "type alias" 49 TYPE_PARAMETERS = "type parameters" 50 TYPE_VARIABLE = "type variable" 51 52 53class SymbolTable: 54 55 def __init__(self, raw_table, filename): 56 self._table = raw_table 57 self._filename = filename 58 self._symbols = {} 59 60 def __repr__(self): 61 if self.__class__ == SymbolTable: 62 kind = "" 63 else: 64 kind = "%s " % self.__class__.__name__ 65 66 if self._table.name == "top": 67 return "<{0}SymbolTable for module {1}>".format(kind, self._filename) 68 else: 69 return "<{0}SymbolTable for {1} in {2}>".format(kind, 70 self._table.name, 71 self._filename) 72 73 def get_type(self): 74 """Return the type of the symbol table. 75 76 The value returned is one of the values in 77 the ``SymbolTableType`` enumeration. 78 """ 79 if self._table.type == _symtable.TYPE_MODULE: 80 return SymbolTableType.MODULE 81 if self._table.type == _symtable.TYPE_FUNCTION: 82 return SymbolTableType.FUNCTION 83 if self._table.type == _symtable.TYPE_CLASS: 84 return SymbolTableType.CLASS 85 if self._table.type == _symtable.TYPE_ANNOTATION: 86 return SymbolTableType.ANNOTATION 87 if self._table.type == _symtable.TYPE_TYPE_ALIAS: 88 return SymbolTableType.TYPE_ALIAS 89 if self._table.type == _symtable.TYPE_TYPE_PARAMETERS: 90 return SymbolTableType.TYPE_PARAMETERS 91 if self._table.type == _symtable.TYPE_TYPE_VARIABLE: 92 return SymbolTableType.TYPE_VARIABLE 93 assert False, f"unexpected type: {self._table.type}" 94 95 def get_id(self): 96 """Return an identifier for the table. 97 """ 98 return self._table.id 99 100 def get_name(self): 101 """Return the table's name. 102 103 This corresponds to the name of the class, function 104 or 'top' if the table is for a class, function or 105 global respectively. 106 """ 107 return self._table.name 108 109 def get_lineno(self): 110 """Return the number of the first line in the 111 block for the table. 112 """ 113 return self._table.lineno 114 115 def is_optimized(self): 116 """Return *True* if the locals in the table 117 are optimizable. 118 """ 119 return bool(self._table.type == _symtable.TYPE_FUNCTION) 120 121 def is_nested(self): 122 """Return *True* if the block is a nested class 123 or function.""" 124 return bool(self._table.nested) 125 126 def has_children(self): 127 """Return *True* if the block has nested namespaces. 128 """ 129 return bool(self._table.children) 130 131 def get_identifiers(self): 132 """Return a view object containing the names of symbols in the table. 133 """ 134 return self._table.symbols.keys() 135 136 def lookup(self, name): 137 """Lookup a *name* in the table. 138 139 Returns a *Symbol* instance. 140 """ 141 sym = self._symbols.get(name) 142 if sym is None: 143 flags = self._table.symbols[name] 144 namespaces = self.__check_children(name) 145 module_scope = (self._table.name == "top") 146 sym = self._symbols[name] = Symbol(name, flags, namespaces, 147 module_scope=module_scope) 148 return sym 149 150 def get_symbols(self): 151 """Return a list of *Symbol* instances for 152 names in the table. 153 """ 154 return [self.lookup(ident) for ident in self.get_identifiers()] 155 156 def __check_children(self, name): 157 return [_newSymbolTable(st, self._filename) 158 for st in self._table.children 159 if st.name == name] 160 161 def get_children(self): 162 """Return a list of the nested symbol tables. 163 """ 164 return [_newSymbolTable(st, self._filename) 165 for st in self._table.children] 166 167 168class Function(SymbolTable): 169 170 # Default values for instance variables 171 __params = None 172 __locals = None 173 __frees = None 174 __globals = None 175 __nonlocals = None 176 177 def __idents_matching(self, test_func): 178 return tuple(ident for ident in self.get_identifiers() 179 if test_func(self._table.symbols[ident])) 180 181 def get_parameters(self): 182 """Return a tuple of parameters to the function. 183 """ 184 if self.__params is None: 185 self.__params = self.__idents_matching(lambda x:x & DEF_PARAM) 186 return self.__params 187 188 def get_locals(self): 189 """Return a tuple of locals in the function. 190 """ 191 if self.__locals is None: 192 locs = (LOCAL, CELL) 193 test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs 194 self.__locals = self.__idents_matching(test) 195 return self.__locals 196 197 def get_globals(self): 198 """Return a tuple of globals in the function. 199 """ 200 if self.__globals is None: 201 glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT) 202 test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob 203 self.__globals = self.__idents_matching(test) 204 return self.__globals 205 206 def get_nonlocals(self): 207 """Return a tuple of nonlocals in the function. 208 """ 209 if self.__nonlocals is None: 210 self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL) 211 return self.__nonlocals 212 213 def get_frees(self): 214 """Return a tuple of free variables in the function. 215 """ 216 if self.__frees is None: 217 is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE 218 self.__frees = self.__idents_matching(is_free) 219 return self.__frees 220 221 222class Class(SymbolTable): 223 224 __methods = None 225 226 def get_methods(self): 227 """Return a tuple of methods declared in the class. 228 """ 229 if self.__methods is None: 230 d = {} 231 232 def is_local_symbol(ident): 233 flags = self._table.symbols.get(ident, 0) 234 return ((flags >> SCOPE_OFF) & SCOPE_MASK) == LOCAL 235 236 for st in self._table.children: 237 # pick the function-like symbols that are local identifiers 238 if is_local_symbol(st.name): 239 match st.type: 240 case _symtable.TYPE_FUNCTION: 241 # generators are of type TYPE_FUNCTION with a ".0" 242 # parameter as a first parameter (which makes them 243 # distinguishable from a function named 'genexpr') 244 if st.name == 'genexpr' and '.0' in st.varnames: 245 continue 246 d[st.name] = 1 247 case _symtable.TYPE_TYPE_PARAMETERS: 248 # Get the function-def block in the annotation 249 # scope 'st' with the same identifier, if any. 250 scope_name = st.name 251 for c in st.children: 252 if c.name == scope_name and c.type == _symtable.TYPE_FUNCTION: 253 # A generic generator of type TYPE_FUNCTION 254 # cannot be a direct child of 'st' (but it 255 # can be a descendant), e.g.: 256 # 257 # class A: 258 # type genexpr[genexpr] = (x for x in []) 259 assert scope_name != 'genexpr' or '.0' not in c.varnames 260 d[scope_name] = 1 261 break 262 self.__methods = tuple(d) 263 return self.__methods 264 265 266class Symbol: 267 268 def __init__(self, name, flags, namespaces=None, *, module_scope=False): 269 self.__name = name 270 self.__flags = flags 271 self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope() 272 self.__namespaces = namespaces or () 273 self.__module_scope = module_scope 274 275 def __repr__(self): 276 flags_str = '|'.join(self._flags_str()) 277 return f'<symbol {self.__name!r}: {self._scope_str()}, {flags_str}>' 278 279 def _scope_str(self): 280 return _scopes_value_to_name.get(self.__scope) or str(self.__scope) 281 282 def _flags_str(self): 283 for flagname, flagvalue in _flags: 284 if self.__flags & flagvalue == flagvalue: 285 yield flagname 286 287 def get_name(self): 288 """Return a name of a symbol. 289 """ 290 return self.__name 291 292 def is_referenced(self): 293 """Return *True* if the symbol is used in 294 its block. 295 """ 296 return bool(self.__flags & _symtable.USE) 297 298 def is_parameter(self): 299 """Return *True* if the symbol is a parameter. 300 """ 301 return bool(self.__flags & DEF_PARAM) 302 303 def is_global(self): 304 """Return *True* if the symbol is global. 305 """ 306 return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT) 307 or (self.__module_scope and self.__flags & DEF_BOUND)) 308 309 def is_nonlocal(self): 310 """Return *True* if the symbol is nonlocal.""" 311 return bool(self.__flags & DEF_NONLOCAL) 312 313 def is_declared_global(self): 314 """Return *True* if the symbol is declared global 315 with a global statement.""" 316 return bool(self.__scope == GLOBAL_EXPLICIT) 317 318 def is_local(self): 319 """Return *True* if the symbol is local. 320 """ 321 return bool(self.__scope in (LOCAL, CELL) 322 or (self.__module_scope and self.__flags & DEF_BOUND)) 323 324 def is_annotated(self): 325 """Return *True* if the symbol is annotated. 326 """ 327 return bool(self.__flags & DEF_ANNOT) 328 329 def is_free(self): 330 """Return *True* if a referenced symbol is 331 not assigned to. 332 """ 333 return bool(self.__scope == FREE) 334 335 def is_imported(self): 336 """Return *True* if the symbol is created from 337 an import statement. 338 """ 339 return bool(self.__flags & DEF_IMPORT) 340 341 def is_assigned(self): 342 """Return *True* if a symbol is assigned to.""" 343 return bool(self.__flags & DEF_LOCAL) 344 345 def is_namespace(self): 346 """Returns *True* if name binding introduces new namespace. 347 348 If the name is used as the target of a function or class 349 statement, this will be true. 350 351 Note that a single name can be bound to multiple objects. If 352 is_namespace() is true, the name may also be bound to other 353 objects, like an int or list, that does not introduce a new 354 namespace. 355 """ 356 return bool(self.__namespaces) 357 358 def get_namespaces(self): 359 """Return a list of namespaces bound to this name""" 360 return self.__namespaces 361 362 def get_namespace(self): 363 """Return the single namespace bound to this name. 364 365 Raises ValueError if the name is bound to multiple namespaces 366 or no namespace. 367 """ 368 if len(self.__namespaces) == 0: 369 raise ValueError("name is not bound to any namespaces") 370 elif len(self.__namespaces) > 1: 371 raise ValueError("name is bound to multiple namespaces") 372 else: 373 return self.__namespaces[0] 374 375 376_flags = [('USE', USE)] 377_flags.extend(kv for kv in globals().items() if kv[0].startswith('DEF_')) 378_scopes_names = ('FREE', 'LOCAL', 'GLOBAL_IMPLICIT', 'GLOBAL_EXPLICIT', 'CELL') 379_scopes_value_to_name = {globals()[n]: n for n in _scopes_names} 380 381 382def main(args): 383 import sys 384 def print_symbols(table, level=0): 385 indent = ' ' * level 386 nested = "nested " if table.is_nested() else "" 387 if table.get_type() == 'module': 388 what = f'from file {table._filename!r}' 389 else: 390 what = f'{table.get_name()!r}' 391 print(f'{indent}symbol table for {nested}{table.get_type()} {what}:') 392 for ident in table.get_identifiers(): 393 symbol = table.lookup(ident) 394 flags = ', '.join(symbol._flags_str()).lower() 395 print(f' {indent}{symbol._scope_str().lower()} symbol {symbol.get_name()!r}: {flags}') 396 print() 397 398 for table2 in table.get_children(): 399 print_symbols(table2, level + 1) 400 401 for filename in args or ['-']: 402 if filename == '-': 403 src = sys.stdin.read() 404 filename = '<stdin>' 405 else: 406 with open(filename, 'rb') as f: 407 src = f.read() 408 mod = symtable(src, filename, 'exec') 409 print_symbols(mod) 410 411 412if __name__ == "__main__": 413 import sys 414 main(sys.argv[1:]) 415