もうすでに文法ファイルがある場合です。この場合は単に 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 | '+' | '-' /* これもコメント */ endRubyスクリプトのように 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 を実行すれば ランタイムをインストールできます。
この方法だと、速度を保ったまま、パーザを配布できるのが利点です。
racc に -E オプションをつけてコンパイルすると、必要なものを全部結合した ファイルが得られます。これだとファイルはひとつだけなので扱いが楽です (この形式のパーザが複数あったとしてもクラスやメソッドが衝突することは ありません)。
この方法だと、特別なことはなにもする必要がないので、非常に扱いが楽です。
ただし、cparse.so が使えませんので、必然的に動作は Ruby スクリプトのみになり
速度は低下します。
ただし、これも例外があって、配布先に既に Racc ランタイムがあるときは
自動的にそちらが使われます。
パーサを使うときは、たいてい文字列をトークンに切りわけてくれる
スキャナが必要になります。しかし実は 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? はポインタが最後までいったかどうかを真偽値で返すメソッドです。
他にもいろいろメソッドがあるのですが、ここでは紹介しません。
StringScanner の詳細は、strscan 本体のパッケージについているドキュメントを
参照してください。
Copyright (c) 1999,2000 Minero Aoki <aamine@dp.u-netsurf.ne.jp>