yabfi 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,223 @@
1
+ require 'spec_helper'
2
+
3
+ describe YABFI::Consumer do
4
+ subject { described_class.new(input) }
5
+ let(:input) { [] }
6
+
7
+ describe '#end_of_input?' do
8
+ let(:input) { [1, 2, 3] }
9
+
10
+ context 'when all of the input has been consumed' do
11
+ before { subject.seek(input.length) }
12
+
13
+ it 'returns true' do
14
+ expect(subject).to be_end_of_input
15
+ end
16
+ end
17
+
18
+ context 'when some of the input has not been consumed' do
19
+ it 'returns false' do
20
+ input.length.times do |int|
21
+ subject.seek(int)
22
+ expect(subject).to_not be_end_of_input
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#peek' do
29
+ let(:input) { %w(a b c) }
30
+
31
+ context 'when all of the input has been consumed' do
32
+ before { subject.seek(input.length) }
33
+
34
+ it 'fails with EndOfInput' do
35
+ expect { subject.peek }.to raise_error(described_class::EndOfInput)
36
+ end
37
+ end
38
+
39
+ context 'when some of the input has not been consumed' do
40
+ it 'returns the next character in the Array' do
41
+ input.each_with_index do |sym, int|
42
+ subject.seek(int)
43
+ expect(subject.peek).to eq(sym)
44
+ end
45
+ end
46
+
47
+ it 'does not advance the parse' do
48
+ expect { subject.peek }.to_not change { subject.consume_index }
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#advance' do
54
+ let(:input) { %w(a b c d e f) }
55
+
56
+ context 'when all of the input has been consumed' do
57
+ before { subject.seek(input.length) }
58
+
59
+ it 'fails with EndOfInput' do
60
+ expect { subject.advance }.to raise_error(described_class::EndOfInput)
61
+ end
62
+ end
63
+
64
+ context 'when some of the input has not been consumed' do
65
+ it 'returns the next character in the Array and advances the parse' do
66
+ input.each { |str| expect(subject.advance).to eq(str) }
67
+ expect { subject.advance }.to raise_error(described_class::EndOfInput)
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#satisfy' do
73
+ let(:input) { (0..9).to_a }
74
+
75
+ context 'when all of the input has been consumed' do
76
+ before { subject.seek(input.length) }
77
+
78
+ it 'fails with EndOfInput' do
79
+ expect { subject.satisfy(&:odd?) }
80
+ .to raise_error(described_class::EndOfInput)
81
+ end
82
+ end
83
+
84
+ context 'when some of the input has not been consumed' do
85
+ context 'but the predicate returns a falsey value' do
86
+ it 'fails with a Unsatisfied' do
87
+ expect { subject.satisfy(&:odd?) }
88
+ .to raise_error(YABFI::Consumer::Unsatisfied)
89
+ end
90
+ end
91
+
92
+ context 'and the predicate returns a truthy value' do
93
+ it 'returns the matched token and advances the parse' do
94
+ input.each do |num|
95
+ predicate = num.even? ? :even? : :odd?
96
+ expect(subject.satisfy(&predicate)).to eq(num)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '#eq' do
104
+ let(:input) { %w(cat dog chicken) }
105
+
106
+ context 'when all of the input has been consumed' do
107
+ before { subject.seek(input.length) }
108
+
109
+ it 'fails with EndOfInput' do
110
+ expect { subject.eq('cat') }.to raise_error(described_class::EndOfInput)
111
+ end
112
+ end
113
+
114
+ context 'when some of the input has not been consumed' do
115
+ context 'but the expected token does not match the actual token' do
116
+ it 'fails with a Unsatisfied' do
117
+ expect { subject.eq('dog') }
118
+ .to raise_error(YABFI::Consumer::Unsatisfied)
119
+ end
120
+ end
121
+
122
+ context 'and the expeted token matches the actual token' do
123
+ it 'returns the matched token and advances the parse' do
124
+ input.each { |str| expect(subject.eq(str)).to eq(str) }
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#one_of' do
131
+ let(:input) { %i(foo bar baz) }
132
+
133
+ context 'when all of the input has been consumed' do
134
+ before { subject.seek(input.length) }
135
+
136
+ it 'fails with EndOfInput' do
137
+ expect { subject.one_of(*input) }
138
+ .to raise_error(described_class::EndOfInput)
139
+ end
140
+ end
141
+
142
+ context 'when some of the input has not been consumed' do
143
+ context 'but the expeted tokens do not match the actual token' do
144
+ it 'fails with a Unsatisfied' do
145
+ expect { subject.one_of(:hey, :hi, :howdy) }
146
+ .to raise_error(YABFI::Consumer::Unsatisfied)
147
+ end
148
+ end
149
+
150
+ context 'and the expeted tokens match the actual token' do
151
+ it 'returns the matched token and advances the parse' do
152
+ expect(subject.one_of(:foo, :bar)).to eq(:foo)
153
+ expect(subject.one_of(:bar, :baz)).to eq(:bar)
154
+ expect(subject.one_of(:baz, :foo)).to eq(:baz)
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ describe '#attempt' do
161
+ let(:input) { [1, :mixed, 'array', nil] }
162
+
163
+ context 'when the parse fails with in the given block' do
164
+ it 'returns nil' do
165
+ expect(subject.attempt { token(:mixed) }).to be(nil)
166
+ end
167
+
168
+ it 'resets the counter' do
169
+ expect { subject.attempt { [1, 2].each { |n| subject.eq(n) } } }
170
+ .to_not change { subject.consume_index }
171
+ end
172
+ end
173
+
174
+ context 'when the parse does not fail with in the given block' do
175
+ it 'returns the result' do
176
+ expect(subject.attempt { subject.satisfy(&:odd?) }).to eq(1)
177
+ end
178
+
179
+ it 'advances the parse' do
180
+ expect { subject.attempt { subject.eq(1) } }
181
+ .to change { subject.consume_index }
182
+ .from(0)
183
+ .to(1)
184
+ end
185
+ end
186
+ end
187
+
188
+ describe '#many' do
189
+ let(:input) { [2, 4, 6, 8, 9] }
190
+
191
+ it 'calls the given block until it fails or returns nil' do
192
+ expect(subject.many { subject.satisfy(&:even?) }).to eq([2, 4, 6, 8])
193
+ expect(subject.eq(9)).to eq(9)
194
+ expect(subject).to be_end_of_input
195
+ end
196
+ end
197
+
198
+ describe '#many_one' do
199
+ let(:input) { [1, 3, 5, 2, 4, 6] }
200
+
201
+ context 'when 0 results can be matched' do
202
+ it 'fails with Unsatisfied' do
203
+ expect { subject.many_one { subject.eq(2) } }
204
+ .to raise_error(described_class::Unsatisfied)
205
+ end
206
+ end
207
+
208
+ context 'when 1 result can be matched' do
209
+ it 'advances the parse and returns the result' do
210
+ expect(subject.many_one { subject.eq(1) }).to eq([1])
211
+ expect(subject.many_one { subject.eq(3) }).to eq([3])
212
+ end
213
+ end
214
+
215
+ context 'when many results can be matched' do
216
+ it 'advances the parse and returns the results' do
217
+ expect(subject.many_one { subject.satisfy(&:odd?) }).to eq([1, 3, 5])
218
+ expect(subject.many_one { subject.satisfy(&:even?) }).to eq([2, 4, 6])
219
+ expect(subject).to be_end_of_input
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe YABFI::Encoder do
4
+ describe '.encode' do
5
+ let(:ary) do
6
+ [
7
+ [:put, 3],
8
+ [:change_value, 4],
9
+ [:branch_if_zero, 4],
10
+ [:change_pointer, 1],
11
+ [:get, 1],
12
+ [:branch_not_zero, -2],
13
+ [:branch_if_zero, 3],
14
+ [:change_value, -1],
15
+ [:branch_not_zero, -1],
16
+ [:change_value, 65],
17
+ [:put, 1]
18
+ ]
19
+ end
20
+ let(:expected) do
21
+ [
22
+ [3, 3],
23
+ [0, 4],
24
+ [4, 4],
25
+ [1, 1],
26
+ [2, 1],
27
+ [5, -2],
28
+ [4, 3],
29
+ [0, -1],
30
+ [5, -1],
31
+ [0, 65],
32
+ [3, 1]
33
+ ]
34
+ end
35
+
36
+ it 'encodes the instructions into integers' do
37
+ expect(subject.encode(ary)).to eq(expected)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe YABFI::Lexer do
4
+ subject { described_class }
5
+
6
+ describe '#run!' do
7
+ context 'when all of the input cannot be consumed' do
8
+ let(:input) do
9
+ %i(
10
+ loop
11
+ end
12
+ pred
13
+ fail
14
+ )
15
+ end
16
+
17
+ it 'fails with Consumer::Unsatisfied' do
18
+ expect { subject.run!(input) }
19
+ .to raise_error(YABFI::Consumer::Unsatisfied)
20
+ end
21
+ end
22
+
23
+ context 'when all of the input can be consumed' do
24
+ let(:input) do
25
+ %i(
26
+ succ
27
+ loop
28
+ loop
29
+ prev
30
+ prev
31
+ end
32
+ put
33
+ end
34
+ next
35
+ get
36
+ pred
37
+ )
38
+ end
39
+ let(:expected) do
40
+ [
41
+ [:change_value, 1],
42
+ [
43
+ :loop,
44
+ [
45
+ [
46
+ :loop,
47
+ [
48
+ [:change_pointer, -2]
49
+ ]
50
+ ],
51
+ [:put, 1]
52
+ ]
53
+ ],
54
+ [:change_pointer, 1],
55
+ [:get, 1],
56
+ [:change_value, -1]
57
+ ]
58
+ end
59
+
60
+ it 'returns the syntax forest' do
61
+ expect(subject.run!(input)).to eq(expected)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe YABFI::Parser do
4
+ describe '.parse' do
5
+ let(:io) { StringIO.new('+-<>,.[] some comment') }
6
+ let(:enum) { subject.parse(io) }
7
+ let(:expected) { %i(succ pred prev next get put loop end) }
8
+
9
+ it 'returns an Enumator that yields each command from the given IO' do
10
+ expect(enum.to_a).to eq(expected)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe YABFI::Unroll do
4
+ describe '.unroll' do
5
+ let(:tokens) do
6
+ %i(
7
+ succ
8
+ succ
9
+ next
10
+ loop
11
+ loop
12
+ prev
13
+ end
14
+ loop
15
+ get
16
+ end
17
+ next
18
+ end
19
+ put
20
+ pred
21
+ )
22
+ end
23
+ let(:input) { YABFI::Lexer.run!(tokens) }
24
+ let(:expected) do
25
+ [
26
+ [:change_value, 2],
27
+ [:change_pointer, 1],
28
+ [:branch_if_zero, 9],
29
+ [:branch_if_zero, 3],
30
+ [:change_pointer, -1],
31
+ [:branch_not_zero, -1],
32
+ [:branch_if_zero, 3],
33
+ [:get, 1],
34
+ [:branch_not_zero, -1],
35
+ [:change_pointer, 1],
36
+ [:branch_not_zero, -7],
37
+ [:put, 1],
38
+ [:change_value, -1]
39
+ ]
40
+ end
41
+
42
+ it 'unrolls the syntax forest' do
43
+ expect(subject.unroll(input)).to eq(expected)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+
3
+ describe YABFI::VM do
4
+ let(:input) { StringIO.new }
5
+ let(:output) { StringIO.new }
6
+ let(:eof) { -1 }
7
+ subject { described_class.new(input, output, eof) }
8
+
9
+ describe '#execute!' do
10
+ let(:commands) { [] }
11
+
12
+ before { subject.load!(YABFI::Encoder.encode(commands)) }
13
+
14
+ context 'when it executes a change_value command' do
15
+ let(:commands) do
16
+ [
17
+ [:change_value, -4],
18
+ [:change_value, 7]
19
+ ]
20
+ end
21
+
22
+ it 'changes the value at the memory cursor by the given delta' do
23
+ expect { subject.execute! }
24
+ .to change { subject.state[:current_value] }
25
+ .from(0)
26
+ .to(3)
27
+ end
28
+
29
+ it 'advances the program counter by 1' do
30
+ expect { subject.execute! }
31
+ .to change { subject.state[:program_counter] }
32
+ .by(commands.length)
33
+ end
34
+ end
35
+
36
+ context 'when it executes a change_pointer command' do
37
+ context 'when the pointer is moved below 0' do
38
+ let(:commands) do
39
+ [
40
+ [:change_pointer, 3],
41
+ [:change_pointer, -4]
42
+ ]
43
+ end
44
+
45
+ it 'fails with MemoryOutOfBounds' do
46
+ expect { subject.execute! }
47
+ .to raise_error(described_class::MemoryOutOfBounds)
48
+ end
49
+ end
50
+
51
+ context 'when the pointer is moved above 0' do
52
+ let(:commands) do
53
+ [
54
+ [:change_pointer, 100],
55
+ [:change_pointer, -65]
56
+ ]
57
+ end
58
+
59
+ it 'moves the cursor' do
60
+ expect { subject.execute! }
61
+ .to change { subject.state[:memory_cursor] }
62
+ .from(0)
63
+ .to(35)
64
+ end
65
+
66
+ it 'advances the program counter by 1' do
67
+ expect { subject.execute! }
68
+ .to change { subject.state[:program_counter] }
69
+ .by(commands.length)
70
+ end
71
+ end
72
+ end
73
+
74
+ context 'when it executes a get command' do
75
+ context 'when EOF is encountered' do
76
+ let(:commands) do
77
+ [
78
+ [:get, 1]
79
+ ]
80
+ end
81
+
82
+ it 'sets the current memory location to the specified EOF value' do
83
+ subject.execute!
84
+ expect(subject.state[:current_value]).to eq(eof)
85
+ end
86
+
87
+ it 'advances the program counter by 1' do
88
+ expect { subject.execute! }
89
+ .to change { subject.state[:program_counter] }
90
+ .by(commands.length)
91
+ end
92
+ end
93
+
94
+ context 'when EOF is not encountered' do
95
+ let(:input) { StringIO.new('ABC') }
96
+ let(:commands) do
97
+ [
98
+ [:get, 2]
99
+ ]
100
+ end
101
+
102
+ it 'sets the current memory localtion to the int value of the input' do
103
+ subject.execute!
104
+ expect(subject.state[:current_value]).to eq('B'.ord)
105
+ end
106
+
107
+ it 'advances the program counter by 1' do
108
+ expect { subject.execute! }
109
+ .to change { subject.state[:program_counter] }
110
+ .by(commands.length)
111
+ end
112
+ end
113
+ end
114
+
115
+ context 'when it executes a put command' do
116
+ let(:commands) do
117
+ [
118
+ [:change_value, 'E'.ord],
119
+ [:put, 5]
120
+ ]
121
+ end
122
+
123
+ it 'prints the current memory location the specified number of times' do
124
+ expect { subject.execute! }
125
+ .to change { output.string }
126
+ .from('')
127
+ .to('EEEEE')
128
+ end
129
+
130
+ it 'advances the program counter by 1' do
131
+ expect { subject.execute! }
132
+ .to change { subject.state[:program_counter] }
133
+ .by(commands.length)
134
+ end
135
+ end
136
+
137
+ context 'when it executes a branch_if_zero command' do
138
+ context 'when the current memory location is zero' do
139
+ let(:commands) do
140
+ [
141
+ [:branch_if_zero, 5]
142
+ ]
143
+ end
144
+
145
+ it 'branches to the specified location' do
146
+ expect { subject.execute! }
147
+ .to change { subject.state[:program_counter] }
148
+ .to(5)
149
+ end
150
+ end
151
+
152
+ context 'when the current memory location is not zero' do
153
+ let(:commands) do
154
+ [
155
+ [:change_value, 1],
156
+ [:branch_if_zero, -1],
157
+ [:change_value, 2]
158
+ ]
159
+ end
160
+
161
+ it 'advances the program counter by 1' do
162
+ expect { subject.execute! }
163
+ .to change { subject.state[:program_counter] }
164
+ .by(commands.length)
165
+ end
166
+ end
167
+ end
168
+
169
+ context 'when it executes a branch_not_zero command' do
170
+ context 'when the current memory location is zero' do
171
+ let(:commands) do
172
+ [
173
+ [:branch_not_zero, -1],
174
+ [:change_value, 1]
175
+ ]
176
+ end
177
+
178
+ it 'advances the program counter by 1' do
179
+ expect { subject.execute! }
180
+ .to change { subject.state[:program_counter] }
181
+ .by(commands.length)
182
+ end
183
+ end
184
+
185
+ context 'when the current memory location is not zero' do
186
+ let(:commands) do
187
+ [
188
+ [:change_value, 3],
189
+ [:change_value, -1],
190
+ [:branch_not_zero, -1]
191
+ ]
192
+ end
193
+
194
+ it 'branches to the specified location' do
195
+ subject.execute!
196
+ expect(subject.state[:current_value]).to be_zero
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe YABFI do
4
+ it 'has a version number' do
5
+ expect(YABFI::VERSION).to_not be(nil)
6
+ end
7
+
8
+ describe '.eval' do
9
+ let(:commands) { '' }
10
+ let(:input) { StringIO.new }
11
+ let(:output) { StringIO.new }
12
+ let(:eof) { 0 }
13
+
14
+ before { subject.eval!(commands, input: input, output: output, eof: eof) }
15
+
16
+ context 'when the program is "Hello World"' do
17
+ let(:commands) do
18
+ <<-EOS
19
+ >++++++++[<+++++++++>-]<.>>+>+>++>[-]+<[>[->+<<++++>]<<]>.+++++++..+++
20
+ .>>+++++++.<<<[[-]<[-]>]<+++++++++++++++.>>.+++.------.--------.>>+.>+
21
+ +++.
22
+ EOS
23
+ end
24
+
25
+ it 'evaluates the commands' do
26
+ expect(output.string).to eq("Hello World!\n")
27
+ end
28
+ end
29
+
30
+ context 'when the program is "cat"' do
31
+ let(:commands) { ',[.,]' }
32
+ let(:string) { 'Howdy ho! Cowby hat!' }
33
+ let(:input) { StringIO.new(string) }
34
+
35
+ it 'evaluates the commands' do
36
+ expect(output.string).to eq(string)
37
+ end
38
+ end
39
+
40
+ context 'when the program is wc' do
41
+ let(:commands) do
42
+ <<-EOS
43
+ >>>+>>>>>+>>+>>+[<<],[
44
+ -[-[-[-[-[-[-[-[<+>-[>+<-[>-<-[-[-[<++[<++++++>-]<
45
+ [>>[-<]<[>]<-]>>[<+>-[<->[-]]]]]]]]]]]]]]]]
46
+ <[-<<[-]+>]<<[>>>>>>+<<<<<<-]>[>]>>>>>>>+>[
47
+ <+[
48
+ >+++++++++<-[>-<-]++>[<+++++++>-[<->-]+[+>>>>>>]]
49
+ <[>+<-]>[>>>>>++>[-]]+<
50
+ ]>[-<<<<<<]>>>>
51
+ ],
52
+ ]+<++>>>[[+++++>>>>>>]<+>+[[<++++++++>-]<.<<<<<]>>>>>>>>]
53
+ EOS
54
+ end
55
+ let(:input) { StringIO.new("one two\nthree four\n") }
56
+
57
+ it 'evaluates the commands' do
58
+ expect(output.string).to eq("\t2\t4\t19\n")
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,21 @@
1
+ require 'pry'
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+
5
+ lib = File.expand_path('../../lib', __FILE__)
6
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
7
+ require 'yabfi'
8
+
9
+ RSpec.configure do |config|
10
+ config.around(:each) do |example|
11
+ if ENV['NO_RESCUE'] == 'true'
12
+ example.run
13
+ else
14
+ Pry.rescue do
15
+ err = example.run
16
+ pending = err.is_a?(RSpec::Core::Pending::PendingExampleFixedError)
17
+ Pry.rescued(err) if err && !pending && $stdin.tty? && $stdout.tty?
18
+ end
19
+ end
20
+ end
21
+ end
data/yabfi.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'yabfi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'yabfi'
8
+ spec.version = YABFI::VERSION
9
+ spec.authors = ['Tom Hulihan']
10
+ spec.email = ['hulihan.tom159@gmail.com']
11
+ spec.summary = 'Yet Another BrainFuck Interpreter'
12
+ spec.description = [
13
+ 'This gem uses native C extensions to interpret Brainfuck programs.',
14
+ 'An optimizing compiler written in Ruby sits on top of the interpreter',
15
+ 'which speeds up the computation quite a bit.'
16
+ ].join(' ')
17
+ spec.homepage = 'https://github.com/nahiluhmot/yabfi'
18
+ spec.license = 'MIT'
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.require_paths = ['lib']
21
+ spec.executables = ['yabfi']
22
+ spec.required_ruby_version = '>= 2.0.0'
23
+ spec.extensions = ['ext/yabfi/extconf.rb']
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rake-compiler', '~> 0.9.5'
27
+ spec.add_development_dependency 'rspec'
28
+ spec.add_development_dependency 'rubocop'
29
+ spec.add_development_dependency 'yard'
30
+ spec.add_development_dependency 'pry'
31
+ spec.add_development_dependency 'pry-stack_explorer'
32
+ spec.add_development_dependency 'pry-rescue'
33
+ spec.add_development_dependency 'simplecov'
34
+ end