今までだいたい手書きでスキャナを書いてたんだけど、re2cというスキャナ生成ツールがあって、flexや手書きよりも高速なスキャナが生成できるという話なので試してみた。
Macでのre2cのインストール:
$ brew install re2c |
使い方:
$ re2c -o 出力ファイル名 入力ファイル名 |
re2cではflexなどと同様に正規表現でルールを書いて、それにマッチした時に行うアクションやトークンの種類などを定義すると、スキャナとなるCのソースを生成してくれる。
flexとの違いは、re2cではスキャンするバッファの管理を自分でする必要があるということだ。スキャナが判定するための入力文字が必要となった時、YYFILL
という関数(?)が呼び出されるので、バッファに必要な文字を読み込んで返す必要がある。しかしこれがどうにもややこしい。なにがわかりづらいかというと、いくつものポインタを操作しなくちゃならないのと、そもそもYYFILL
に渡される値が「追加で何文字必要」という数ではないことだと思う。
というわけで、試したソースはこちら:
|
マニュアルとかサンプルとか難しくてちゃんと読んでないけど、とりあえず動きます。
まず、re2cで生成されたスキャナはYYCURSOR
, YYLIMIT
, YYMARKER
という3つのポインタを必要とする。YYCURSOR
はスキャナが今見ているトークンのポイント(トークンの先頭ではない)、YYLIMIT
は読み込まれたバッファの上限、YYMARKER
はなんかわからないけどバックトラックの情報らしい(ルールによっては使われない場合もある)。
re2cではスキャンを進めるごとにポインタを進めていってしまい、トークンが受理された時にはそのトークンがどういう文字列だったかという情報は管理していない。必要な場合には利用する側で管理する必要がある(上記のプログラムではtoken_というメンバ変数で保持している)。
YYFILL
に渡ってくる値は「次に何文字必要か」じゃなくて、「現在見ているYYCURSOR
から何文字必要か」なので、新たに読み込む必要のある文字数は n - (YYLIMIT - YYCURSOR)
となる。
でその数が確保しているバッファ(buffer_
)に収まりきらない場合にはバッファを操作する必要がある。バッファの前半部分にはすでに消費済みの領域がある場合があるので、メモリの動的確保を最小限にするため、それを詰めて入るようだったら詰める(shiftBuffer
)。詰めても入らないようであればバッファ自体を拡張してやる(expandBuffer
)。で必要な文字数がバッファに収まるようになったので、YYLIMIT
から必要な文字数分だけ読み込んでやり、YYLIMIT
を末尾にあわせる。
バッファをシフトする場合は、トークンの開始位置(token_
)から上限(YYLIMIT
)までをバッファの先頭に移動する。バッファを拡張する場合は、新しいバッファを確保して古いバッファから必要な内容をコピーする。どちらも管理しているポインタを新しいバッファの位置を指すように調整する必要がある。
使用する側は定義したスキャン関数(scan
)を呼び出すと対応するトークンが返ってくる。トークンの値が必要な場合には、スキャナのアクションでtoken_
からYYCURSOR
までを使って、atoi
なりなんなりして、yylval
などという共用体を作って値を格納しておけばよろしいかと。
生成されたスキャナが高速化どうかの比較などは試してない。またre2cの導入は、Lispではリードマクロで読み込みの動作を変えることができてしまい、その際にスキャナ側に先読みされた文字をポート側にungetc
したりする必要が発生するのではないかということで、ためらっている。自作の言語を作る場合には使ってみようかな。