TinyRubyコンパイラ/条件分岐を実装する
ifによる条件分岐を実装する。
if の構文木を確認
まずは構文木を確認する。
code:irb
irb(main):001:0> minruby_parse "if (0 == 0); p(42); else p(43); end"
=> ["if", ["==", "lit", 0, "lit", 0], ["func_call", "p", "lit", 42], ["func_call", "p", "lit", 43]] 構文木には以下の要素が格納されている。
配列の0番目の要素
ノードの種類:if
配列の1番目の要素
分岐先を決めるための条件式
配列の2番目の要素
条件式が真の場合に実行される式
配列の3番目の要素
条件式が偽の場合に実行される式
if を実装
if による条件分岐を実装する。
条件式(cond_exp)を評価し、結果が真の場合は then_exp を評価し、偽の場合は else_exp を評価する
結果が 0 の場合は偽と判断し、それ以外の場合は真となる
if が複数あると .Lelse ラベルと .Lend ラベルが重複してしまうため、if の構文木が持つ object_id をラベルに付与する
code:diff
diff --git a/tinyrubyc.rb b/tinyrubyc.rb
index 649dd71..954af19 100644
--- a/tinyrubyc.rb
+++ b/tinyrubyc.rb
@@ -162,6 +162,31 @@ def gen(node, env)
# スタック上のローカル変数領域から値を取得
offset = var_offset(var_name, env)
+ when "if"
+ # if の場合
+ # 例 :
+ # if (0 == 0); p(42); else; p(43); end
+ # => ["if",
+
+ # 条件式を評価
+ gen(cond_exp, env)
+
+ # 真の場合は then_exp を評価
+ puts " cmp rax, 1"
+ puts " jne .Lelse#{node.object_id}"
+ gen(then_exp, env)
+ puts " jmp .Lend#{node.object_id}"
+
+ # 偽の場合は else_exp を評価
+ puts ".Lelse#{node.object_id}:"
+ gen(else_exp, env) if else_exp
+ puts ".Lend#{node.object_id}:"
else
end
diff --git a/test.sh b/test.sh
index 9a99306..7b724b9 100755
--- a/test.sh
+++ b/test.sh
@@ -32,11 +32,11 @@ assert() {
# # while
# assert 55 'i = 1; sum = 0; while i <= 10; sum = sum + i; i = i + 1; end; p(sum)'
-# # if
-# assert 42 'if (0 == 0); p(42); else p(43); end'
-# assert 43 'if (0 == 1); p(42); else p(43); end'
-# assert 41 'if (0 == 0); p(41); end'
-# assert '' 'if (0 == 1); p(41); end'
+# if
+assert 42 'if (0 == 0); p(42); else p(43); end'
+assert 43 'if (0 == 1); p(42); else p(43); end'
+assert 41 'if (0 == 0); p(41); end'
+assert '' 'if (0 == 1); p(41); end'
# 真の場合は1、偽の場合は0を返す
assert 1 'p(1 == 1)'
if のサンプルコード実行
実行してみる。aとbの値は異なるので、else の 5963 が出力されればOK
code:sh
$ echo "a = 10; b = 20; if (a == b); p(4649); else p(5963); end" | ruby tinyrubyc.rb > tmp.s
$ gcc -z noexecstack tmp.s libtinyruby.c && ./a.out
5963
while の構文木を確認
次は while を実装する。
while の構文木を確認。
code:irb
irb(main):008:0> minruby_parse "while (0==0); p(10) end"
構文木には以下の要素が格納されている。
配列の0番目の要素
ノードの種類:while
配列の1番目の要素
繰り返しの有無を判断するための条件式
配列の2番目の要素
繰り返し実行される式
while を実装
if と同じような方法で while を実装する。
code:diff
diff --git a/test.sh b/test.sh
index 7b724b9..b3d783b 100755
--- a/test.sh
+++ b/test.sh
@@ -29,8 +29,8 @@ assert() {
# assert 2 'case 42; when 0; p(0); when 1; p(1); else p(2); end'
# assert 1 'case 42; when 0; p(0); when 42; p(1); else p(2); end'
-# # while
-# assert 55 'i = 1; sum = 0; while i <= 10; sum = sum + i; i = i + 1; end; p(sum)'
+# while
+assert 55 'i = 1; sum = 0; while i <= 10; sum = sum + i; i = i + 1; end; p(sum)'
# if
assert 42 'if (0 == 0); p(42); else p(43); end'
diff --git a/tinyrubyc.rb b/tinyrubyc.rb
index 467eb46..ab3ad34 100644
--- a/tinyrubyc.rb
+++ b/tinyrubyc.rb
@@ -187,6 +187,29 @@ def gen(node, env)
puts ".Lelse#{node.object_id}:"
gen(else_exp, env) if else_exp
puts ".Lend#{node.object_id}:"
+ when "while"
+ # while の場合
+ # 例 : while (0==0); p(10) end
+
+ # 開始ラベル
+ puts ".Lwhile_begin#{node.object_id}:"
+
+ # 条件式を評価
+ gen(cond_exp, env)
+
+ # 真の場合は body_exp を評価
+ puts " cmp rax, 0"
+ puts " je .Lwhile_end#{node.object_id}"
+ gen(body_exp, env)
+
+ # 条件式を評価するため、ループの先頭にジャンプ
+ puts " jmp .Lwhile_begin#{node.object_id}"
+
+ # 終了ラベル
+ puts ".Lwhile_end#{node.object_id}:"
else
end
while の実行
while を使って1から10までの合計を計算。55が出力されればOK
code:sh
$ echo "i = 1; sum = 0; while i <= 10; sum = sum + i; i = i + 1; end; p(sum)" | ruby tinyrubyc.rb > tmp.s
$ gcc -z noexecstack tmp.s libtinyruby.c && ./a.out
55
ブロック内の変数
最後に、if や while のブロックの中で定義された変数を利用できるように修正する。
code:diff
diff --git a/test.sh b/test.sh
index 26ccf97..e51c440 100755
--- a/test.sh
+++ b/test.sh
@@ -31,12 +31,14 @@ assert 1 'case 42; when 0; p(0); when 42; p(1); else p(2); end'
# while
assert 55 'i = 1; sum = 0; while i <= 10; sum = sum + i; i = i + 1; end; p(sum)'
+assert 10 'i = 0; while (i < 10); i = i + 1; foo = 10; end; p(foo)'
# if
assert 42 'if (0 == 0); p(42); else p(43); end'
assert 43 'if (0 == 1); p(42); else p(43); end'
assert 41 'if (0 == 0); p(41); end'
assert '' 'if (0 == 1); p(41); end'
+assert 10 'if (0 == 0); a = 10; end; p(a)'
# 真の場合は1、偽の場合は0を返す
assert 1 'p(1 == 1)'
diff --git a/tinyrubyc.rb b/tinyrubyc.rb
index ab3ad34..8cc1375 100644
--- a/tinyrubyc.rb
+++ b/tinyrubyc.rb
@@ -15,6 +15,24 @@ def collect_var_assign_nodes(node)
var_assigns += collect_var_assign_nodes(stmt)
end
var_assigns
+ when "if"
+ var_assigns = []
+ # 条件式内の var_assigns ノードを収集
+ var_assigns += collect_var_assign_nodes(node1) + # then ブロック内の var_assigns ノードを収集
+ var_assigns += collect_var_assign_nodes(node2) + # else ブロック内の var_assigns ノードを収集
+ var_assigns += collect_var_assign_nodes(node3) + end
+ var_assigns
+ when "while"
+ var_assigns = []
+ # 条件式内の var_assigns ノードを収集
+ var_assigns += collect_var_assign_nodes(node1) + # body ブロック内の var_assigns ノードを収集
+ var_assigns += collect_var_assign_nodes(node2) + var_assigns
else
[]
end