mirror of
https://github.com/python/cpython.git
synced 2024-11-28 12:31:14 +08:00
f37708e048
svn+ssh://pythondev@svn.python.org/python/trunk The merge ran into a lot of conflicts because dicts were replaced with sets in the Python 3 version of the symbol table. ........ r70801 | jeremy.hylton | 2009-03-31 09:17:03 -0400 (Tue, 31 Mar 2009) | 3 lines Add is_declared_global() which distinguishes between implicit and explicit global variables. ........ r70809 | jeremy.hylton | 2009-03-31 09:48:15 -0400 (Tue, 31 Mar 2009) | 14 lines Global statements from one function leaked into parallel functions. Re http://bugs.python.org/issue4315 The symbol table used the same name dictionaries to recursively analyze each of its child blocks, even though the dictionaries are modified during analysis. The fix is to create new temporary dictionaries via the analyze_child_block(). The only information that needs to propagate back up is the names of the free variables. Add more comments and break out a helper function. This code doesn't get any easier to understand when you only look at it once a year. ........
242 lines
7.2 KiB
Python
242 lines
7.2 KiB
Python
"""Interface to the compiler's internal symbol tables"""
|
|
|
|
import _symtable
|
|
from _symtable import (USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM,
|
|
DEF_FREE_GLOBAL, DEF_FREE_CLASS, DEF_IMPORT, DEF_BOUND,
|
|
OPT_IMPORT_STAR, SCOPE_OFF, SCOPE_MASK, FREE,
|
|
GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
|
|
|
|
import weakref
|
|
|
|
__all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"]
|
|
|
|
def symtable(code, filename, compile_type):
|
|
raw = _symtable.symtable(code, filename, compile_type)
|
|
for top in raw.values():
|
|
if top.name == 'top':
|
|
break
|
|
return _newSymbolTable(top, filename)
|
|
|
|
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
|
|
|
|
_newSymbolTable = SymbolTableFactory()
|
|
|
|
|
|
class SymbolTable(object):
|
|
|
|
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__
|
|
|
|
if self._table.name == "global":
|
|
return "<{0}SymbolTable for module {1}>".format(kind, self._filename)
|
|
else:
|
|
return "<{0}SymbolTable for {1} in {2}>".format(kind,
|
|
self._table.name,
|
|
self._filename)
|
|
|
|
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), \
|
|
"unexpected type: {0}".format(self._table.type)
|
|
|
|
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):
|
|
return bool(self._table.type == _symtable.TYPE_FUNCTION
|
|
and not self._table.optimized)
|
|
|
|
def is_nested(self):
|
|
return bool(self._table.nested)
|
|
|
|
def has_children(self):
|
|
return bool(self._table.children)
|
|
|
|
def has_exec(self):
|
|
"""Return true if the scope uses exec. Deprecated method."""
|
|
return False
|
|
|
|
def has_import_star(self):
|
|
"""Return true if the scope uses import *"""
|
|
return bool(self._table.optimized & OPT_IMPORT_STAR)
|
|
|
|
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):
|
|
return [_newSymbolTable(st, self._filename)
|
|
for st in self._table.children
|
|
if st.name == name]
|
|
|
|
def get_children(self):
|
|
return [_newSymbolTable(st, self._filename)
|
|
for st in self._table.children]
|
|
|
|
|
|
class Function(SymbolTable):
|
|
|
|
# Default values for instance variables
|
|
__params = None
|
|
__locals = None
|
|
__frees = None
|
|
__globals = None
|
|
|
|
def __idents_matching(self, test_func):
|
|
return tuple([ident for ident in self.get_identifiers()
|
|
if test_func(self._table.symbols[ident])])
|
|
|
|
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:
|
|
self.__locals = self.__idents_matching(lambda x:x & DEF_BOUND)
|
|
return self.__locals
|
|
|
|
def get_globals(self):
|
|
if self.__globals is None:
|
|
glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
|
|
test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob
|
|
self.__globals = self.__idents_matching(test)
|
|
return self.__globals
|
|
|
|
def get_frees(self):
|
|
if self.__frees is None:
|
|
is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE
|
|
self.__frees = self.__idents_matching(is_free)
|
|
return self.__frees
|
|
|
|
|
|
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
|
|
self.__methods = tuple(d)
|
|
return self.__methods
|
|
|
|
|
|
class Symbol(object):
|
|
|
|
def __init__(self, name, flags, namespaces=None):
|
|
self.__name = name
|
|
self.__flags = flags
|
|
self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
|
|
self.__namespaces = namespaces or ()
|
|
|
|
def __repr__(self):
|
|
return "<symbol {0!r}>".format(self.__name)
|
|
|
|
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):
|
|
return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
|
|
|
|
def is_declared_global(self):
|
|
return bool(self.__scope == GLOBAL_EXPLICIT)
|
|
|
|
def is_local(self):
|
|
return bool(self.__flags & DEF_BOUND)
|
|
|
|
def is_free(self):
|
|
return bool(self.__scope == FREE)
|
|
|
|
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:
|
|
raise ValueError("name is bound to multiple namespaces")
|
|
return self.__namespaces[0]
|
|
|
|
if __name__ == "__main__":
|
|
import os, sys
|
|
src = open(sys.argv[0]).read()
|
|
mod = symtable(src, os.path.split(sys.argv[0])[1], "exec")
|
|
for ident in mod.get_identifiers():
|
|
info = mod.lookup(ident)
|
|
print(info, info.is_local(), info.is_namespace())
|