二項演算が右結合になっていたのを左結合に変えます。
例として 1 + 2 + 3
で見てみます。
変更前は
[:+, 1, [:+, 2, 3]]
となるようにパースされていて、最終的に機械語になって実行されるときには
2 + 3 1 + {2 + 3 の結果}
という順番で実行されるようになっていました。
今回の変更により
[:+, [:+, 1, 2], 3]
となるようにパースされ、
1 + 2 {1 + 2 の結果} + 3
という順番で実行されるようになります。
なぜ変更するかという話で言えば、 v3 を作っているときに右結合なのを忘れていて少しハマったのがきっかけです。 コードを書いている側としては、無意識のうちに他の一般的な言語と同じで左結合だろうと思っていたわけですね。自分で作ったのに。
すごく困るというわけではないですし、 「ライフゲームがコンパイルできればよい」という観点で言えば正直どっちでも大差ありません。
どっちでもいいのですが、実装を大きく変えなければいけないわけでもないので、 だったらより良い(一般的で直感に反していない、自然な)形に変えておこう、という程度の動機です。
意図的に右結合にしていたということもなかったと思いますし、変えてしまっていいでしょう。
diff です。
--- a/vgparser.rb +++ b/vgparser.rb @@ -182,39 +182,39 @@ def parse_var end end -def parse_expr_right(expr_l) +def parse_expr_right t = peek() - if t.value == ";" || t.value == ")" - return expr_l - end - case t.value when "+" consume "+" expr_r = parse_expr() - [:+, expr_l, expr_r] + [:+, expr_r] when "*" consume "*" expr_r = parse_expr() - [:*, expr_l, expr_r] + [:*, expr_r] when "==" consume "==" expr_r = parse_expr() - [:eq, expr_l, expr_r] + [:eq, expr_r] when "!=" consume "!=" expr_r = parse_expr() - [:neq, expr_l, expr_r] + [:neq, expr_r] else raise ParseError end end +def next_binop? + %w[+ * == !=].include?(peek().value) +end + def parse_expr t_left = peek() @@ -223,7 +223,12 @@ def parse_expr expr_l = parse_expr() consume ")" - return parse_expr_right(expr_l) + if next_binop? + op, expr_r = parse_expr_right() + return [op, expr_l, expr_r] + else + return expr_l + end end if t_left.type == :int || t_left.type == :ident @@ -237,8 +242,12 @@ def parse_expr t_left.value end - parse_expr_right(expr_l) - + if next_binop? + op, expr_r = parse_expr_right() + [op, expr_l, expr_r] + else + expr_l + end else raise ParseError end
重複している部分は次回リファクタリングします(diff が見づらくなるので)。
その他の修正
- ラベルが見つからない場合はエラーにする (vgasm.rb)
- VMコメントの整理
- rename:
codegen_〜
=>gen_〜
(vgcg.rb)- メソッド名を短くしました。
gen_〜
でも分かるからいいかなと。
- メソッド名を短くしました。