From 7dd854725b0ecdd47b3818fe12ce2a3f9948f3ce Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sun, 17 Aug 2008 17:13:26 +0000 Subject: [PATCH] get the symtable module back in working order - Fix broken functions - Add (hopefully) extensive tests - Modernize a little --- Lib/symtable.py | 53 ++++++----- Lib/test/test_symtable.py | 180 +++++++++++++++++++++++++++++++++----- Modules/symtablemodule.c | 3 + 3 files changed, 188 insertions(+), 48 deletions(-) diff --git a/Lib/symtable.py b/Lib/symtable.py index 3f1332e8ed3..66c0e0354c5 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -1,10 +1,11 @@ """Interface to the compiler's internal symbol tables""" import _symtable -from _symtable import USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM, \ - DEF_STAR, DEF_DOUBLESTAR, DEF_INTUPLE, DEF_FREE, \ - DEF_FREE_GLOBAL, DEF_FREE_CLASS, DEF_IMPORT, DEF_BOUND, \ - OPT_IMPORT_STAR, OPT_EXEC, OPT_BARE_EXEC +from _symtable import (USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM, + DEF_STAR, DEF_DOUBLESTAR, DEF_INTUPLE, DEF_FREE, + DEF_FREE_GLOBAL, DEF_FREE_CLASS, DEF_IMPORT, DEF_BOUND, + OPT_IMPORT_STAR, OPT_EXEC, OPT_BARE_EXEC, SCOPE_OFF, SCOPE_MASK, + FREE, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT) import weakref @@ -38,15 +39,9 @@ class SymbolTableFactory: newSymbolTable = SymbolTableFactory() -def is_free(flags): - if (flags & (USE | DEF_FREE)) \ - and (flags & (DEF_LOCAL | DEF_PARAM | DEF_GLOBAL)): - return True - if flags & DEF_FREE_CLASS: - return True - return False -class SymbolTable: +class SymbolTable(object): + def __init__(self, raw_table, filename): self._table = raw_table self._filename = filename @@ -59,10 +54,11 @@ class SymbolTable: kind = "%s " % self.__class__.__name__ if self._table.name == "global": - return "<%sSymbolTable for module %s>" % (kind, self._filename) + return "<{0}SymbolTable for module {1}>".format(kind, self._filename) else: - return "<%sSymbolTable for %s in %s>" % (kind, self._table.name, - self._filename) + 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: @@ -72,7 +68,7 @@ class SymbolTable: if self._table.type == _symtable.TYPE_CLASS: return "class" assert self._table.type in (1, 2, 3), \ - "unexpected type: %s" % self._table.type + "unexpected type: {0}".format(self._table.type) def get_id(self): return self._table.id @@ -124,6 +120,7 @@ class SymbolTable: return [newSymbolTable(st, self._filename) for st in self._table.children] + class Function(SymbolTable): # Default values for instance variables @@ -148,15 +145,18 @@ class Function(SymbolTable): def get_globals(self): if self.__globals is None: - glob = DEF_GLOBAL | DEF_FREE_GLOBAL - self.__globals = self.__idents_matching(lambda x:x & glob) + 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 @@ -169,14 +169,17 @@ class Class(SymbolTable): self.__methods = tuple(d) return self.__methods -class Symbol: + +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 "" % self.__name + return "".format(self.__name) def get_name(self): return self.__name @@ -188,8 +191,7 @@ class Symbol: return bool(self.__flags & DEF_PARAM) def is_global(self): - return bool((self.__flags & DEF_GLOBAL) - or (self.__flags & DEF_FREE_GLOBAL)) + return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)) def is_vararg(self): return bool(self.__flags & DEF_STAR) @@ -201,12 +203,7 @@ class Symbol: return bool(self.__flags & DEF_BOUND) def is_free(self): - if (self.__flags & (USE | DEF_FREE)) \ - and (self.__flags & (DEF_LOCAL | DEF_PARAM | DEF_GLOBAL)): - return True - if self.__flags & DEF_FREE_CLASS: - return True - return False + return bool(self.__scope == FREE) def is_imported(self): return bool(self.__flags & DEF_IMPORT) diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index 74a7c8559a6..fc678a84a9a 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -1,31 +1,171 @@ -from test import test_support - +""" +Test the API of the symtable module. +""" import symtable import unittest +import warnings + +from test import test_support -## XXX -## Test disabled because symtable module needs to be rewritten for new compiler +TEST_CODE = """ +import sys -##vereq(symbols[0].name, "global") -##vereq(len([ste for ste in symbols.values() if ste.name == "f"]), 1) +glob = 42 + +class Mine: + instance_var = 24 + def a_method(p1, p2): + pass + +def spam(a, b, *var, **kw): + global bar + bar = 47 + x = 23 + glob + def internal(): + return x + return internal + +def foo(): + exec 'm' + from sys import * + +def namespace_test(): pass +def namespace_test(): pass +""" + + +def find_block(block, name): + for ch in block.get_children(): + if ch.get_name() == name: + return ch -### Bug tickler: SyntaxError file name correct whether error raised -### while parsing or building symbol table. -##def checkfilename(brokencode): -## try: -## _symtable.symtable(brokencode, "spam", "exec") -## except SyntaxError, e: -## vereq(e.filename, "spam") -## else: -## raise TestFailed("no SyntaxError for %r" % (brokencode,)) -##checkfilename("def f(x): foo)(") # parse-time -##checkfilename("def f(x): global x") # symtable-build-time class SymtableTest(unittest.TestCase): - def test_invalid_args(self): - self.assertRaises(TypeError, symtable.symtable, "42") - self.assertRaises(ValueError, symtable.symtable, "42", "?", "") + + with test_support.catch_warning(record=False): + # Ignore warnings about "from blank import *" + warnings.simplefilter("ignore", SyntaxWarning) + top = symtable.symtable(TEST_CODE, "?", "exec") + # These correspond to scopes in TEST_CODE + Mine = find_block(top, "Mine") + a_method = find_block(Mine, "a_method") + spam = find_block(top, "spam") + internal = find_block(spam, "internal") + foo = find_block(top, "foo") + + def test_type(self): + self.assertEqual(self.top.get_type(), "module") + self.assertEqual(self.Mine.get_type(), "class") + self.assertEqual(self.a_method.get_type(), "function") + self.assertEqual(self.spam.get_type(), "function") + self.assertEqual(self.internal.get_type(), "function") + + def test_optimized(self): + self.assertFalse(self.top.is_optimized()) + self.assertFalse(self.top.has_exec()) + self.assertFalse(self.top.has_import_star()) + + self.assertTrue(self.spam.is_optimized()) + + self.assertFalse(self.foo.is_optimized()) + self.assertTrue(self.foo.has_exec()) + self.assertTrue(self.foo.has_import_star()) + + def test_nested(self): + self.assertFalse(self.top.is_nested()) + self.assertFalse(self.Mine.is_nested()) + self.assertFalse(self.spam.is_nested()) + self.assertTrue(self.internal.is_nested()) + + def test_children(self): + self.assertTrue(self.top.has_children()) + self.assertTrue(self.Mine.has_children()) + self.assertFalse(self.foo.has_children()) + + def test_lineno(self): + self.assertEqual(self.top.get_lineno(), 0) + self.assertEqual(self.spam.get_lineno(), 11) + + def test_function_info(self): + func = self.spam + self.assertEqual(func.get_parameters(), ("a", "b", "kw", "var")) + self.assertEqual(func.get_locals(), + ("a", "b", "bar", "internal", "kw", "var", "x")) + self.assertEqual(func.get_globals(), ("bar", "glob")) + self.assertEqual(self.internal.get_frees(), ("x",)) + + def test_globals(self): + self.assertTrue(self.spam.lookup("glob").is_global()) + self.assertTrue(self.spam.lookup("bar").is_global()) + self.assertFalse(self.internal.lookup("x").is_global()) + self.assertFalse(self.Mine.lookup("instance_var").is_global()) + + def test_local(self): + self.assertTrue(self.spam.lookup("x").is_local()) + self.assertFalse(self.internal.lookup("x").is_local()) + + def test_referenced(self): + self.assertTrue(self.internal.lookup("x").is_referenced()) + self.assertTrue(self.spam.lookup("internal").is_referenced()) + self.assertFalse(self.spam.lookup("x").is_referenced()) + + def test_parameters(self): + for sym in ("a", "var", "kw"): + self.assertTrue(self.spam.lookup(sym).is_parameter()) + self.assertFalse(self.spam.lookup("x").is_parameter()) + + def test_symbol_lookup(self): + self.assertEqual(len(self.top.get_identifiers()), + len(self.top.get_symbols())) + + self.assertRaises(KeyError, self.top.lookup, "not_here") + + def test_namespaces(self): + self.assertTrue(self.top.lookup("Mine").is_namespace()) + self.assertTrue(self.Mine.lookup("a_method").is_namespace()) + self.assertTrue(self.top.lookup("spam").is_namespace()) + self.assertTrue(self.spam.lookup("internal").is_namespace()) + self.assertTrue(self.top.lookup("namespace_test").is_namespace()) + self.assertFalse(self.spam.lookup("x").is_namespace()) + + self.assert_(self.top.lookup("spam").get_namespace() is self.spam) + ns_test = self.top.lookup("namespace_test") + self.assertEqual(len(ns_test.get_namespaces()), 2) + self.assertRaises(ValueError, ns_test.get_namespace) + + def test_assigned(self): + self.assertTrue(self.spam.lookup("x").is_assigned()) + self.assertTrue(self.spam.lookup("bar").is_assigned()) + self.assertTrue(self.top.lookup("spam").is_assigned()) + self.assertTrue(self.Mine.lookup("a_method").is_assigned()) + self.assertFalse(self.internal.lookup("x").is_assigned()) + + def test_imported(self): + self.assertTrue(self.top.lookup("sys").is_imported()) + + def test_name(self): + self.assertEqual(self.top.get_name(), "top") + self.assertEqual(self.spam.get_name(), "spam") + self.assertEqual(self.spam.lookup("x").get_name(), "x") + self.assertEqual(self.Mine.get_name(), "Mine") + + def test_class_info(self): + self.assertEqual(self.Mine.get_methods(), ('a_method',)) + + def test_filename_correct(self): + ### Bug tickler: SyntaxError file name correct whether error raised + ### while parsing or building symbol table. + def checkfilename(brokencode): + try: + symtable.symtable(brokencode, "spam", "exec") + except SyntaxError as e: + self.assertEqual(e.filename, "spam") + else: + self.fail("no SyntaxError for %r" % (brokencode,)) + checkfilename("def f(x): foo)(") # parse-time + checkfilename("def f(x): global x") # symtable-build-time def test_eval(self): symbols = symtable.symtable("42", "?", "eval") diff --git a/Modules/symtablemodule.c b/Modules/symtablemodule.c index c90d7650a16..53e987e9249 100644 --- a/Modules/symtablemodule.c +++ b/Modules/symtablemodule.c @@ -81,4 +81,7 @@ init_symtable(void) PyModule_AddIntConstant(m, "GLOBAL_IMPLICIT", GLOBAL_IMPLICIT); PyModule_AddIntConstant(m, "FREE", FREE); PyModule_AddIntConstant(m, "CELL", CELL); + + PyModule_AddIntConstant(m, "SCOPE_OFF", SCOPE_OFF); + PyModule_AddIntConstant(m, "SCOPE_MASK", SCOPE_MASK); }