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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rbenv-version +1 -0
- data/.rspec +4 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +72 -0
- data/Rakefile +53 -0
- data/bin/yabfi +56 -0
- data/example/brainfuck-to-c.b +31 -0
- data/example/cat.b +1 -0
- data/example/mandelbrot.b +144 -0
- data/ext/yabfi/extconf.rb +7 -0
- data/ext/yabfi/vm.c +340 -0
- data/lib/yabfi/consumer.rb +131 -0
- data/lib/yabfi/encoder.rb +21 -0
- data/lib/yabfi/lexer.rb +73 -0
- data/lib/yabfi/parser.rb +54 -0
- data/lib/yabfi/unroll.rb +33 -0
- data/lib/yabfi/version.rb +5 -0
- data/lib/yabfi.rb +36 -0
- data/spec/lib/yabfi/consumer_spec.rb +223 -0
- data/spec/lib/yabfi/encoder_spec.rb +40 -0
- data/spec/lib/yabfi/lexer_spec.rb +65 -0
- data/spec/lib/yabfi/parser_spec.rb +13 -0
- data/spec/lib/yabfi/unroll_spec.rb +46 -0
- data/spec/lib/yabfi/vm_spec.rb +201 -0
- data/spec/lib/yabfi_spec.rb +62 -0
- data/spec/spec_helper.rb +21 -0
- data/yabfi.gemspec +34 -0
- metadata +219 -0
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|