Racc の使い方

ひとの作ったパーサを使う

もうすでに文法ファイルがある場合です。この場合は単に racc コマンドが使えればよく、 文法ファイルの書きかたを覚えたりする必要はありません。

文法ファイルの名前が parse.y と仮定すると、コマンドラインから以下のように 打ちこむことで、パーサを含んだファイルが得られます。


$ racc parse.y

生成されるファイルはデフォルトでは "ファイル名.tab.rb" になります。 これは -o オプションで変更できます。

自分でパーサを作る

自分で racc の文法ファイルを記述する場合です。 racc は yacc を知っていることを前提にしていますので、もし知らないのなら 先に yacc を勉強しましょう。いきなり racc を使うのは不可能です。(これは断言できます)

概観

yacc は yyparse (関数)を生成しますが、racc は yyparse に相当する do_parse メソッドを持ったパーサクラスを生成します。 生成されるクラスは全て Racc::Parser の下位クラスで、規則ファイル中で指定します。 規則ファイルの文法の詳細は、文法リファレンスを 参照してください。

規則ファイル

以下には規則ファイルの文法の概略だけ書いておきます。
まずは、全体の概形です。


class MyParser

rule
  target : exp

  exp    : tok { print val[0] }
         | exp tok    # これはコメント
               { print val[1] }

  tok    : A
         | '+'
         | '-'       /* これもコメント */
end

Rubyスクリプトのように class でクラス名を指定し、rule ... end の間に文法を記述します。 トークンは Ruby のローカル変数/定数として有効なものが使えます。 yacc だと終端記号を %token で指定する必要がありますが、racc ではそのような 指定は必要ありません。左辺に来ないトークンはすべて終端記号とみなされます。

アクションは、yacc と同じように規則のあとに { と } で囲んで指定します。 当然ながら、アクションは Ruby の文で記述します。
yacc での $$ は Racc ではローカル変数 result で、$1,$2... は配列 val です。 result は val[0]($1) の値に初期化され、アクションを抜けたときの result の値が 左辺値になります。Racc ではアクション中の return はアクションから抜けるだけで、 パーズ自体は終わりません。
アクション中からパーズを終了するには、メソッド yyaccept を使ってください。

演算子の優先順位、スタートルールなどの yacc の一般的な機能も用意されています。 ただしこちらも少し文法が違います。

yacc では生成されたコードに直接転写されるコードがありました。Racc でも同じように、 ユーザ指定のコードが書けます。racc ではクラスを生成するので、クラス定義の前/中/後の 三個所があります。Racc ではそれを上から順番に header inner footer と呼んでいます。

ユーザが用意すべきコード

パースのエントリポイントとなるメソッドは二つあります。ひとつは do_parse で、こちらはトークンを パーサ#next_token から得ます。 もうひとつは yyparse で、こちらはスキャナから yield されることに よってトークンを得ます。ユーザ側ではこのどちらか(両方でもいいけど)を 起動する簡単なメソッドを inner に書いてください。 これらメソッドの引数など、詳しいことはリファレンスを見てください。
do_parse
yyparse

どちらのメソッドにも共通なのはトークンの形式です。必ずトークンシンボルと その値の二要素を持つ配列を返すようにします。またスキャンが終了して、 もう送るものがない場合は [false,なにか] を返してください。これは一回 返せば十分です(逆に yyparse を使う場合は二回以上 yield しないこと)。

パーザは別に文字列処理にだけ使われるものではありませんが、実際問題として、 パーザを作る場面ではたいてい文字列のスキャナとセットで使うことが多いでしょう。 Ruby ならスキャナくらい楽勝で作れますが、高速なスキャナとなると実は難しかったり します。そこで高速なスキャナを作成するためのライブラリも作っています。 詳しくは「スキャナを作る」の項を見てください。

さらに Racc には error トークンを使ったエラー回復機能もあります。
yacc の yyerror() は Racc では Parser#on_errorで、 エラーがおきたトークンとその値、値スタックの 3 引数をとります。 on_error はデフォルトでは例外 ParseError を発生します。
ユーザがアクション中でパースエラーを発見した場合は、 メソッド yyerror を呼べばパーサがエラー回復モードに入ります。 このときは on_error は呼ばれないので、なにか報告をしたい時は ユーザが明示的に on_error を呼んだりする必要があります。

パーザを生成する

これだけあればだいたい書けると思います。あとは、最初に示した方法でコンパイルし、 Ruby スクリプトを得ます。
うまくいけばいいのですが、大きいものだと最初からはうまくいかないでしょう。 racc に -g オプションをつけてコンパイルし、@yydebug を true にすると デバッグ用の出力が得られます。デバッグ出力はパーザの @racc_debug_out に 出力されます。(デフォルトは stderr。)
また、racc に -v オプションをつけると、状態遷移表を読みやすい形で出力したファイル (*.output)が得られます。どちらもデバッグの参考になるでしょう。

作ったパーザを配布する

Racc の生成したパーザは動作時にランタイムルーチンが必要になります。 具体的には parser.rb と cparse.so です。ただし cparse.so は単に パースを高速化するためにあり、必須ではありません。これらのファイルを 自分でセットアップするのはなかなか面倒です。Racc をユーザみんなに インストールしてもらうのも一つの手ですが、これでは不親切です。そこで Racc では回避策を用意しました。

racc に -E オプションをつけてコンパイルすると必要なものを全部結合した ファイルが得られます。これだとファイルはひとつだけなので扱いが楽です (この形式のパーザが複数あったとしてもクラスやメソッドが衝突することは ありません)。ただし cparse.so が使えませんので、必然的に動作は全て Ruby スクリプトレベルで行われ、速度は低下します。ただしこれにも例外が あって、配布先に既に Racc ランタイムがあるときは自動的にそちらが使われ ます。

おまけ:スキャナを作る

パーサを使うときは、たいてい文字列をトークンに切りわけてくれる スキャナが必要になります。しかし実は Ruby は文字列の最初からトークンに 切りわけていくという作業があまり得意ではありません。正確に言うと、 簡単にできるのですが、非常に大きいオーバーヘッドがかかります。
そのオーバーヘッドを回避しつつ、手軽にスキャナをつくれるように Racc とは別に strscan というパッケージを提供しています。 strscan はRAA筆者のホームページ から取れるので、試してみてください。


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