vaporware-compiler 0.1.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 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: []