whitespace-ruby 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/LICENSE.md +21 -0
- data/README.md +52 -0
- data/Rakefile +10 -0
- data/bin/whitespace +67 -0
- data/examples/count.ws +76 -0
- data/examples/fact.ws +135 -0
- data/examples/hello.ws +110 -0
- data/examples/name.ws +135 -0
- data/lib/whitespace.rb +14 -0
- data/lib/whitespace/data_structures/console.rb +56 -0
- data/lib/whitespace/data_structures/counter.rb +24 -0
- data/lib/whitespace/data_structures/memory.rb +19 -0
- data/lib/whitespace/data_structures/stack.rb +25 -0
- data/lib/whitespace/instructions/arithmetic/add.rb +9 -0
- data/lib/whitespace/instructions/arithmetic/binop.rb +18 -0
- data/lib/whitespace/instructions/arithmetic/div.rb +9 -0
- data/lib/whitespace/instructions/arithmetic/mod.rb +9 -0
- data/lib/whitespace/instructions/arithmetic/mul.rb +9 -0
- data/lib/whitespace/instructions/arithmetic/sub.rb +9 -0
- data/lib/whitespace/instructions/flow_control/call.rb +19 -0
- data/lib/whitespace/instructions/flow_control/end.rb +7 -0
- data/lib/whitespace/instructions/flow_control/label.rb +16 -0
- data/lib/whitespace/instructions/flow_control/njmp.rb +20 -0
- data/lib/whitespace/instructions/flow_control/return.rb +7 -0
- data/lib/whitespace/instructions/flow_control/ujmp.rb +18 -0
- data/lib/whitespace/instructions/flow_control/zjmp.rb +20 -0
- data/lib/whitespace/instructions/heap_access/retrieve.rb +9 -0
- data/lib/whitespace/instructions/heap_access/store.rb +10 -0
- data/lib/whitespace/instructions/instruction.rb +13 -0
- data/lib/whitespace/instructions/io/putc.rb +15 -0
- data/lib/whitespace/instructions/io/putn.rb +15 -0
- data/lib/whitespace/instructions/io/readc.rb +16 -0
- data/lib/whitespace/instructions/io/readn.rb +16 -0
- data/lib/whitespace/instructions/stack_manipulation/discard.rb +7 -0
- data/lib/whitespace/instructions/stack_manipulation/dup.rb +7 -0
- data/lib/whitespace/instructions/stack_manipulation/push.rb +17 -0
- data/lib/whitespace/instructions/stack_manipulation/swap.rb +11 -0
- data/lib/whitespace/isa.rb +9 -0
- data/lib/whitespace/parser.rb +243 -0
- data/lib/whitespace/util.rb +37 -0
- data/lib/whitespace/version.rb +3 -0
- data/lib/whitespace/vm.rb +44 -0
- data/test/test_helper.rb +4 -0
- data/test/whitespace/data_structures/console_test.rb +113 -0
- data/test/whitespace/data_structures/counter_test.rb +37 -0
- data/test/whitespace/data_structures/memory_test.rb +25 -0
- data/test/whitespace/data_structures/stack_test.rb +63 -0
- data/test/whitespace/instructions/arithmetic/add_test.rb +43 -0
- data/test/whitespace/instructions/arithmetic/div_test.rb +52 -0
- data/test/whitespace/instructions/arithmetic/mod_test.rb +52 -0
- data/test/whitespace/instructions/arithmetic/mul_test.rb +43 -0
- data/test/whitespace/instructions/arithmetic/sub_test.rb +43 -0
- data/test/whitespace/instructions/flow_control/call_test.rb +50 -0
- data/test/whitespace/instructions/flow_control/end_test.rb +15 -0
- data/test/whitespace/instructions/flow_control/label_test.rb +24 -0
- data/test/whitespace/instructions/flow_control/njmp_test.rb +94 -0
- data/test/whitespace/instructions/flow_control/return_test.rb +33 -0
- data/test/whitespace/instructions/flow_control/ujmp_test.rb +44 -0
- data/test/whitespace/instructions/flow_control/zjmp_test.rb +94 -0
- data/test/whitespace/instructions/heap_access/retrieve_test.rb +49 -0
- data/test/whitespace/instructions/heap_access/store_test.rb +44 -0
- data/test/whitespace/instructions/instruction_test.rb +11 -0
- data/test/whitespace/instructions/io/putc_test.rb +42 -0
- data/test/whitespace/instructions/io/putn_test.rb +42 -0
- data/test/whitespace/instructions/io/readc_test.rb +71 -0
- data/test/whitespace/instructions/io/readn_test.rb +79 -0
- data/test/whitespace/instructions/stack_manipulation/discard_test.rb +30 -0
- data/test/whitespace/instructions/stack_manipulation/dup_test.rb +32 -0
- data/test/whitespace/instructions/stack_manipulation/push_test.rb +30 -0
- data/test/whitespace/instructions/stack_manipulation/swap_test.rb +43 -0
- data/test/whitespace/parser_test.rb +362 -0
- data/test/whitespace/util_test.rb +87 -0
- data/test/whitespace/vm_test.rb +80 -0
- data/whitespace-ruby.gemspec +30 -0
- metadata +178 -0
@@ -0,0 +1,362 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace
|
4
|
+
describe Parser do
|
5
|
+
before do
|
6
|
+
@parser = Parser.new(:vm, :console)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#parse" do
|
10
|
+
describe "individual instructions" do
|
11
|
+
describe "stack manipulation" do
|
12
|
+
it "parses Push" do
|
13
|
+
instructions = @parser.parse(" \t\t \t\n")
|
14
|
+
instruction = instructions.first
|
15
|
+
|
16
|
+
expect(instructions.length).must_equal 1
|
17
|
+
expect(instruction).must_be_instance_of ISA::Push
|
18
|
+
expect(instruction.vm).must_equal :vm
|
19
|
+
expect(instruction.n).must_equal -5
|
20
|
+
end
|
21
|
+
|
22
|
+
it "parses Dup" do
|
23
|
+
instructions = @parser.parse(" \n ")
|
24
|
+
instruction = instructions.first
|
25
|
+
|
26
|
+
expect(instructions.length).must_equal 1
|
27
|
+
expect(instruction).must_be_instance_of ISA::Dup
|
28
|
+
expect(instruction.vm).must_equal :vm
|
29
|
+
end
|
30
|
+
|
31
|
+
it "parses Swap" do
|
32
|
+
instructions = @parser.parse(" \n\t")
|
33
|
+
instruction = instructions.first
|
34
|
+
|
35
|
+
expect(instructions.length).must_equal 1
|
36
|
+
expect(instruction).must_be_instance_of ISA::Swap
|
37
|
+
expect(instruction.vm).must_equal :vm
|
38
|
+
end
|
39
|
+
|
40
|
+
it "parses Discard" do
|
41
|
+
instructions = @parser.parse(" \n\n")
|
42
|
+
instruction = instructions.first
|
43
|
+
|
44
|
+
expect(instructions.length).must_equal 1
|
45
|
+
expect(instruction).must_be_instance_of ISA::Discard
|
46
|
+
expect(instruction.vm).must_equal :vm
|
47
|
+
end
|
48
|
+
|
49
|
+
it "raises Whitespace::ParseError otherwise" do
|
50
|
+
[" ", " \t", " \n"].each do |bad_src|
|
51
|
+
e = expect { @parser.parse(" \t") }.must_raise ParseError
|
52
|
+
expect(e.message).must_match \
|
53
|
+
/must be a stack manipulation instruction/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "arithmetic" do
|
59
|
+
it "parses Add" do
|
60
|
+
instructions = @parser.parse("\t ")
|
61
|
+
instruction = instructions.first
|
62
|
+
|
63
|
+
expect(instructions.length).must_equal 1
|
64
|
+
expect(instruction).must_be_instance_of ISA::Add
|
65
|
+
expect(instruction.vm).must_equal :vm
|
66
|
+
end
|
67
|
+
|
68
|
+
it "parses Sub" do
|
69
|
+
instructions = @parser.parse("\t \t")
|
70
|
+
instruction = instructions.first
|
71
|
+
|
72
|
+
expect(instructions.length).must_equal 1
|
73
|
+
expect(instruction).must_be_instance_of ISA::Sub
|
74
|
+
expect(instruction.vm).must_equal :vm
|
75
|
+
end
|
76
|
+
|
77
|
+
it "parses Mul" do
|
78
|
+
instructions = @parser.parse("\t \n")
|
79
|
+
instruction = instructions.first
|
80
|
+
|
81
|
+
expect(instructions.length).must_equal 1
|
82
|
+
expect(instruction).must_be_instance_of ISA::Mul
|
83
|
+
expect(instruction.vm).must_equal :vm
|
84
|
+
end
|
85
|
+
|
86
|
+
it "parses Div" do
|
87
|
+
instructions = @parser.parse("\t \t ")
|
88
|
+
instruction = instructions.first
|
89
|
+
|
90
|
+
expect(instructions.length).must_equal 1
|
91
|
+
expect(instruction).must_be_instance_of ISA::Div
|
92
|
+
expect(instruction.vm).must_equal :vm
|
93
|
+
end
|
94
|
+
|
95
|
+
it "parses Mod" do
|
96
|
+
instructions = @parser.parse("\t \t\t")
|
97
|
+
instruction = instructions.first
|
98
|
+
|
99
|
+
expect(instructions.length).must_equal 1
|
100
|
+
expect(instruction).must_be_instance_of ISA::Mod
|
101
|
+
expect(instruction.vm).must_equal :vm
|
102
|
+
end
|
103
|
+
|
104
|
+
it "raises Whitespace::ParseError otherwise" do
|
105
|
+
["\t ", "\t \n", "\t ", "\t \t", "\t \t\n"].each do |bad_src|
|
106
|
+
e = expect { @parser.parse(bad_src) }.must_raise ParseError
|
107
|
+
expect(e.message).must_match /must be an arithmetic instruction/
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "heap access" do
|
113
|
+
it "parses Store" do
|
114
|
+
instructions = @parser.parse("\t\t ")
|
115
|
+
instruction = instructions.first
|
116
|
+
|
117
|
+
expect(instructions.length).must_equal 1
|
118
|
+
expect(instruction).must_be_instance_of ISA::Store
|
119
|
+
expect(instruction.vm).must_equal :vm
|
120
|
+
end
|
121
|
+
|
122
|
+
it "parses Retrieve" do
|
123
|
+
instructions = @parser.parse("\t\t\t")
|
124
|
+
instruction = instructions.first
|
125
|
+
|
126
|
+
expect(instructions.length).must_equal 1
|
127
|
+
expect(instruction).must_be_instance_of ISA::Retrieve
|
128
|
+
expect(instruction.vm).must_equal :vm
|
129
|
+
end
|
130
|
+
|
131
|
+
it "raises Whitespace::ParseError otherwise" do
|
132
|
+
["\t\t", "\t\t\n"].each do |bad_src|
|
133
|
+
e = expect { @parser.parse(bad_src) }.must_raise ParseError
|
134
|
+
expect(e.message).must_match /must be a heap instruction/
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "flow control" do
|
140
|
+
it "parses Label" do
|
141
|
+
instructions = @parser.parse("\n \n")
|
142
|
+
instruction = instructions.first
|
143
|
+
|
144
|
+
expect(instructions.length).must_equal 1
|
145
|
+
expect(instruction).must_be_instance_of ISA::Label
|
146
|
+
expect(instruction.vm).must_equal :vm
|
147
|
+
expect(instruction.name).must_equal " "
|
148
|
+
end
|
149
|
+
|
150
|
+
it "parses Call" do
|
151
|
+
instructions = @parser.parse("\n \t \n")
|
152
|
+
instruction = instructions.first
|
153
|
+
|
154
|
+
expect(instructions.length).must_equal 1
|
155
|
+
expect(instruction).must_be_instance_of ISA::Call
|
156
|
+
expect(instruction.vm).must_equal :vm
|
157
|
+
expect(instruction.name).must_equal " "
|
158
|
+
end
|
159
|
+
|
160
|
+
it "parses Ujmp" do
|
161
|
+
instructions = @parser.parse("\n \n \n")
|
162
|
+
instruction = instructions.first
|
163
|
+
|
164
|
+
expect(instructions.length).must_equal 1
|
165
|
+
expect(instruction).must_be_instance_of ISA::Ujmp
|
166
|
+
expect(instruction.vm).must_equal :vm
|
167
|
+
expect(instruction.name).must_equal " "
|
168
|
+
end
|
169
|
+
|
170
|
+
it "parses Zjmp" do
|
171
|
+
instructions = @parser.parse("\n\t \n")
|
172
|
+
instruction = instructions.first
|
173
|
+
|
174
|
+
expect(instructions.length).must_equal 1
|
175
|
+
expect(instruction).must_be_instance_of ISA::Zjmp
|
176
|
+
expect(instruction.vm).must_equal :vm
|
177
|
+
expect(instruction.name).must_equal " "
|
178
|
+
end
|
179
|
+
|
180
|
+
it "parses Njmp" do
|
181
|
+
instructions = @parser.parse("\n\t\t \n")
|
182
|
+
instruction = instructions.first
|
183
|
+
|
184
|
+
expect(instructions.length).must_equal 1
|
185
|
+
expect(instruction).must_be_instance_of ISA::Njmp
|
186
|
+
expect(instruction.vm).must_equal :vm
|
187
|
+
expect(instruction.name).must_equal " "
|
188
|
+
end
|
189
|
+
|
190
|
+
it "parses Return" do
|
191
|
+
instructions = @parser.parse("\n\t\n")
|
192
|
+
instruction = instructions.first
|
193
|
+
|
194
|
+
expect(instructions.length).must_equal 1
|
195
|
+
expect(instruction).must_be_instance_of ISA::Return
|
196
|
+
expect(instruction.vm).must_equal :vm
|
197
|
+
end
|
198
|
+
|
199
|
+
it "parses End" do
|
200
|
+
instructions = @parser.parse("\n\n\n")
|
201
|
+
instruction = instructions.first
|
202
|
+
|
203
|
+
expect(instructions.length).must_equal 1
|
204
|
+
expect(instruction).must_be_instance_of ISA::End
|
205
|
+
expect(instruction.vm).must_equal :vm
|
206
|
+
end
|
207
|
+
|
208
|
+
it "raises Whitespace::ParseError otherwise" do
|
209
|
+
["\n", "\n ", "\n\t", "\n\n", "\n\n ", "\n\n\t"].each do |bad_src|
|
210
|
+
e = expect { @parser.parse(bad_src) }.must_raise ParseError
|
211
|
+
expect(e.message).must_match /must be a flow control instruction/
|
212
|
+
end
|
213
|
+
|
214
|
+
["\n ", "\n \t", "\n \n", "\n\t ", "\n\t\t"].each do |bad_src|
|
215
|
+
e = expect { @parser.parse(bad_src) }.must_raise ParseError
|
216
|
+
expect(e.message).must_match /name must be terminated by a LF/
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe "I/O" do
|
222
|
+
it "parses Putc" do
|
223
|
+
instructions = @parser.parse("\t\n ")
|
224
|
+
instruction = instructions.first
|
225
|
+
|
226
|
+
expect(instructions.length).must_equal 1
|
227
|
+
expect(instruction).must_be_instance_of ISA::Putc
|
228
|
+
expect(instruction.vm).must_equal :vm
|
229
|
+
expect(instruction.console).must_equal :console
|
230
|
+
end
|
231
|
+
|
232
|
+
it "parses Putn" do
|
233
|
+
instructions = @parser.parse("\t\n \t")
|
234
|
+
instruction = instructions.first
|
235
|
+
|
236
|
+
expect(instructions.length).must_equal 1
|
237
|
+
expect(instruction).must_be_instance_of ISA::Putn
|
238
|
+
expect(instruction.vm).must_equal :vm
|
239
|
+
expect(instruction.console).must_equal :console
|
240
|
+
end
|
241
|
+
|
242
|
+
it "parses Readc" do
|
243
|
+
instructions = @parser.parse("\t\n\t ")
|
244
|
+
instruction = instructions.first
|
245
|
+
|
246
|
+
expect(instructions.length).must_equal 1
|
247
|
+
expect(instruction).must_be_instance_of ISA::Readc
|
248
|
+
expect(instruction.vm).must_equal :vm
|
249
|
+
expect(instruction.console).must_equal :console
|
250
|
+
end
|
251
|
+
|
252
|
+
it "parses Readn" do
|
253
|
+
instructions = @parser.parse("\t\n\t\t")
|
254
|
+
instruction = instructions.first
|
255
|
+
|
256
|
+
expect(instructions.length).must_equal 1
|
257
|
+
expect(instruction).must_be_instance_of ISA::Readn
|
258
|
+
expect(instruction.vm).must_equal :vm
|
259
|
+
expect(instruction.console).must_equal :console
|
260
|
+
end
|
261
|
+
|
262
|
+
it "raises Whitespace::ParseError otherwise" do
|
263
|
+
["\t\n", "\t\n ", "\t\n\t", "\t\n\n", "\t\n \n", "\t\n\t\n"].each do |bad_src|
|
264
|
+
e = expect { @parser.parse(bad_src) }.must_raise ParseError
|
265
|
+
expect(e.message).must_match /must be an I\/O instruction/
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "invalid IMP" do
|
271
|
+
it "raises Whitespace::ParseError" do
|
272
|
+
e = expect { @parser.parse("\t") }.must_raise ParseError
|
273
|
+
expect(e.message).must_match /must be an IMP/
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe "numbers" do
|
279
|
+
describe "positive" do
|
280
|
+
it "parses 1" do
|
281
|
+
instruction = @parser.parse(" \t\n").first
|
282
|
+
expect(instruction.n).must_equal 1
|
283
|
+
end
|
284
|
+
|
285
|
+
it "parses 2" do
|
286
|
+
instruction = @parser.parse(" \t \n").first
|
287
|
+
expect(instruction.n).must_equal 2
|
288
|
+
end
|
289
|
+
|
290
|
+
it "parses 5" do
|
291
|
+
instruction = @parser.parse(" \t \t\n").first
|
292
|
+
expect(instruction.n).must_equal 5
|
293
|
+
end
|
294
|
+
|
295
|
+
it "raises Whitespace::ParseError otherwise" do
|
296
|
+
e = expect { @parser.parse(" \n") }.must_raise ParseError
|
297
|
+
expect(e.message).must_match /number must have a value part/
|
298
|
+
|
299
|
+
e = expect { @parser.parse(" \t") }.must_raise ParseError
|
300
|
+
expect(e.message).must_match /number must be terminated by a LF/
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
describe "negative" do
|
305
|
+
it "parses -1" do
|
306
|
+
instruction = @parser.parse(" \t\t\n").first
|
307
|
+
expect(instruction.n).must_equal -1
|
308
|
+
end
|
309
|
+
|
310
|
+
it "parses -2" do
|
311
|
+
instruction = @parser.parse(" \t\t \n").first
|
312
|
+
expect(instruction.n).must_equal -2
|
313
|
+
end
|
314
|
+
|
315
|
+
it "parses -5" do
|
316
|
+
instruction = @parser.parse(" \t\t \t\n").first
|
317
|
+
expect(instruction.n).must_equal -5
|
318
|
+
end
|
319
|
+
|
320
|
+
it "raises Whitespace::ParseError otherwise" do
|
321
|
+
e = expect { @parser.parse(" \t\n") }.must_raise ParseError
|
322
|
+
expect(e.message).must_match /number must have a value part/
|
323
|
+
|
324
|
+
e = expect { @parser.parse(" \t\t") }.must_raise ParseError
|
325
|
+
expect(e.message).must_match /number must be terminated by a LF/
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
describe "zero" do
|
330
|
+
it "parses 0" do
|
331
|
+
# there is an infinite number of representations of 0
|
332
|
+
[" \n", " \t \n", " \n", " \t \n"].each do |zero_src|
|
333
|
+
instruction = @parser.parse(zero_src).first
|
334
|
+
expect(instruction.n).must_equal 0
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
describe "names" do
|
341
|
+
it "parses one or more LF terminated spaces/tabs" do
|
342
|
+
instruction = @parser.parse("\n \n").first
|
343
|
+
expect(instruction.name).must_equal " "
|
344
|
+
|
345
|
+
instruction = @parser.parse("\n \t\n").first
|
346
|
+
expect(instruction.name).must_equal "\t"
|
347
|
+
|
348
|
+
instruction = @parser.parse("\n \t \t \n").first
|
349
|
+
expect(instruction.name).must_equal "\t \t "
|
350
|
+
end
|
351
|
+
|
352
|
+
it "raises Whitespace::ParseError otherwise" do
|
353
|
+
e = expect { @parser.parse("\n \n") }.must_raise ParseError
|
354
|
+
expect(e.message).must_match /name must be non-empty/
|
355
|
+
|
356
|
+
e = expect { @parser.parse("\n ") }.must_raise ParseError
|
357
|
+
expect(e.message).must_match /name must be terminated by a LF/
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace
|
4
|
+
describe Util do
|
5
|
+
describe "::is_integer?" do
|
6
|
+
it "returns true if the value is an integer" do
|
7
|
+
[-1, 0, 1, 1000000000000000000000].each do |v|
|
8
|
+
expect(Util.is_integer?(v)).must_equal true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns false if the value is not an integer" do
|
13
|
+
["1", :x, 3.14].each do |v|
|
14
|
+
expect(Util.is_integer?(v)).must_equal false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "::is_ascii?" do
|
20
|
+
it "returns true if the value is within a certain subset of the " \
|
21
|
+
"ASCII character set" do
|
22
|
+
[10, 13, 32, 65, 127].each do |v|
|
23
|
+
expect(Util.is_ascii?(v)).must_equal true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns false if the value is not within a certain subset of the " \
|
28
|
+
"ASCII character set" do
|
29
|
+
[-10, 0, 31, 128, 255, 1023, 1000000000].each do |v|
|
30
|
+
expect(Util.is_ascii?(v)).must_equal false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "::is_binop?" do
|
36
|
+
it "returns true for the binary operators :add, :sub, :mul, :div, :mod" do
|
37
|
+
[:add, :sub, :mul, :div, :mod].each do |op|
|
38
|
+
expect(Util.is_binop?(op)).must_equal true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "::is_label?" do
|
44
|
+
it "returns true if the value is a label" do
|
45
|
+
[" ", " ", "\t", "\t\t\t\t", " \t \t\t \t"].each do |v|
|
46
|
+
expect(Util.is_label?(v)).must_equal true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "returns false if the value is not a label" do
|
51
|
+
["", "\n", 1, :x].each do |v|
|
52
|
+
expect(Util.is_label?(v)).must_equal false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "::find_label" do
|
58
|
+
describe "when the label doesn't exist" do
|
59
|
+
it "raises Whitespace::LabelError" do
|
60
|
+
instructions = [
|
61
|
+
"instruction 1",
|
62
|
+
"instruction 2",
|
63
|
+
ISA::Label.new(:vm, " "),
|
64
|
+
"instruction 4"
|
65
|
+
]
|
66
|
+
|
67
|
+
e = expect { Util.find_label(instructions, " ") }.must_raise \
|
68
|
+
Whitespace::LabelError
|
69
|
+
expect(e.message).must_match /missing: " "/
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "when the label does exist" do
|
74
|
+
it "returns the index of the label" do
|
75
|
+
instructions = [
|
76
|
+
"instruction 1",
|
77
|
+
ISA::Label.new(:vm, " "),
|
78
|
+
"instruction 3",
|
79
|
+
"instruction 4"
|
80
|
+
]
|
81
|
+
|
82
|
+
expect(Util.find_label(instructions, " ")).must_equal 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace
|
4
|
+
describe VM do
|
5
|
+
before do
|
6
|
+
@vm = VM.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#run" do
|
10
|
+
it "executes each instruction one by one until an explicit " \
|
11
|
+
"end instruction is reached" do
|
12
|
+
@vm.load [
|
13
|
+
ISA::Push.new(@vm, 3),
|
14
|
+
ISA::Dup.new(@vm),
|
15
|
+
ISA::Mul.new(@vm),
|
16
|
+
ISA::End.new(@vm),
|
17
|
+
ISA::Dup.new(@vm)
|
18
|
+
]
|
19
|
+
|
20
|
+
@vm.run
|
21
|
+
|
22
|
+
expect(@vm.vstack.size).must_equal 1
|
23
|
+
expect(@vm.vstack.top).must_equal 9
|
24
|
+
end
|
25
|
+
|
26
|
+
it "raises IndexError when no end instruction is given" do
|
27
|
+
@vm.load [
|
28
|
+
ISA::Push.new(@vm, 3),
|
29
|
+
ISA::Dup.new(@vm),
|
30
|
+
ISA::Mul.new(@vm)
|
31
|
+
]
|
32
|
+
|
33
|
+
expect { @vm.run }.must_raise IndexError
|
34
|
+
end
|
35
|
+
|
36
|
+
it "allows any exceptions raised during instruction execution " \
|
37
|
+
"to pass through" do
|
38
|
+
instruction = Object.new
|
39
|
+
def instruction.execute
|
40
|
+
any_exception = Class.new(StandardError)
|
41
|
+
raise any_exception, "any exception"
|
42
|
+
end
|
43
|
+
|
44
|
+
@vm.load instruction
|
45
|
+
|
46
|
+
e = expect { @vm.run }.must_raise StandardError
|
47
|
+
expect(e.message).must_match /any exception/
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "an example program" do
|
52
|
+
it "counts from 1 to 10" do
|
53
|
+
expect do
|
54
|
+
console = Console.new
|
55
|
+
|
56
|
+
@vm.load [
|
57
|
+
ISA::Push.new(@vm, 1), # Put a 1 on the stack
|
58
|
+
ISA::Label.new(@vm, " "), # Set a Label at this point
|
59
|
+
ISA::Dup.new(@vm), # Duplicate the top stack item
|
60
|
+
ISA::Putn.new(@vm, console), # Output the current value
|
61
|
+
ISA::Push.new(@vm, 10), # Put 10 (newline) on the stack...
|
62
|
+
ISA::Putc.new(@vm, console), # ...and output the newline
|
63
|
+
ISA::Push.new(@vm, 1), # Put a 1 on the stack
|
64
|
+
ISA::Add.new(@vm), # Increment our current value
|
65
|
+
ISA::Dup.new(@vm), # Duplicate the value to test it
|
66
|
+
ISA::Push.new(@vm, 11), # Push 11 onto the stack
|
67
|
+
ISA::Sub.new(@vm), # Subtraction
|
68
|
+
ISA::Zjmp.new(@vm, "\t"), # If we have a 0, jump to the end
|
69
|
+
ISA::Ujmp.new(@vm, " "), # Jump to the start
|
70
|
+
ISA::Label.new(@vm, "\t"), # Set the end label
|
71
|
+
ISA::Discard.new(@vm), # Discard our accumulator, to be tidy
|
72
|
+
ISA::End.new(@vm) # Finish
|
73
|
+
]
|
74
|
+
|
75
|
+
@vm.run
|
76
|
+
end.must_output("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|