| import textwrap | |
| from test.support.bytecode_helper import CodegenTestCase | |
| # Tests for the code-generation stage of the compiler. | |
| # Examine the un-optimized code generated from the AST. | |
| class IsolatedCodeGenTests(CodegenTestCase): | |
| def assertInstructionsMatch_recursive(self, insts, expected_insts): | |
| expected_nested = [i for i in expected_insts if isinstance(i, list)] | |
| expected_insts = [i for i in expected_insts if not isinstance(i, list)] | |
| self.assertInstructionsMatch(insts, expected_insts) | |
| self.assertEqual(len(insts.get_nested()), len(expected_nested)) | |
| for n_insts, n_expected in zip(insts.get_nested(), expected_nested): | |
| self.assertInstructionsMatch_recursive(n_insts, n_expected) | |
| def codegen_test(self, snippet, expected_insts): | |
| import ast | |
| a = ast.parse(snippet, "my_file.py", "exec") | |
| insts = self.generate_code(a) | |
| self.assertInstructionsMatch_recursive(insts, expected_insts) | |
| def test_if_expression(self): | |
| snippet = "42 if True else 24" | |
| false_lbl = self.Label() | |
| expected = [ | |
| ('RESUME', 0, 0), | |
| ('ANNOTATIONS_PLACEHOLDER', None), | |
| ('LOAD_CONST', 0, 1), | |
| ('TO_BOOL', 0, 1), | |
| ('POP_JUMP_IF_FALSE', false_lbl := self.Label(), 1), | |
| ('LOAD_CONST', 1, 1), # 42 | |
| ('JUMP_NO_INTERRUPT', exit_lbl := self.Label()), | |
| false_lbl, | |
| ('LOAD_CONST', 2, 1), # 24 | |
| exit_lbl, | |
| ('POP_TOP', None), | |
| ('LOAD_CONST', 1), | |
| ('RETURN_VALUE', None), | |
| ] | |
| self.codegen_test(snippet, expected) | |
| def test_for_loop(self): | |
| snippet = "for x in l:\n\tprint(x)" | |
| false_lbl = self.Label() | |
| expected = [ | |
| ('RESUME', 0, 0), | |
| ('ANNOTATIONS_PLACEHOLDER', None), | |
| ('LOAD_NAME', 0, 1), | |
| ('GET_ITER', None, 1), | |
| loop_lbl := self.Label(), | |
| ('FOR_ITER', exit_lbl := self.Label(), 1), | |
| ('NOP', None, 1, 1), | |
| ('STORE_NAME', 1, 1), | |
| ('LOAD_NAME', 2, 2), | |
| ('PUSH_NULL', None, 2), | |
| ('LOAD_NAME', 1, 2), | |
| ('CALL', 1, 2), | |
| ('POP_TOP', None), | |
| ('JUMP', loop_lbl), | |
| exit_lbl, | |
| ('END_FOR', None), | |
| ('POP_ITER', None), | |
| ('LOAD_CONST', 0), | |
| ('RETURN_VALUE', None), | |
| ] | |
| self.codegen_test(snippet, expected) | |
| def test_function(self): | |
| snippet = textwrap.dedent(""" | |
| def f(x): | |
| return x + 42 | |
| """) | |
| expected = [ | |
| # Function definition | |
| ('RESUME', 0), | |
| ('ANNOTATIONS_PLACEHOLDER', None), | |
| ('LOAD_CONST', 0), | |
| ('MAKE_FUNCTION', None), | |
| ('STORE_NAME', 0), | |
| ('LOAD_CONST', 1), | |
| ('RETURN_VALUE', None), | |
| [ | |
| # Function body | |
| ('RESUME', 0), | |
| ('LOAD_FAST', 0), | |
| ('LOAD_CONST', 42), | |
| ('BINARY_OP', 0), | |
| ('RETURN_VALUE', None), | |
| ('LOAD_CONST', 0), | |
| ('RETURN_VALUE', None), | |
| ] | |
| ] | |
| self.codegen_test(snippet, expected) | |
| def test_nested_functions(self): | |
| snippet = textwrap.dedent(""" | |
| def f(): | |
| def h(): | |
| return 12 | |
| def g(): | |
| x = 1 | |
| y = 2 | |
| z = 3 | |
| u = 4 | |
| return 42 | |
| """) | |
| expected = [ | |
| # Function definition | |
| ('RESUME', 0), | |
| ('ANNOTATIONS_PLACEHOLDER', None), | |
| ('LOAD_CONST', 0), | |
| ('MAKE_FUNCTION', None), | |
| ('STORE_NAME', 0), | |
| ('LOAD_CONST', 1), | |
| ('RETURN_VALUE', None), | |
| [ | |
| # Function body | |
| ('RESUME', 0), | |
| ('LOAD_CONST', 1), | |
| ('MAKE_FUNCTION', None), | |
| ('STORE_FAST', 0), | |
| ('LOAD_CONST', 2), | |
| ('MAKE_FUNCTION', None), | |
| ('STORE_FAST', 1), | |
| ('LOAD_CONST', 0), | |
| ('RETURN_VALUE', None), | |
| [ | |
| ('RESUME', 0), | |
| ('NOP', None), | |
| ('LOAD_CONST', 12), | |
| ('RETURN_VALUE', None), | |
| ('LOAD_CONST', 1), | |
| ('RETURN_VALUE', None), | |
| ], | |
| [ | |
| ('RESUME', 0), | |
| ('LOAD_CONST', 1), | |
| ('STORE_FAST', 0), | |
| ('LOAD_CONST', 2), | |
| ('STORE_FAST', 1), | |
| ('LOAD_CONST', 3), | |
| ('STORE_FAST', 2), | |
| ('LOAD_CONST', 4), | |
| ('STORE_FAST', 3), | |
| ('NOP', None), | |
| ('LOAD_CONST', 42), | |
| ('RETURN_VALUE', None), | |
| ('LOAD_CONST', 0), | |
| ('RETURN_VALUE', None), | |
| ], | |
| ], | |
| ] | |
| self.codegen_test(snippet, expected) | |
| def test_del_for_store_global(self): | |
| snippet = "global x\ndel x" | |
| expected = [ | |
| ('RESUME', 0), | |
| ('ANNOTATIONS_PLACEHOLDER', None), | |
| ('PUSH_NULL', None), | |
| ('STORE_GLOBAL', 0), | |
| ('LOAD_CONST', 0), | |
| ('RETURN_VALUE', None), | |
| ] | |
| self.codegen_test(snippet, expected) | |
| def test_syntax_error__return_not_in_function(self): | |
| snippet = "return 42" | |
| with self.assertRaisesRegex(SyntaxError, "'return' outside function") as cm: | |
| self.codegen_test(snippet, None) | |
| self.assertIsNone(cm.exception.text) | |
| self.assertEqual(cm.exception.offset, 1) | |
| self.assertEqual(cm.exception.end_offset, 10) | |
| def test_del_for_store_name(self): | |
| snippet = "del x" | |
| expected = [ | |
| ('RESUME', 0), | |
| ('ANNOTATIONS_PLACEHOLDER', None), | |
| ('PUSH_NULL', None), | |
| ('STORE_NAME', 0), | |
| ('LOAD_CONST', 0), | |
| ('RETURN_VALUE', None), | |
| ] | |
| self.codegen_test(snippet, expected) | |
| def test_frozenset_optimization(self): | |
| l1 = self.Label() | |
| snippet = "frozenset({1, 2, 3})" | |
| expected = [ | |
| ('RESUME', 0), | |
| ('ANNOTATIONS_PLACEHOLDER', None), | |
| ('LOAD_NAME', 0), | |
| ('COPY', 1), | |
| ('LOAD_COMMON_CONSTANT', 12), | |
| ('IS_OP', 0), | |
| ('POP_JUMP_IF_FALSE', l1), | |
| ('POP_TOP', None), | |
| ('LOAD_CONST', 1), | |
| ('LOAD_CONST', 2), | |
| ('LOAD_CONST', 3), | |
| ('BUILD_SET', 3), | |
| ('CALL_INTRINSIC_1', 12), | |
| ('JUMP', 0), | |
| l1, | |
| ('PUSH_NULL', None), | |
| ('LOAD_CONST', 1), | |
| ('LOAD_CONST', 2), | |
| ('LOAD_CONST', 3), | |
| ('BUILD_SET', 3), | |
| ('CALL', 1), | |
| ('POP_TOP', None), | |
| ('LOAD_CONST', 0), | |
| ('RETURN_VALUE', None) | |
| ] | |
| self.codegen_test(snippet, expected) | |
| def test_comp_without_target_optimization(self): | |
| snippet = "[i for i in range(10)]" | |
| expected = [ | |
| ('RESUME', 0), | |
| ('ANNOTATIONS_PLACEHOLDER', None), | |
| ('LOAD_NAME', 0), | |
| ('PUSH_NULL', None), | |
| ('LOAD_CONST', 0), | |
| ('CALL', 1), | |
| ('LOAD_FAST_AND_CLEAR', 0), | |
| ('SWAP', 2), | |
| ('SETUP_FINALLY', 20), | |
| ('COPY', 1), | |
| ('GET_ITER', 0), | |
| ('FOR_ITER', 16), | |
| ('STORE_FAST', 0), | |
| ('LOAD_FAST', 0), | |
| ('POP_TOP', None), | |
| ('JUMP', 11), | |
| ('END_FOR', None), | |
| ('POP_ITER', None), | |
| ('POP_BLOCK', None), | |
| ('JUMP_NO_INTERRUPT', 25), | |
| ('SWAP', 2), | |
| ('POP_TOP', None), | |
| ('SWAP', 2), | |
| ('STORE_FAST_MAYBE_NULL', 0), | |
| ('RERAISE', 0), | |
| ('SWAP', 2), | |
| ('STORE_FAST_MAYBE_NULL', 0), | |
| ('POP_TOP', None), | |
| ('LOAD_CONST', 1), | |
| ('RETURN_VALUE', None), | |
| ] | |
| self.codegen_test(snippet, expected) |