Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
symtable.py452 linesDownload Raw Back to Lib
1"""Interface to the compiler's internal symbol tables"""2 3import _symtable4from _symtable import (5    USE,6    DEF_GLOBAL,  # noqa: F4017    DEF_NONLOCAL, DEF_LOCAL,8    DEF_PARAM, DEF_TYPE_PARAM, DEF_FREE_CLASS,9    DEF_IMPORT, DEF_BOUND, DEF_ANNOT,10    DEF_COMP_ITER, DEF_COMP_CELL,11    SCOPE_OFF, SCOPE_MASK,12    FREE, LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL13)14 15import weakref16from enum import StrEnum17 18__all__ = ["symtable", "SymbolTableType", "SymbolTable", "Class", "Function", "Symbol"]19 20def symtable(code, filename, compile_type):21    """ Return the toplevel *SymbolTable* for the source code.22 23    *filename* is the name of the file with the code24    and *compile_type* is the *compile()* mode argument.25    """26    top = _symtable.symtable(code, filename, compile_type)27    return _newSymbolTable(top, filename)28 29class SymbolTableFactory:30    def __init__(self):31        self.__memo = weakref.WeakValueDictionary()32 33    def new(self, table, filename):34        if table.type == _symtable.TYPE_FUNCTION:35            return Function(table, filename)36        if table.type == _symtable.TYPE_CLASS:37            return Class(table, filename)38        return SymbolTable(table, filename)39 40    def __call__(self, table, filename):41        key = table, filename42        obj = self.__memo.get(key, None)43        if obj is None:44            obj = self.__memo[key] = self.new(table, filename)45        return obj46 47_newSymbolTable = SymbolTableFactory()48 49 50class SymbolTableType(StrEnum):51    MODULE = "module"52    FUNCTION = "function"53    CLASS = "class"54    ANNOTATION = "annotation"55    TYPE_ALIAS = "type alias"56    TYPE_PARAMETERS = "type parameters"57    TYPE_VARIABLE = "type variable"58 59 60class SymbolTable:61 62    def __init__(self, raw_table, filename):63        self._table = raw_table64        self._filename = filename65        self._symbols = {}66 67    def __repr__(self):68        if self.__class__ == SymbolTable:69            kind = ""70        else:71            kind = "%s " % self.__class__.__name__72 73        if self._table.name == "top":74            return "<{0}SymbolTable for module {1}>".format(kind, self._filename)75        else:76            return "<{0}SymbolTable for {1} in {2}>".format(kind,77                                                            self._table.name,78                                                            self._filename)79 80    def get_type(self):81        """Return the type of the symbol table.82 83        The value returned is one of the values in84        the ``SymbolTableType`` enumeration.85        """86        if self._table.type == _symtable.TYPE_MODULE:87            return SymbolTableType.MODULE88        if self._table.type == _symtable.TYPE_FUNCTION:89            return SymbolTableType.FUNCTION90        if self._table.type == _symtable.TYPE_CLASS:91            return SymbolTableType.CLASS92        if self._table.type == _symtable.TYPE_ANNOTATION:93            return SymbolTableType.ANNOTATION94        if self._table.type == _symtable.TYPE_TYPE_ALIAS:95            return SymbolTableType.TYPE_ALIAS96        if self._table.type == _symtable.TYPE_TYPE_PARAMETERS:97            return SymbolTableType.TYPE_PARAMETERS98        if self._table.type == _symtable.TYPE_TYPE_VARIABLE:99            return SymbolTableType.TYPE_VARIABLE100        assert False, f"unexpected type: {self._table.type}"101 102    def get_id(self):103        """Return an identifier for the table.104        """105        return self._table.id106 107    def get_name(self):108        """Return the table's name.109 110        This corresponds to the name of the class, function111        or 'top' if the table is for a class, function or112        global respectively.113        """114        return self._table.name115 116    def get_lineno(self):117        """Return the number of the first line in the118        block for the table.119        """120        return self._table.lineno121 122    def is_optimized(self):123        """Return *True* if the locals in the table124        are optimizable.125        """126        return bool(self._table.type == _symtable.TYPE_FUNCTION)127 128    def is_nested(self):129        """Return *True* if the block is a nested class130        or function."""131        return bool(self._table.nested)132 133    def has_children(self):134        """Return *True* if the block has nested namespaces.135        """136        return bool(self._table.children)137 138    def get_identifiers(self):139        """Return a view object containing the names of symbols in the table.140        """141        return self._table.symbols.keys()142 143    def lookup(self, name):144        """Lookup a *name* in the table.145 146        Returns a *Symbol* instance.147        """148        sym = self._symbols.get(name)149        if sym is None:150            flags = self._table.symbols[name]151            namespaces = self.__check_children(name)152            module_scope = (self._table.name == "top")153            sym = self._symbols[name] = Symbol(name, flags, namespaces,154                                               module_scope=module_scope)155        return sym156 157    def get_symbols(self):158        """Return a list of *Symbol* instances for159        names in the table.160        """161        return [self.lookup(ident) for ident in self.get_identifiers()]162 163    def __check_children(self, name):164        return [_newSymbolTable(st, self._filename)165                for st in self._table.children166                if st.name == name]167 168    def get_children(self):169        """Return a list of the nested symbol tables.170        """171        return [_newSymbolTable(st, self._filename)172                for st in self._table.children]173 174 175def _get_scope(flags):  # like _PyST_GetScope()176    return (flags >> SCOPE_OFF) & SCOPE_MASK177 178 179class Function(SymbolTable):180 181    # Default values for instance variables182    __params = None183    __locals = None184    __frees = None185    __globals = None186    __nonlocals = None187 188    def __idents_matching(self, test_func):189        return tuple(ident for ident in self.get_identifiers()190                     if test_func(self._table.symbols[ident]))191 192    def get_parameters(self):193        """Return a tuple of parameters to the function.194        """195        if self.__params is None:196            self.__params = self.__idents_matching(lambda x:x & DEF_PARAM)197        return self.__params198 199    def get_locals(self):200        """Return a tuple of locals in the function.201        """202        if self.__locals is None:203            locs = (LOCAL, CELL)204            test = lambda x: _get_scope(x) in locs205            self.__locals = self.__idents_matching(test)206        return self.__locals207 208    def get_globals(self):209        """Return a tuple of globals in the function.210        """211        if self.__globals is None:212            glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)213            test = lambda x: _get_scope(x) in glob214            self.__globals = self.__idents_matching(test)215        return self.__globals216 217    def get_nonlocals(self):218        """Return a tuple of nonlocals in the function.219        """220        if self.__nonlocals is None:221            self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL)222        return self.__nonlocals223 224    def get_frees(self):225        """Return a tuple of free variables in the function.226        """227        if self.__frees is None:228            is_free = lambda x: _get_scope(x) == FREE229            self.__frees = self.__idents_matching(is_free)230        return self.__frees231 232 233class Class(SymbolTable):234 235    __methods = None236 237    def get_methods(self):238        """Return a tuple of methods declared in the class.239        """240        import warnings241        typename = f'{self.__class__.__module__}.{self.__class__.__name__}'242        warnings.warn(f'{typename}.get_methods() is deprecated '243                      f'and will be removed in Python 3.16.',244                      DeprecationWarning, stacklevel=2)245 246        if self.__methods is None:247            d = {}248 249            def is_local_symbol(ident):250                flags = self._table.symbols.get(ident, 0)251                return ((flags >> SCOPE_OFF) & SCOPE_MASK) == LOCAL252 253            for st in self._table.children:254                # pick the function-like symbols that are local identifiers255                if is_local_symbol(st.name):256                    match st.type:257                        case _symtable.TYPE_FUNCTION:258                            # generators are of type TYPE_FUNCTION with a ".0"259                            # parameter as a first parameter (which makes them260                            # distinguishable from a function named 'genexpr')261                            if st.name == 'genexpr' and '.0' in st.varnames:262                                continue263                            d[st.name] = 1264                        case _symtable.TYPE_TYPE_PARAMETERS:265                            # Get the function-def block in the annotation266                            # scope 'st' with the same identifier, if any.267                            scope_name = st.name268                            for c in st.children:269                                if c.name == scope_name and c.type == _symtable.TYPE_FUNCTION:270                                    # A generic generator of type TYPE_FUNCTION271                                    # cannot be a direct child of 'st' (but it272                                    # can be a descendant), e.g.:273                                    #274                                    # class A:275                                    #   type genexpr[genexpr] = (x for x in [])276                                    assert scope_name != 'genexpr' or '.0' not in c.varnames277                                    d[scope_name] = 1278                                    break279            self.__methods = tuple(d)280        return self.__methods281 282 283class Symbol:284 285    def __init__(self, name, flags, namespaces=None, *, module_scope=False):286        self.__name = name287        self.__flags = flags288        self.__scope = _get_scope(flags)289        self.__namespaces = namespaces or ()290        self.__module_scope = module_scope291 292    def __repr__(self):293        flags_str = '|'.join(self._flags_str())294        return f'<symbol {self.__name!r}: {self._scope_str()}, {flags_str}>'295 296    def _scope_str(self):297        return _scopes_value_to_name.get(self.__scope) or str(self.__scope)298 299    def _flags_str(self):300        for flagname, flagvalue in _flags:301            if self.__flags & flagvalue == flagvalue:302                yield flagname303 304    def get_name(self):305        """Return a name of a symbol.306        """307        return self.__name308 309    def is_referenced(self):310        """Return *True* if the symbol is used in311        its block.312        """313        return bool(self.__flags & USE)314 315    def is_parameter(self):316        """Return *True* if the symbol is a parameter.317        """318        return bool(self.__flags & DEF_PARAM)319 320    def is_type_parameter(self):321        """Return *True* if the symbol is a type parameter.322        """323        return bool(self.__flags & DEF_TYPE_PARAM)324 325    def is_global(self):326        """Return *True* if the symbol is global.327        """328        return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)329                    or (self.__module_scope and self.__flags & DEF_BOUND))330 331    def is_nonlocal(self):332        """Return *True* if the symbol is nonlocal."""333        return bool(self.__flags & DEF_NONLOCAL)334 335    def is_declared_global(self):336        """Return *True* if the symbol is declared global337        with a global statement."""338        return bool(self.__scope == GLOBAL_EXPLICIT)339 340    def is_local(self):341        """Return *True* if the symbol is local.342        """343        return bool(self.__scope in (LOCAL, CELL)344                    or (self.__module_scope and self.__flags & DEF_BOUND))345 346    def is_annotated(self):347        """Return *True* if the symbol is annotated.348        """349        return bool(self.__flags & DEF_ANNOT)350 351    def is_free(self):352        """Return *True* if a referenced symbol is353        not assigned to.354        """355        return bool(self.__scope == FREE)356 357    def is_free_class(self):358        """Return *True* if a class-scoped symbol is free from359        the perspective of a method."""360        return bool(self.__flags & DEF_FREE_CLASS)361 362    def is_imported(self):363        """Return *True* if the symbol is created from364        an import statement.365        """366        return bool(self.__flags & DEF_IMPORT)367 368    def is_assigned(self):369        """Return *True* if a symbol is assigned to."""370        return bool(self.__flags & DEF_LOCAL)371 372    def is_comp_iter(self):373        """Return *True* if the symbol is a comprehension iteration variable.374        """375        return bool(self.__flags & DEF_COMP_ITER)376 377    def is_comp_cell(self):378        """Return *True* if the symbol is a cell in an inlined comprehension.379        """380        return bool(self.__flags & DEF_COMP_CELL)381 382    def is_namespace(self):383        """Returns *True* if name binding introduces new namespace.384 385        If the name is used as the target of a function or class386        statement, this will be true.387 388        Note that a single name can be bound to multiple objects.  If389        is_namespace() is true, the name may also be bound to other390        objects, like an int or list, that does not introduce a new391        namespace.392        """393        return bool(self.__namespaces)394 395    def get_namespaces(self):396        """Return a list of namespaces bound to this name"""397        return self.__namespaces398 399    def get_namespace(self):400        """Return the single namespace bound to this name.401 402        Raises ValueError if the name is bound to multiple namespaces403        or no namespace.404        """405        if len(self.__namespaces) == 0:406            raise ValueError("name is not bound to any namespaces")407        elif len(self.__namespaces) > 1:408            raise ValueError("name is bound to multiple namespaces")409        else:410            return self.__namespaces[0]411 412 413_flags = [('USE', USE)]414_flags.extend(kv for kv in globals().items() if kv[0].startswith('DEF_'))415_scopes_names = ('FREE', 'LOCAL', 'GLOBAL_IMPLICIT', 'GLOBAL_EXPLICIT', 'CELL')416_scopes_value_to_name = {globals()[n]: n for n in _scopes_names}417 418 419def main(args):420    import sys421    def print_symbols(table, level=0):422        indent = '    ' * level423        nested = "nested " if table.is_nested() else ""424        if table.get_type() == 'module':425            what = f'from file {table._filename!r}'426        else:427            what = f'{table.get_name()!r}'428        print(f'{indent}symbol table for {nested}{table.get_type()} {what}:')429        for ident in table.get_identifiers():430            symbol = table.lookup(ident)431            flags = ', '.join(symbol._flags_str()).lower()432            print(f'    {indent}{symbol._scope_str().lower()} symbol {symbol.get_name()!r}: {flags}')433        print()434 435        for table2 in table.get_children():436            print_symbols(table2, level + 1)437 438    for filename in args or ['-']:439        if filename == '-':440            src = sys.stdin.read()441            filename = '<stdin>'442        else:443            with open(filename, 'rb') as f:444                src = f.read()445        mod = symtable(src, filename, 'exec')446        print_symbols(mod)447 448 449if __name__ == "__main__":450    import sys451    main(sys.argv[1:])452 
codekingpro/portable-devtools · Team Ai