Home | History | Annotate | Download | only in Lib
      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