diff --git a/lib/vaporware/compiler.rb b/lib/vaporware/compiler.rb index bd48cfc..96f6493 100644 --- a/lib/vaporware/compiler.rb +++ b/lib/vaporware/compiler.rb @@ -28,13 +28,13 @@ def compile(compiler: "gcc", compiler_options: ["-O0"]) output.puts " push rbp" output.puts " mov rbp, rsp" output.puts " sub rsp, #{@generator.defined_variables.size * 8}" - @generator.build(@generator.ast, output) + @generator.to_asm(@generator.ast, output) # epilogue @generator.epilogue(output) else @generator.prologue_methods(output) output.puts ".globl main" unless @generator.shared - @generator.build(@generator.ast, output) + @generator.to_asm(@generator.ast, output) # epilogue @generator.epilogue(output) end diff --git a/lib/vaporware/compiler/generator.rb b/lib/vaporware/compiler/generator.rb index 2fd562a..3a15d85 100644 --- a/lib/vaporware/compiler/generator.rb +++ b/lib/vaporware/compiler/generator.rb @@ -1,36 +1,37 @@ # frozen_string_literal: true -require "parser/current" module Vaporware class Compiler class Generator + REGISTER = %w(rdi rsi rdx rcx r8 r9) attr_accessor :main attr_reader :ast, :precompile, :debug, :seq, :defined_variables, :doned, :shared, :defined_methods def initialize(source, precompile:, debug:, shared:) @precompile, @debug, @shared = precompile, debug, shared @doned, @defined_methods, @defined_variables = Set.new, Set.new, Set.new @seq, @main = 0, false - src = File.read(File.expand_path(source)) - @ast = Parser::CurrentRuby.parse(src) + @ast = RubyVM::AbstractSyntaxTree.parse_file(source) end def compile_shared_option = %w(-shared -fPIC) def register_var_and_method(node) - return unless node.kind_of?(Parser::AST::Node) + return unless node.kind_of?(RubyVM::AbstractSyntaxTree::Node) type = node.type - if variable_or_method?(type) - name, _ = node.children - name = [:lvasgn, :arg].include?(type) ? "lvar_#{name}".to_sym : name - type == :def ? @defined_methods << name : @defined_variables << name + variables, *_ = node.children + case type + when :SCOPE + variables.each { |v| @defined_variables << v } + when :DEFN + @defined_methods << variables end node.children.each { |n| register_var_and_method(n) } + nil end def already_build_methods? = defined_methods.sort == @doned.to_a.sort - def variable_or_method?(type) = [:lvasgn, :arg, :def].include?(type) - def call_compiler(output: precompile, compiler: "gcc", compiler_options: ["-O0"], debug: false) + def call_compiler(output: precompile, compiler: "gcc", compiler_options: ["-O0"]) base_name = File.basename(output, ".*") name = shared ? "lib#{base_name}.so" : base_name compile_commands = [compiler, *compiler_options, "-o", name, output].compact @@ -69,8 +70,8 @@ def method(method, node, output) output.puts "#{method}:" define_method_prologue(node, output) node.children.each do |child| - next unless child.kind_of?(Parser::AST::Node) - build(child, output, true) + next unless child.kind_of?(RubyVM::AbstractSyntaxTree::Node) + to_asm(child, output, true) end ret(output) @doned << method @@ -79,7 +80,7 @@ def method(method, node, output) def args(node, output) node.children.each do |child| - name = "arg_#{child.children.first}".to_sym + name = "arg_#{child.children.first}".to_sym rescue binding.irb lvar(name, output) output.puts " pop rax" output.puts " mov rax, [rax]" @@ -101,7 +102,7 @@ def call_method(node, output, method_tree) output.puts " push rax" _, name, *args = node.children args.each_with_index do |arg, i| - build(arg, output, method_tree) + to_asm(arg, output, method_tree) output.puts " pop #{REGISTER[i]}" end @@ -146,14 +147,17 @@ def ret(output) output.puts " ret" end - def build(node, output, method_tree = false) - return unless node.kind_of?(Parser::AST::Node) + def to_asm(node, output, method_tree = false) + return unless node.kind_of?(RubyVM::AbstractSyntaxTree::Node) type = node.type center = case type - when :int + when :LIT output.puts " push #{node.children.last}" return - when :begin + when :LIST, :BLOCK + node.children.each { |n| to_asm(n, output, method_tree) } + return + when :SCOPE node.children.each do |child| if already_build_methods? && !@main return if shared @@ -164,32 +168,28 @@ def build(node, output, method_tree = false) output.puts " push rax" @main = true end - build(child, output) + to_asm(child, output) end return - when :def + when :DEFN name, _ = node.children method(name, node, output) return - when :args - args(node, output) - return - when :lvar + when :LVAR return if method_tree - name = "lvar_#{node.children.last}".to_sym + name = node.children.last lvar(name, output) # lvar output.puts " pop rax" output.puts " mov rax, [rax]" output.puts " push rax" return - when :lvasgn - left, right = node.children + when :LASGN + name, right = node.children # rvar - name = "lvar_#{left}".to_sym lvar(name, output) - build(right, output, method_tree) + to_asm(right, output, method_tree) output.puts " pop rdi" output.puts " pop rax" @@ -197,49 +197,49 @@ def build(node, output, method_tree = false) output.puts " push rdi" output.puts " pop rax" return - when :if + when :IF cond, tblock, fblock = node.children - build(cond, output) + to_asm(cond, output) output.puts " pop rax" output.puts " push rax" output.puts " cmp rax, 0" if fblock output.puts " je .Lelse#{seq}" - build(tblock, output, method_tree) + to_asm(tblock, output, method_tree) ret(output) output.puts " jmp .Lend#{seq}" output.puts ".Lelse#{seq}:" - build(fblock, output, method_tree) + to_asm(fblock, output, method_tree) ret(output) output.puts ".Lend#{seq}:" else output.puts " je .Lend#{seq}" - build(tblock, output, method_tree) + to_asm(tblock, output, method_tree) ret(output) output.puts ".Lend#{seq}:" end @seq += 1 return - when :while + when :WHILE cond, tblock = node.children output.puts ".Lbegin#{seq}:" - build(cond, output, method_tree) + to_asm(cond, output, method_tree) output.puts " pop rax" output.puts " push rax" output.puts " cmp rax, 0" output.puts " je .Lend#{seq}" - build(tblock, output, method_tree) + to_asm(tblock, output, method_tree) output.puts " jmp .Lbegin#{seq}" output.puts ".Lend#{seq}:" @seq += 1 return - when :send + when :OPCALL left, center, right = node.children - build(left, output, method_tree) unless left.nil? + to_asm(left, output, method_tree) unless left.nil? if left.nil? call_method(node, output, method_tree) else - build(right, output, method_tree) + to_asm(right, output, method_tree) output.puts " pop rdi" end output.puts " pop rax" diff --git a/sig/vaporware.rbs b/sig/vaporware.rbs index d96a8ee..95de99f 100644 --- a/sig/vaporware.rbs +++ b/sig/vaporware.rbs @@ -7,51 +7,49 @@ module Vaporware # class methods def self.compile: (String, ?compiler: String, ?dest: String, ?debug: bool, ?compiler_options: Array[String], ?shared: bool) -> nil def initialize: (String, ?_precompile: String, ?debug: bool, ?shared: bool) -> untyped + @generator: Vaporware::Compiler::Generator # instance methods def compile: (?compiler: String, ?compiler_options: Array[String]) -> nil class Generator REGISTER: Array[String] - # instance variables - @precompile: String - @debug: bool - @doned: Set[Symbol] - @defined_methods: Set[Symbol] - @defined_variables: Set[Symbol] - @seq: Integer + + # attr_accessor and define instance variables @main: bool - @shared: bool + attr_accessor main (): bool + + # attr_reader for instance variables + attr_reader debug: bool + attr_reader doned: Set[Symbol] + attr_reader defined_methods: Set[Symbol] + attr_reader defined_variables: Set[Symbol] + attr_reader precompile: String + attr_reader seq: Integer + attr_reader shared: bool + # temporarily using untyped types since parser.gem's rbs information is unchecked. - @ast: untyped + attr_reader ast: RubyVM::AbstractSyntaxTree::Node + # class methods def initialize: (String, precompile: String, debug: bool, shared: bool) -> untyped - # attr reader for instance variables - def precompile: -> String - def ast: -> untyped # Parser::AST::Node - def defined_methods: -> Set[Symbol] - def defined_variables: -> Set[Symbol] - def debug: -> bool - def doned: -> Set[Symbol] - def seq: -> Integer - def shared: -> bool # instance private methods def already_build_methods?: -> bool def args: (untyped, File) -> nil - def build: (untyped, File, ?bool) -> nil - def call_compiler: (?output: String, ?compiler: String, ?compiler_options: Array[String], ?debug: bool) -> nil + def call_compiler: (?output: String, ?compiler: String, ?compiler_options: Array[String]) -> nil def call_method: (untyped, File, bool) -> nil def comp: (String, File) -> nil def compile_shared_option: () -> Array[String] def define_method_prologue: (untyped, File) -> nil def epilogue: (File) -> nil def lvar: (untyped, File) -> nil + def lvar_offset: (Symbol | nil) -> Integer def method: (Symbol, untyped, File) -> nil def prologue: (untyped, File) -> nil def prologue_methods: (File) -> nil + def register_var_and_method: (RubyVM::AbstractSyntaxTree::Node) -> nil def ret: (File) -> nil - def lvar_offset: (Symbol | nil) -> Integer - def register_var_and_method: (untyped) -> nil + def to_asm: (untyped, File, ?bool) -> nil def variable_or_method?: (Symbol) -> bool end end