interface to the compiler s internal symbol tables import symtable from symtable import use defglobal defnonlocal deflocal defparam defimport defbound defannot scopeoff scopemask free local globalimplicit globalexplicit cell import weakref all symtable symboltable class function symbol def symtablecode filename compiletype top symtable symtablecode filename compiletype return newsymboltabletop filename class symboltablefactory def initself self memo weakref weakvaluedictionary def newself table filename if table type symtable typefunction return functiontable filename if table type symtable typeclass return classtable filename return symboltabletable filename def callself table filename key table filename obj self memo getkey none if obj is none obj self memokey self newtable filename return obj newsymboltable symboltablefactory class symboltable def initself rawtable filename self table rawtable self filename filename self symbols def reprself if self class symboltable kind else kind s self class name if self table name top return 0symboltable for module 1 formatkind self filename else return 0symboltable for 1 in 2 formatkind self table name self filename def gettypeself if self table type symtable typemodule return module if self table type symtable typefunction return function if self table type symtable typeclass return class if self table type symtable typeannotation return annotation if self table type symtable typetypevarbound return typevar bound if self table type symtable typetypealias return type alias if self table type symtable typetypeparam return type parameter assert false funexpected type self table type def getidself return self table id def getnameself return self table name def getlinenoself return self table lineno def isoptimizedself return boolself table type symtable typefunction def isnestedself return true if the block has nested namespaces return a view object containing the names of symbols in the table lookup a name in the table returns a symbol instance return a list of symbol instances for names in the table return a list of the nested symbol tables default values for instance variables return a tuple of parameters to the function return a tuple of locals in the function return a tuple of globals in the function return a tuple of nonlocals in the function return a tuple of free variables in the function return a tuple of methods declared in the class return a name of a symbol return true if the symbol is used in its block return true if the symbol is a parameter return true if the symbol is global return true if the symbol is nonlocal return boolself flags defnonlocal def isdeclaredglobalself return true if the symbol is local return true if the symbol is annotated return true if a referenced symbol is not assigned to return true if the symbol is created from an import statement return true if a symbol is assigned to return boolself flags deflocal def isnamespaceself return boolself namespaces def getnamespacesself return the single namespace bound to this name raises valueerror if the name is bound to multiple namespaces or no namespace return the toplevel symboltable for the source code filename is the name of the file with the code and compile_type is the compile mode argument return the type of the symbol table the values returned are class module function annotation typevar bound type alias and type parameter return an identifier for the table return the table s name this corresponds to the name of the class function or top if the table is for a class function or global respectively return the number of the first line in the block for the table return true if the locals in the table are optimizable return true if the block is a nested class or function return true if the block has nested namespaces return a view object containing the names of symbols in the table lookup a name in the table returns a symbol instance return a list of symbol instances for names in the table return a list of the nested symbol tables default values for instance variables return a tuple of parameters to the function return a tuple of locals in the function return a tuple of globals in the function return a tuple of nonlocals in the function return a tuple of free variables in the function return a tuple of methods declared in the class like pyst_getscope return a name of a symbol return true if the symbol is used in its block return true if the symbol is a parameter return true if the symbol is global return true if the symbol is nonlocal return true if the symbol is declared global with a global statement return true if the symbol is local return true if the symbol is annotated return true if a referenced symbol is not assigned to return true if the symbol is created from an import statement return true if a symbol is assigned to returns true if name binding introduces new namespace if the name is used as the target of a function or class statement this will be true note that a single name can be bound to multiple objects if is_namespace is true the name may also be bound to other objects like an int or list that does not introduce a new namespace return a list of namespaces bound to this name return the single namespace bound to this name raises valueerror if the name is bound to multiple namespaces or no namespace
import _symtable
from _symtable import (USE, DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL, DEF_PARAM,
     DEF_IMPORT, DEF_BOUND, DEF_ANNOT, SCOPE_OFF, SCOPE_MASK, FREE,
     LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL)

import weakref

__all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"]

def symtable(code, filename, compile_type):
    
    top = _symtable.symtable(code, filename, compile_type)
    return _newSymbolTable(top, filename)

class SymbolTableFactory:
    def __init__(self):
        self.__memo = weakref.WeakValueDictionary()

    def new(self, table, filename):
        if table.type == _symtable.TYPE_FUNCTION:
            return Function(table, filename)
        if table.type == _symtable.TYPE_CLASS:
            return Class(table, filename)
        return SymbolTable(table, filename)

    def __call__(self, table, filename):
        key = table, filename
        obj = self.__memo.get(key, None)
        if obj is None:
            obj = self.__memo[key] = self.new(table, filename)
        return obj

_newSymbolTable = SymbolTableFactory()


class SymbolTable:

    def __init__(self, raw_table, filename):
        self._table = raw_table
        self._filename = filename
        self._symbols = {}

    def __repr__(self):
        if self.__class__ == SymbolTable:
            kind = ""
        else:
            kind = "%s " % self.__class__.__name__

        if self._table.name == "top":
            return "<{0}SymbolTable for module {1}>".format(kind, self._filename)
        else:
            return "<{0}SymbolTable for {1} in {2}>".format(kind,
                                                            self._table.name,
                                                            self._filename)

    def get_type(self):
        
        if self._table.type == _symtable.TYPE_MODULE:
            return "module"
        if self._table.type == _symtable.TYPE_FUNCTION:
            return "function"
        if self._table.type == _symtable.TYPE_CLASS:
            return "class"
        if self._table.type == _symtable.TYPE_ANNOTATION:
            return "annotation"
        if self._table.type == _symtable.TYPE_TYPE_VAR_BOUND:
            return "TypeVar bound"
        if self._table.type == _symtable.TYPE_TYPE_ALIAS:
            return "type alias"
        if self._table.type == _symtable.TYPE_TYPE_PARAM:
            return "type parameter"
        assert False, f"unexpected type: {self._table.type}"

    def get_id(self):
        
        return self._table.id

    def get_name(self):
        
        return self._table.name

    def get_lineno(self):
        
        return self._table.lineno

    def is_optimized(self):
        
        return bool(self._table.type == _symtable.TYPE_FUNCTION)

    def is_nested(self):
        
        return bool(self._table.nested)

    def has_children(self):
        
        return bool(self._table.children)

    def get_identifiers(self):
        
        return self._table.symbols.keys()

    def lookup(self, name):
        
        sym = self._symbols.get(name)
        if sym is None:
            flags = self._table.symbols[name]
            namespaces = self.__check_children(name)
            module_scope = (self._table.name == "top")
            sym = self._symbols[name] = Symbol(name, flags, namespaces,
                                               module_scope=module_scope)
        return sym

    def get_symbols(self):
        
        return [self.lookup(ident) for ident in self.get_identifiers()]

    def __check_children(self, name):
        return [_newSymbolTable(st, self._filename)
                for st in self._table.children
                if st.name == name]

    def get_children(self):
        
        return [_newSymbolTable(st, self._filename)
                for st in self._table.children]


class Function(SymbolTable):

    
    __params = None
    __locals = None
    __frees = None
    __globals = None
    __nonlocals = None

    def __idents_matching(self, test_func):
        return tuple(ident for ident in self.get_identifiers()
                     if test_func(self._table.symbols[ident]))

    def get_parameters(self):
        
        if self.__params is None:
            self.__params = self.__idents_matching(lambda x:x & DEF_PARAM)
        return self.__params

    def get_locals(self):
        
        if self.__locals is None:
            locs = (LOCAL, CELL)
            test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs
            self.__locals = self.__idents_matching(test)
        return self.__locals

    def get_globals(self):
        
        if self.__globals is None:
            glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
            test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob
            self.__globals = self.__idents_matching(test)
        return self.__globals

    def get_nonlocals(self):
        
        if self.__nonlocals is None:
            self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL)
        return self.__nonlocals

    def get_frees(self):
        
        if self.__frees is None:
            is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE
            self.__frees = self.__idents_matching(is_free)
        return self.__frees


class Class(SymbolTable):

    __methods = None

    def get_methods(self):
        
        if self.__methods is None:
            d = {}
            for st in self._table.children:
                d[st.name] = 1
            self.__methods = tuple(d)
        return self.__methods


class Symbol:

    def __init__(self, name, flags, namespaces=None, *, module_scope=False):
        self.__name = name
        self.__flags = flags
        self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK 
        self.__namespaces = namespaces or ()
        self.__module_scope = module_scope

    def __repr__(self):
        flags_str = '|'.join(self._flags_str())
        return f'<symbol {self.__name!r}: {self._scope_str()}, {flags_str}>'

    def _scope_str(self):
        return _scopes_value_to_name.get(self.__scope) or str(self.__scope)

    def _flags_str(self):
        for flagname, flagvalue in _flags:
            if self.__flags & flagvalue == flagvalue:
                yield flagname

    def get_name(self):
        
        return self.__name

    def is_referenced(self):
        
        return bool(self.__flags & _symtable.USE)

    def is_parameter(self):
        
        return bool(self.__flags & DEF_PARAM)

    def is_global(self):
        
        return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
                    or (self.__module_scope and self.__flags & DEF_BOUND))

    def is_nonlocal(self):
        
        return bool(self.__flags & DEF_NONLOCAL)

    def is_declared_global(self):
        
        return bool(self.__scope == GLOBAL_EXPLICIT)

    def is_local(self):
        
        return bool(self.__scope in (LOCAL, CELL)
                    or (self.__module_scope and self.__flags & DEF_BOUND))

    def is_annotated(self):
        
        return bool(self.__flags & DEF_ANNOT)

    def is_free(self):
        
        return bool(self.__scope == FREE)

    def is_imported(self):
        
        return bool(self.__flags & DEF_IMPORT)

    def is_assigned(self):
        
        return bool(self.__flags & DEF_LOCAL)

    def is_namespace(self):
        
        return bool(self.__namespaces)

    def get_namespaces(self):
        
        return self.__namespaces

    def get_namespace(self):
        
        if len(self.__namespaces) == 0:
            raise ValueError("name is not bound to any namespaces")
        elif len(self.__namespaces) > 1:
            raise ValueError("name is bound to multiple namespaces")
        else:
            return self.__namespaces[0]


_flags = [('USE', USE)]
_flags.extend(kv for kv in globals().items() if kv[0].startswith('DEF_'))
_scopes_names = ('FREE', 'LOCAL', 'GLOBAL_IMPLICIT', 'GLOBAL_EXPLICIT', 'CELL')
_scopes_value_to_name = {globals()[n]: n for n in _scopes_names}


def main(args):
    import sys
    def print_symbols(table, level=0):
        indent = '    ' * level
        nested = "nested " if table.is_nested() else ""
        if table.get_type() == 'module':
            what = f'from file {table._filename!r}'
        else:
            what = f'{table.get_name()!r}'
        print(f'{indent}symbol table for {nested}{table.get_type()} {what}:')
        for ident in table.get_identifiers():
            symbol = table.lookup(ident)
            flags = ', '.join(symbol._flags_str()).lower()
            print(f'    {indent}{symbol._scope_str().lower()} symbol {symbol.get_name()!r}: {flags}')
        print()

        for table2 in table.get_children():
            print_symbols(table2, level + 1)

    for filename in args or ['-']:
        if filename == '-':
            src = sys.stdin.read()
            filename = '<stdin>'
        else:
            with open(filename, 'rb') as f:
                src = f.read()
        mod = symtable(src, filename, 'exec')
        print_symbols(mod)


if __name__ == "__main__":
    import sys
    main(sys.argv[1:])