symtable.py 7.1 KB
Newer Older
1 2 3
"""Interface to the compiler's internal symbol tables"""

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 9

import weakref

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 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

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

34
_newSymbolTable = SymbolTableFactory()
Tim Peters's avatar
Tim Peters committed
35

36

37 38
class SymbolTable(object):

39 40 41 42 43 44 45 46 47 48
    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__
Tim Peters's avatar
Tim Peters committed
49

50
        if self._table.name == "global":
51
            return "<{0}SymbolTable for module {1}>".format(kind, self._filename)
52
        else:
53 54 55
            return "<{0}SymbolTable for {1} in {2}>".format(kind,
                                                            self._table.name,
                                                            self._filename)
56 57 58 59 60 61 62 63 64

    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"
        assert self._table.type in (1, 2, 3), \
65
               "unexpected type: {0}".format(self._table.type)
66 67 68 69 70 71 72 73 74 75 76

    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):
77
        return bool(self._table.type == _symtable.TYPE_FUNCTION)
78 79 80 81 82 83 84 85

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

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

    def has_exec(self):
86 87
        """Return true if the scope uses exec.  Deprecated method."""
        return False
88

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    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)
            sym = self._symbols[name] = Symbol(name, flags, namespaces)
        return sym

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

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

Jeremy Hylton's avatar
Jeremy Hylton committed
108
    def get_children(self):
109
        return [_newSymbolTable(st, self._filename)
Jeremy Hylton's avatar
Jeremy Hylton committed
110 111
                for st in self._table.children]

112

113 114 115 116 117 118 119 120 121
class Function(SymbolTable):

    # Default values for instance variables
    __params = None
    __locals = None
    __frees = None
    __globals = None

    def __idents_matching(self, test_func):
122 123
        return tuple(ident for ident in self.get_identifiers()
                     if test_func(self._table.symbols[ident]))
124 125 126 127 128 129 130 131

    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:
132 133
            locs = (LOCAL, CELL)
            test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs
Benjamin Peterson's avatar
Benjamin Peterson committed
134
            self.__locals = self.__idents_matching(test)
135
        return self.__locals
Tim Peters's avatar
Tim Peters committed
136

137 138
    def get_globals(self):
        if self.__globals is None:
139 140 141
            glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
            test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob
            self.__globals = self.__idents_matching(test)
142 143 144 145
        return self.__globals

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

150

151 152 153 154 155 156 157 158 159
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
Benjamin Peterson's avatar
Benjamin Peterson committed
160
            self.__methods = tuple(d)
161 162
        return self.__methods

163 164 165

class Symbol(object):

166 167 168
    def __init__(self, name, flags, namespaces=None):
        self.__name = name
        self.__flags = flags
169
        self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
170 171 172
        self.__namespaces = namespaces or ()

    def __repr__(self):
173
        return "<symbol {0!r}>".format(self.__name)
174 175 176 177 178 179 180 181 182 183 184

    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):
185
        return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
186

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

190 191 192
    def is_local(self):
        return bool(self.__flags & DEF_BOUND)

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

196
    def is_free(self):
197
        return bool(self.__scope == FREE)
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227

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

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

    def is_namespace(self):
        """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 bool(self.__namespaces)

    def get_namespaces(self):
        """Return a list of namespaces bound to this name"""
        return self.__namespaces

    def get_namespace(self):
        """Returns the single namespace bound to this name.

        Raises ValueError if the name is bound to multiple namespaces.
        """
        if len(self.__namespaces) != 1:
228
            raise ValueError("name is bound to multiple namespaces")
229 230 231 232
        return self.__namespaces[0]

if __name__ == "__main__":
    import os, sys
233 234
    with open(sys.argv[0]) as f:
        src = f.read()
235 236 237
    mod = symtable(src, os.path.split(sys.argv[0])[1], "exec")
    for ident in mod.get_identifiers():
        info = mod.lookup(ident)
238
        print(info, info.is_local(), info.is_namespace())