mirror of https://github.com/python/cpython
gh-85098: Implement functional CLI of symtable (#109112)
Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
This commit is contained in:
parent
f55cb44359
commit
70afb8d732
|
@ -207,3 +207,21 @@ Examining Symbol Tables
|
|||
|
||||
Return the namespace bound to this name. If more than one or no namespace
|
||||
is bound to this name, a :exc:`ValueError` is raised.
|
||||
|
||||
|
||||
.. _symtable-cli:
|
||||
|
||||
Command-Line Usage
|
||||
------------------
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
The :mod:`symtable` module can be executed as a script from the command line.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python -m symtable [infile...]
|
||||
|
||||
Symbol tables are generated for the specified Python source files and
|
||||
dumped to stdout.
|
||||
If no input file is specified, the content is read from stdin.
|
||||
|
|
|
@ -233,7 +233,16 @@ class Symbol:
|
|||
self.__module_scope = module_scope
|
||||
|
||||
def __repr__(self):
|
||||
return "<symbol {0!r}>".format(self.__name)
|
||||
flags_str = '|'.join(self._flags_str())
|
||||
return f'<symbol {self.__name!r}: {self._scope_str()}, {flags_str}>'
|
||||
|
||||
def _scope_str(self):
|
||||
return _scopes_value_to_name.get(self.__scope) or str(self.__scope)
|
||||
|
||||
def _flags_str(self):
|
||||
for flagname, flagvalue in _flags:
|
||||
if self.__flags & flagvalue == flagvalue:
|
||||
yield flagname
|
||||
|
||||
def get_name(self):
|
||||
"""Return a name of a symbol.
|
||||
|
@ -323,11 +332,43 @@ class Symbol:
|
|||
else:
|
||||
return self.__namespaces[0]
|
||||
|
||||
|
||||
_flags = [('USE', USE)]
|
||||
_flags.extend(kv for kv in globals().items() if kv[0].startswith('DEF_'))
|
||||
_scopes_names = ('FREE', 'LOCAL', 'GLOBAL_IMPLICIT', 'GLOBAL_EXPLICIT', 'CELL')
|
||||
_scopes_value_to_name = {globals()[n]: n for n in _scopes_names}
|
||||
|
||||
|
||||
def main(args):
|
||||
import sys
|
||||
def print_symbols(table, level=0):
|
||||
indent = ' ' * level
|
||||
nested = "nested " if table.is_nested() else ""
|
||||
if table.get_type() == 'module':
|
||||
what = f'from file {table._filename!r}'
|
||||
else:
|
||||
what = f'{table.get_name()!r}'
|
||||
print(f'{indent}symbol table for {nested}{table.get_type()} {what}:')
|
||||
for ident in table.get_identifiers():
|
||||
symbol = table.lookup(ident)
|
||||
flags = ', '.join(symbol._flags_str()).lower()
|
||||
print(f' {indent}{symbol._scope_str().lower()} symbol {symbol.get_name()!r}: {flags}')
|
||||
print()
|
||||
|
||||
for table2 in table.get_children():
|
||||
print_symbols(table2, level + 1)
|
||||
|
||||
for filename in args or ['-']:
|
||||
if filename == '-':
|
||||
src = sys.stdin.read()
|
||||
filename = '<stdin>'
|
||||
else:
|
||||
with open(filename, 'rb') as f:
|
||||
src = f.read()
|
||||
mod = symtable(src, filename, 'exec')
|
||||
print_symbols(mod)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os, sys
|
||||
with open(sys.argv[0]) as f:
|
||||
src = f.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())
|
||||
import sys
|
||||
main(sys.argv[1:])
|
||||
|
|
|
@ -4,6 +4,8 @@ Test the API of the symtable module.
|
|||
import symtable
|
||||
import unittest
|
||||
|
||||
from test import support
|
||||
from test.support import os_helper
|
||||
|
||||
|
||||
TEST_CODE = """
|
||||
|
@ -282,10 +284,62 @@ class SymtableTest(unittest.TestCase):
|
|||
self.assertEqual(str(self.top), "<SymbolTable for module ?>")
|
||||
self.assertEqual(str(self.spam), "<Function SymbolTable for spam in ?>")
|
||||
|
||||
def test_symbol_repr(self):
|
||||
self.assertEqual(repr(self.spam.lookup("glob")),
|
||||
"<symbol 'glob': GLOBAL_IMPLICIT, USE>")
|
||||
self.assertEqual(repr(self.spam.lookup("bar")),
|
||||
"<symbol 'bar': GLOBAL_EXPLICIT, DEF_GLOBAL|DEF_LOCAL>")
|
||||
self.assertEqual(repr(self.spam.lookup("a")),
|
||||
"<symbol 'a': LOCAL, DEF_PARAM>")
|
||||
self.assertEqual(repr(self.spam.lookup("internal")),
|
||||
"<symbol 'internal': LOCAL, USE|DEF_LOCAL>")
|
||||
self.assertEqual(repr(self.spam.lookup("other_internal")),
|
||||
"<symbol 'other_internal': LOCAL, DEF_LOCAL>")
|
||||
self.assertEqual(repr(self.internal.lookup("x")),
|
||||
"<symbol 'x': FREE, USE>")
|
||||
self.assertEqual(repr(self.other_internal.lookup("some_var")),
|
||||
"<symbol 'some_var': FREE, USE|DEF_NONLOCAL|DEF_LOCAL>")
|
||||
|
||||
def test_symtable_entry_repr(self):
|
||||
expected = f"<symtable entry top({self.top.get_id()}), line {self.top.get_lineno()}>"
|
||||
self.assertEqual(repr(self.top._table), expected)
|
||||
|
||||
|
||||
class CommandLineTest(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def test_file(self):
|
||||
filename = os_helper.TESTFN
|
||||
self.addCleanup(os_helper.unlink, filename)
|
||||
with open(filename, 'w') as f:
|
||||
f.write(TEST_CODE)
|
||||
with support.captured_stdout() as stdout:
|
||||
symtable.main([filename])
|
||||
out = stdout.getvalue()
|
||||
self.assertIn('\n\n', out)
|
||||
self.assertNotIn('\n\n\n', out)
|
||||
lines = out.splitlines()
|
||||
self.assertIn(f"symbol table for module from file {filename!r}:", lines)
|
||||
self.assertIn(" local symbol 'glob': def_local", lines)
|
||||
self.assertIn(" global_implicit symbol 'glob': use", lines)
|
||||
self.assertIn(" local symbol 'spam': def_local", lines)
|
||||
self.assertIn(" symbol table for function 'spam':", lines)
|
||||
|
||||
def test_stdin(self):
|
||||
with support.captured_stdin() as stdin:
|
||||
stdin.write(TEST_CODE)
|
||||
stdin.seek(0)
|
||||
with support.captured_stdout() as stdout:
|
||||
symtable.main([])
|
||||
out = stdout.getvalue()
|
||||
stdin.seek(0)
|
||||
with support.captured_stdout() as stdout:
|
||||
symtable.main(['-'])
|
||||
self.assertEqual(stdout.getvalue(), out)
|
||||
lines = out.splitlines()
|
||||
print(out)
|
||||
self.assertIn("symbol table for module from file '<stdin>':", lines)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Implement the CLI of the :mod:`symtable` module and improve the repr of
|
||||
:class:`~symtable.Symbol`.
|
Loading…
Reference in New Issue