スキャナ作成キットの使いかた


パーサを使うときは、たいてい文字列をトークンに切りわけてくれる スキャナが必要になります。しかし実は 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 に セットされます。先頭からマッチしなかった場合、それを知る手段はありません。

あとは、以下のリファレンスなどを見て考えてください(いいかげんだな)。


StringScannerクラスリファレンス

クラスメソッド

new( str, dup_p = true )
新しいStrScannerオブジェクトを生成します。strはスキャンする文字列、dup_pは 文字列を複製して使うかどうかを真偽値で指定します。

dupしないと生成が高速になりますが、その場合もとの文字列からとりだしたポインタを そのまま使うので、もしスキャン中にその文字列がガーベージコレクトされると落ちます。 また、他のスレッドがその文字列を触れるときも危険です。StrScannerでは最初に取得した ポインタと長さを最後まで使うので、もし文字列が短かく変更されたり、realloc がおこったり すると落ちます。

メソッド

scan( regex )
正規表現regexとマッチを行って、マッチしたらスキャンポインタを進めたうえで その部分の文字列を返し、マッチしなかったらnilを返します。
skip( regex )
正規表現regexとマッチを行って、マッチしたらスキャンポインタを進めたうえで マッチした文字列の長さを返し、マッチしなかったらnilを返します。
match?( regex )
正規表現regexとマッチを行って、マッチしたらスキャンポインタは進めずに マッチした文字列の長さを返し、マッチしなかったらnilを返します。
getch
スキャンポインタから一文字を文字列として返し、ポインタを進めます。
empty?
ポインタの後ろにもう文字列がないとき真。
rest?
ポインタの後ろにまだ文字列があるとき真。
rest
ポインタ以降の文字列を返します。
restsize
ポインタ以降の文字列の長さを返します。
matched?
前回のマッチが成功したかどうかを真偽値で返します。
matched
前回マッチした部分の文字列を返します。
matchedsize
前回マッチした部分の文字列の長さを返します。
self[n]
前回マッチした正規表現の n 番目のかっこに対応する部分文字列を返します。 0 はマッチした部分全体です。
string
スキャン対象にしている文字列を返します。
reset
ポインタを最初に戻し、マッチ記録を捨てます。
clear
ポインタを最後まで進め、マッチ記録を捨てます。
unscan
ポインタをスキャン一回分もとに戻します。一回分以上は戻せません。

Copyright (c) 1999,2000 Minero Aoki <aamine@dp.u-netsurf.ne.jp>