akaza 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8f268d8e1d526b8e7d63dba375322624e27a5af9c281f91101aa60c3ba5efc6
4
- data.tar.gz: 561b42e5bac690f8c983cb03f992558ee5a35d24c81da8025973ff1a11b332a1
3
+ metadata.gz: 7a887b99455c8e17fb594fe02a570c5ea0251c57b5d3e0a73030aeff7c7d5c35
4
+ data.tar.gz: 5f57819daa32f8560816bb36a45930acef8aeecb11d9bc1acf7f247d75c9484f
5
5
  SHA512:
6
- metadata.gz: 250e590b52669d99cbd3bac3c942c38558f9fb270b60a273507d4695c249afdd238011013b364db491c9945c7b383446563533b18ee39ff71af1c26b5047d948
7
- data.tar.gz: 2f6bac299e64ecaaa42a21d04de0c20393b9caab95cc1a01c5e9bdb3c486453f8fb53ea2aa68379758d3226c1a45299d8733c8dddfb15553c1bc0814dba3f246
6
+ metadata.gz: 4bbe86bad2ad3096bd1d6845bd6a88f831977022c7b31cf4bd0aa8b35f6f89e68907bb1cd813fd25b0f656e2c4e2be936d77a51efef554245ebae3ed05349584
7
+ data.tar.gz: f426f4188fdb40d9632042941c2633922df440bf9fea1f08338e2ebdc43b86bf6029bad3986e8cd2431df1bc1258e75d83bee6edb9d67459e1471ce591abe953
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /Gemfile.lock
data/README.md CHANGED
@@ -66,8 +66,7 @@ class A
66
66
  Akaza::Body
67
67
 
68
68
 
69
-
70
-
69
+
71
70
  output.string.to_i
72
71
 
73
72
  end
@@ -112,8 +111,7 @@ This code is never evaluated.
112
111
  So you can write any
113
112
  sentences as comments!
114
113
 
115
-
116
-
114
+
117
115
 
118
116
 
119
117
  end
@@ -123,7 +121,7 @@ a = A.new
123
121
  input = StringIO.new("20\n22\n")
124
122
  output = StringIO.new
125
123
  a.sum(input, output)
126
- p output.string
124
+ p output.string # => "42"
127
125
  ```
128
126
 
129
127
  ## Development
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_development_dependency "bundler", "~> 2.1.a"
30
+ spec.add_development_dependency "bundler"
31
31
  spec.add_development_dependency "rake", "~> 12.0"
32
32
  spec.add_development_dependency "minitest", ">= 5"
33
33
  end
@@ -3,6 +3,7 @@ require 'akaza/ast_ext'
3
3
  require 'akaza/annotation'
4
4
  require 'akaza/parser'
5
5
  require 'akaza/vm'
6
+ require 'akaza/ruby2ws'
6
7
 
7
8
  module Akaza
8
9
  def self.eval(code, input: $stdin, output: $stdout)
@@ -5,10 +5,13 @@ module Akaza
5
5
  file_content(path)[first_index(path)..last_index(path)]
6
6
  end
7
7
 
8
- def traverse(&block)
9
- block.call self
10
- children.each do |child|
11
- child.traverse(&block) if child.is_a?(RubyVM::AbstractSyntaxTree::Node)
8
+ def traverse(&on_enter)
9
+ opt = {}
10
+ on_enter.call self, opt
11
+ unless opt[:skip_children]
12
+ children.each do |child|
13
+ child.traverse(&on_enter) if child.is_a?(RubyVM::AbstractSyntaxTree::Node)
14
+ end
12
15
  end
13
16
  end
14
17
 
@@ -115,7 +115,7 @@ module Akaza
115
115
  [:io, :write_num]
116
116
  when [TAB, SPACE]
117
117
  [:io, :read_char]
118
- when [TAB, NL]
118
+ when [TAB, TAB]
119
119
  [:io, :read_num]
120
120
  else
121
121
  raise "unreachable: #{c}"
@@ -0,0 +1,320 @@
1
+ module Akaza
2
+ # Convert Ruby like script to Whitespace.
3
+ # The syntax is a subset of Ruby,
4
+ # but it has different semantics with Ruby.
5
+ #
6
+ # # sample code
7
+ # # output
8
+ # put_as_number n
9
+ # put_as_char ch
10
+ # put_as_number 42
11
+ # put_as_char 'a'
12
+ #
13
+ # # input
14
+ # num = get_as_number
15
+ # char = get_as_char
16
+ #
17
+ # # flow
18
+ # def foo
19
+ # end
20
+ #
21
+ # exit
22
+ #
23
+ # if x == 0
24
+ # end
25
+ #
26
+ # if x < 0
27
+ # end
28
+ #
29
+ # # heap
30
+ # x = 10
31
+ # push x
32
+ module Ruby2ws
33
+ using AstExt
34
+ SPACE = ' '
35
+ TAB = "\t"
36
+ NL = "\n"
37
+
38
+ class ParseError < StandardError; end
39
+
40
+ def self.ruby_to_ws(ruby_code)
41
+ Transpiler.new(ruby_code).transpile
42
+ end
43
+
44
+ class Transpiler
45
+ def initialize(ruby_code)
46
+ @ruby_code = ruby_code
47
+ @label_index = 0
48
+ end
49
+
50
+ def transpile
51
+ ast = RubyVM::AbstractSyntaxTree.parse(@ruby_code)
52
+ commands = ast_to_commands(ast, main: true)
53
+ commands_to_ws(commands)
54
+ end
55
+
56
+ private def ast_to_commands(ast, main:)
57
+ commands = []
58
+ methods = []
59
+ lvars = []
60
+
61
+ ast.traverse do |node, opt|
62
+ case node
63
+ in [:FCALL, :put_as_number, [:ARRAY, arg, nil]]
64
+ commands.concat(push_value(arg))
65
+ commands << [:io, :write_num]
66
+ opt[:skip_children] = true
67
+ in [:FCALL, :put_as_char, [:ARRAY, arg, nil]]
68
+ commands.concat(push_value(arg))
69
+ commands << [:io, :write_char]
70
+ opt[:skip_children] = true
71
+ in [:VCALL, :exit]
72
+ commands << [:flow, :exit]
73
+ in [:LASGN, var, arg]
74
+ var_addr = str_to_int(var, type: :variable)
75
+ commands << [:stack, :push, var_addr]
76
+ commands.concat(push_value(arg))
77
+ commands << [:heap, :save]
78
+ opt[:skip_children] = true
79
+ lvars << var_addr
80
+ in [:DEFN, name, [:SCOPE, lvar_table, [:ARGS, args_count ,*_], body]]
81
+ m = [
82
+ [:flow, :def, str_to_int(name, type: :method)],
83
+ ]
84
+ lvar_table[0...args_count].reverse.each do |args_name|
85
+ m << [:stack, :push, str_to_int(args_name, type: :variable)]
86
+ m << [:stack, :swap]
87
+ m << [:heap, :save]
88
+ end
89
+ m.concat(ast_to_commands(body, main: false))
90
+ m << [:flow, :end]
91
+
92
+ methods << m
93
+ opt[:skip_children] = true
94
+ in [:SCOPE, *_] | [:BLOCK, *_]
95
+ # skip
96
+ in [:VCALL, name]
97
+ with_storing_lvars(lvars, commands) do
98
+ commands << [:flow, :call, str_to_int(name, type: :method)]
99
+ end
100
+ opt[:skip_children] = true
101
+ in [:FCALL, name, [:ARRAY, *args, nil]]
102
+ with_storing_lvars(lvars, commands) do
103
+ args.each do |arg|
104
+ commands.concat(push_value(arg))
105
+ end
106
+ commands << [:flow, :call, str_to_int(name, type: :method)]
107
+ end
108
+ opt[:skip_children] = true
109
+ in [:IF, cond, if_body, else_body]
110
+ commands.concat(compile_if(cond, if_body, else_body))
111
+ opt[:skip_children] = true
112
+ in [:UNLESS, cond, else_body, if_body]
113
+ commands.concat(compile_if(cond, if_body, else_body))
114
+ opt[:skip_children] = true
115
+ in [:WHILE, cond, body]
116
+ commands.concat(compile_while(cond, body))
117
+ opt[:skip_children] = true
118
+ end
119
+ end
120
+
121
+ commands << [:flow, :exit] if main
122
+ commands.concat(*methods)
123
+ commands
124
+ end
125
+
126
+ private def commands_to_ws(commands)
127
+ buf = +""
128
+ commands.each do |command|
129
+ case command
130
+ in [:stack, :push, num]
131
+ buf << SPACE << SPACE << num_to_ws(num)
132
+ in [:stack, :swap]
133
+ buf << SPACE << NL << TAB
134
+ in [:heap, :save]
135
+ buf << TAB << TAB << SPACE
136
+ in [:heap, :load]
137
+ buf << TAB << TAB << TAB
138
+ in [:io, :write_char]
139
+ buf << TAB << NL << SPACE << SPACE
140
+ in [:io, :write_num]
141
+ buf << TAB << NL << SPACE << TAB
142
+ in [:io, :read_char]
143
+ buf << TAB << NL << TAB << SPACE
144
+ in [:io, :read_num]
145
+ buf << TAB << NL << TAB << TAB
146
+ in [:flow, :exit]
147
+ buf << NL << NL << NL
148
+ in [:flow, :call, num]
149
+ buf << NL << SPACE << TAB << num_to_ws(num)
150
+ in [:flow, :def, num]
151
+ buf << NL << SPACE << SPACE << num_to_ws(num)
152
+ in [:flow, :end]
153
+ buf << NL << TAB << NL
154
+ in [:flow, :jump_if_zero, label]
155
+ buf << NL << TAB << SPACE << num_to_ws(label)
156
+ in [:flow, :jump, label]
157
+ buf << NL << SPACE << NL << num_to_ws(label)
158
+ in [:flow, :jump_if_neg, label]
159
+ buf << NL << TAB << TAB << num_to_ws(label)
160
+ in [:calc, :add]
161
+ buf << TAB << SPACE << SPACE << SPACE
162
+ in [:calc, :sub]
163
+ buf << TAB << SPACE << SPACE << TAB
164
+ in [:calc, :multi]
165
+ buf << TAB << SPACE << SPACE << NL
166
+ in [:calc, :div]
167
+ buf << TAB << SPACE << TAB << SPACE
168
+ in [:calc, :mod]
169
+ buf << TAB << SPACE << TAB << TAB
170
+ end
171
+ end
172
+ buf
173
+ end
174
+
175
+ private def with_storing_lvars(lvars, commands, &block)
176
+ lvars.each do |var_addr|
177
+ # stack.push(addr); stack.push(val)
178
+ commands << [:stack, :push, var_addr]
179
+ commands << [:stack, :push, var_addr]
180
+ commands << [:heap, :load]
181
+ # Fill zero
182
+ commands << [:stack, :push, var_addr]
183
+ commands << [:stack, :push, 0]
184
+ commands << [:heap, :save]
185
+ end
186
+
187
+ block.call
188
+
189
+ lvars.size.times do
190
+ commands << [:heap, :save]
191
+ end
192
+ end
193
+
194
+ private def push_value(ast)
195
+ commands = []
196
+
197
+ case ast
198
+ in [:LIT, num]
199
+ commands << [:stack, :push, num]
200
+ in [:STR, str]
201
+ check_char!(str)
202
+ commands << [:stack, :push, str.ord]
203
+ in [:LVAR, name]
204
+ commands << [:stack, :push, str_to_int(name, type: :variable)]
205
+ commands << [:heap, :load]
206
+ in [:VCALL, :get_as_number]
207
+ tmp_var = str_to_int("__tmp", type: :variable)
208
+ commands << [:stack, :push, tmp_var]
209
+ commands << [:io, :read_num]
210
+ commands << [:stack, :push, tmp_var]
211
+ commands << [:heap, :load]
212
+ in [:VCALL, :get_as_char]
213
+ tmp_var = str_to_int("__tmp", type: :variable)
214
+ commands << [:stack, :push, tmp_var]
215
+ commands << [:io, :read_char]
216
+ commands << [:stack, :push, tmp_var]
217
+ commands << [:heap, :load]
218
+ in [:OPCALL, l, sym, [:ARRAY, r, nil]]
219
+ com = {'+': :add, '-': :sub, '*': :multi, '/': :div, '%': :mod}[sym]
220
+ raise ParserError, "Unknown symbol: #{sym}" unless com
221
+ commands.concat(push_value(l))
222
+ commands.concat(push_value(r))
223
+ commands << [:calc, com]
224
+ end
225
+
226
+ commands
227
+ end
228
+
229
+ private def compile_if(cond, if_body, else_body)
230
+ commands = []
231
+ else_label = str_to_int("else_#{next_label_index}", type: :condition)
232
+ end_label = str_to_int("end_#{next_label_index}", type: :condition)
233
+
234
+ body = -> (x, sym) do
235
+ commands.concat(push_value(x))
236
+ commands << [:flow, sym, else_label]
237
+ commands.concat(ast_to_commands(else_body, main: false)) if else_body
238
+ commands << [:flow, :jump, end_label]
239
+ commands << [:flow, :def, else_label]
240
+ commands.concat(ast_to_commands(if_body, main: false)) if if_body
241
+ commands << [:flow, :def, end_label]
242
+ end
243
+
244
+ case cond
245
+ in [:OPCALL, [:LIT, 0], :==, [:ARRAY, x, nil]]
246
+ body.(x, :jump_if_zero)
247
+ in [:OPCALL, x, :==, [:ARRAY, [:LIT, 0], nil]]
248
+ body.(x, :jump_if_zero)
249
+ in [:OPCALL, x, :<, [:ARRAY, [:LIT, 0], nil]]
250
+ body.(x, :jump_if_neg)
251
+ in [:OPCALL, [:LIT, 0], :<, [:ARRAY, x, nil]]
252
+ body.(x, :jump_if_neg)
253
+ end
254
+
255
+ commands
256
+ end
257
+
258
+ private def compile_while(cond, body)
259
+ commands = []
260
+ cond_label = str_to_int("cond_#{next_label_index}", type: :condition)
261
+ body_label = str_to_int("body_#{next_label_index}", type: :condition)
262
+ end_label = str_to_int("end_#{next_label_index}", type: :condition)
263
+
264
+ make_body = -> (x, sym) do
265
+ commands << [:flow, :def, cond_label]
266
+ commands.concat(push_value(x))
267
+ commands << [:flow, sym, body_label]
268
+ commands << [:flow, :jump, end_label]
269
+ commands << [:flow, :def, body_label]
270
+ commands.concat(ast_to_commands(body, main: false))
271
+ commands << [:flow, :jump, cond_label]
272
+ commands << [:flow, :def, end_label]
273
+ end
274
+
275
+ case cond
276
+ in [:OPCALL, [:LIT, 0], :==, [:ARRAY, x, nil]]
277
+ make_body.(x, :jump_if_zero)
278
+ in [:OPCALL, x, :==, [:ARRAY, [:LIT, 0], nil]]
279
+ make_body.(x, :jump_if_zero)
280
+ in [:OPCALL, x, :<, [:ARRAY, [:LIT, 0], nil]]
281
+ make_body.(x, :jump_if_neg)
282
+ in [:OPCALL, [:LIT, 0], :<, [:ARRAY, x, nil]]
283
+ make_body.(x, :jump_if_neg)
284
+ end
285
+
286
+ commands
287
+ end
288
+
289
+ private def check_char!(char)
290
+ raise ParserError, "String size must be 1, but it's #{char} (#{char.size})" if char.size != 1
291
+ end
292
+
293
+ private def str_to_int(str, type:)
294
+ prefix =
295
+ case type
296
+ when :variable then 'v'
297
+ when :method then 'f'
298
+ when :condition then 'c'
299
+ else
300
+ raise "Unknown type: #{type}"
301
+ end
302
+ "#{prefix}_#{str}".bytes.inject { |a, b| (a << 8) + b }
303
+ end
304
+
305
+ private def num_to_ws(num)
306
+ sign =
307
+ if num < 0
308
+ TAB
309
+ else
310
+ SPACE
311
+ end
312
+ sign + num.abs.to_s(2).gsub("1", TAB).gsub('0', SPACE) + NL
313
+ end
314
+
315
+ private def next_label_index
316
+ @label_index += 1
317
+ end
318
+ end
319
+ end
320
+ end
@@ -1,3 +1,3 @@
1
1
  module Akaza
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -25,15 +25,25 @@ module Akaza
25
25
  in [:stack, :pop]
26
26
  @stack.pop
27
27
  in [:calc, :add]
28
- @stack.push @stack.pop + @stack.pop
28
+ r = @stack.pop
29
+ l = @stack.pop
30
+ @stack.push l + r
29
31
  in [:calc, :sub]
30
- @stack.push @stack.pop - @stack.pop
32
+ r = @stack.pop
33
+ l = @stack.pop
34
+ @stack.push l - r
31
35
  in [:calc, :multi]
32
- @stack.push @stack.pop * @stack.pop
36
+ r = @stack.pop
37
+ l = @stack.pop
38
+ @stack.push l * r
33
39
  in [:calc, :div]
34
- @stack.push @stack.pop / @stack.pop
40
+ r = @stack.pop
41
+ l = @stack.pop
42
+ @stack.push l / r
35
43
  in [:calc, :mod]
36
- @stack.push @stack.pop % @stack.pop
44
+ r = @stack.pop
45
+ l = @stack.pop
46
+ @stack.push l % r
37
47
  in [:heap, :save]
38
48
  val = @stack.pop
39
49
  addr = @stack.pop
@@ -42,13 +52,13 @@ module Akaza
42
52
  addr = @stack.pop
43
53
  @stack.push @heap[addr]
44
54
  in [:flow, :def, label]
45
- # does nothing
55
+ # skip
46
56
  in [:flow, :call, label]
47
- raise "unknwon label:#{label}" unless @labels.key?(label)
57
+ raise "unknwon label:#{label.inspect}" unless @labels.key?(label)
48
58
  @call_stack.push @index
49
59
  @index = @labels[label]
50
60
  in [:flow, :jump, label]
51
- raise "unknwon label:#{label}" unless @labels.key?(label)
61
+ raise "unknwon label:#{label.inspect}" unless @labels.key?(label)
52
62
  @index = @labels[label]
53
63
  in [:flow, :jump_if_zero, label]
54
64
  @index = @labels[label] if @stack.pop == 0
@@ -77,7 +87,7 @@ module Akaza
77
87
  @commands.each.with_index do |command, index|
78
88
  case command
79
89
  in [:flow, :def, label]
80
- @labels[label] = index - 1
90
+ @labels[label] = index
81
91
  else
82
92
  # skip
83
93
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: akaza
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masataka Pocke Kuwabara
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-28 00:00:00.000000000 Z
11
+ date: 2019-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.1.a
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 2.1.a
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -61,7 +61,6 @@ extra_rdoc_files: []
61
61
  files:
62
62
  - ".gitignore"
63
63
  - Gemfile
64
- - Gemfile.lock
65
64
  - README.md
66
65
  - Rakefile
67
66
  - akaza.gemspec
@@ -72,6 +71,7 @@ files:
72
71
  - lib/akaza/annotation.rb
73
72
  - lib/akaza/ast_ext.rb
74
73
  - lib/akaza/parser.rb
74
+ - lib/akaza/ruby2ws.rb
75
75
  - lib/akaza/version.rb
76
76
  - lib/akaza/vm.rb
77
77
  homepage: https://github.com/pocke/akaza
@@ -1,22 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- akaza (0.1.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- minitest (5.11.3)
10
- rake (12.3.2)
11
-
12
- PLATFORMS
13
- ruby
14
-
15
- DEPENDENCIES
16
- akaza!
17
- bundler (~> 2.1.a)
18
- minitest (>= 5)
19
- rake (~> 12.0)
20
-
21
- BUNDLED WITH
22
- 2.1.0.pre.1