diff --git a/pytest/test_single_compile.py b/pytest/test_single_compile.py new file mode 100644 index 00000000..ac55c0fc --- /dev/null +++ b/pytest/test_single_compile.py @@ -0,0 +1,22 @@ +import pytest +from uncompyle6 import PYTHON_VERSION, PYTHON3, deparse_code + +@pytest.mark.skip(reason="Reinstate when we have compiilation single") +def test_single_mode(): + single_expressions = ( + 'i = 1', + 'i and (j or k)', + 'i += 1', + 'i = j % 4', + 'i = {}', + 'i = []', + 'while i < 1 or stop:\n i\n', + 'while i < 1 or stop:\n print%s\n' % ('(i)' if PYTHON3 else ' i'), + 'for i in range(10):\n i\n', + 'for i in range(10):\n for j in range(10):\n i + j\n', + 'try:\n i\nexcept Exception:\n j\nelse:\n k\n' + ) + + for expr in single_expressions: + code = compile(expr + '\n', '', 'single') + assert deparse_code(PYTHON_VERSION, code, compile_mode='single').text == expr + '\n' diff --git a/uncompyle6/parser.py b/uncompyle6/parser.py index 2951bf70..b1376c83 100644 --- a/uncompyle6/parser.py +++ b/uncompyle6/parser.py @@ -76,10 +76,13 @@ def parse(p, tokens, customize): return ast -def get_python_parser(version, debug_parser): +def get_python_parser(version, debug_parser, compile_mode='exec'): """ Returns parser object for Python version 2 or 3 - depending on the parameter passed. + depending on the parameter passed. *compile_mode* + is either 'exec', 'eval', or 'single'. See + https://docs.python.org/3.6/library/functions.html#compile for an explanation + of the different modes. """ if version < 3.0: import uncompyle6.parsers.parse2 as parse2 diff --git a/uncompyle6/parsers/parse2.py b/uncompyle6/parsers/parse2.py index 35d5c170..bb16ea79 100644 --- a/uncompyle6/parsers/parse2.py +++ b/uncompyle6/parsers/parse2.py @@ -209,10 +209,14 @@ class Python2Parser(PythonParser): load_attrs ::= load_attrs LOAD_ATTR ''' - def p_grammar(self, args): + def p_start(self, args): ''' stmts ::= stmts sstmt stmts ::= sstmt + ''' + + def p_grammar(self, args): + ''' sstmt ::= stmt sstmt ::= ifelsestmtr sstmt ::= return_stmt RETURN_LAST @@ -710,3 +714,8 @@ class Python2Parser(PythonParser): else: raise Exception('unknown customize token %s' % k) self.addRule(rule, nop_func) + +class Python2ParserSingle(Python2Parser): + # Add: + # call_stmt ::= expr PRINT_EXPR + pass diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 10ab24c9..6e0fb194 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -818,3 +818,8 @@ class Python3Parser(PythonParser): % ('expr ' * token.attr, opname)) self.add_unique_rule(rule, opname, token.attr, customize) return + +class Python3ParserSingle(Python3Parser): + # Add: + # call_stmt ::= expr PRINT_EXPR + pass diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 6a3c65ba..5bf06dfb 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -487,7 +487,8 @@ class SourceWalker(GenericASTTraversal, object): stacked_params = ('f', 'indent', 'isLambda', '_globals') def __init__(self, version, out, scanner, showast=False, - debug_parser=PARSER_DEFAULT_DEBUG): + debug_parser=PARSER_DEFAULT_DEBUG, + compile_mode='exec'): GenericASTTraversal.__init__(self, ast=None) self.scanner = scanner params = { @@ -495,7 +496,8 @@ class SourceWalker(GenericASTTraversal, object): 'indent': '', } self.version = version - self.p = get_python_parser(version, debug_parser=debug_parser) + self.p = get_python_parser(version, debug_parser=debug_parser, + compile_mode=compile_mode) self.debug_parser = dict(debug_parser) self.showast = showast self.params = params @@ -1615,7 +1617,7 @@ class SourceWalker(GenericASTTraversal, object): return ast def deparse_code(version, co, out=sys.stdout, showasm=False, showast=False, - showgrammar=False, code_objects={}): + showgrammar=False, code_objects={}, compile_mode='exec'): """ disassembles and deparses a given code block 'co' """ @@ -1633,7 +1635,8 @@ def deparse_code(version, co, out=sys.stdout, showasm=False, showast=False, debug_parser['reduce'] = showgrammar # Build AST from disassembly. - deparsed = SourceWalker(version, out, scanner, showast=showast, debug_parser=debug_parser) + deparsed = SourceWalker(version, out, scanner, showast=showast, + debug_parser=debug_parser, compile_mode=compile_mode) deparsed.ast = deparsed.build_ast(tokens, customize)