raccのparser.rbへのとりあえずのパッチ作成

racc 1.2.6のcparseで無限ループ発生せずで、parser.rbとcparseの参照するテーブルの内容が異なるのではないかという予想を立てていましたが、またしてもこの予想は外れていました.
cparseはC言語によるrubyの拡張ライブラリとして実装されてており、参照するテーブルは同一でした.
拡張ライブラリ側で参照する際に、参照する位置がずれたりしないかとも考えましたが、拡張ライブラリのデバッグ用出力を有効にして、parse.rbとcparser.cの両者の出力内容を比較した結果、文法エラーになるトークンを読み込んで、エラーと判定し、エラー回復モードに入るまではまったく同じでした。
違いは、cparse.cがエラー処理として、(仮想的な)エラートークンをシフトするのに対し、parser.rbは文法エラーになるトークンをそのままシフトすることでした。

振り出しに戻ってしまったのですが、よくよくcparse.cを調べてみると、エラー回復モード中に、エラートークンを読み込む処理をする処理がか書かれていました。
それに対して、parser.rbには、該当する処理が存在しませんでした.

そこで、parser.rbに必要な処理を追加すのが、以下に示すパッチです.

ただし、cparse.cでは、(エラー回復モード中のエラー処理には)goto文で飛んできて、(エラー処理が終われば)goto文で(飛んできた所の次の行に)戻っているため、parser.rbにどう書くのが適切かは自信がないです。
また、tecsgenに同梱されているテストケースを実行すると、@racc_state(パーサ内部の状態遷移を管理する配列)がnilになる場合があり、とりあえず動作させるために、@racc_stateがnliの場合には、処理を打ちきるという修正を加えました.

上記の事情を踏まえた上での、ruby 1.8.7に同梱されたraccのparser.rbに対するパッチを以下に示します.

これにより、cparseでなくparser.rbで実行した時でも、テストケースを全て実行できました。
すくなくとも無限ループにはなりませんでした。
#Linuxのruby1.8.7での実行結果です.
#もともとの発端になったJRubyではまだ試していません.

Index: parser.rb
===================================================================
--- parser.rb   (リビジョン 23907)
+++ parser.rb   (作業コピー)
@@ -115,6 +115,10 @@
 
       catch(:racc_end_parse) {
         while true
+#addeb by komianmi
+          break unless @racc_state[-1]
+          break if @racc_state.length < 1
+#end
           if i = action_pointer[@racc_state[-1]]
             if @racc_read_next
               if @racc_t != 0   # not EOF
@@ -294,6 +298,47 @@
             racc_e_pop @racc_state, @racc_tstack, @racc_vstack
           end
         end
+#added by kominami
+        # shiftreduce error token 
+        if act > 0 and act < shift_n
+          #
+          # shift
+          #
+          @racc_vstack.push @racc_val
+          @racc_state.push act
+          @racc_read_next = true
+          if @yydebug
+            @racc_tstack.push @racc_t
+            racc_shift 1, @racc_tstack, @racc_vstack
+          end
+        elsif (act < 0 && act > -(reduce_n)) 
+          #
+          # reduce
+          #
+          code = catch(:racc_jump) {
+            @racc_state.push _racc_do_reduce(arg, act)
+            false
+          }
+          if code
+            case code
+            when 1 # yyerror
+              @racc_user_yyerror = true   # user_yyerror
+              return -reduce_n
+            when 2 # yyaccept
+              return shift_n
+            else
+            raise '[Racc Bug] unknown jump code'
+            end
+          end
+
+        elsif (act == shift_n)
+          racc_accept if yydebug
+          return vstack[0]
+
+        else 
+            rb_raise(RaccBug, "[Racc Bug] unknown act value %ld", act);
+        end
+#end
         return act
 
       else

“raccのparser.rbへのとりあえずのパッチ作成” への1件の返信

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です