1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
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),
('LOAD_CONST', 0, 1),
('TO_BOOL', 0, 1),
('POP_JUMP_IF_FALSE', false_lbl := self.Label(), 1),
('LOAD_CONST', 1, 1),
('JUMP_NO_INTERRUPT', exit_lbl := self.Label()),
false_lbl,
('LOAD_CONST', 2, 1),
exit_lbl,
('POP_TOP', None),
('LOAD_CONST', 3),
('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),
('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_TOP', 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),
('LOAD_CONST', 0),
('MAKE_FUNCTION', None),
('STORE_NAME', 0),
('LOAD_CONST', 1),
('RETURN_VALUE', None),
[
# Function body
('RESUME', 0),
('LOAD_FAST', 0),
('LOAD_CONST', 1),
('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),
('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', 1),
('RETURN_VALUE', None),
('LOAD_CONST', 0),
('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', 5),
('RETURN_VALUE', None),
('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)
|