• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Interface to the compiler's internal symbol tables"""
2
3import _symtable
4from _symtable import (USE, DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL, DEF_PARAM,
5     DEF_IMPORT, DEF_BOUND, DEF_ANNOT, SCOPE_OFF, SCOPE_MASK, FREE,
6     LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL)
7
8import weakref
9
10__all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"]
11
12def symtable(code, filename, compile_type):
13    """ Return the toplevel *SymbolTable* for the source code.
14
15    *filename* is the name of the file with the code
16    and *compile_type* is the *compile()* mode argument.
17    """
18    top = _symtable.symtable(code, filename, compile_type)
19    return _newSymbolTable(top, filename)
20
21class SymbolTableFactory:
22    def __init__(self):
23        self.__memo = weakref.WeakValueDictionary()
24
25    def new(self, table, filename):
26        if table.type == _symtable.TYPE_FUNCTION:
27            return Function(table, filename)
28        if table.type == _symtable.TYPE_CLASS:
29            return Class(table, filename)
30        return SymbolTable(table, filename)
31
32    def __call__(self, table, filename):
33        key = table, filename
34        obj = self.__memo.get(key, None)
35        if obj is None:
36            obj = self.__memo[key] = self.new(table, filename)
37        return obj
38
39_newSymbolTable = SymbolTableFactory()
40
41
42class SymbolTable:
43
44    def __init__(self, raw_table, filename):
45        self._table = raw_table
46        self._filename = filename
47        self._symbols = {}
48
49    def __repr__(self):
50        if self.__class__ == SymbolTable:
51            kind = ""
52        else:
53            kind = "%s " % self.__class__.__name__
54
55        if self._table.name == "top":
56            return "<{0}SymbolTable for module {1}>".format(kind, self._filename)
57        else:
58            return "<{0}SymbolTable for {1} in {2}>".format(kind,
59                                                            self._table.name,
60                                                            self._filename)
61
62    def get_type(self):
63        """Return the type of the symbol table.
64
65        The values retuned are 'class', 'module' and
66        'function'.
67        """
68        if self._table.type == _symtable.TYPE_MODULE:
69            return "module"
70        if self._table.type == _symtable.TYPE_FUNCTION:
71            return "function"
72        if self._table.type == _symtable.TYPE_CLASS:
73            return "class"
74        assert self._table.type in (1, 2, 3), \
75               "unexpected type: {0}".format(self._table.type)
76
77    def get_id(self):
78        """Return an identifier for the table.
79        """
80        return self._table.id
81
82    def get_name(self):
83        """Return the table's name.
84
85        This corresponds to the name of the class, function
86        or 'top' if the table is for a class, function or
87        global respectively.
88        """
89        return self._table.name
90
91    def get_lineno(self):
92        """Return the number of the first line in the
93        block for the table.
94        """
95        return self._table.lineno
96
97    def is_optimized(self):
98        """Return *True* if the locals in the table
99        are optimizable.
100        """
101        return bool(self._table.type == _symtable.TYPE_FUNCTION)
102
103    def is_nested(self):
104        """Return *True* if the block is a nested class
105        or function."""
106        return bool(self._table.nested)
107
108    def has_children(self):
109        """Return *True* if the block has nested namespaces.
110        """
111        return bool(self._table.children)
112
113    def get_identifiers(self):
114        """Return a list of names of symbols in the table.
115        """
116        return self._table.symbols.keys()
117
118    def lookup(self, name):
119        """Lookup a *name* in the table.
120
121        Returns a *Symbol* instance.
122        """
123        sym = self._symbols.get(name)
124        if sym is None:
125            flags = self._table.symbols[name]
126            namespaces = self.__check_children(name)
127            module_scope = (self._table.name == "top")
128            sym = self._symbols[name] = Symbol(name, flags, namespaces,
129                                               module_scope=module_scope)
130        return sym
131
132    def get_symbols(self):
133        """Return a list of *Symbol* instances for
134        names in the table.
135        """
136        return [self.lookup(ident) for ident in self.get_identifiers()]
137
138    def __check_children(self, name):
139        return [_newSymbolTable(st, self._filename)
140                for st in self._table.children
141                if st.name == name]
142
143    def get_children(self):
144        """Return a list of the nested symbol tables.
145        """
146        return [_newSymbolTable(st, self._filename)
147                for st in self._table.children]
148
149
150class Function(SymbolTable):
151
152    # Default values for instance variables
153    __params = None
154    __locals = None
155    __frees = None
156    __globals = None
157    __nonlocals = None
158
159    def __idents_matching(self, test_func):
160        return tuple(ident for ident in self.get_identifiers()
161                     if test_func(self._table.symbols[ident]))
162
163    def get_parameters(self):
164        """Return a tuple of parameters to the function.
165        """
166        if self.__params is None:
167            self.__params = self.__idents_matching(lambda x:x & DEF_PARAM)
168        return self.__params
169
170    def get_locals(self):
171        """Return a tuple of locals in the function.
172        """
173        if self.__locals is None:
174            locs = (LOCAL, CELL)
175            test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs
176            self.__locals = self.__idents_matching(test)
177        return self.__locals
178
179    def get_globals(self):
180        """Return a tuple of globals in the function.
181        """
182        if self.__globals is None:
183            glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
184            test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob
185            self.__globals = self.__idents_matching(test)
186        return self.__globals
187
188    def get_nonlocals(self):
189        """Return a tuple of nonlocals in the function.
190        """
191        if self.__nonlocals is None:
192            self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL)
193        return self.__nonlocals
194
195    def get_frees(self):
196        """Return a tuple of free variables in the function.
197        """
198        if self.__frees is None:
199            is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE
200            self.__frees = self.__idents_matching(is_free)
201        return self.__frees
202
203
204class Class(SymbolTable):
205
206    __methods = None
207
208    def get_methods(self):
209        """Return a tuple of methods declared in the class.
210        """
211        if self.__methods is None:
212            d = {}
213            for st in self._table.children:
214                d[st.name] = 1
215            self.__methods = tuple(d)
216        return self.__methods
217
218
219class Symbol:
220
221    def __init__(self, name, flags, namespaces=None, *, module_scope=False):
222        self.__name = name
223        self.__flags = flags
224        self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
225        self.__namespaces = namespaces or ()
226        self.__module_scope = module_scope
227
228    def __repr__(self):
229        return "<symbol {0!r}>".format(self.__name)
230
231    def get_name(self):
232        """Return a name of a symbol.
233        """
234        return self.__name
235
236    def is_referenced(self):
237        """Return *True* if the symbol is used in
238        its block.
239        """
240        return bool(self.__flags & _symtable.USE)
241
242    def is_parameter(self):
243        """Return *True* if the symbol is a parameter.
244        """
245        return bool(self.__flags & DEF_PARAM)
246
247    def is_global(self):
248        """Return *True* if the sysmbol is global.
249        """
250        return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
251                    or (self.__module_scope and self.__flags & DEF_BOUND))
252
253    def is_nonlocal(self):
254        """Return *True* if the symbol is nonlocal."""
255        return bool(self.__flags & DEF_NONLOCAL)
256
257    def is_declared_global(self):
258        """Return *True* if the symbol is declared global
259        with a global statement."""
260        return bool(self.__scope == GLOBAL_EXPLICIT)
261
262    def is_local(self):
263        """Return *True* if the symbol is local.
264        """
265        return bool(self.__scope in (LOCAL, CELL)
266                    or (self.__module_scope and self.__flags & DEF_BOUND))
267
268    def is_annotated(self):
269        """Return *True* if the symbol is annotated.
270        """
271        return bool(self.__flags & DEF_ANNOT)
272
273    def is_free(self):
274        """Return *True* if a referenced symbol is
275        not assigned to.
276        """
277        return bool(self.__scope == FREE)
278
279    def is_imported(self):
280        """Return *True* if the symbol is created from
281        an import statement.
282        """
283        return bool(self.__flags & DEF_IMPORT)
284
285    def is_assigned(self):
286        """Return *True* if a symbol is assigned to."""
287        return bool(self.__flags & DEF_LOCAL)
288
289    def is_namespace(self):
290        """Returns *True* if name binding introduces new namespace.
291
292        If the name is used as the target of a function or class
293        statement, this will be true.
294
295        Note that a single name can be bound to multiple objects.  If
296        is_namespace() is true, the name may also be bound to other
297        objects, like an int or list, that does not introduce a new
298        namespace.
299        """
300        return bool(self.__namespaces)
301
302    def get_namespaces(self):
303        """Return a list of namespaces bound to this name"""
304        return self.__namespaces
305
306    def get_namespace(self):
307        """Return the single namespace bound to this name.
308
309        Raises ValueError if the name is bound to multiple namespaces.
310        """
311        if len(self.__namespaces) != 1:
312            raise ValueError("name is bound to multiple namespaces")
313        return self.__namespaces[0]
314
315if __name__ == "__main__":
316    import os, sys
317    with open(sys.argv[0]) as f:
318        src = f.read()
319    mod = symtable(src, os.path.split(sys.argv[0])[1], "exec")
320    for ident in mod.get_identifiers():
321        info = mod.lookup(ident)
322        print(info, info.is_local(), info.is_namespace())
323