yabfi 0.1.1

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.
@@ -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