vaporware-compiler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 13390207490f478cdad891ae8c368d3fb3062c1b2005bdf331a25f3b2dc29dc4
4
+ data.tar.gz: 823150e9b1439dad476284f9b3ec00a8d86c427cd95d08f5ec19a75159cdf4f3
5
+ SHA512:
6
+ metadata.gz: c6050037946286c6ddf340dada87bfa3847504feb1814bfd3e56bd5b467c5f30fe6a433d8d5372e7bc94722c09229c42a0bf17b0255ba8efc29d22fd696016b0
7
+ data.tar.gz: c45ee87e4728a90dbb6cfeb5b12c61048dd9ff82b6639d858cadbd211340f27c319ef0ab83fb43366deb1352b8d5912d8c6a0080ae0e6363b17df67421088e1d
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-03-05
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in vaporware.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Vaporware
2
+
3
+ it's the vaporware.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ task default: %i[test]
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.test_files = FileList['test/test*.rb']
9
+ end
data/Steepfile ADDED
@@ -0,0 +1,12 @@
1
+ D = Steep::Diagnostic
2
+ #
3
+ target :lib do
4
+ signature "sig"
5
+ check "lib" # Directory name
6
+ configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
7
+ configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
8
+ configure_code_diagnostics do |hash| # You can setup everything yourself
9
+ hash[D::Ruby::NoMethod] = :information
10
+ hash[D::Ruby::UnknownConstant] = :information
11
+ end
12
+ end
data/exe/vaporware ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "vaporware"
4
+ require "optparse"
5
+ opt = OptionParser.new
6
+ options = {}
7
+ opt.on("-c", "--compiler [VAL]", "this option is selecting compiler precompiled file, default: gcc") { |v| options[:compiler] = v }
8
+ opt.on("-D", "--debug") { |v| options[:debug] = v }
9
+ opt.on("-o", "--objects [VAL]") { |v| options[:dest] = v }
10
+ opt.on("--compiler-options[=VAL]", "compiler options") { |v| options[:compiler_options] = v.split(",") }
11
+ opt.on("-s", "--shared") { |v| options[:shared] = v }
12
+
13
+ begin
14
+ opt.parse!(ARGV)
15
+ raise "please compile target file" if ARGV.empty?
16
+ rescue => e
17
+ STDERR.puts(e.message)
18
+ exit 1
19
+ end
20
+
21
+ Vaporware::Compiler.compile(ARGV.shift, **options)
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vaporware
4
+ VERSION = "0.1.0"
5
+ end
data/lib/vaporware.rb ADDED
@@ -0,0 +1,317 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "vaporware/version"
4
+ require "parser/current"
5
+
6
+ module Vaporware
7
+ class Error < StandardError; end
8
+ # Your code goes here...
9
+ class Compiler
10
+ REGISTER = %w(r9 r8 rcx rdx rsi rdi).reverse
11
+ attr_reader :ast, :_precompile, :debug, :seq, :defined_variables, :doned, :main, :shared, :defined_methods
12
+ def self.compile(source, compiler: "gcc", dest: "tmp", debug: false, compiler_options: ["-O0"], shared: false)
13
+ _precompile = "#{dest}.s"
14
+ s = new(source, _precompile: _precompile, debug:, shared:)
15
+ s.compile(compiler:, compiler_options:)
16
+ end
17
+
18
+ def initialize(source, _precompile: "tmp.s", debug: false, shared: false)
19
+ @_precompile, @debug, @seq, @shared = _precompile, debug, 0, shared
20
+ @defined_methods = Set.new
21
+ @defined_variables = Set.new
22
+ @doned = Set.new
23
+ src = File.read(File.expand_path(source))
24
+ @ast = Parser::CurrentRuby.parse(src)
25
+ @main = false
26
+ end
27
+
28
+ def compile(compiler: "gcc", compiler_options: ["-O0"])
29
+ puts ast if debug
30
+
31
+ register_var_and_method(ast)
32
+
33
+ output = File.open(_precompile, "w")
34
+ # prologue
35
+ output.puts ".intel_syntax noprefix"
36
+ if defined_methods.empty?
37
+ @main = true
38
+ output.puts ".globl main"
39
+ output.puts "main:"
40
+ output.puts " push rbp"
41
+ output.puts " mov rbp, rsp"
42
+ output.puts " sub rsp, #{defined_variables.size * 8}"
43
+ gen(ast, output)
44
+ # epilogue
45
+ gen_epilogue(output)
46
+ else
47
+ gen_prologue_methods(output)
48
+ output.puts ".globl main" unless shared
49
+ gen(ast, output)
50
+ # epilogue
51
+ gen_epilogue(output)
52
+ end
53
+ output.close
54
+ compiler_options += compile_shared_option if shared
55
+ call_compiler(compiler:, compiler_options:)
56
+ end
57
+
58
+ private
59
+
60
+ def compile_shared_option = %w(-shared -fPIC)
61
+
62
+ def register_var_and_method(node)
63
+ return unless node.kind_of?(Parser::AST::Node)
64
+ type = node.type
65
+ if variable_or_method?(type)
66
+ name, _ = node.children
67
+ name = [:lvasgn, :arg].include?(type) ? "lvar_#{name}".to_sym : name
68
+ type == :def ? @defined_methods << name : @defined_variables << name
69
+ end
70
+ node.children.each { |n| register_var_and_method(n) }
71
+ end
72
+
73
+ def already_build_methods? = defined_methods.sort == @doned.to_a.sort
74
+ def variable_or_method?(type) = [:lvasgn, :arg, :def].include?(type)
75
+
76
+ def call_compiler(output: _precompile, compiler: "gcc", compiler_options: ["-O0"])
77
+ base_name = File.basename(output, ".*")
78
+ name = shared ? "lib#{base_name}.so" : base_name
79
+ compile_commands = [compiler, *compiler_options, "-o", name, output].compact
80
+ IO.popen(compile_commands).close
81
+
82
+ puts File.read(output) if debug
83
+ nil
84
+ end
85
+
86
+ def gen_epilogue(output)
87
+ output.puts " mov rsp, rbp"
88
+ output.puts " pop rbp"
89
+ output.puts " ret"
90
+ end
91
+
92
+ def gen_prologue_methods(output)
93
+ defined_methods.each do |name|
94
+ output.puts ".globl #{name}"
95
+ output.puts ".type #{name}, @function" if shared
96
+ end
97
+ nil
98
+ end
99
+
100
+ def gen_define_method_prologue(node, output)
101
+ output.puts " push rbp"
102
+ output.puts " mov rbp, rsp"
103
+ output.puts " sub rsp, #{lvar_offset(nil) * 8}"
104
+ _name, args, _block = node.children
105
+ args.children.each_with_index do |_, i|
106
+ output.puts " mov [rbp-#{(i + 1) * 8}], #{REGISTER[i]}"
107
+ end
108
+ nil
109
+ end
110
+
111
+ def gen_method(method, node, output)
112
+ output.puts "#{method}:"
113
+ gen_define_method_prologue(node, output)
114
+ node.children.each do |child|
115
+ next unless child.kind_of?(Parser::AST::Node)
116
+ gen(child, output, true)
117
+ end
118
+ gen_ret(output)
119
+ @doned << method
120
+ nil
121
+ end
122
+
123
+ def gen_args(node, output)
124
+ node.children.each do |child|
125
+ name = "arg_#{child.children.first}".to_sym
126
+ gen_lvar(name, output)
127
+ output.puts " pop rax"
128
+ output.puts " mov rax, [rax]"
129
+ output.puts " push rax"
130
+ end
131
+ end
132
+
133
+ def gen_call_method(node, output, method_tree)
134
+ output.puts " mov rax, rsp"
135
+ output.puts " mov rdi, 16"
136
+ output.puts " cqo"
137
+ output.puts " idiv rdi"
138
+ output.puts " mov rax, 0"
139
+ output.puts " cmp rdi, 0"
140
+ output.puts " jne .Lprecall#{seq}"
141
+ output.puts " push 0"
142
+ output.puts " mov rax, 1"
143
+ output.puts ".Lprecall#{seq}:"
144
+ output.puts " push rax"
145
+ _, name, *args = node.children
146
+ args.each_with_index do |arg, i|
147
+ gen(arg, output, method_tree)
148
+ output.puts " pop #{REGISTER[i]}"
149
+ end
150
+
151
+ output.puts " call #{name}"
152
+ output.puts " pop rdi"
153
+ output.puts " cmp rdi, 0"
154
+ output.puts " je .Lpostcall#{seq}"
155
+ output.puts " pop rdi"
156
+ output.puts ".Lpostcall#{seq}:"
157
+ output.puts " push rax"
158
+ @seq += 1
159
+ nil
160
+ end
161
+
162
+ def gen_comp(op, output)
163
+ output.puts " cmp rax, rdi"
164
+ output.puts " #{op} al"
165
+ output.puts " movzb rax, al"
166
+ output.puts " push rax"
167
+ nil
168
+ end
169
+
170
+ def gen_lvar(var, output)
171
+ output.puts " mov rax, rbp"
172
+ output.puts " sub rax, #{lvar_offset(var) * 8}"
173
+ output.puts " push rax"
174
+ nil
175
+ end
176
+
177
+ def lvar_offset(var)
178
+ return @defined_variables.size if var.nil?
179
+ @defined_variables.find_index(var).then do |i|
180
+ raise "unknown local variable...: #{var}" if i.nil?
181
+ i + 1
182
+ end
183
+ end
184
+
185
+ def gen_ret(output)
186
+ output.puts " pop rax"
187
+ output.puts " mov rsp, rbp"
188
+ output.puts " pop rbp"
189
+ output.puts " ret"
190
+ end
191
+
192
+ def gen(node, output, method_tree = false)
193
+ return unless node.kind_of?(Parser::AST::Node)
194
+ type = node.type
195
+ center = case type
196
+ when :int
197
+ output.puts " push #{node.children.last}"
198
+ return
199
+ when :begin
200
+ node.children.each do |child|
201
+ if already_build_methods? && !@main
202
+ return if shared
203
+ output.puts "main:"
204
+ output.puts " push rbp"
205
+ output.puts " mov rbp, rsp"
206
+ output.puts " sub rsp, 0"
207
+ output.puts " push rax"
208
+ @main = true
209
+ end
210
+ gen(child, output)
211
+ end
212
+ return
213
+ when :def
214
+ name, _ = node.children
215
+ gen_method(name, node, output)
216
+ return
217
+ when :args
218
+ gen_args(node, output)
219
+ return
220
+ when :lvar
221
+ return if method_tree
222
+ name = "lvar_#{node.children.last}".to_sym
223
+ gen_lvar(name, output)
224
+ # lvar
225
+ output.puts " pop rax"
226
+ output.puts " mov rax, [rax]"
227
+ output.puts " push rax"
228
+ return
229
+ when :lvasgn
230
+ left, right = node.children
231
+
232
+ # rvar
233
+ name = "lvar_#{left}".to_sym
234
+ gen_lvar(name, output)
235
+ gen(right, output, method_tree)
236
+
237
+ output.puts " pop rdi"
238
+ output.puts " pop rax"
239
+ output.puts " mov [rax], rdi"
240
+ output.puts " push rdi"
241
+ output.puts " pop rax"
242
+ return
243
+ when :if
244
+ cond, tblock, fblock = node.children
245
+ gen(cond, output)
246
+ output.puts " pop rax"
247
+ output.puts " push rax"
248
+ output.puts " cmp rax, 0"
249
+ if fblock
250
+ output.puts " je .Lelse#{seq}"
251
+ gen(tblock, output, method_tree)
252
+ gen_ret(output)
253
+ output.puts " jmp .Lend#{seq}"
254
+ output.puts ".Lelse#{seq}:"
255
+ gen(fblock, output, method_tree)
256
+ gen_ret(output)
257
+ output.puts ".Lend#{seq}:"
258
+ else
259
+ output.puts " je .Lend#{seq}"
260
+ gen(tblock, output, method_tree)
261
+ gen_ret(output)
262
+ output.puts ".Lend#{seq}:"
263
+ end
264
+ @seq += 1
265
+ return
266
+ when :while
267
+ cond, tblock = node.children
268
+ output.puts ".Lbegin#{seq}:"
269
+ gen(cond, output, method_tree)
270
+ output.puts " pop rax"
271
+ output.puts " push rax"
272
+ output.puts " cmp rax, 0"
273
+ output.puts " je .Lend#{seq}"
274
+ gen(tblock, output, method_tree)
275
+ output.puts " jmp .Lbegin#{seq}"
276
+ output.puts ".Lend#{seq}:"
277
+ @seq += 1
278
+ return
279
+ when :send
280
+ left, center, right = node.children
281
+ gen(left, output, method_tree) unless left.nil?
282
+ if left.nil?
283
+ gen_call_method(node, output, method_tree)
284
+ else
285
+ gen(right, output, method_tree)
286
+ output.puts " pop rdi"
287
+ end
288
+ output.puts " pop rax"
289
+ center
290
+ end
291
+
292
+ case center
293
+ when :+
294
+ output.puts " add rax, rdi"
295
+ output.puts " push rax"
296
+ when :-
297
+ output.puts " sub rax, rdi"
298
+ output.puts " push rax"
299
+ when :*
300
+ output.puts " imul rax, rdi"
301
+ output.puts " push rax"
302
+ when :/
303
+ output.puts " cqo"
304
+ output.puts " idiv rdi"
305
+ output.puts " push rax"
306
+ when :==
307
+ gen_comp("sete", output)
308
+ when :!=
309
+ gen_comp("setne", output)
310
+ when :<
311
+ gen_comp("setl", output)
312
+ when :<=
313
+ gen_comp("setle", output)
314
+ end
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,18 @@
1
+ ---
2
+ sources:
3
+ - type: git
4
+ name: ruby/gem_rbs_collection
5
+ revision: 12631190e75c631eca0908ecc8c03bcfd78c0c99
6
+ remote: https://github.com/ruby/gem_rbs_collection.git
7
+ repo_dir: gems
8
+ path: ".gem_rbs_collection"
9
+ gems:
10
+ - name: ast
11
+ version: '2.4'
12
+ source:
13
+ type: git
14
+ name: ruby/gem_rbs_collection
15
+ revision: 12631190e75c631eca0908ecc8c03bcfd78c0c99
16
+ remote: https://github.com/ruby/gem_rbs_collection.git
17
+ repo_dir: gems
18
+ gemfile_lock_path: Gemfile.lock
@@ -0,0 +1,22 @@
1
+ # Download sources
2
+ sources:
3
+ - name: ruby/gem_rbs_collection
4
+ remote: https://github.com/ruby/gem_rbs_collection.git
5
+ revision: main
6
+ repo_dir: gems
7
+
8
+ # A directory to install the downloaded RBSs
9
+ path: .gem_rbs_collection
10
+
11
+ gems:
12
+ # Skip loading rbs gem's RBS.
13
+ # It's unnecessary if you don't use rbs as a library.
14
+ - name: steep
15
+ ignore: true
16
+ - name: rbs
17
+ ignore: true
18
+ - name: vaporware
19
+ ignore: true
20
+ - name: parser
21
+ - name: set
22
+ ignore: true
data/sample/else.rb ADDED
@@ -0,0 +1,5 @@
1
+ if 0
2
+ 1
3
+ else
4
+ 2
5
+ end
data/sample/fiddle.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "fiddle/import"
2
+
3
+ module X
4
+ extend Fiddle::Importer
5
+ dlload "./libtmp.so"
6
+ extern "int aibo()"
7
+ end
data/sample/hello.rb ADDED
@@ -0,0 +1 @@
1
+ puts "hello"
data/sample/if.rb ADDED
@@ -0,0 +1,3 @@
1
+ a = 1
2
+ b = 1
3
+ 1 if a == b
data/sample/method.rb ADDED
@@ -0,0 +1,2 @@
1
+ def aibo = 10
2
+ def bibo = 11
data/sample/plus.rb ADDED
@@ -0,0 +1 @@
1
+ ((1 + 2) * 3) / (5-4)
@@ -0,0 +1,4 @@
1
+ a = 1
2
+ b = 2
3
+ c = (a + b) / 3
4
+ c
data/sample/while.rb ADDED
@@ -0,0 +1,11 @@
1
+ a = 0
2
+ b = 1
3
+ c = a + b
4
+ n = 1
5
+ while n < 9
6
+ a = b
7
+ b = c
8
+ c = a + b
9
+ n = n + 1
10
+ end
11
+ c
data/sig/vaporware.rbs ADDED
@@ -0,0 +1,57 @@
1
+ module Vaporware
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ class Error
5
+ end
6
+ class Compiler
7
+ REGISTER: Array[String]
8
+ # instance variables
9
+ @_precompile: String
10
+ @debug: bool
11
+ @doned: Set[Symbol]
12
+ @defined_methods: Set[Symbol]
13
+ @defined_variables: Set[Symbol]
14
+ @seq: Integer
15
+ @main: bool
16
+ @shared: bool
17
+
18
+ # temporarily using untyped types since parser.gem's rbs information is unchecked.
19
+ @ast: untyped
20
+
21
+ # attr reader for instance variables
22
+ def _precompile: -> String
23
+ def ast: -> untyped # Parser::AST::Node
24
+ def defined_methods: -> Set[Symbol]
25
+ def defined_variables: -> Set[Symbol]
26
+ def debug: -> bool
27
+ def doned: -> Set[Symbol]
28
+ def seq: -> Integer
29
+ def shared: -> bool
30
+
31
+ # class methods
32
+ def self.compile: (String, ?compiler: String, ?dest: String, ?debug: bool, ?compiler_options: Array[String], ?shared: bool) -> nil
33
+ def initialize: (String, ?_precompile: String, ?debug: bool, ?shared: bool) -> untyped
34
+
35
+ # instance methods
36
+ def compile: (?compiler: String, ?compiler_options: Array[String]) -> nil
37
+
38
+ # instance private methods
39
+ def already_build_methods?: -> bool
40
+ def call_compiler: (?output: String, ?compiler: String, ?compiler_options: Array[String]) -> nil
41
+ def compile_shared_option: () -> Array[String]
42
+ def gen: (untyped, File, ?bool) -> nil
43
+ def gen_args: (untyped, File) -> nil
44
+ def gen_call_method: (untyped, File, bool) -> nil
45
+ def gen_comp: (String, File) -> nil
46
+ def gen_lvar: (untyped, File) -> nil
47
+ def gen_method: (Symbol, untyped, File) -> nil
48
+ def gen_define_method_prologue: (untyped, File) -> nil
49
+ def gen_epilogue: (File) -> nil
50
+ def gen_prologue: (untyped, File) -> nil
51
+ def gen_prologue_methods: (File) -> nil
52
+ def gen_ret: (File) -> nil
53
+ def lvar_offset: (Symbol | nil) -> Integer
54
+ def register_var_and_method: (untyped) -> nil
55
+ def variable_or_method?: (Symbol) -> bool
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vaporware-compiler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - MATSUMOTO, Katsuyoshi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-unit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: steep
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Vaporware is the vaporware.
56
+ email:
57
+ - github@katsyoshi.org
58
+ executables:
59
+ - vaporware
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - CHANGELOG.md
64
+ - Gemfile
65
+ - README.md
66
+ - Rakefile
67
+ - Steepfile
68
+ - exe/vaporware
69
+ - lib/vaporware.rb
70
+ - lib/vaporware/version.rb
71
+ - rbs_collection.lock.yaml
72
+ - rbs_collection.yaml
73
+ - sample/else.rb
74
+ - sample/fiddle.rb
75
+ - sample/hello.rb
76
+ - sample/if.rb
77
+ - sample/method.rb
78
+ - sample/plus.rb
79
+ - sample/variable.rb
80
+ - sample/while.rb
81
+ - sig/vaporware.rbs
82
+ homepage:
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 3.2.0
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 3.4.10
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Vaporware is the vaporware.
105
+ test_files: []