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,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,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,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,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
|