パーサを使うときは、たいてい文字列をトークンに切りわけてくれる
スキャナが必要になります。しかし実は Ruby は文字列の最初からトークンに
切りわけていくという作業があまり得意ではありません。正確に言うと、
簡単にできるのですが、非常に大きいオーバーヘッドがかかります。
racc に添付されている scanner.rb と strscan はこのオーバーヘッドを
回避しつつ、手軽にスキャナをつくるためのライブラリです。
まあ、ちょっとしか手軽にならないんですけど…
以下に、scanner.rb を利用する簡単な例を示します。
require 'racc/scanner' class MyScanner < Racc::Scanner def scan while true do return [false, false] if @scan.empty? @scan.skip /\A\s+/ if tmp = @scan.scan( /\A\w+/ ) then return [:WORD, tmp] end end end endこのクラスは、文字列から単語を切りだすスキャナです。 小さいクラスですが、必要な要素はつまっています。 まず、利用するときには require 'racc/scanner' すること。 自分のスキャナクラスは Racc::Scanner クラスから継承すること。 @scan がなにやらカギらしいこと。などです。
@scan に入っているのは StringScanner のインスタンスで、拡張モジュールです。
実はこのクラスがこのライブラリの核で、スキャンはこのライブラリを中心にして
行われます。
Racc::Scanner は文字列を引数にして生成します。Racc::Scanner はそれを使って
StringScanner を準備し、@scan にセットしてくれます。
StringScanner は文字列と「どこまでスキャンしたか」を示すポインタがセットに
なったようなオブジェクトで、正規表現とのマッチをおこなってそのポインタを
進めていくことでスキャンを行います。
StringScanner の重要なメソッドは 3 つです。
scan は最も重要なメソッドで、与えられた正規表現がマッチする部分を切り出して
返します。skip はマッチした部分の先にポインタを進めるだけで、真偽値を返します。
empty? はポインタが最後までいったかどうかを真偽値で返すメソッドです。
ちなみに、マッチに使う正規表現は必ず文字列の先頭からマッチしなければいけません (ようは、 /\A…/ じゃないとだめってことです)。もし先頭より後でマッチした場合は、 マッチしたところまで全部がマッチしたものとみなされてしまいます。例えば次のような場合は
s = StringScanner.new( "word word" ) ret = s.scan( /\s+/ )何事もなかったかのように ret には "word " が返り、ポインタはその先の w に セットされます。先頭からマッチしなかった場合、それを知る手段はありません。
あとは、以下のリファレンスなどを見て考えてください(いいかげんだな)。
dupしないと生成が高速になりますが、その場合もとの文字列からとりだしたポインタを そのまま使うので、もしスキャン中にその文字列がガーベージコレクトされると落ちます。 また、他のスレッドがその文字列を触れるときも危険です。StrScannerでは最初に取得した ポインタと長さを最後まで使うので、もし文字列が短かく変更されたり、realloc がおこったり すると落ちます。
Copyright (c) 1999,2000 Minero Aoki <aamine@dp.u-netsurf.ne.jp>