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,44 @@
|
|
1
|
+
require_relative "data_structures/console"
|
2
|
+
require_relative "data_structures/counter"
|
3
|
+
require_relative "data_structures/memory"
|
4
|
+
require_relative "data_structures/stack"
|
5
|
+
|
6
|
+
module Whitespace
|
7
|
+
class VM
|
8
|
+
attr_reader :instructions, :vstack, :cstack, :memory, :pc
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@instructions = []
|
12
|
+
reset
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(instructions)
|
16
|
+
@instructions = Array(instructions)
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
reset
|
21
|
+
|
22
|
+
loop do
|
23
|
+
instruction = instructions.fetch pc
|
24
|
+
pc.increment
|
25
|
+
execute instruction
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def reset
|
32
|
+
@vstack = Stack.new # a value stack
|
33
|
+
@cstack = Stack.new # a call stack
|
34
|
+
@memory = Memory.new # heap memory
|
35
|
+
@pc = Counter.new # program counter
|
36
|
+
end
|
37
|
+
|
38
|
+
def execute(instruction)
|
39
|
+
instruction.execute
|
40
|
+
rescue Halt
|
41
|
+
raise StopIteration
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace
|
4
|
+
describe Console do
|
5
|
+
before do
|
6
|
+
@stdin = Object.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#printc" do
|
10
|
+
describe "when given an ASCII character" do
|
11
|
+
it "prints it" do
|
12
|
+
expect { Console.new.printc 65 }.must_output("A")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "when not given an ASCII character" do
|
17
|
+
it "raises ArgumentError" do
|
18
|
+
e = expect { Console.new.printc 0 }.must_raise ArgumentError
|
19
|
+
expect(e.message).must_match /must be an ASCII character: 0/
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#printn" do
|
25
|
+
describe "when given an integer" do
|
26
|
+
it "prints it" do
|
27
|
+
expect { Console.new.printn 65 }.must_output("65")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "when not given an integer" do
|
32
|
+
it "raises ArgumentError" do
|
33
|
+
e = expect { Console.new.printn :x }.must_raise ArgumentError
|
34
|
+
expect(e.message).must_match /must be an integer/
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#getc" do
|
40
|
+
describe "when given an ASCII character" do
|
41
|
+
it "gets it" do
|
42
|
+
def @stdin.getc
|
43
|
+
"A"
|
44
|
+
end
|
45
|
+
|
46
|
+
expect(Console.new(stdin: @stdin).getc).must_equal "A"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "when not given an ASCII character" do
|
51
|
+
describe "when there is a character" do
|
52
|
+
it "raises ArgumentError" do
|
53
|
+
def @stdin.getc
|
54
|
+
"\a"
|
55
|
+
end
|
56
|
+
|
57
|
+
e = expect { Console.new(stdin: @stdin).getc }.must_raise ArgumentError
|
58
|
+
expect(e.message).must_match /must be an ASCII character/
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "when EOF" do
|
63
|
+
it "raises ArgumentError" do
|
64
|
+
def @stdin.getc
|
65
|
+
end
|
66
|
+
|
67
|
+
e = expect { Console.new(stdin: @stdin).getc }.must_raise ArgumentError
|
68
|
+
expect(e.message).must_match /must be an ASCII character: EOF/
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#getn" do
|
75
|
+
describe "when given an integer" do
|
76
|
+
it "gets it" do
|
77
|
+
def @stdin.getc
|
78
|
+
@chars = ["1", "2", "3"]
|
79
|
+
@i ||= 0
|
80
|
+
|
81
|
+
@i += 1
|
82
|
+
@chars[@i - 1]
|
83
|
+
end
|
84
|
+
|
85
|
+
expect(Console.new(stdin: @stdin).getn).must_equal 123
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "when not given an integer" do
|
90
|
+
describe "when there is at least one character" do
|
91
|
+
it "raises ArgumentError" do
|
92
|
+
def @stdin.getc
|
93
|
+
"\n"
|
94
|
+
end
|
95
|
+
|
96
|
+
e = expect { Console.new(stdin: @stdin).getn }.must_raise ArgumentError
|
97
|
+
expect(e.message).must_match /must be an integer: /
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "when EOF" do
|
102
|
+
it "raises ArgumentError" do
|
103
|
+
def @stdin.getc
|
104
|
+
end
|
105
|
+
|
106
|
+
e = expect { Console.new(stdin: @stdin).getn }.must_raise ArgumentError
|
107
|
+
expect(e.message).must_match /must be an integer: EOF/
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace
|
4
|
+
describe Counter do
|
5
|
+
before do
|
6
|
+
@counter = Counter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "is initially 0" do
|
10
|
+
expect(@counter.to_int).must_equal 0
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#increment" do
|
14
|
+
it "increases its value by 1" do
|
15
|
+
@counter.increment
|
16
|
+
|
17
|
+
expect(@counter.to_int).must_equal 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#change_to" do
|
22
|
+
describe "when the value is non-negative" do
|
23
|
+
it "changes the counter's value to the given value" do
|
24
|
+
@counter.change_to 5
|
25
|
+
|
26
|
+
expect(@counter.to_int).must_equal 5
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "when the value is negative" do
|
31
|
+
it "raises ArgumentError" do
|
32
|
+
expect { @counter.change_to -1 }.must_raise ArgumentError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace
|
4
|
+
describe Memory do
|
5
|
+
before do
|
6
|
+
@memory = Memory.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "how values are accessed" do
|
10
|
+
describe "when the given address exists" do
|
11
|
+
it "returns the value" do
|
12
|
+
@memory[:address] = :value
|
13
|
+
|
14
|
+
expect(@memory[:address]).must_equal :value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "when the given address does not exist" do
|
19
|
+
it "raises Whitespace::AddressError" do
|
20
|
+
expect { @memory[:address] }.must_raise AddressError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace
|
4
|
+
describe Stack do
|
5
|
+
before do
|
6
|
+
@stack = Stack.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#push" do
|
10
|
+
it "places an element on top the stack" do
|
11
|
+
@stack.push :a
|
12
|
+
@stack.push :b
|
13
|
+
|
14
|
+
expect(@stack.top).must_equal :b
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#pop" do
|
19
|
+
describe "when the stack is empty" do
|
20
|
+
it "raises Whitespace::EmptyError" do
|
21
|
+
expect { @stack.pop }.must_raise EmptyError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "when the stack is not empty" do
|
26
|
+
it "removes and returns the top element" do
|
27
|
+
@stack.push :a
|
28
|
+
@stack.push :b
|
29
|
+
|
30
|
+
expect(@stack.pop).must_equal :b
|
31
|
+
expect(@stack.top).must_equal :a
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#top" do
|
37
|
+
describe "when the stack is empty" do
|
38
|
+
it "raises Whitespace::EmptyError" do
|
39
|
+
expect { @stack.top }.must_raise EmptyError
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "when the stack is not empty" do
|
44
|
+
it "returns the top element" do
|
45
|
+
@stack.push :a
|
46
|
+
expect(@stack.top).must_equal :a
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#size" do
|
52
|
+
it "returns the number of elements on the stack" do
|
53
|
+
expect(@stack.size).must_equal 0
|
54
|
+
|
55
|
+
@stack.push :a
|
56
|
+
@stack.push :b
|
57
|
+
@stack.push :c
|
58
|
+
|
59
|
+
expect(@stack.size).must_equal 3
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace::ISA
|
4
|
+
describe Add do
|
5
|
+
before do
|
6
|
+
@vm = Whitespace::VM.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#execute" do
|
10
|
+
describe "when the value stack is empty" do
|
11
|
+
it "raises Whitespace::EmptyError" do
|
12
|
+
expect(@vm.vstack.size).must_equal 0
|
13
|
+
|
14
|
+
expect { Add.new(@vm).execute }.must_raise Whitespace::EmptyError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "when the value stack has one element" do
|
19
|
+
it "raises Whitespace::EmptyError" do
|
20
|
+
@vm.vstack.push 1
|
21
|
+
expect(@vm.vstack.size).must_equal 1
|
22
|
+
|
23
|
+
expect { Add.new(@vm).execute }.must_raise Whitespace::EmptyError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when the value stack has at least 2 elements" do
|
28
|
+
it "replaces the top 2 elements with their sum" do
|
29
|
+
@vm.vstack.push 1
|
30
|
+
@vm.vstack.push 2
|
31
|
+
@vm.vstack.push 3
|
32
|
+
expect(@vm.vstack.size).must_equal 3
|
33
|
+
|
34
|
+
Add.new(@vm).execute
|
35
|
+
|
36
|
+
expect(@vm.vstack.pop).must_equal 5
|
37
|
+
expect(@vm.vstack.pop).must_equal 1
|
38
|
+
expect(@vm.vstack.size).must_equal 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace::ISA
|
4
|
+
describe Div do
|
5
|
+
before do
|
6
|
+
@vm = Whitespace::VM.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#execute" do
|
10
|
+
describe "when the value stack is empty" do
|
11
|
+
it "raises Whitespace::EmptyError" do
|
12
|
+
expect(@vm.vstack.size).must_equal 0
|
13
|
+
|
14
|
+
expect { Div.new(@vm).execute }.must_raise Whitespace::EmptyError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "when the value stack has one element" do
|
19
|
+
it "raises Whitespace::EmptyError" do
|
20
|
+
@vm.vstack.push 1
|
21
|
+
expect(@vm.vstack.size).must_equal 1
|
22
|
+
|
23
|
+
expect { Div.new(@vm).execute }.must_raise Whitespace::EmptyError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when the value stack has at least 2 elements" do
|
28
|
+
it "replaces the top 2 elements with their quotient" do
|
29
|
+
@vm.vstack.push 1
|
30
|
+
@vm.vstack.push 6
|
31
|
+
@vm.vstack.push 2
|
32
|
+
expect(@vm.vstack.size).must_equal 3
|
33
|
+
|
34
|
+
Div.new(@vm).execute
|
35
|
+
|
36
|
+
expect(@vm.vstack.pop).must_equal 3
|
37
|
+
expect(@vm.vstack.pop).must_equal 1
|
38
|
+
expect(@vm.vstack.size).must_equal 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "division by 0" do
|
43
|
+
it "raises ZeroDivisionError" do
|
44
|
+
@vm.vstack.push 1
|
45
|
+
@vm.vstack.push 0
|
46
|
+
|
47
|
+
expect { Div.new(@vm).execute }.must_raise ZeroDivisionError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Whitespace::ISA
|
4
|
+
describe Mod do
|
5
|
+
before do
|
6
|
+
@vm = Whitespace::VM.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#execute" do
|
10
|
+
describe "when the value stack is empty" do
|
11
|
+
it "raises Whitespace::EmptyError" do
|
12
|
+
expect(@vm.vstack.size).must_equal 0
|
13
|
+
|
14
|
+
expect { Mod.new(@vm).execute }.must_raise Whitespace::EmptyError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "when the value stack has one element" do
|
19
|
+
it "raises Whitespace::EmptyError" do
|
20
|
+
@vm.vstack.push 1
|
21
|
+
expect(@vm.vstack.size).must_equal 1
|
22
|
+
|
23
|
+
expect { Mod.new(@vm).execute }.must_raise Whitespace::EmptyError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when the value stack has at least 2 elements" do
|
28
|
+
it "replaces the top 2 elements with their remainder" do
|
29
|
+
@vm.vstack.push 1
|
30
|
+
@vm.vstack.push 5
|
31
|
+
@vm.vstack.push 3
|
32
|
+
expect(@vm.vstack.size).must_equal 3
|
33
|
+
|
34
|
+
Mod.new(@vm).execute
|
35
|
+
|
36
|
+
expect(@vm.vstack.pop).must_equal 2
|
37
|
+
expect(@vm.vstack.pop).must_equal 1
|
38
|
+
expect(@vm.vstack.size).must_equal 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "modulo 0" do
|
43
|
+
it "raises ZeroDivisionError" do
|
44
|
+
@vm.vstack.push 1
|
45
|
+
@vm.vstack.push 0
|
46
|
+
|
47
|
+
expect { Mod.new(@vm).execute }.must_raise ZeroDivisionError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|