TinyRubyコンパイラ/関数を定義する
#TinyRuby
form TinyRubyコンパイラ
最後に、ユーザー定義関数を呼び出せるようにする。
構文木を確認
ユーザー定義関数とその呼び出しはこんな感じ。
code:ruby
def double(n)
n * 2
end
p double(100)
以下は構文解析した結果。
code:ruby
irb(main):002:0> minruby_parse "def double(n) n * 2; end; p double(100)"
=> ["stmts", ["func_def", "double", "n", "var_ref", "n", "lit", 2], ["func_call", "p", ["func_call", "double", "lit", 100]]]
構文木の0番目の要素
ノードの種類: "func_def"
構文木の1番目の要素
関数名: "double"
構文技の2番目の要素
引数名の配列: ["n"]
構文技の3番目の要素
関数の本体の式:["*", ["var_ref", "n"], ["lit", 2]]
実装してみる
ユーザー定義関数を雑に実装。
code:diff
diff --git a/fib.rb b/fib.rb
new file mode 100644
index 0000000..c7d8c1d
--- /dev/null
+++ b/fib.rb
@@ -0,0 +1,16 @@
+# fib.rb: Calculate Fibonacci number
+#
+# usage:
+# $ ruby tinyrubyc.rb fib.rb > tmp.s
+# $ gcc -z noexecstack tmp.s libtinyruby.c
+# $ ./a.out
+# => 55
+def fib(n)
+ if n < 2
+ n
+ else
+ fib(n-1) + fib(n-2)
+ end
+end
+
+p fib(10)
diff --git a/test.sh b/test.sh
index 027c90e..7b61fbd 100755
--- a/test.sh
+++ b/test.sh
@@ -17,10 +17,10 @@ assert() {
fi
}
-# # func_def
-# assert 120 'def hello(a) b = 20; a + b; end; p hello(100)'
-# assert 30 'def hello() a = 10; b = 20; a + b; end; p hello()'
-# assert 4649 'def hello() 4649; end; p hello()'
+# func_def
+assert 120 'def foo(a) b = 20; a + b; end; p foo(100)'
+assert 30 'def foo() a = 10; b = 20; a + b; end; p foo()'
+assert 4649 'def foo() 4649; end; p foo()'
# # func_call
assert 21 'p sum6(1, 2, 3, 4, 5, 6)'
diff --git a/tinyrubyc.rb b/tinyrubyc.rb
index daf0c05..22afe2b 100644
--- a/tinyrubyc.rb
+++ b/tinyrubyc.rb
@@ -6,6 +6,23 @@ VAR_BYTE_WIDTH = 8 # 変数のバイト幅は8バイト (= 64ビット)
# see: https://scrapbox.io/htkymtks/x86-64%E3%81%AE%E3%83%AC%E3%82%B8%E3%82%B9%E3%82%BF
ARG_REGISTERS = "rdi", "rsi", "rdx", "rcx", "r8", "r9"
+# 構文木内の func_def ノードを収集する
+def collect_func_def_nodes(node)
+ case node0
+ when "func_def"
+ node
+ when "stmts"
+ stmts = node1..
+ func_defs = []
+ stmts.each do |stmt|
+ func_defs += collect_func_def_nodes(stmt)
+ end
+ func_defs
+ else
+ []
+ end
+end
+
# 構文木内の var_assigns ノードを収集する
def collect_var_assign_nodes(node)
case node0
@@ -245,23 +262,62 @@ def gen(node, env)
# 終了ラベル
puts ".Lwhile_end#{node.object_id}:"
+ when "func_def"
+ # ここでは何も行わない
else
raise "invalid AST error: #{node}"
end
end
# 入力をパースする
-node = minruby_parse(gets)
+node = minruby_parse(ARGF.read)
-# 変数名一覧を取得
-env = collect_var_names(node)
+# 関数一覧を取得
+func_defs = collect_func_def_nodes(node)
puts " .intel_syntax noprefix"
+
+# ユーザー定義関数
+func_defs.each do |func_def|
+ func_name = func_def1
+ func_args = func_def2
+ func_body = func_def3
+
+ puts " .globl #{func_name}"
+ puts "#{func_name}:"
+ puts " push rbp"
+ puts " mov rbp, rsp"
+
+ # ローカル変数一覧(含む引き数)
+ env = func_args + collect_var_names(func_body)
+
+ # ローカル変数用の領域をスタック上に確保
+ puts " sub rsp, #{env.size * VAR_BYTE_WIDTH}"
+
+ # 引数をローカル変数領域へ格納
+ func_args.each_with_index do |arg, i|
+ offset = var_offset(arg, env)
+ puts " mov rbp-#{offset}, #{ARG_REGISTERSi}"
+ end
+
+ gen(func_body, env)
+
+ # スタック上に確保したローカル変数領域を解放
+ puts " add rsp, #{env.size * VAR_BYTE_WIDTH}"
+
+ puts " pop rbp"
+ puts " ret"
+end
+
+# メイン関数
puts " .globl main"
puts "main:"
puts " push rbp"
puts " mov rbp, rsp"
+# ローカル変数一覧
+env = collect_var_names(node)
+
# ローカル変数用の領域をスタック上に確保
puts " sub rsp, #{env.size * VAR_BYTE_WIDTH}"
フィボナッチ数を計算してみる。
code:fib.rb
def fib(n)
if n < 2
n
else
fib(n-1) + fib(n-2)
end
end
p fib(10)
10番目のフィボナッチ数である55が出力されればOK
code:sh
$ ruby tinyrubyc.rb fib.rb > tmp.s
$ gcc -z noexecstack tmp.s libtinyruby.c
$ ./a.out
55