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 と呼んでいます。 0.10.1 までは prepare inner driver でしたがかっこわるいので変更しました。 ただし、互換性のため前のままでも使えるようになっています。

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

inner では、Racc の yylex に相当するメソッド next_token を定義する必要があります。 このメソッドは、トークンシンボルとその値の二要素を持つ配列を返すようにします。 この配列を Racc が破壊することはありません。また、どこか他のところで使うことも ありません。
スキャンが終了して、もう送るものがない場合は [false,なにか] を返してください。 [false,なにか] を一回受けとったらそれ以上 next_token は呼びだされません。

パーザは別に文字列処理にだけ使われるものではありませんが、実際問題として、 パーザを作る場面ではたいてい文字列のスキャナとセットで使うことが多いでしょう。 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 それと amstd/ のスクリプトが少しです。 cparse.so は高速にパーズするための拡張モジュールで、amstd/ はユーティリティ、 parser.rb はそれらすべてのフロントエンドです。
これらのファイルを自分でセットアップするのはなかなか面倒です。 Racc をユーザみんなにインストールしてもらうのも一つの手ですが、 これではユーザにとって不親切です。 そこで、Racc では回避策をふたつ用意しました。

ランタイム配布の自動化

ひとつめは、ランタイムとそのインストーラをまるごと自分のソフトのパッケージに 含めてしまうことです。方法は簡単で、Racc パッケージのトップディレクトリに 入って次のように起動します。


ruby rtpack.rb TARGET/

これで、ディレクトリ TARGET/ 以下に必要なファイルがインストールされます。 このとき、一度 TARGET/ 以下をクリアするので気をつけてください。

また、インストール時には、TARGET/ に移動して ruby setup.rb を実行すれば ランタイムをインストールできます。

この方法だと、速度を保ったまま、パーザを配布できるのが利点です。

-E オブションを使う

racc に -E オプションをつけてコンパイルすると、必要なものを全部結合した ファイルが得られます。これだとファイルはひとつだけなので扱いが楽です (この形式のパーザが複数あったとしてもクラスやメソッドが衝突することは ありません)。

この方法だと、特別なことはなにもする必要がないので、非常に扱いが楽です。 ただし、cparse.so が使えませんので、必然的に動作は Ruby スクリプトのみになり 速度は低下します。
ただし、これも例外があって、配布先に既に Racc ランタイムがあるときは 自動的にそちらが使われます。

おまけ:スキャナを作る

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


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