minrubyコンパイラ / 関数を定義できるようにする
#MinRuby
from MinRubyコンパイラ
ユーザー定義関数を呼び出せるようにする。
code:ruby
def hello()
4649
end
p hello()
上記のようなminrubyコードをパースすると以下のような構文木が返ってくる。
code:sh
["stmts",
["func_def", "hello", [], "lit", 4649], ["func_call", "p", "func_call", "hello"]]
ユーザー関数の定義では主に以下のことを行う
構文木から func_def を抽出してアセンブリコードを出力
ローカル変数のスタック上の位置を管理していた env に、ユーザー定義関数の引数も追加
code:diff
diff --git a/minrubyc.rb b/minrubyc.rb
index df5e320..920c987 100644
--- a/minrubyc.rb
+++ b/minrubyc.rb
@@ -63,6 +63,8 @@ def gen(tree, env)
tree1...each do |statement|
gen(statement, env)
end
+ elsif tree0 == "func_def"
+ # 上で定義済みなのでここでは何もしない
elsif tree0 == "func_call"
# WORK_REGISTERS の値をスタックに退避
puts "\tsub sp, sp, ##{WORK_REGISTERS.size * 4}"
@@ -149,14 +151,64 @@ def var_assigns(hash, tree)
hash
end
+def func_defs(hash, tree)
+ if tree0 == "func_def"
+ # 関数名をキーにして 関数名, 引数, 関数本体 を格納
+ hash[tree1] = tree1..
+ elsif tree0 == "stmts"
+ tree1...each do |statement|
+ func_defs(hash, statement)
+ end
+ end
+ hash
+end
+
tree = minruby_parse(ARGF.read)
# pp tree
-# ローカル変数のインデックスを計算
+# ローカル変数を構文木より抽出し、各ローカル変数のスタック上の位置を算出
env = var_assigns({}, tree)
+# ユーザー定義関数を構文木より抽出
+func_defs = func_defs({}, tree)
+
puts "\t.text"
puts "\t.align 2"
+
+# ユーザー定義関数をアセンブリとして出力
+func_defs.each do |key, func_def|
+ name, args, body = func_def
+
+ # 引数とローカル変数のスタック上の位置を算出
+ env = args.each_with_index.map { |arg, i|
+ arg, i * 16
+ }.to_h
+ env = var_assigns(env, body)
+
+ puts "\t.globl _#{name}"
+ puts "_#{name}:"
+
+ # fp と lr をスタックへ退避
+ puts "\tsub sp, sp, #{16 + env.size * 16}"
+ puts "\tstp x29, x30, sp, ##{env.size * 16}"
+ puts "\tmov x29, sp"
+
+ # args をスタックへ退避
+ args.each_with_index do |arg, i|
+ puts "\t; 引数 #{arg} をスタックへ退避"
+ puts "\tstr w#{i}, [x29, ##{envarg}]"
+ end
+
+ gen(body, env)
+
+ # fp と lr をスタックから復元
+ puts "\tldp x29, x30, sp, ##{env.size * 16}"
+ puts "\tadd sp, sp, #{16 + env.size * 16}"
+
+ puts "\tret"
+end
+
+# メイン関数
puts "\t.globl _main"
puts "_main:"
puts "\tsub sp, sp, #{16 + env.size * 16}"
diff --git a/test.sh b/test.sh
index 4bf3534..0e26756 100755
--- a/test.sh
+++ b/test.sh
@@ -17,6 +17,10 @@ assert() {
fi
}
+# func_def
+assert 4649 'def hello() 4649; end; p hello()'
+assert 30 'def hello() a = 10; b = 20; a + b; end; p hello()'
+assert 120 'def hello(a) b = 20; a + b; end; p hello(100)'
# func_call
assert 30 'p add(10, 20)'
10番目のフィボナッチ数を計算してみる。
code:fib.rb
def fib(n)
if n < 2
n
else
fib(n - 1) + fib(n - 2)
end
end
p fib(10)
いい感じに動いてそう!
https://gyazo.com/6c261c3ae4d9ec283dbda38e3b6b91b1