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_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_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_contraction_overflow_movement(self): # Argument overflow on movement operations only add more # operations self._run_and_check_ir(['>'] * 255, [ir.RIGHT(255)]) self._run_and_check_ir(['>'] * 256, [ir.RIGHT(255), ir.RIGHT(1)]) self._run_and_check_ir(['>'] * 257, [ir.RIGHT(255), ir.RIGHT(2)]) self._run_and_check_ir(['<'] * 255, [ir.LEFT(255)]) self._run_and_check_ir(['<'] * 256, [ir.LEFT(255), ir.LEFT(1)]) self._run_and_check_ir(['<'] * 257, [ir.LEFT(255), ir.LEFT(2)])
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_contraction(self): self._run_and_check_ir("+++", [ir.ADD(3)]) self._run_and_check_ir(['+'] * 255, [ir.ADD(255)]) self._run_and_check_ir(['+'] * 256, []) self._run_and_check_ir("---", [ir.SUB(3)]) self._run_and_check_ir(['-'] * 255, [ir.SUB(255)]) self._run_and_check_ir(['-'] * 256, []) self._run_and_check_ir("<<<", [ir.LEFT(3)]) self._run_and_check_ir(['<'] * 128, [ir.LEFT(127), ir.LEFT(1)]) self._run_and_check_ir(">>>", [ir.RIGHT(3)]) self._run_and_check_ir(['>'] * 128, [ir.RIGHT(127), ir.RIGHT(1)])
def test_contraction_simple_cases(self): # Sequences of multiple brainfuck instructions can be # contracted into single bytecode operations self._run_and_check_ir("++", [ir.ADD(2)]) self._run_and_check_ir("+++", [ir.ADD(3)]) self._run_and_check_ir("+" * 128, [ir.ADD(128)]) self._run_and_check_ir("--", [ir.SUB(2)]) self._run_and_check_ir("---", [ir.SUB(3)]) self._run_and_check_ir("-" * 128, [ir.SUB(128)]) self._run_and_check_ir("<<", [ir.LEFT(2)]) self._run_and_check_ir("<<<", [ir.LEFT(3)]) self._run_and_check_ir("<" * 128, [ir.LEFT(128)]) self._run_and_check_ir(">>", [ir.RIGHT(2)]) self._run_and_check_ir(">>>", [ir.RIGHT(3)]) self._run_and_check_ir(">" * 128, [ir.RIGHT(128)])
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_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_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_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_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 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_target_386_linux(self): self._run_and_check_target(17, _386_LINUX, "@386_linux ") self._run_and_check_target(17, _386_LINUX, "@386_linux,,", ir=[ir.INPUT(), ir.INPUT()]) self._run_and_check_target(17, _386_LINUX, "@386_linux->", ir=[ir.SUB(1), ir.RIGHT(1)]) self._run_and_check_target(17, _386_LINUX, "@386_linux[foobar]") self._run_and_check_target(17, _386_LINUX, "@386_linux foobar")
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_cancellation_partial(self): # Cancellation can be partial, leaving part of the cancelling # instructions behind. # The basic brainfuck instructions self._run_and_check_ir("++++--", [ir.ADD(2)]) self._run_and_check_ir("--++++", [ir.ADD(2)]) self._run_and_check_ir("++----", [ir.SUB(2)]) self._run_and_check_ir("----++", [ir.SUB(2)]) self._run_and_check_ir(">>>><<", [ir.RIGHT(2)]) self._run_and_check_ir("<<>>>>", [ir.RIGHT(2)]) self._run_and_check_ir(">><<<<", [ir.LEFT(2)]) self._run_and_check_ir("<<<<>>", [ir.LEFT(2)]) # Cases with argument overflow, i.e. >255 of the same # instructions in a row, are also cancelled correctly. self._run_and_check_ir(['>'] * 258 + ['<', '<'], [ir.RIGHT(255), ir.RIGHT(1)]) self._run_and_check_ir(['>'] * 258 + ['<', '<', '<'], [ir.RIGHT(255)]) self._run_and_check_ir(['>'] * 258 + ['<', '<', '<', '<'], [ir.RIGHT(254)]) self._run_and_check_ir(['<'] * 258 + ['>', '>'], [ir.LEFT(255), ir.LEFT(1)]) self._run_and_check_ir(['<'] * 258 + ['>', '>', '>'], [ir.LEFT(255)]) self._run_and_check_ir(['<'] * 258 + ['>', '>', '>', '>'], [ir.LEFT(254)])
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_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_contraction_and_cancellation(self): # basic cases self._run_and_check_ir("++++--", [ir.ADD(2)]) self._run_and_check_ir("--++++", [ir.ADD(2)]) self._run_and_check_ir("++----", [ir.SUB(2)]) self._run_and_check_ir("----++", [ir.SUB(2)]) self._run_and_check_ir(">>>><<", [ir.RIGHT(2)]) self._run_and_check_ir("<<>>>>", [ir.RIGHT(2)]) self._run_and_check_ir(">><<<<", [ir.LEFT(2)]) self._run_and_check_ir("<<<<>>", [ir.LEFT(2)]) # cases triggering argument overflow self._run_and_check_ir(['>'] * 130 + ['<', '<'], [ir.RIGHT(127), ir.RIGHT(1)]) self._run_and_check_ir(['>'] * 130 + ['<', '<', '<'], [ir.RIGHT(127)]) self._run_and_check_ir(['>'] * 130 + ['<', '<', '<', '<'], [ir.RIGHT(126)]) self._run_and_check_ir(['<'] * 130 + ['>', '>'], [ir.LEFT(127), ir.LEFT(1)]) self._run_and_check_ir(['<'] * 130 + ['>', '>', '>'], [ir.LEFT(127)]) self._run_and_check_ir(['<'] * 130 + ['>', '>', '>', '>'], [ir.LEFT(126)])
def test_copy_loop_not_optimizeable(self): # loop must end where it started self._run_and_check_ir(',[->+]', [ ir.INPUT(), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(1), ir.CLOSE() ], maxdepth=2) self._run_and_check_ir(',[->+<<]', [ ir.INPUT(), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(1), ir.LEFT(2), ir.CLOSE() ], maxdepth=2) # must subtract 1 from cell 0 exactly once self._run_and_check_ir(',[->+<-]', [ ir.INPUT(), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(1), ir.LEFT(1), ir.SUB(1), ir.CLOSE() ], maxdepth=2) self._run_and_check_ir(',[+>+<--]', [ ir.INPUT(), ir.OPEN(), ir.ADD(1), ir.RIGHT(1), ir.ADD(1), ir.LEFT(1), ir.SUB(2), ir.CLOSE() ], maxdepth=2) self._run_and_check_ir(',[->+<+<->-]', [ ir.INPUT(), ir.OPEN(), ir.SUB(1), ir.RIGHT(1), ir.ADD(1), ir.LEFT(1), ir.ADD(1), ir.LEFT(1), ir.SUB(1), ir.RIGHT(1), ir.SUB(1), ir.CLOSE() ], maxdepth=2) # must be fewer than 127 < and 127 > self._run_and_check_ir(',[-' + '>' * 127 + '+' + '<' * 127 + ']', [ ir.INPUT(), ir.OPEN(), ir.SUB(1), ir.RIGHT(127), ir.ADD(1), ir.LEFT(127), ir.CLOSE() ], maxdepth=2) self._run_and_check_ir(',[-' + '<' * 127 + '+' + '>' * 127 + ']', [ ir.INPUT(), ir.OPEN(), ir.SUB(1), ir.LEFT(127), ir.ADD(1), ir.RIGHT(127), ir.CLOSE() ], maxdepth=2)