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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE.md +21 -0
  5. data/README.md +52 -0
  6. data/Rakefile +10 -0
  7. data/bin/whitespace +67 -0
  8. data/examples/count.ws +76 -0
  9. data/examples/fact.ws +135 -0
  10. data/examples/hello.ws +110 -0
  11. data/examples/name.ws +135 -0
  12. data/lib/whitespace.rb +14 -0
  13. data/lib/whitespace/data_structures/console.rb +56 -0
  14. data/lib/whitespace/data_structures/counter.rb +24 -0
  15. data/lib/whitespace/data_structures/memory.rb +19 -0
  16. data/lib/whitespace/data_structures/stack.rb +25 -0
  17. data/lib/whitespace/instructions/arithmetic/add.rb +9 -0
  18. data/lib/whitespace/instructions/arithmetic/binop.rb +18 -0
  19. data/lib/whitespace/instructions/arithmetic/div.rb +9 -0
  20. data/lib/whitespace/instructions/arithmetic/mod.rb +9 -0
  21. data/lib/whitespace/instructions/arithmetic/mul.rb +9 -0
  22. data/lib/whitespace/instructions/arithmetic/sub.rb +9 -0
  23. data/lib/whitespace/instructions/flow_control/call.rb +19 -0
  24. data/lib/whitespace/instructions/flow_control/end.rb +7 -0
  25. data/lib/whitespace/instructions/flow_control/label.rb +16 -0
  26. data/lib/whitespace/instructions/flow_control/njmp.rb +20 -0
  27. data/lib/whitespace/instructions/flow_control/return.rb +7 -0
  28. data/lib/whitespace/instructions/flow_control/ujmp.rb +18 -0
  29. data/lib/whitespace/instructions/flow_control/zjmp.rb +20 -0
  30. data/lib/whitespace/instructions/heap_access/retrieve.rb +9 -0
  31. data/lib/whitespace/instructions/heap_access/store.rb +10 -0
  32. data/lib/whitespace/instructions/instruction.rb +13 -0
  33. data/lib/whitespace/instructions/io/putc.rb +15 -0
  34. data/lib/whitespace/instructions/io/putn.rb +15 -0
  35. data/lib/whitespace/instructions/io/readc.rb +16 -0
  36. data/lib/whitespace/instructions/io/readn.rb +16 -0
  37. data/lib/whitespace/instructions/stack_manipulation/discard.rb +7 -0
  38. data/lib/whitespace/instructions/stack_manipulation/dup.rb +7 -0
  39. data/lib/whitespace/instructions/stack_manipulation/push.rb +17 -0
  40. data/lib/whitespace/instructions/stack_manipulation/swap.rb +11 -0
  41. data/lib/whitespace/isa.rb +9 -0
  42. data/lib/whitespace/parser.rb +243 -0
  43. data/lib/whitespace/util.rb +37 -0
  44. data/lib/whitespace/version.rb +3 -0
  45. data/lib/whitespace/vm.rb +44 -0
  46. data/test/test_helper.rb +4 -0
  47. data/test/whitespace/data_structures/console_test.rb +113 -0
  48. data/test/whitespace/data_structures/counter_test.rb +37 -0
  49. data/test/whitespace/data_structures/memory_test.rb +25 -0
  50. data/test/whitespace/data_structures/stack_test.rb +63 -0
  51. data/test/whitespace/instructions/arithmetic/add_test.rb +43 -0
  52. data/test/whitespace/instructions/arithmetic/div_test.rb +52 -0
  53. data/test/whitespace/instructions/arithmetic/mod_test.rb +52 -0
  54. data/test/whitespace/instructions/arithmetic/mul_test.rb +43 -0
  55. data/test/whitespace/instructions/arithmetic/sub_test.rb +43 -0
  56. data/test/whitespace/instructions/flow_control/call_test.rb +50 -0
  57. data/test/whitespace/instructions/flow_control/end_test.rb +15 -0
  58. data/test/whitespace/instructions/flow_control/label_test.rb +24 -0
  59. data/test/whitespace/instructions/flow_control/njmp_test.rb +94 -0
  60. data/test/whitespace/instructions/flow_control/return_test.rb +33 -0
  61. data/test/whitespace/instructions/flow_control/ujmp_test.rb +44 -0
  62. data/test/whitespace/instructions/flow_control/zjmp_test.rb +94 -0
  63. data/test/whitespace/instructions/heap_access/retrieve_test.rb +49 -0
  64. data/test/whitespace/instructions/heap_access/store_test.rb +44 -0
  65. data/test/whitespace/instructions/instruction_test.rb +11 -0
  66. data/test/whitespace/instructions/io/putc_test.rb +42 -0
  67. data/test/whitespace/instructions/io/putn_test.rb +42 -0
  68. data/test/whitespace/instructions/io/readc_test.rb +71 -0
  69. data/test/whitespace/instructions/io/readn_test.rb +79 -0
  70. data/test/whitespace/instructions/stack_manipulation/discard_test.rb +30 -0
  71. data/test/whitespace/instructions/stack_manipulation/dup_test.rb +32 -0
  72. data/test/whitespace/instructions/stack_manipulation/push_test.rb +30 -0
  73. data/test/whitespace/instructions/stack_manipulation/swap_test.rb +43 -0
  74. data/test/whitespace/parser_test.rb +362 -0
  75. data/test/whitespace/util_test.rb +87 -0
  76. data/test/whitespace/vm_test.rb +80 -0
  77. data/whitespace-ruby.gemspec +30 -0
  78. 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