Ruby の予約語に endif やら endfor やらを追加するパッチ

前のエントリで、Ruby では複文の終わりを表すのが end しかないことを問題にし、それに対して endif や endfor を予約語に追加することを解決策として示した。
で、言うだけでなくて実際にパッチを作ってみた。
mismatched-end-1.0.0.tar.gz (追記: RubyForge.org に移動)
(追記2: arton氏によるWindows用バイナリ。多謝)
http://arton.no-ip.info/data/asr/kwruby.zip

このパッチを当てると、以下の予約語が追加される。意味については説明不要だろう。

  • endif
  • endunless
  • endfor
  • endwhile
  • enduntil
  • endcase
  • endbegin
  • enddo
  • enddef
  • endclass
  • endmodule

インストール方法は次の通り。コンパイルには、gperf (keywors から lex.c を生成する) と bison (parse.y から parse.c を生成する) が必要なことに注意。

$ tar xzf ruby-1.8.6-p114.tar.gz
$ cd ruby-1.8.6-p114/
$ patch -p1 < /tmp/end-mismatch-patch/ruby-1.8.6/patch.diff
$ ./configure --prefix=/usr/local
$ make
$ make test
$ sudo cp ruby /usr/local/bin/myruby

実際の例を見てみる。たとえば次のスクリプトでは、10 行目が end ではなく enb になっている。

test1.rb:

001:  module Foo
002:    class Bar
003:      def example(arg)
004:        i = 0
005:        for item in arg
006:          if item
007:            p item
008:          end
009:        end 
010:      enb    # error
011:    end
012:  end

これをふつうの Ruby で実行すると、12 行目に文法エラーがあり、'end' がくるはずなのにファイルの終わりが来てる (unexpected $end, expecting kEND) と報告される。しかし、これだと実際のエラーがどこにあるのかはわからない。

$ ruby test1.rb
test1.rb:12: syntax error, unexpected $end, expecting kEND

ここで end のかわりに、たとえば 9 行目を endfor に、また 11 行目を endclass に変えてみる。

test2.rb:

001:  module Foo
002:    class Bar
003:      def example(arg)
004:        i = 0
005:        for item in arg
006:          if item
007:            p item
008:          end
009:        endfor
010:      enb    # error
011:    endclass
012:  end

これを実行してみると、9 行目に問題はなく、11 行目に問題があることがわかる。つまり、9 行目から 11 行目の間に問題があると推測できる。またエラーメッセージも expecting kEND or kENDDEF とあるので、enddef が見つからない、つまり def に対応した end が見つからないということがすぐに分かる。

$ myruby test2.rb
test2.rb:11: syntax error, unexpected kENDCLASS, expecting kEND or kENDDEF

これだけの情報があれば、どこが間違っているかを特定するのは簡単だろう。

また、このパッチは eRuby ファイルが複雑な場合に特に便利だ。eRuby ファイルは手軽なのがいいのだが、HTML が複雑になるとすっごーく読みづらくなる。しかし endif や endfor が使えると、end がどの if や for と関連しているかがよくわかるようになる。

<% if @list %>
<table>
<% for item in @list %>
  <tr>
<%   if !item.name %>
    <td cols="2">-</td>
<%   elsif item.email %>
    <td><a href="mailto:<%=item.email%>"><%=h item.name %></a></td>
    <td><a href="mailto:<%=item.email%>"><%=h item.email %></a></td>
<%   else %>
    <td><%= item.name %></td>
    <td>-</td>
<%   endif %>
  </tr>
<% endfor %>
</table>
<% else %>
<p>Not found.</p>
<% endif %>

ということで、Ruby の mismatched-end 問題で悩んでいるひとがいたら、このパッチを試してみればいいと思うよ。エディタのあやふやな自動インデントに頼った方法とは大違いだから!