クリスマスRubyの作り方

作り方と言うかやったことのメモ。結論から言うとparse.yを"here"で検索してヒアドキュメントのところを片っ端からコピペしてちょっと修正。
まずはparse.yのスキャナ部分をいろいろコピペして、XMLリテラルの開始<?識別子>と終了</識別子>を処理できるように変更。細かいのが分散してて面倒くさいので詳細は省略。ヒアドキュメントと変えたところだけ大まかに書くと

  • "<"のあとに"<"が来たらヒアドキュメントなのに対して、"<"のあとに"?"が来たらXMLリテラルということにした
  • リテラルの終了用のトークンは敢えて識別子の前後に""つけてタグっぽくした

の2点。
これで <?xml> とか書いたときスキャナから tHEREXML_BEG が飛んでくるようになったので次はパーサー。

/* parse.y */
...
/* 1. */
%type <node> singleton strings string string1 xstring xml regexp
...
/* 2. */
primary : literal
        | strings
        | xstring
        | xml
...
/* 3. */
xml     : tHEREXML_BEG string_contents tSTRING_END
/* 4. */
            {
            /*%%%*/
                NODE *node = $2;
                if (!node) node = NEW_STR(STR_NEW0());
                $$ = NEW_CALL(node, rb_intern("xml"), 0);
            /*%
                $$ = dispatch1(xstring_literal, $2);
            %*/
            }
        ;
  1. とりあえずxmlノードを追加しておく
  2. xmlノードは数値や文字列と同じような扱いにする
  3. <?識別子>、文字列、文字列終了、の並びをxmlノードとする
  4. xmlノードは文字列インスタンス(node)のxmlメソッドを呼び出すためのコードを吐くようにする。(NEW_CALLは、第一引数にレシーバ、第二引数にメソッドのID、第三引数にメソッドの引数(引数なしの場合は0)、を受け取ってメソッド呼び出し用のコードを吐くっぽい)

で、最後にStringクラスにインスタンスメソッドxmlを追加すればX'ruby完成。

# prelude.rb
.. snip ..
require 'rexml/document'
class String
  def xml
    REXML::Document.new(self)
  end
end

prelude.rbはなんだかよくわかってないけど、ここに書いておくと最終的にCの文字列になってrubyに組み込まれる模様。もしかしたらもっといい場所があるのかも。
さっそくmakeしてinstallして動作確認。

$ autoconf
$ configure --prefix=/Users/ando/xruby
$ make
$ make install
$ cd /Users/ando/xruby
$ bin/irb
irb(main):001:0> xml = <?xml>
irb(main):002:0* <greeting>hello</greeting>
irb(main):003:0/ </xml>
irb(main):004:0> xml.elements['/greeting'].text
=> "hello"

カンペキ。
いやー、Rubyにオレオレ文法を足すのは思いのほかたのしいすね。



注意C言語は10年以上前に新入社員研修でやったのがほぼ全てで正直よく知りません。そんなんでもコピペで意外となんとかなったよ、という例として御覧下さい。(なんとかなっていないかも・・・)