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, OPT_IMPORT_STAR, OPT_EXEC, OPT_BARE_EXEC,
      6      SCOPE_OFF, SCOPE_MASK, FREE, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL, LOCAL)
      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                     and not self._table.optimized)
     79 
     80     def is_nested(self):
     81         return bool(self._table.nested)
     82 
     83     def has_children(self):
     84         return bool(self._table.children)
     85 
     86     def has_exec(self):
     87         """Return true if the scope uses exec"""
     88         return bool(self._table.optimized & (OPT_EXEC | OPT_BARE_EXEC))
     89 
     90     def has_import_star(self):
     91         """Return true if the scope uses import *"""
     92         return bool(self._table.optimized & OPT_IMPORT_STAR)
     93 
     94     def get_identifiers(self):
     95         return self._table.symbols.keys()
     96 
     97     def lookup(self, name):
     98         sym = self._symbols.get(name)
     99         if sym is None:
    100             flags = self._table.symbols[name]
    101             namespaces = self.__check_children(name)
    102             sym = self._symbols[name] = Symbol(name, flags, namespaces)
    103         return sym
    104 
    105     def get_symbols(self):
    106         return [self.lookup(ident) for ident in self.get_identifiers()]
    107 
    108     def __check_children(self, name):
    109         return [_newSymbolTable(st, self._filename)
    110                 for st in self._table.children
    111                 if st.name == name]
    112 
    113     def get_children(self):
    114         return [_newSymbolTable(st, self._filename)
    115                 for st in self._table.children]
    116 
    117 
    118 class Function(SymbolTable):
    119 
    120     # Default values for instance variables
    121     __params = None
    122     __locals = None
    123     __frees = None
    124     __globals = None
    125 
    126     def __idents_matching(self, test_func):
    127         return tuple([ident for ident in self.get_identifiers()
    128                       if test_func(self._table.symbols[ident])])
    129 
    130     def get_parameters(self):
    131         if self.__params is None:
    132             self.__params = self.__idents_matching(lambda x:x & DEF_PARAM)
    133         return self.__params
    134 
    135     def get_locals(self):
    136         if self.__locals is None:
    137             locs = (LOCAL, CELL)
    138             test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs
    139             self.__locals = self.__idents_matching(test)
    140         return self.__locals
    141 
    142     def get_globals(self):
    143         if self.__globals is None:
    144             glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
    145             test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob
    146             self.__globals = self.__idents_matching(test)
    147         return self.__globals
    148 
    149     def get_frees(self):
    150         if self.__frees is None:
    151             is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE
    152             self.__frees = self.__idents_matching(is_free)
    153         return self.__frees
    154 
    155 
    156 class Class(SymbolTable):
    157 
    158     __methods = None
    159 
    160     def get_methods(self):
    161         if self.__methods is None:
    162             d = {}
    163             for st in self._table.children:
    164                 d[st.name] = 1
    165             self.__methods = tuple(d)
    166         return self.__methods
    167 
    168 
    169 class Symbol(object):
    170 
    171     def __init__(self, name, flags, namespaces=None):
    172         self.__name = name
    173         self.__flags = flags
    174         self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
    175         self.__namespaces = namespaces or ()
    176 
    177     def __repr__(self):
    178         return "<symbol {0!r}>".format(self.__name)
    179 
    180     def get_name(self):
    181         return self.__name
    182 
    183     def is_referenced(self):
    184         return bool(self.__flags & _symtable.USE)
    185 
    186     def is_parameter(self):
    187         return bool(self.__flags & DEF_PARAM)
    188 
    189     def is_global(self):
    190         return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
    191 
    192     def is_declared_global(self):
    193         return bool(self.__scope == GLOBAL_EXPLICIT)
    194 
    195     def is_local(self):
    196         return bool(self.__flags & DEF_BOUND)
    197 
    198     def is_free(self):
    199         return bool(self.__scope == FREE)
    200 
    201     def is_imported(self):
    202         return bool(self.__flags & DEF_IMPORT)
    203 
    204     def is_assigned(self):
    205         return bool(self.__flags & DEF_LOCAL)
    206 
    207     def is_namespace(self):
    208         """Returns true if name binding introduces new namespace.
    209 
    210         If the name is used as the target of a function or class
    211         statement, this will be true.
    212 
    213         Note that a single name can be bound to multiple objects.  If
    214         is_namespace() is true, the name may also be bound to other
    215         objects, like an int or list, that does not introduce a new
    216         namespace.
    217         """
    218         return bool(self.__namespaces)
    219 
    220     def get_namespaces(self):
    221         """Return a list of namespaces bound to this name"""
    222         return self.__namespaces
    223 
    224     def get_namespace(self):
    225         """Returns the single namespace bound to this name.
    226 
    227         Raises ValueError if the name is bound to multiple namespaces.
    228         """
    229         if len(self.__namespaces) != 1:
    230             raise ValueError, "name is bound to multiple namespaces"
    231         return self.__namespaces[0]
    232 
    233 if __name__ == "__main__":
    234     import os, sys
    235     src = open(sys.argv[0]).read()
    236     mod = symtable(src, os.path.split(sys.argv[0])[1], "exec")
    237     for ident in mod.get_identifiers():
    238         info = mod.lookup(ident)
    239         print info, info.is_local(), info.is_namespace()
    240