vm2gol v2 (57) 二項演算を左結合に変更



二項演算が右結合になっていたのを左結合に変えます。

例として 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 での実行時に発生するようになっていて困ったので、 アセンブルの段階でエラーにしてしまうことに
  • VMコメントの整理
  • rename: codegen_〜 => gen_〜 (vgcg.rb)
    • メソッド名を短くしました。 gen_〜 でも分かるからいいかなと。