2009-01-30 03:58:14 +0900 (155d); rev 7
Ripper のトークン指向インターフェイスを 利用したプログラムの作例を示します。
第一段階として、Ruby スクリプトを HTML の <span> でタグ付けするプログラムを示します。 すべてのトークンをタグ付けしても構いませんが、 今回はコメントと文字列だけを <span> でくくることにしました。
ではまずプログラムを見てください。
require 'ripper'
require 'cgi'
class Ruby2HTML < Ripper::Filter
def on_default(event, tok, f)
f << CGI.escapeHTML(tok)
end
def on_comment(tok, f)
f << %Q[<span class="comment">#{CGI.escapeHTML(tok)}</span>]
end
def on_tstring_beg(tok, f)
f << %Q[<span class="string">#{CGI.escapeHTML(tok)}]
end
def on_tstring_end(tok, f)
f << %Q[#{CGI.escapeHTML(tok)}</span>]
end
end
if $0 == __FILE__
Ruby2HTML.new(ARGF).parse($stdout)
end
今回の主役は Ripper::Filter クラスです。 Ripper::Filter クラスはイベントドリブン インターフェイスを持つクラスなので、 まず Ripper::Filter を継承したクラスを作り、 このクラスに適当にイベントハンドラを定義していきます。
イベントハンドラは「on_XXX」という名前のメソッドです。 「XXX」には "comment" (コメント) や "tstring_beg" (文字列開始) "ident" (識別子) などが入ります。 イベント名のリストは Ripper::SCANNER_EVENTS に格納されています。
今回使うのは on_comment と on_tstring_beg、それに on_tstring_end です。 この三つのメソッドをオーバーライドし、タグ付けを行います。
イベントハンドラメソッドには二つの引数があります。 第一引数は問題のトークンで、第二引数はユーザデータです。 この第二引数は Enumerable#inject の引数と似ていて、 イベントハンドラの値が次のイベントハンドラの第二引数へと、 次々に渡されます。 一番最初のハンドラの引数は Ripper::Filter#parse の引数です。
Ripper::Filter#parse(data)
↓
result = Ripper::Filter#on_XXX(tok, data)
|
+----------------------------------+
↓
result = Ripper::Filter#on_XXX(tok, data)
|
+----------------------------------+
↓
result = Ripper::Filter#on_XXX(tok, data)
|
+----------------------------------+
↓
result = Ripper::Filter#on_XXX(tok, data)
↓
ちなみに最後のハンドラの値は Ripper::Filter#parse 全体の値になります。
それから、Ripper::Filter で便利なのが on_default イベントです。 このメソッドは、明示的に拾ったイベント以外の すべてのイベントをまとめて渡してくれます。 つまりこの場合ならば、on_comment と on_tstring_beg/end だけが 専用メソッドに渡り、残りのイベントはすべて on_default に渡ります。 ですから、特別な処理をしたいイベントだけ明示的に拾い、 あとは on_default で拾えば、すべてのイベントに対応できるわけです。
Ripper::Filter に代表されるトークン指向インターフェイスの 挙動を調べるときに役立つメソッドを紹介しておきます。
使用例
require 'ripper'
p Ripper.tokenize("def m(a) nil end")
#=> ["def", " ", "m", "(", "a", ")", " ", "nil", " ", "end"]
Ripper.tokenize は最も単純なトークンストリーム API です。 特に説明することはありません。
Ripper.tokenize は空白やコメントも含め、 元の文字列にある文字は 1 バイトも残さずに分割しますが、 そのごく僅かな例外として、__END__ 以降の文字列は黙って捨てられます。 これは現在のところ仕様と考えてください。
使用例
require 'ripper'
require 'pp'
pp Ripper.lex("def m(a) nil end")
#=> [[[1, 0], :on_kw, "def"],
[[1, 3], :on_sp, " "],
[[1, 4], :on_ident, "m"],
[[1, 5], :on_lparen, "("],
[[1, 6], :on_ident, "a"],
[[1, 7], :on_rparen, ")"],
[[1, 8], :on_sp, " "],
[[1, 9], :on_kw, "nil"],
[[1, 12], :on_sp, " "],
[[1, 13], :on_kw, "end"]]
Ripper.lex は分割したトークンを詳しい情報とともに返します。 返り値の配列の要素は 3 要素の配列 (概念的にはタプル) です。 その内訳を以下に示します。
Related Pages: Ripper RipperTutorial
system revision 1.162