brainclusterfuck 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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