def test_nested_loops(self): # ++[->++[->++[->++[->++[->++<]<]<]<]<]>>>>>.[-]. self.run_ir([ir.ADD(2), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(2), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(2), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(2), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(2), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(2), ir.LEFT(1), ir.CLOSE(), ir.LEFT(1), ir.CLOSE(), ir.LEFT(1), ir.CLOSE(), ir.LEFT(1), ir.CLOSE(), ir.LEFT(1), ir.CLOSE(), ir.RIGHT(5), ir.OUTPUT(), ir.SET(0), ir.OUTPUT()], [], [64, 0]) # ++>+[[[[->]<]>]<].<. self.run_ir([ir.ADD(2), ir.RIGHT(1), ir.ADD(1), ir.OPEN(), ir.OPEN(), ir.OPEN(), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.CLOSE(), ir.LEFT(1), ir.CLOSE(), ir.RIGHT(1), ir.CLOSE(), ir.LEFT(1), ir.CLOSE(), ir.OUTPUT(), ir.LEFT(1), ir.OUTPUT()], [], [0, 2])
def test_left_right(self): # the 386_linux backend outputs different machine instructions # for LEFT and RIGHT depending on the size of the # argument. this tests the edge cases. ops = [] # set up memory to 0,1,2,...,255,0,0,0,... for i in range(256): ops.append(ir.SET(i)) ops.append(ir.RIGHT(1)) # jump all the "edgy" distances left and right and output edges = [1, 2, 3, 4, 126, 127, 128, 129, 130, 131, 253, 254, 255] for i in edges: ops.append(ir.LEFT(i)) ops.append(ir.OUTPUT()) ops.append(ir.RIGHT(i)) ops.append(ir.OUTPUT()) # expected output is 0 alternated with all the edges expected = [] for i in edges: expected.append(256 - i) expected.append(0) self.run_ir(ops, [], expected)
def test_mul(self): self.run_ir([ir.ADD(4), ir.RMUL(2, 4), ir.RIGHT(2), ir.OUTPUT()], [], [16]) self.run_ir([ir.ADD(4), ir.RIGHT(2), ir.ADD(7), ir.LMUL(2, 254), ir.LEFT(2), ir.OUTPUT()], [], [246]) self.run_ir([ir.RIGHT(127), ir.SUB(2), ir.LMUL(127, 1), ir.LEFT(127), ir.OUTPUT()], [], [254]) self.run_ir([ir.RIGHT(127), ir.SUB(2), ir.LMUL(127, 12), ir.LEFT(127), ir.OUTPUT()], [], [232]) self.run_ir([ir.RIGHT(127), ir.SUB(2), ir.LMUL(127, 255), ir.LEFT(127), ir.OUTPUT()], [], [2]) self.run_ir([ir.SET(3), ir.RMUL(127, 1), ir.RIGHT(127), ir.OUTPUT()], [], [3]) self.run_ir([ir.SET(3), ir.RMUL(127, 12), ir.RIGHT(127), ir.OUTPUT()], [], [36]) self.run_ir([ir.SET(3), ir.RMUL(127, 255), ir.RIGHT(127), ir.OUTPUT()], [], [253]) self.run_ir([ir.ADD(12), ir.RMUL(1, 12), ir.RIGHT(1), ir.OUTPUT()], [], [144]) self.run_ir([ir.RIGHT(1), ir.ADD(12), ir.LMUL(1, 12), ir.LEFT(1), ir.OUTPUT()], [], [144])
def test_nested_loops(self): # Loops can be nested # Loop depth is calculated self._run_and_check_ir("+[-[+<].]>[[[]+]+]+", [ ir.ADD(1), ir.OPEN(), ir.SUB(1), ir.OPEN(), ir.ADD(1), ir.LEFT(1), ir.CLOSE(), ir.OUTPUT(), ir.CLOSE(), ir.RIGHT(1), ir.OPEN(), ir.OPEN(), ir.OPEN(), ir.CLOSE(), ir.ADD(1), ir.CLOSE(), ir.ADD(1), ir.CLOSE(), ir.ADD(1) ], maxdepth=4) self._run_and_check_ir("+[[[[[[[[.]]]]],[[[]]]]]]", [ir.ADD(1)] + [ir.OPEN()] * 8 + [ir.OUTPUT()] + [ir.CLOSE()] * 5 + [ir.INPUT()] + [ir.OPEN()] * 3 + [ir.CLOSE()] * 6, maxdepth=9)
def test_basic_operations(self): # ,[->>++++++++<<]>>.[-].[-]+ self.run_ir([ir.INPUT(), ir.OPEN(), ir.SUB(1), ir.RIGHT(2), ir.ADD(8), ir.LEFT(2), ir.CLOSE(), ir.RIGHT(2), ir.OUTPUT(), ir.SET(0), ir.OUTPUT()], [8], [64, 0])
def test_target_lang_c(self): self._run_and_check_target(17, _LANG_C, "@lang_c ") self._run_and_check_target(17, _LANG_C, "@lang_c\n") self._run_and_check_target(17, _LANG_C, "@lang_c+", ir=[ir.ADD(1)]) self._run_and_check_target(17, _LANG_C, "@lang_c.+", ir=[ir.OUTPUT(), ir.ADD(1)]) self._run_and_check_target(17, _LANG_C, "@lang_c[.]") self._run_and_check_target(17, _LANG_C, "@lang_c..", ir=[ir.OUTPUT(), ir.OUTPUT()])
def test_nested_loops(self): self._run_and_check_ir("+[-[+<].]>[[[]+]+]+[+]", [ ir.ADD(1), ir.OPEN(), ir.SUB(1), ir.OPEN(), ir.ADD(1), ir.LEFT(1), ir.CLOSE(), ir.OUTPUT(), ir.CLOSE(), ir.RIGHT(1), ir.OPEN(), ir.OPEN(), ir.OPEN(), ir.CLOSE(), ir.ADD(1), ir.CLOSE(), ir.ADD(1), ir.CLOSE(), ir.ADD(1), ir.OPEN(), ir.ADD(1), ir.CLOSE() ], maxdepth=4)
def test_loop_elimination(self): # Obviously dead loops are skipped entirely # Loop open as first op => dead self._run_and_check_ir("[+,-.]", []) self._run_and_check_ir("[+[[[,]-]-]-.]", []) self._run_and_check_ir("[+[[[,]-]-]-.][-]", []) self._run_and_check_ir("[+[[[,]-]-]-.][+]", []) self._run_and_check_ir("[+[[[,]-]-]-.][>>>]", []) self._run_and_check_ir("[+[[[,]-]-]-.][>>>][.]", []) # Loop open after loop close => dead self._run_and_check_ir( "+[>][.]", [ir.ADD(1), ir.OPEN(), ir.RIGHT(1), ir.CLOSE()], maxdepth=2) # Loop open after SET(0) => dead self._run_and_check_ir(",[-][>>]", [ir.INPUT(), ir.SET(0)]) self._run_and_check_ir(",[-][>>][..]", [ir.INPUT(), ir.SET(0)]) self._run_and_check_ir( ",[-][>>].", [ir.INPUT(), ir.SET(0), ir.OUTPUT()]) # But open after SET(x) where x > 0 => alive self._run_and_check_ir( ",[-]+[>>]", [ir.INPUT(), ir.SET(1), ir.OPEN(), ir.RIGHT(2), ir.CLOSE()], maxdepth=2)
def test_single_instruction_with_comment(self): # Single instructions with comments can be valid programs self._run_and_check_ir("a>", [ir.RIGHT(1)]) self._run_and_check_ir("<a", [ir.LEFT(1)]) self._run_and_check_ir("a.a", [ir.OUTPUT()]) self._run_and_check_ir("aa,", [ir.INPUT()]) self._run_and_check_ir("+aa", [ir.ADD(1)]) self._run_and_check_ir("aa-aa", [ir.SUB(1)])
def test_single_instruction(self): # Single brainfuck instructions can be valid programs self._run_and_check_ir(">", [ir.RIGHT(1)]) self._run_and_check_ir("<", [ir.LEFT(1)]) self._run_and_check_ir(",", [ir.INPUT()]) self._run_and_check_ir(".", [ir.OUTPUT()]) self._run_and_check_ir("+", [ir.ADD(1)]) self._run_and_check_ir("-", [ir.SUB(1)])
def test_clear_loops(self): # The [-] and [+] constructs become SET(0) # These are not included when calculating loop depth self._run_and_check_ir( ",[-].[+]", [ir.INPUT(), ir.SET(0), ir.OUTPUT(), ir.SET(0)], maxdepth=1)
def test_set(self): self.run_ir([ir.SET(0), ir.OUTPUT()], [], [0]) self.run_ir([ir.SET(1), ir.OUTPUT()], [], [1]) self.run_ir([ir.SET(2), ir.OUTPUT()], [], [2]) self.run_ir([ir.SET(255), ir.OUTPUT()], [], [255]) self.run_ir([ir.ADD(12), ir.SET(0), ir.OUTPUT()], [], [0]) self.run_ir([ir.ADD(12), ir.SET(1), ir.OUTPUT()], [], [1]) self.run_ir([ir.ADD(12), ir.SET(2), ir.OUTPUT()], [], [2]) self.run_ir([ir.ADD(12), ir.SET(255), ir.OUTPUT()], [], [255])
def test_deep_nested_loops(self): # +[{many}-(>+.<]){many} self.run_ir([ir.ADD(1)] + ([ir.OPEN()] * self.MAX_NESTED_LOOPS) + [ir.SUB(1)] + ([ir.RIGHT(1), ir.ADD(1), ir.OUTPUT(), ir.LEFT(1), ir.CLOSE()] * self.MAX_NESTED_LOOPS), [], [i % 256 for i in range(1, self.MAX_NESTED_LOOPS + 1)], steps=50000000)
def test_target_incomplete(self): self._run_and_check_target(17, 17, "@lang_") self._run_and_check_target(17, 17, "@lang_ ") self._run_and_check_target(17, 17, "@lang_\n") self._run_and_check_target(17, 17, "@lang_+", ir=[ir.ADD(1)]) self._run_and_check_target(17, 17, "@386_linu") self._run_and_check_target(17, 17, "@386_linu ") self._run_and_check_target(17, 17, "@386_linu\n") self._run_and_check_target(17, 17, "@386_linu_.", ir=[ir.OUTPUT()])
def test_cancellation_of_basic_instructions(self): # Two pairs of the basic brainfuck instructions are mutually # cancelling # Pairs of cancelling instructions are reduced self._run_and_check_ir("+-", []) self._run_and_check_ir("+-", []) self._run_and_check_ir("><", []) self._run_and_check_ir("<>", []) # Longer sequences of cancelling instructions are reduced self._run_and_check_ir("-+<>+-><", []) self._run_and_check_ir(".<<<<>>>>++++----.", [ir.OUTPUT(), ir.OUTPUT()]) # The cancelling instructions do not have to be immediately adjacent self._run_and_check_ir("+++-->><-+<-, <>-++-+-<><>.", [ir.INPUT(), ir.OUTPUT()]) self._run_and_check_ir(",>>+++>+--+<----+<<++>><<--", [ir.INPUT()])
def ops(d): ops = [] # first use only LEFT(1) and RIGHT(1) to set up memory ops += ([ir.RIGHT(1)] * (d) + [ir.SET(12)] + [ir.RIGHT(1)] * (d) + [ir.SET(32)] + [ir.RIGHT(1)] * (d) + [ir.SET(17)] + [ir.LEFT(1)] * (d * 2)) # state: 0(d) *12 0(d - 1) 32 0(d - 1) 17 0 # run a [.>>>>] construct to move up the pointer. this # should output 12, 32 and 17. ops += ([ir.OPEN(), ir.OUTPUT(), ir.RIGHT(d), ir.CLOSE()]) # state: 0(d) 12 0(d - 1) 32 0(d - 1) 17 0(d - 1) *0 # same thing in other direction. should output 17,32 ops += [ir.LEFT(d)] ops += ([ir.OPEN(), ir.OUTPUT(), ir.LEFT(d), ir.CLOSE()]) return ops
def test_loop_elimination(self): self._run_and_check_ir("[.]", []) self._run_and_check_ir( "[+++]+[][+++].[-][+++]", [ir.ADD(1), ir.OPEN(), ir.CLOSE(), ir.OUTPUT(), ir.SET(0)], maxdepth=2) self._run_and_check_ir("[-][+++]", [])
def test_basic_operations(self): self._run_and_check_ir(">+.<-[,]", [ ir.RIGHT(1), ir.ADD(1), ir.OUTPUT(), ir.LEFT(1), ir.SUB(1), ir.OPEN(), ir.INPUT(), ir.CLOSE() ], maxdepth=2)
def test_mul_overreaching(self): """Backend should handle MUL writing up to 127 cells outside of tape. There are perfectly valid programs for which the frontend will output multiplication operations that write to a position outside of the tape. One example would be '.[-<+>]' for which an OUTPUT() followed by LMUL(1,1) will be produced. There are other less obvious cases that the frontend cannot catch (and my gut says catching them all it's equivalent to the halting problem). One option is for the backends to always check that the current cell is non-zero before writing the product of a multiplication operation. Another option is to simply pad the tape with 127 cells on each end and to initialize the pointer at position 127 instead of 0 (and 127 is magic because it's the max offset the frontend emits for the multipication operations). """ # offsets on the left self.run_ir([ir.LMUL(1, 1), ir.OUTPUT()], [], [0]) self.run_ir([ir.LMUL(2, 1), ir.OUTPUT()], [], [0]) self.run_ir([ir.LMUL(3, 1), ir.OUTPUT()], [], [0]) self.run_ir([ir.LMUL(126, 1), ir.OUTPUT()], [], [0]) self.run_ir([ir.LMUL(127, 1), ir.OUTPUT()], [], [0]) # offsets on the right movetolastcell = [ir.RIGHT(127) for _ in range(516)] + [ir.RIGHT(2)] self.run_ir(movetolastcell + [ir.RMUL(1, 1), ir.OUTPUT()], [], [0], steps=10000000) self.run_ir(movetolastcell + [ir.RMUL(2, 1), ir.OUTPUT()], [], [0], steps=10000000) self.run_ir(movetolastcell + [ir.RMUL(3, 1), ir.OUTPUT()], [], [0], steps=10000000) self.run_ir(movetolastcell + [ir.RMUL(126, 1), ir.OUTPUT()], [], [0], steps=10000000) self.run_ir(movetolastcell + [ir.RMUL(127, 1), ir.OUTPUT()], [], [0], steps=10000000)
def test_cells_wrap(self): # .-.++.+{254}.+.-. self.run_ir([ir.OUTPUT(), ir.SUB(1), ir.OUTPUT(), ir.ADD(2), ir.OUTPUT(), ir.ADD(254), ir.OUTPUT(), ir.ADD(1), ir.OUTPUT(), ir.SUB(1), ir.OUTPUT()], [], [0, 255, 1, 255, 0, 255])
def test_all_instructions(self): # All instructions can live in the same program self._run_and_check_ir(">+.<-[,]", [ ir.RIGHT(1), ir.ADD(1), ir.OUTPUT(), ir.LEFT(1), ir.SUB(1), ir.OPEN(), ir.INPUT(), ir.CLOSE() ], maxdepth=2) self._run_and_check_ir("+,[->.]<", [ ir.ADD(1), ir.INPUT(), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.OUTPUT(), ir.CLOSE(), ir.LEFT(1) ], maxdepth=2)
def test_cancellation(self): self._run_and_check_ir("+++-->><-+<-, <>-++-+-<><>.", [ir.INPUT(), ir.OUTPUT()]) self._run_and_check_ir("-+<>+-><", []) self._run_and_check_ir(".<<<<>>>>++++----.", [ir.OUTPUT(), ir.OUTPUT()])
def test_eof_behaviour(self): # do no-change on EOF # ++++++++++,. self.run_ir([ir.ADD(10), ir.INPUT(), ir.OUTPUT()], [], [10])