本を読む

読書やコンピュータなどに関するメモ

RubyにYada Yada Operatorを追加してみる

 Perl 5.12では、Yada Yada Operatorというのが追加されています。コード中で未実装の部分を「...」と書いておくと、文法エラーにはならないけど、実行時にそこを通るとエラー(例外)が発生するというもの。いわば「あとで書く」機能です。

sub foo {
    ...
}

 遊びで、Ruby 1.9.1のソースをいじって同じようなことをしてみました。

ふつうのRubyで実行すると

 ためしに、ふつうのRubyで実行してみます。

$ ruby -e 'if true; ...; end'
-e:1: syntax error, unexpected tDOT3
if true; ...; end
            ^

 「...」は範囲演算子なので、1つのトークンとして認識されるんですね。トークンとしてはtDOT3というもののようです。

方法1:メソッドとして定義

 パーサーをいじってみます。文として認識されるのが混乱がなさそうなので、stmtの最後に追加してみます。

| tDOT3
    {
        $$ = NEW_FCALL(rb_intern("..."), 0)
    }

 tDOT3が文として書かれていたら、「...」というグローバルメソッドを呼ぶ式を構文木に突っ込むわけです。「...」というメソッド名だったら、Rubyからは定義できないのでカブらないでしょう。

 で、error.cあたりにメソッドの処理を追加。Rubyの「raise NotImplementedError, 'Unimplemented'」相当です。"Unimplemented"という文字列は、Perl 5.12のperly.yからのコピペです。

static VALUE
rb_yadayada(int argc, VALUE *argv, VALUE obj)
{
    rb_raise(rb_eNotImpError, "Unimplemented");
}

 これを「...」メソッドとして定義。

    rb_define_global_function("...", rb_yadayada, -1);

 ビルドしたら実行してみます。

$ ./ruby -e 'if true; ...; end'
-e:1:in `...': Unimplemented (NotImplementedError)
        from -e:1:in `
'

 期待した結果が得られました。ただ、「in `...'」ってあたりが、いまいち格好よくないですね。

方法2:raise式を構文木に突っ込む

 方法1では、「...」の中で例外が発生したと表に出てしまうのがいまいちでした。そこで、方法1の変更を戻して、構文木の中にraise式を直接突っ込んでみます。

| tDOT3
    {
        NODE *args = NEW_LIST(NEW_CONST(rb_intern("NotImplementedError")));
        args = list_append(args, NEW_STR(rb_external_str_new_cstr("Unimplemented")));
        $$ = NEW_FCALL(rb_intern("raise"), args);
    }

 ビルドしたら実行してみます。

$ ./ruby -e 'if true; ...; end'
-e:1:in `
': Unimplemented (NotImplementedError)

 より、それっぽくなりました。

テストを書く

 やっぱりテストを書かなくちゃね、ということでtest/ruby/test_yadayada.rbを作ってみます。

require 'test/unit'

class TestYadayada < Test::Unit::TestCase
  def test_yadayada
    assert_nothing_raised do
      if false
        ...
      end
    end

    assert_raise(NotImplementedError) do
      if true
        ...
      end
    end
  end
end

 テストを実行してみます。

$ ./ruby test/ruby/test_yadayada.rb
Loaded suite test/ruby/test_yadayada
Started
.
Finished in 0.000710 seconds.

1 tests, 2 assertions, 0 failures, 0 errors, 0 skips

 めでたしめでたし。

まとめ

  • グローバルメソッド呼び出しを構文木に突っ込むにはNEW_FCALL()で
  • 引数リストはNEW_LISTで
  • Cの文字列から文字列リテラルを作るにはrb_external_str_new_cstr()で、メソッド名などのシンボル表記はrb_intern()で

コメント

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://emasaka.blog65.fc2.com/tb.php/774-f6ec0d54

 | HOME | 

Categories

Recent Entries

Recent Comments

Recent Trackbacks

Appendix

emasaka

emasaka

フリーター。
連絡先はこのへん

Monthly


FC2Ad