vm2gol v2 (50) コード生成処理の変更にあわせたパーサの修正など



変数宣言周りの修正(パーサ編)

第48回 変数宣言のコード生成処理の改善など で変数宣言まわりを変更したときは特に考えてなかったんですが、これはパーサも合わせておいた方が良さそうに思えます。


まずは var文が書ける場所を関数の直下のみに限定する変更。

--- a/vgparser.rb
+++ b/vgparser.rb
@@ -185,7 +185,20 @@ class Parser
     consume ")"
 
     consume "{"
-    stmts = parse_stmts()
+
+    stmts = []
+    loop do
+      t = peek()
+      break if t.value == "}"
+
+      stmts <<
+        if t.value == "var"
+          parse_var()
+        else
+          parse_stmt()
+        end
+    end
+
     consume "}"
 
     [:func, func_name, args, stmts]
@@ -434,7 +447,6 @@ class Parser
 
     case t.value
     when "func"     then parse_func()
-    when "var"      then parse_var()  # parse_stmt() からは削除
     when "set"      then parse_set()
     when "call"     then parse_call()
     when "call_set" then parse_call_set()

これにより while文やcase文の中で var文が使えなくなってテストが壊れるので適宜 set文に変えるなどしました。

--- a/test/test_vgparser.rb
+++ b/test/test_vgparser.rb
@@ -286,14 +286,16 @@ class ParserTest < Minitest::Test
 
   def test_while_2
     src = <<-EOS
+      var a;
       while (a == 1) {
-        var b;
+        set a = 2;
       }
     EOS
 
     tree_exp = [
+      [:var, "a"],
       [:while, [:eq, "a", 1], [
-         [:var, "b"]]]]
+         [:set, "a", 2]]]]

次いで top_stmt(s) 関連の修正。

  • parse_top_stmt(), parse_top_stmts() を追加
  • parse() から parse_top_stmts() を呼び出すように切り替え
  • parse_stmt() から関数定義の分岐を削除
--- a/vgparser.rb
+++ b/vgparser.rb
@@ -446,7 +446,6 @@ class Parser
     return nil if t.value == "}"
 
     case t.value
-    when "func"     then parse_func()
     when "set"      then parse_set()
     when "call"     then parse_call()
     when "call_set" then parse_call_set()
@@ -474,8 +473,31 @@ class Parser
     stmts
   end
 
+  def parse_top_stmt
+    t = peek()
+
+    case t.value
+    when "func" then parse_func()
+    else
+      raise ParseError, "Unexpected token (#{t.inspect})"
+    end
+  end
+
+  def parse_top_stmts
+    stmts = []
+
+    loop do
+      break if end?()
+
+      stmts << parse_top_stmt()
+    end
+
+    stmts
+  end
+
   def parse
-    stmts = parse_stmts()
+    stmts = parse_top_stmts()
     [:stmts, *stmts]
   end
 end

これで、 関数定義の中に関数定義を書くのはダメ、などのルールがパーサのコードで表現できている状態になりました。 今まで結構いい加減にやっていたところが多少はマシになった気がします。

vgtコードのルート要素の名前を変更

いい機会なのでこれも今回あわせて変更しました。 地味に気になっていた。

// before
[ "stmts", ... ]

// after
[ "top_stmts", ... ]

ちなみに Ruby でも top_stmts という名前になっています。

https://github.com/ruby/ruby/blob/v2_7_1/parse.y#L1208

codegen_return で出力する命令を変更

整数の即値を return で返却する場合は

set_reg_a {値}

というアセンブリコードを出力していましたが、他との統一性のため

cp {値} reg_a

のように cp を使うように変えました。 こうしておいた方が都合が良さそうなのです。 出力されるアセンブリコード、機械語コードが変わりますが動作は何も変わりません。

Perl に移植 していたときに気付いて Ruby版にフィードバック。

peek

parse_var() では先読みのため @tokens[@pos + 1] としていて、 ここだけ peek に置き換えられていなかったんですが、 offset という引数を追加して peek(1) とすると 現在位置の1個先を覗き見できるようにしました。

--- a/vgparser.rb
+++ b/vgparser.rb
@@ -87,8 +87,8 @@ class Parser
       .map { |t| format("%s<%s>", t.type, t.value) }
   end
 
-  def peek
-    @tokens[@pos]
+  def peek(offset = 0)
+    @tokens[@pos + offset]
   end
 
   def dump_state(msg = nil)

これも移植版で先行して試していたものをそろそろいいかなと思って取り込んだ形。

その他

  • メソッドの位置の変更
  • ParseError のメッセージを改善
  • テストまわりの改善