mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +00:00 
			
		
		
		
	get the symtable module back in working order
- Fix broken functions - Add (hopefully) extensive tests - Modernize a little
This commit is contained in:
		
							parent
							
								
									fd7c43e7be
								
							
						
					
					
						commit
						7dd854725b
					
				
					 3 changed files with 188 additions and 48 deletions
				
			
		|  | @ -1,10 +1,11 @@ | ||||||
| """Interface to the compiler's internal symbol tables""" | """Interface to the compiler's internal symbol tables""" | ||||||
| 
 | 
 | ||||||
| import _symtable | import _symtable | ||||||
| from _symtable import USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM, \ | from _symtable import (USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM, | ||||||
|      DEF_STAR, DEF_DOUBLESTAR, DEF_INTUPLE, DEF_FREE, \ |      DEF_STAR, DEF_DOUBLESTAR, DEF_INTUPLE, DEF_FREE, | ||||||
|      DEF_FREE_GLOBAL, DEF_FREE_CLASS, DEF_IMPORT, DEF_BOUND, \ |      DEF_FREE_GLOBAL, DEF_FREE_CLASS, DEF_IMPORT, DEF_BOUND, | ||||||
|      OPT_IMPORT_STAR, OPT_EXEC, OPT_BARE_EXEC |      OPT_IMPORT_STAR, OPT_EXEC, OPT_BARE_EXEC, SCOPE_OFF, SCOPE_MASK, | ||||||
|  |      FREE, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT) | ||||||
| 
 | 
 | ||||||
| import weakref | import weakref | ||||||
| 
 | 
 | ||||||
|  | @ -38,15 +39,9 @@ def __call__(self, table, filename): | ||||||
| 
 | 
 | ||||||
| newSymbolTable = 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): |     def __init__(self, raw_table, filename): | ||||||
|         self._table = raw_table |         self._table = raw_table | ||||||
|         self._filename = filename |         self._filename = filename | ||||||
|  | @ -59,9 +54,10 @@ def __repr__(self): | ||||||
|             kind = "%s " % self.__class__.__name__ |             kind = "%s " % self.__class__.__name__ | ||||||
| 
 | 
 | ||||||
|         if self._table.name == "global": |         if self._table.name == "global": | ||||||
|             return "<%sSymbolTable for module %s>" % (kind, self._filename) |             return "<{0}SymbolTable for module {1}>".format(kind, self._filename) | ||||||
|         else: |         else: | ||||||
|             return "<%sSymbolTable for %s in %s>" % (kind, self._table.name, |             return "<{0}SymbolTable for {1} in {2}>".format(kind, | ||||||
|  |                                                             self._table.name, | ||||||
|                                                             self._filename) |                                                             self._filename) | ||||||
| 
 | 
 | ||||||
|     def get_type(self): |     def get_type(self): | ||||||
|  | @ -72,7 +68,7 @@ def get_type(self): | ||||||
|         if self._table.type == _symtable.TYPE_CLASS: |         if self._table.type == _symtable.TYPE_CLASS: | ||||||
|             return "class" |             return "class" | ||||||
|         assert self._table.type in (1, 2, 3), \ |         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): |     def get_id(self): | ||||||
|         return self._table.id |         return self._table.id | ||||||
|  | @ -124,6 +120,7 @@ def get_children(self): | ||||||
|         return [newSymbolTable(st, self._filename) |         return [newSymbolTable(st, self._filename) | ||||||
|                 for st in self._table.children] |                 for st in self._table.children] | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Function(SymbolTable): | class Function(SymbolTable): | ||||||
| 
 | 
 | ||||||
|     # Default values for instance variables |     # Default values for instance variables | ||||||
|  | @ -148,15 +145,18 @@ def get_locals(self): | ||||||
| 
 | 
 | ||||||
|     def get_globals(self): |     def get_globals(self): | ||||||
|         if self.__globals is None: |         if self.__globals is None: | ||||||
|             glob = DEF_GLOBAL | DEF_FREE_GLOBAL |             glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT) | ||||||
|             self.__globals = self.__idents_matching(lambda x:x & glob) |             test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob | ||||||
|  |             self.__globals = self.__idents_matching(test) | ||||||
|         return self.__globals |         return self.__globals | ||||||
| 
 | 
 | ||||||
|     def get_frees(self): |     def get_frees(self): | ||||||
|         if self.__frees is None: |         if self.__frees is None: | ||||||
|  |             is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE | ||||||
|             self.__frees = self.__idents_matching(is_free) |             self.__frees = self.__idents_matching(is_free) | ||||||
|         return self.__frees |         return self.__frees | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Class(SymbolTable): | class Class(SymbolTable): | ||||||
| 
 | 
 | ||||||
|     __methods = None |     __methods = None | ||||||
|  | @ -169,14 +169,17 @@ def get_methods(self): | ||||||
|             self.__methods = tuple(d) |             self.__methods = tuple(d) | ||||||
|         return self.__methods |         return self.__methods | ||||||
| 
 | 
 | ||||||
| class Symbol: | 
 | ||||||
|  | class Symbol(object): | ||||||
|  | 
 | ||||||
|     def __init__(self, name, flags, namespaces=None): |     def __init__(self, name, flags, namespaces=None): | ||||||
|         self.__name = name |         self.__name = name | ||||||
|         self.__flags = flags |         self.__flags = flags | ||||||
|  |         self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope() | ||||||
|         self.__namespaces = namespaces or () |         self.__namespaces = namespaces or () | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return "<symbol '%s'>" % self.__name |         return "<symbol {0!r}>".format(self.__name) | ||||||
| 
 | 
 | ||||||
|     def get_name(self): |     def get_name(self): | ||||||
|         return self.__name |         return self.__name | ||||||
|  | @ -188,8 +191,7 @@ def is_parameter(self): | ||||||
|         return bool(self.__flags & DEF_PARAM) |         return bool(self.__flags & DEF_PARAM) | ||||||
| 
 | 
 | ||||||
|     def is_global(self): |     def is_global(self): | ||||||
|         return bool((self.__flags & DEF_GLOBAL) |         return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)) | ||||||
|                     or (self.__flags & DEF_FREE_GLOBAL)) |  | ||||||
| 
 | 
 | ||||||
|     def is_vararg(self): |     def is_vararg(self): | ||||||
|         return bool(self.__flags & DEF_STAR) |         return bool(self.__flags & DEF_STAR) | ||||||
|  | @ -201,12 +203,7 @@ def is_local(self): | ||||||
|         return bool(self.__flags & DEF_BOUND) |         return bool(self.__flags & DEF_BOUND) | ||||||
| 
 | 
 | ||||||
|     def is_free(self): |     def is_free(self): | ||||||
|         if (self.__flags & (USE | DEF_FREE)) \ |         return bool(self.__scope == FREE) | ||||||
|             and (self.__flags & (DEF_LOCAL | DEF_PARAM | DEF_GLOBAL)): |  | ||||||
|             return True |  | ||||||
|         if self.__flags & DEF_FREE_CLASS: |  | ||||||
|             return True |  | ||||||
|         return False |  | ||||||
| 
 | 
 | ||||||
|     def is_imported(self): |     def is_imported(self): | ||||||
|         return bool(self.__flags & DEF_IMPORT) |         return bool(self.__flags & DEF_IMPORT) | ||||||
|  |  | ||||||
|  | @ -1,31 +1,171 @@ | ||||||
| from test import test_support | """ | ||||||
| 
 | Test the API of the symtable module. | ||||||
|  | """ | ||||||
| import symtable | import symtable | ||||||
| import unittest | import unittest | ||||||
|  | import warnings | ||||||
|  | 
 | ||||||
|  | from test import test_support | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## XXX | TEST_CODE = """ | ||||||
| ## Test disabled because symtable module needs to be rewritten for new compiler | import sys | ||||||
| 
 | 
 | ||||||
| ##vereq(symbols[0].name, "global") | glob = 42 | ||||||
| ##vereq(len([ste for ste in symbols.values() if ste.name == "f"]), 1) | 
 | ||||||
|  | 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): | class SymtableTest(unittest.TestCase): | ||||||
|     def test_invalid_args(self): | 
 | ||||||
|         self.assertRaises(TypeError, symtable.symtable, "42") |     with test_support.catch_warning(record=False): | ||||||
|         self.assertRaises(ValueError, symtable.symtable, "42", "?", "") |         # 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): |     def test_eval(self): | ||||||
|         symbols = symtable.symtable("42", "?", "eval") |         symbols = symtable.symtable("42", "?", "eval") | ||||||
|  |  | ||||||
|  | @ -81,4 +81,7 @@ init_symtable(void) | ||||||
| 	PyModule_AddIntConstant(m, "GLOBAL_IMPLICIT", GLOBAL_IMPLICIT); | 	PyModule_AddIntConstant(m, "GLOBAL_IMPLICIT", GLOBAL_IMPLICIT); | ||||||
| 	PyModule_AddIntConstant(m, "FREE", FREE); | 	PyModule_AddIntConstant(m, "FREE", FREE); | ||||||
| 	PyModule_AddIntConstant(m, "CELL", CELL); | 	PyModule_AddIntConstant(m, "CELL", CELL); | ||||||
|  | 
 | ||||||
|  | 	PyModule_AddIntConstant(m, "SCOPE_OFF", SCOPE_OFF); | ||||||
|  | 	PyModule_AddIntConstant(m, "SCOPE_MASK", SCOPE_MASK); | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Benjamin Peterson
						Benjamin Peterson