brainclusterfuck 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. data/.travis.yml +6 -0
  2. data/Gemfile +1 -1
  3. data/README.md +4 -0
  4. data/lib/brainclusterfuck/compile_error.rb +10 -0
  5. data/lib/brainclusterfuck/compiler.rb +57 -35
  6. data/lib/brainclusterfuck/error.rb +5 -0
  7. data/lib/brainclusterfuck/interpreter.rb +84 -0
  8. data/lib/brainclusterfuck/interpreter_error.rb +4 -0
  9. data/lib/brainclusterfuck/lexer.rb +2 -2
  10. data/lib/brainclusterfuck/loop_error.rb +6 -0
  11. data/lib/brainclusterfuck/memory.rb +33 -0
  12. data/lib/brainclusterfuck/memory_error.rb +6 -0
  13. data/lib/brainclusterfuck/opcodes.rb +5 -0
  14. data/lib/brainclusterfuck/opcodes/base.rb +19 -0
  15. data/lib/brainclusterfuck/opcodes/loop_base.rb +27 -0
  16. data/lib/brainclusterfuck/opcodes/loop_end.rb +10 -0
  17. data/lib/brainclusterfuck/opcodes/loop_end_placeholder.rb +6 -0
  18. data/lib/brainclusterfuck/opcodes/loop_placeholder.rb +20 -0
  19. data/lib/brainclusterfuck/opcodes/loop_start.rb +10 -0
  20. data/lib/brainclusterfuck/opcodes/loop_start_placeholder.rb +6 -0
  21. data/lib/brainclusterfuck/opcodes/modify_pointer.rb +9 -0
  22. data/lib/brainclusterfuck/opcodes/modify_value.rb +6 -0
  23. data/lib/brainclusterfuck/opcodes/modifying_base.rb +27 -0
  24. data/lib/brainclusterfuck/opcodes/print.rb +13 -0
  25. data/lib/brainclusterfuck/terminal.rb +4 -0
  26. data/lib/brainclusterfuck/version.rb +1 -1
  27. data/spec/compiler_spec.rb +82 -16
  28. data/spec/integration/basic_loops_spec.rb +55 -0
  29. data/spec/integration/basic_print_spec.rb +65 -0
  30. data/spec/integration/boundary_conditions_spec.rb +81 -0
  31. data/spec/interpreter_spec.rb +154 -0
  32. data/spec/lexer_spec.rb +2 -2
  33. data/spec/memory_spec.rb +33 -0
  34. data/spec/opcodes/base_spec.rb +26 -0
  35. data/spec/opcodes/loop_end_placeholder_spec.rb +5 -0
  36. data/spec/opcodes/loop_end_spec.rb +7 -0
  37. data/spec/opcodes/loop_start_placeholder_spec.rb +5 -0
  38. data/spec/opcodes/loop_start_spec.rb +7 -0
  39. data/spec/opcodes/modify_pointer_spec.rb +6 -0
  40. data/spec/opcodes/modify_value_spec.rb +6 -0
  41. data/spec/{opcode → opcodes}/print_spec.rb +3 -3
  42. data/spec/spec_helper.rb +2 -0
  43. data/spec/support/loop_opcode_shared_behavior.rb +22 -0
  44. data/spec/support/loop_placeholder_shared_behavior.rb +12 -0
  45. data/spec/{opcode/modify_value_spec.rb → support/modifying_opcode_shared_behavior.rb} +4 -7
  46. data/spec/terminal_spec.rb +11 -0
  47. metadata +60 -20
  48. data/lib/brainclusterfuck/cells.rb +0 -9
  49. data/lib/brainclusterfuck/opcode.rb +0 -11
  50. data/lib/brainclusterfuck/opcode/modify_pointer.rb +0 -27
  51. data/lib/brainclusterfuck/opcode/modify_value.rb +0 -27
  52. data/lib/brainclusterfuck/opcode/print.rb +0 -13
  53. data/lib/brainclusterfuck/vm.rb +0 -11
  54. data/spec/cells_spec.rb +0 -8
  55. data/spec/opcode/modify_pointer_spec.rb +0 -37
  56. data/spec/opcode_spec.rb +0 -10
  57. data/spec/vm_spec.rb +0 -22
@@ -0,0 +1,9 @@
1
+ require 'brainclusterfuck/opcodes/modifying_base'
2
+
3
+ module Brainclusterfuck::Opcodes
4
+ class ModifyPointer < ModifyingBase
5
+ def can_squeeze_with?(other)
6
+ super && (modify_by * other.modify_by) > 0 # same sign
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ require 'brainclusterfuck/opcodes/modifying_base'
2
+
3
+ module Brainclusterfuck::Opcodes
4
+ class ModifyValue < ModifyingBase
5
+ end
6
+ end
@@ -0,0 +1,27 @@
1
+ require 'brainclusterfuck/opcodes/base'
2
+
3
+ module Brainclusterfuck::Opcodes
4
+ class ModifyingBase < Base
5
+ attr_reader :modify_by
6
+
7
+ def initialize(modify_by, cycles)
8
+ @modify_by = modify_by.to_i
9
+ @cycles = cycles.to_i
10
+ end
11
+
12
+ def ==(other)
13
+ other.class == self.class &&
14
+ other.modify_by == modify_by &&
15
+ other.cycles == cycles
16
+ end
17
+
18
+ def can_squeeze_with?(other)
19
+ other.class == self.class
20
+ end
21
+
22
+ def squeeze_with(other)
23
+ raise "Cannot squeeze: #{self}, #{other}" unless can_squeeze_with?(other)
24
+ self.class.new(modify_by + other.modify_by, cycles + other.cycles)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ require 'brainclusterfuck/opcodes/base'
2
+
3
+ module Brainclusterfuck::Opcodes
4
+ class Print < Base
5
+ def initialize
6
+ @cycles = 1
7
+ end
8
+
9
+ def ==(other)
10
+ other.is_a?(Print)
11
+ end
12
+ end
13
+ end
@@ -5,5 +5,9 @@ module Brainclusterfuck
5
5
  def initialize
6
6
  @text = ''
7
7
  end
8
+
9
+ def print(char)
10
+ @text << char
11
+ end
8
12
  end
9
13
  end
@@ -1,3 +1,3 @@
1
1
  module Brainclusterfuck
2
- VERSION = "0.0.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,31 +1,78 @@
1
1
  require 'spec_helper'
2
2
  require 'brainclusterfuck/compiler'
3
- require 'brainclusterfuck/opcode'
3
+ require 'brainclusterfuck/compile_error'
4
+ require 'brainclusterfuck/opcodes'
4
5
 
5
6
  describe Brainclusterfuck::Compiler do
6
- it 'raises if there are no tokens' do
7
- expect { Brainclusterfuck::Compiler.new([]) }.to raise_error
7
+ describe 'invalid token streams' do
8
+ it 'raises if there are no tokens' do
9
+ expect { Brainclusterfuck::Compiler.new([]) }.to raise_error(Brainclusterfuck::CompileError)
10
+ end
11
+
12
+ it 'raises if there are unrecognized tokens' do
13
+ expect { Brainclusterfuck::Compiler.new([:foo]) }.to raise_error(Brainclusterfuck::CompileError)
14
+ end
15
+
16
+ it 'raises if an end loop occurs before an open loop' do
17
+ expect { Brainclusterfuck::Compiler.new([:loop_end]) }.to raise_error(Brainclusterfuck::PrematurelyTerminatedLoopError)
18
+ end
19
+
20
+ it 'raises if an open loop does not have an end loop' do
21
+ expect { Brainclusterfuck::Compiler.new([:loop_start]) }.to raise_error(Brainclusterfuck::UnterminatedLoopError)
22
+ end
23
+
24
+ it 'raises if there are too many end loops' do
25
+ expect { Brainclusterfuck::Compiler.new([:loop_start, :loop_end, :loop_end]) }.to raise_error(Brainclusterfuck::PrematurelyTerminatedLoopError)
26
+ end
27
+
28
+ it 'raises if there are not enough open loops' do
29
+ expect { Brainclusterfuck::Compiler.new([:loop_start, :loop_start, :loop_end]) }.to raise_error(Brainclusterfuck::UnterminatedLoopError)
30
+ end
8
31
  end
9
32
 
10
33
  it 'converts tokens to the correct operations' do
11
- compiler = Brainclusterfuck::Compiler.new([:v_incr, :p_incr, :v_decr, :p_decr])
34
+ compiler = Brainclusterfuck::Compiler.new([:v_incr, :p_incr, :loop_start, :v_decr, :loop_end, :p_decr, :print])
12
35
  expect(compiler.bytecode).to eq([
13
- Brainclusterfuck::Opcode::ModifyValue.new(1, 1),
14
- Brainclusterfuck::Opcode::ModifyPointer.new(1, 1),
15
- Brainclusterfuck::Opcode::ModifyValue.new(-1, 1),
16
- Brainclusterfuck::Opcode::ModifyPointer.new(-1, 1)
36
+ Brainclusterfuck::Opcodes::ModifyValue.new(1, 1),
37
+ Brainclusterfuck::Opcodes::ModifyPointer.new(1, 1),
38
+ Brainclusterfuck::Opcodes::LoopStart.new(1),
39
+ Brainclusterfuck::Opcodes::ModifyValue.new(-1, 1),
40
+ Brainclusterfuck::Opcodes::LoopEnd.new(1),
41
+ Brainclusterfuck::Opcodes::ModifyPointer.new(-1, 1),
42
+ Brainclusterfuck::Opcodes::Print.new
17
43
  ])
18
44
  end
19
45
 
20
46
  describe '#squeeze_operations' do
21
- it 'compresses the incr/decr operations' do
47
+ it 'compresses the lone incr/decr operations' do
22
48
  tokens = (Array.new(8) { :v_incr }).concat(Array.new(3) { :p_decr })
23
49
  compiler = Brainclusterfuck::Compiler.new(tokens)
24
50
  compiler.squeeze_operations!
25
51
 
26
52
  expect(compiler.bytecode).to eq([
27
- Brainclusterfuck::Opcode::ModifyValue.new(8, 8),
28
- Brainclusterfuck::Opcode::ModifyPointer.new(-3, 3)
53
+ Brainclusterfuck::Opcodes::ModifyValue.new(8, 8),
54
+ Brainclusterfuck::Opcodes::ModifyPointer.new(-3, 3)
55
+ ])
56
+ end
57
+
58
+ it 'merges opposite-direction value modifiers' do
59
+ tokens = (Array.new(8) { :v_incr }).concat(Array.new(3) { :v_decr })
60
+ compiler = Brainclusterfuck::Compiler.new(tokens)
61
+ compiler.squeeze_operations!
62
+
63
+ expect(compiler.bytecode).to eq([
64
+ Brainclusterfuck::Opcodes::ModifyValue.new(5, 11)
65
+ ])
66
+ end
67
+
68
+ it 'does not merge opposite-direction pointer modifiers' do
69
+ tokens = (Array.new(8) { :p_incr }).concat(Array.new(3) { :p_decr })
70
+ compiler = Brainclusterfuck::Compiler.new(tokens)
71
+ compiler.squeeze_operations!
72
+
73
+ expect(compiler.bytecode).to eq([
74
+ Brainclusterfuck::Opcodes::ModifyPointer.new(8, 8),
75
+ Brainclusterfuck::Opcodes::ModifyPointer.new(-3, 3)
29
76
  ])
30
77
  end
31
78
 
@@ -39,8 +86,9 @@ describe Brainclusterfuck::Compiler do
39
86
  compiler.squeeze_operations!
40
87
 
41
88
  expect(compiler.bytecode).to eq([
42
- Brainclusterfuck::Opcode::ModifyValue.new(2, 4),
43
- Brainclusterfuck::Opcode::ModifyPointer.new(-4, 8)
89
+ Brainclusterfuck::Opcodes::ModifyValue.new(2, 4),
90
+ Brainclusterfuck::Opcodes::ModifyPointer.new(-6, 6),
91
+ Brainclusterfuck::Opcodes::ModifyPointer.new(2, 2)
44
92
  ])
45
93
  end
46
94
 
@@ -49,9 +97,27 @@ describe Brainclusterfuck::Compiler do
49
97
  compiler.squeeze_operations!
50
98
 
51
99
  expect(compiler.bytecode).to eq([
52
- Brainclusterfuck::Opcode::Print.new,
53
- Brainclusterfuck::Opcode::Print.new,
54
- Brainclusterfuck::Opcode::Print.new
100
+ Brainclusterfuck::Opcodes::Print.new,
101
+ Brainclusterfuck::Opcodes::Print.new,
102
+ Brainclusterfuck::Opcodes::Print.new
103
+ ])
104
+ end
105
+
106
+ it 'handles loops properly' do
107
+ compiler = Brainclusterfuck::Compiler.new([
108
+ :loop_start,
109
+ :v_decr,
110
+ :v_decr,
111
+ :v_decr,
112
+ :loop_end
113
+ ])
114
+
115
+ compiler.squeeze_operations!
116
+
117
+ expect(compiler.bytecode).to eq([
118
+ Brainclusterfuck::Opcodes::LoopStart.new(1),
119
+ Brainclusterfuck::Opcodes::ModifyValue.new(-3, 3),
120
+ Brainclusterfuck::Opcodes::LoopEnd.new(1)
55
121
  ])
56
122
  end
57
123
  end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Basic loops' do
4
+ let(:lexer) { Brainclusterfuck::Lexer.new(program) }
5
+ let(:compiler) { Brainclusterfuck::Compiler.new(lexer.tokens) }
6
+ let(:terminal) { Brainclusterfuck::Terminal.new }
7
+ let(:memory) { Brainclusterfuck::Memory.new(1) }
8
+
9
+ let(:interpreter) do
10
+ Brainclusterfuck::Interpreter.new(
11
+ bytecode: compiler.bytecode,
12
+ terminal: terminal,
13
+ memory: memory
14
+ )
15
+ end
16
+
17
+ describe 'the decrement macro' do
18
+ let(:program) { '[-]' }
19
+ let(:expected_cycles) do
20
+ %w(
21
+ open_loop
22
+ decr
23
+ end_loop
24
+ decr
25
+ end_loop
26
+ decr
27
+ end_loop
28
+ ).size
29
+ end
30
+
31
+ def self.it_clears_the_cell
32
+ it 'reduces the value of that cell to zero' do
33
+ memory.modify_value(3)
34
+ interpreter.step(expected_cycles)
35
+ expect(interpreter).to be_finished
36
+
37
+ expect(interpreter.cycles).to eq(expected_cycles)
38
+ expect(memory.current_value).to eq(0)
39
+ end
40
+ end
41
+
42
+ context 'without optimizing' do
43
+ it_clears_the_cell
44
+ end
45
+
46
+ context 'with optimizing' do
47
+ before do
48
+ compiler.squeeze_operations!
49
+ end
50
+
51
+ it_clears_the_cell
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Printing simple text' do
4
+ let(:lexer) { Brainclusterfuck::Lexer.new(program) }
5
+ let(:compiler) { Brainclusterfuck::Compiler.new(lexer.tokens) }
6
+ let(:terminal) { Brainclusterfuck::Terminal.new }
7
+ let(:memory) { Brainclusterfuck::Memory.new(5) }
8
+
9
+ let(:interpreter) do
10
+ Brainclusterfuck::Interpreter.new(
11
+ bytecode: compiler.bytecode,
12
+ terminal: terminal,
13
+ memory: memory
14
+ )
15
+ end
16
+
17
+ describe 'printing "!"' do
18
+ let(:program) { ('+' * 33) + '.' }
19
+
20
+ def self.it_prints
21
+ it 'sends "!" to the terminal object' do
22
+ interpreter.step(35)
23
+ expect(interpreter).to be_finished
24
+ expect(interpreter.cycles).to eq(34) # 33 increments + 1 print
25
+ expect(terminal.text).to eq('!')
26
+ end
27
+ end
28
+
29
+ context 'without optimizing' do
30
+ it_prints
31
+ end
32
+
33
+ context 'when optimizing' do
34
+ before do
35
+ compiler.squeeze_operations!
36
+ end
37
+
38
+ it_prints
39
+ end
40
+ end
41
+
42
+ describe 'printing "X" using multiple memory locations' do
43
+ let(:program) { '>++++++++[<+++++++++++>-]<.' }
44
+
45
+ def self.it_prints_x
46
+ it 'sends "X" to the terminal object' do
47
+ interpreter.step(500)
48
+ expect(interpreter).to be_finished
49
+ expect(terminal.text).to eq('X')
50
+ end
51
+ end
52
+
53
+ context 'without optimizing' do
54
+ it_prints_x
55
+ end
56
+
57
+ context 'when optimizing' do
58
+ before do
59
+ compiler.squeeze_operations!
60
+ end
61
+
62
+ it_prints_x
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Boundary conditions' do
4
+ let(:lexer) { Brainclusterfuck::Lexer.new(program) }
5
+ let(:compiler) { Brainclusterfuck::Compiler.new(lexer.tokens) }
6
+ let(:terminal) { Brainclusterfuck::Terminal.new }
7
+ let(:memory) { Brainclusterfuck::Memory.new(5) }
8
+
9
+ let(:interpreter) do
10
+ Brainclusterfuck::Interpreter.new(
11
+ bytecode: compiler.bytecode,
12
+ terminal: terminal,
13
+ memory: memory
14
+ )
15
+ end
16
+
17
+ describe 'adjusting pointer out of bounds' do
18
+ let(:program) { '<>>' }
19
+
20
+ def self.it_raises
21
+ it 'raises an exception' do
22
+ expect { interpreter.step(3) }.to raise_error(Brainclusterfuck::MemoryError)
23
+ end
24
+ end
25
+
26
+ context 'without optimizing' do
27
+ it_raises
28
+ end
29
+
30
+ context 'when optimizing' do
31
+ before do
32
+ compiler.squeeze_operations!
33
+ end
34
+
35
+ it_raises
36
+ end
37
+ end
38
+
39
+ describe 'overflowing memory value' do
40
+ def self.it_rolls_over
41
+ it 'rolls over' do
42
+ interpreter.step(num_steps)
43
+ expect(memory.current_value).to eq(255)
44
+ end
45
+ end
46
+
47
+ describe 'going negative' do
48
+ let(:program) { '-' }
49
+ let(:num_steps) { 1 }
50
+
51
+ context 'without optimizing' do
52
+ it_rolls_over
53
+ end
54
+
55
+ context 'when optimizing' do
56
+ before do
57
+ compiler.squeeze_operations!
58
+ end
59
+
60
+ it_rolls_over
61
+ end
62
+ end
63
+
64
+ describe 'going positive' do
65
+ let(:program) { '+' * num_steps }
66
+ let(:num_steps) { 255 + 256 }
67
+
68
+ context 'without optimizing' do
69
+ it_rolls_over
70
+ end
71
+
72
+ context 'when optimizing' do
73
+ before do
74
+ compiler.squeeze_operations!
75
+ end
76
+
77
+ it_rolls_over
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+ require 'brainclusterfuck/interpreter'
3
+
4
+ describe Brainclusterfuck::Interpreter do
5
+ let(:bytecode) { double(:bytecode, size: 5) }
6
+ let(:terminal) { double(:terminal) }
7
+ let(:memory) { double(:memory) }
8
+
9
+ let(:interpreter) do
10
+ described_class.new(
11
+ bytecode: bytecode,
12
+ terminal: terminal,
13
+ memory: memory
14
+ )
15
+ end
16
+
17
+ it 'exposes terminal and memory' do
18
+ expect(interpreter.terminal).to equal(terminal)
19
+ expect(interpreter.memory).to equal(memory)
20
+ end
21
+
22
+ it 'exposes cycles, starting at 0' do
23
+ expect(interpreter.cycles).to eq(0)
24
+ end
25
+
26
+ it 'exposes the instruction pointer, starting at 0' do
27
+ expect(interpreter.instruction_pointer).to eq(0)
28
+ end
29
+
30
+ describe '#step' do
31
+ it 'raises if step is 0 or negative' do
32
+ expect { interpreter.step(0) }.to raise_error(ArgumentError)
33
+ expect { interpreter.step(-1) }.to raise_error(ArgumentError)
34
+ end
35
+
36
+ it 'raises an ArgumentError if attempting to execute an unknown opcode' do
37
+ weird_opcode = Brainclusterfuck::Opcodes::Base.new
38
+ weird_opcode.stub(cycles: 1)
39
+ bytecode.should_receive(:[]).with(0).and_return(weird_opcode)
40
+
41
+ expect { interpreter.step(1) }.to raise_error(ArgumentError)
42
+ end
43
+
44
+ it 'does nothing when stepping past a fully-executed program' do
45
+ bytecode.should_receive(:[]).with(0).and_return(nil)
46
+
47
+ interpreter.step(1)
48
+
49
+ expect(interpreter).to be_finished
50
+ end
51
+
52
+ it 'will step exactly that number of cycles if possible' do
53
+ bytecode.should_receive(:[]).with(0).and_return(Brainclusterfuck::Opcodes::ModifyValue.new(1, 1))
54
+ memory.should_receive(:modify_value).with(1)
55
+
56
+ interpreter.step(1)
57
+ expect(interpreter.cycles).to eq(1)
58
+ end
59
+
60
+ it 'will step more than the asked cycles if necessary' do
61
+ bytecode.should_receive(:[]).with(0).and_return(Brainclusterfuck::Opcodes::ModifyValue.new(1, 5))
62
+ memory.should_receive(:modify_value).with(1)
63
+
64
+ interpreter.step(1)
65
+ expect(interpreter.cycles).to eq(5)
66
+ end
67
+
68
+ describe 'opcodes' do
69
+ def set_up_bytecode(hash)
70
+ hash.each do |index, op|
71
+ bytecode.stub(:[]).with(index).and_return(op)
72
+ end
73
+ end
74
+
75
+ it 'handles ModifyValue' do
76
+ set_up_bytecode(0 => Brainclusterfuck::Opcodes::ModifyValue.new(1, 9))
77
+ memory.should_receive(:modify_value).with(1)
78
+
79
+ interpreter.step(1)
80
+ expect(interpreter.cycles).to eq(9)
81
+ end
82
+
83
+ it 'handles ModifyValue' do
84
+ set_up_bytecode(0 => Brainclusterfuck::Opcodes::ModifyPointer.new(1, 9))
85
+ memory.should_receive(:modify_pointer).with(1)
86
+
87
+ interpreter.step(1)
88
+ expect(interpreter.cycles).to eq(9)
89
+ end
90
+
91
+ it 'handles Print' do
92
+ set_up_bytecode(0 => Brainclusterfuck::Opcodes::Print.new)
93
+ memory.stub(current_char: 'x')
94
+ terminal.should_receive(:print).with('x')
95
+
96
+ interpreter.step(1)
97
+ expect(interpreter.cycles).to eq(1)
98
+ end
99
+
100
+ describe 'loops' do
101
+ before do
102
+ set_up_bytecode(
103
+ 0 => Brainclusterfuck::Opcodes::LoopStart.new(1),
104
+ 1 => Brainclusterfuck::Opcodes::ModifyPointer.new(1, 1),
105
+ 2 => Brainclusterfuck::Opcodes::LoopEnd.new(1),
106
+ 3 => Brainclusterfuck::Opcodes::ModifyValue.new(1, 1),
107
+ )
108
+ end
109
+
110
+ describe 'LoopStart' do
111
+ it 'stays in the loop when the current value is non-zero' do
112
+ memory.stub(current_value: 1)
113
+ memory.should_receive(:modify_pointer).with(1)
114
+ memory.should_not_receive(:modify_value)
115
+
116
+ interpreter.step(2)
117
+ expect(interpreter.cycles).to eq(2)
118
+ end
119
+
120
+ it 'jumps past the end loop when the current value is zero' do
121
+ memory.stub(current_value: 0)
122
+ memory.should_receive(:modify_value).with(1)
123
+ memory.should_not_receive(:modify_pointer)
124
+
125
+ interpreter.step(2)
126
+ expect(interpreter.cycles).to eq(2)
127
+ end
128
+ end
129
+
130
+ describe 'LoopEnd' do
131
+ it 'goes past the loop when the value is zero' do
132
+ memory.stub(current_value: 1)
133
+ memory.should_receive(:modify_pointer).with(1) do
134
+ memory.stub(current_value: 0)
135
+ memory.should_receive(:modify_value).with(1)
136
+ end
137
+
138
+ interpreter.step(4)
139
+
140
+ expect(interpreter.cycles).to eq(4)
141
+ end
142
+
143
+ it 'goes back to the beginning of the loop when the value is non-zero' do
144
+ memory.stub(current_value: 1)
145
+ memory.should_receive(:modify_pointer).with(1).twice
146
+
147
+ interpreter.step(4)
148
+ expect(interpreter.cycles).to eq(4)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end