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,20 @@
1
+ module Whitespace::ISA
2
+ class Zjmp < Instruction
3
+ attr_reader :name
4
+
5
+ def initialize(vm, name)
6
+ unless Whitespace::Util.is_label?(name)
7
+ raise ArgumentError, "must be a label: #{name}"
8
+ end
9
+ super(vm)
10
+ @name = name
11
+ end
12
+
13
+ def execute
14
+ if vm.vstack.pop == 0
15
+ index = Whitespace::Util.find_label(vm.instructions, name)
16
+ vm.pc.change_to index + 1
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module Whitespace::ISA
2
+ class Retrieve < Instruction
3
+ def execute
4
+ address = vm.vstack.pop
5
+
6
+ vm.vstack.push vm.memory[address]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module Whitespace::ISA
2
+ class Store < Instruction
3
+ def execute
4
+ value = vm.vstack.pop
5
+ address = vm.vstack.pop
6
+
7
+ vm.memory[address] = value
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Whitespace::ISA
2
+ class Instruction
3
+ attr_reader :vm
4
+
5
+ def initialize(vm)
6
+ @vm = vm
7
+ end
8
+
9
+ def execute
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Whitespace::ISA
2
+ class Putc < Instruction
3
+ attr_reader :console
4
+
5
+ def initialize(vm, console)
6
+ super(vm)
7
+ @console = console
8
+ end
9
+
10
+ def execute
11
+ n = vm.vstack.pop
12
+ console.printc n
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Whitespace::ISA
2
+ class Putn < Instruction
3
+ attr_reader :console
4
+
5
+ def initialize(vm, console)
6
+ super(vm)
7
+ @console = console
8
+ end
9
+
10
+ def execute
11
+ n = vm.vstack.pop
12
+ console.printn n
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module Whitespace::ISA
2
+ class Readc < Instruction
3
+ attr_reader :console
4
+
5
+ def initialize(vm, console)
6
+ super(vm)
7
+ @console = console
8
+ end
9
+
10
+ def execute
11
+ ch = console.getc
12
+ address = vm.vstack.pop
13
+ vm.memory[address] = ch.ord
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Whitespace::ISA
2
+ class Readn < Instruction
3
+ attr_reader :console
4
+
5
+ def initialize(vm, console)
6
+ super(vm)
7
+ @console = console
8
+ end
9
+
10
+ def execute
11
+ n = console.getn
12
+ address = vm.vstack.pop
13
+ vm.memory[address] = n
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module Whitespace::ISA
2
+ class Discard < Instruction
3
+ def execute
4
+ vm.vstack.pop
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Whitespace::ISA
2
+ class Dup < Instruction
3
+ def execute
4
+ vm.vstack.push vm.vstack.top
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module Whitespace::ISA
2
+ class Push < Instruction
3
+ attr_reader :n
4
+
5
+ def initialize(vm, n)
6
+ unless Whitespace::Util.is_integer?(n)
7
+ raise ArgumentError, "must be an integer: #{n}"
8
+ end
9
+ super(vm)
10
+ @n = n
11
+ end
12
+
13
+ def execute
14
+ vm.vstack.push n
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Whitespace::ISA
2
+ class Swap < Instruction
3
+ def execute
4
+ a = vm.vstack.pop
5
+ b = vm.vstack.pop
6
+
7
+ vm.vstack.push a
8
+ vm.vstack.push b
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Whitespace
2
+ module ISA
3
+ end
4
+ end
5
+
6
+ require_relative "instructions/instruction"
7
+ Dir.glob(File.expand_path("instructions/**/*.rb", File.dirname(__FILE__))) do |f|
8
+ require f
9
+ end
@@ -0,0 +1,243 @@
1
+ module Whitespace
2
+ class Parser
3
+ attr_reader :vm, :console
4
+
5
+ def initialize(vm, console)
6
+ @vm = vm
7
+ @console = console
8
+ end
9
+
10
+ def parse(src)
11
+ parse_init(src)
12
+ parse_start
13
+ end
14
+
15
+ SPACE = " "
16
+ TAB = "\t"
17
+ LF = "\n"
18
+
19
+ private
20
+
21
+ attr_reader :instructions
22
+
23
+ def parse_init(src)
24
+ @tokens = src.to_s.gsub(/[^ \t\n]+/, '')
25
+ @index = 0
26
+ @instructions = []
27
+ end
28
+
29
+ def parse_start
30
+ case next_token
31
+ when SPACE
32
+ parse_stack_manipulation
33
+ when TAB
34
+ case next_token
35
+ when SPACE
36
+ parse_arithmetic
37
+ when TAB
38
+ parse_heap
39
+ when LF
40
+ parse_io
41
+ else
42
+ raise ParseError, "must be an IMP"
43
+ end
44
+ when LF
45
+ parse_flow_control
46
+ else
47
+ instructions
48
+ end
49
+ end
50
+
51
+ def parse_stack_manipulation
52
+ case next_token
53
+ when SPACE
54
+ n = parse_number
55
+ append_and_continue ISA::Push.new(vm, n)
56
+ when LF
57
+ case next_token
58
+ when SPACE
59
+ append_and_continue ISA::Dup.new(vm)
60
+ when TAB
61
+ append_and_continue ISA::Swap.new(vm)
62
+ when LF
63
+ append_and_continue ISA::Discard.new(vm)
64
+ else
65
+ raise ParseError, "must be a stack manipulation instruction"
66
+ end
67
+ else
68
+ raise ParseError, "must be a stack manipulation instruction"
69
+ end
70
+ end
71
+
72
+ def parse_arithmetic
73
+ case next_token
74
+ when SPACE
75
+ case next_token
76
+ when SPACE
77
+ append_and_continue ISA::Add.new(vm)
78
+ when TAB
79
+ append_and_continue ISA::Sub.new(vm)
80
+ when LF
81
+ append_and_continue ISA::Mul.new(vm)
82
+ else
83
+ raise ParseError, "must be an arithmetic instruction"
84
+ end
85
+ when TAB
86
+ case next_token
87
+ when SPACE
88
+ append_and_continue ISA::Div.new(vm)
89
+ when TAB
90
+ append_and_continue ISA::Mod.new(vm)
91
+ else
92
+ raise ParseError, "must be an arithmetic instruction"
93
+ end
94
+ else
95
+ raise ParseError, "must be an arithmetic instruction"
96
+ end
97
+ end
98
+
99
+ def parse_heap
100
+ case next_token
101
+ when SPACE
102
+ append_and_continue ISA::Store.new(vm)
103
+ when TAB
104
+ append_and_continue ISA::Retrieve.new(vm)
105
+ else
106
+ raise ParseError, "must be a heap instruction"
107
+ end
108
+ end
109
+
110
+ def parse_flow_control
111
+ case next_token
112
+ when SPACE
113
+ case next_token
114
+ when SPACE
115
+ name = parse_name
116
+ append_and_continue ISA::Label.new(vm, name)
117
+ when TAB
118
+ name = parse_name
119
+ append_and_continue ISA::Call.new(vm, name)
120
+ when LF
121
+ name = parse_name
122
+ append_and_continue ISA::Ujmp.new(vm, name)
123
+ else
124
+ raise ParseError, "must be a flow control instruction"
125
+ end
126
+ when TAB
127
+ case next_token
128
+ when SPACE
129
+ name = parse_name
130
+ append_and_continue ISA::Zjmp.new(vm, name)
131
+ when TAB
132
+ name = parse_name
133
+ append_and_continue ISA::Njmp.new(vm, name)
134
+ when LF
135
+ append_and_continue ISA::Return.new(vm)
136
+ else
137
+ raise ParseError, "must be a flow control instruction"
138
+ end
139
+ when LF
140
+ case next_token
141
+ when LF
142
+ append_and_continue ISA::End.new(vm)
143
+ else
144
+ raise ParseError, "must be a flow control instruction"
145
+ end
146
+ else
147
+ raise ParseError, "must be a flow control instruction"
148
+ end
149
+ end
150
+
151
+ def parse_io
152
+ case next_token
153
+ when SPACE
154
+ case next_token
155
+ when SPACE
156
+ append_and_continue ISA::Putc.new(vm, console)
157
+ when TAB
158
+ append_and_continue ISA::Putn.new(vm, console)
159
+ else
160
+ raise ParseError, "must be an I/O instruction"
161
+ end
162
+ when TAB
163
+ case next_token
164
+ when SPACE
165
+ append_and_continue ISA::Readc.new(vm, console)
166
+ when TAB
167
+ append_and_continue ISA::Readn.new(vm, console)
168
+ else
169
+ raise ParseError, "must be an I/O instruction"
170
+ end
171
+ else
172
+ raise ParseError, "must be an I/O instruction"
173
+ end
174
+ end
175
+
176
+ def parse_number
177
+ parse_sign * parse_value
178
+ end
179
+
180
+ def parse_sign
181
+ case next_token
182
+ when SPACE
183
+ 1
184
+ when TAB
185
+ -1
186
+ else
187
+ raise ParseError, "must be a sign token"
188
+ end
189
+ end
190
+
191
+ def parse_value
192
+ parse_value_rec(0, 0, next_token)
193
+ end
194
+
195
+ def parse_value_rec(n, len, token)
196
+ case token
197
+ when SPACE
198
+ parse_value_rec(2 * n, len + 1, next_token)
199
+ when TAB
200
+ parse_value_rec(2 * n + 1, len + 1, next_token)
201
+ when LF
202
+ if len > 0
203
+ n
204
+ else
205
+ raise ParseError, "number must have a value part"
206
+ end
207
+ else
208
+ raise ParseError, "number must be terminated by a LF"
209
+ end
210
+ end
211
+
212
+ def parse_name
213
+ parse_name_rec("", 0, next_token)
214
+ end
215
+
216
+ def parse_name_rec(name, len, token)
217
+ case token
218
+ when SPACE
219
+ parse_name_rec(name + " ", len + 1, next_token)
220
+ when TAB
221
+ parse_name_rec(name + "\t", len + 1, next_token)
222
+ when LF
223
+ if len > 0
224
+ name
225
+ else
226
+ raise ParseError, "name must be non-empty"
227
+ end
228
+ else
229
+ raise ParseError, "name must be terminated by a LF"
230
+ end
231
+ end
232
+
233
+ def next_token
234
+ @index += 1
235
+ @tokens[@index - 1]
236
+ end
237
+
238
+ def append_and_continue(instruction)
239
+ @instructions << instruction
240
+ parse_start
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,37 @@
1
+ module Whitespace
2
+ module Util
3
+ class << self
4
+ def is_integer?(n)
5
+ n.is_a? Integer
6
+ end
7
+
8
+ def is_ascii?(n)
9
+ n == 10 || n == 13 || (n >= 32 && n <= 127)
10
+ end
11
+
12
+ def is_binop?(op)
13
+ BINOPS.include? op
14
+ end
15
+
16
+ def is_label?(name)
17
+ name.instance_of?(String) && !/\A[ \t]+\z/.match(name).nil?
18
+ end
19
+
20
+ def find_label(instructions, name)
21
+ instructions.each_with_index do |instr, i|
22
+ return i if instr.instance_of?(ISA::Label) && instr.name == name
23
+ end
24
+
25
+ raise LabelError, "missing: \"#{name}\""
26
+ end
27
+ end
28
+
29
+ BINOPS = {
30
+ add: lambda { |l, r| l + r },
31
+ sub: lambda { |l, r| l - r },
32
+ mul: lambda { |l, r| l * r },
33
+ div: lambda { |l, r| l / r },
34
+ mod: lambda { |l, r| l % r }
35
+ }
36
+ end
37
+ end