トップレベルは、規則部とユーザーコード部に分けられます。 ユーザーコード部はクラス定義の後に来なければいけません。
文法ファイルには、一部例外を除いて、ほとんどどこにでもコメントを 書くことができます。コメントは、Rubyの #.....(行末) スタイルと、 Cの /*......*/ スタイルを使うことができます。
規則部は以下のような形をしています。
class クラス名 [< スーパークラス] [演算子順位] [トークン宣言] [オプション] [トークンシンボル値おきかえ] [スタート規則] rule 文法記述 end"クラス名"はここで定義するパーサクラスの名前です。 これはそのままRubyのクラス名になります。
また M::C のように「::」を使った名前を使うと、クラス定義を その module にネストさせます。つまり class M::C ならば
module M class C < Racc::Parser いろいろ end endのようになります。
さらに Ruby と同じ構文でスーパークラスを指定できます。 ただしこの指定をするとパーサの動作に重大な影響を与えるので、 特に必要がない限り指定してはいけません。
racc で生成するパーサが理解できる文法を記述します。 文法は、予約語 rule と end の間に、以下のような書式で書きます。
トークン: トークンの並び アクション トークン: トークンの並び アクション | トークンの並び アクション | トークンの並び アクション (必要なだけ同じようにつづける)アクションは { } で囲みます。ただしまだ対応が不十分なので、 中ではヒアドキュメントが使えません。コメントは # タイプのみです。 (本当は'}'がはいってなければどれも大丈夫ですが、やらないほうが無難)。
左辺の値($$)は、オプションによって返し方がかわります。
まずデフォルトではローカル変数 result (そのデフォルト値は val[0])
が左辺値を表し、アクションブロックを抜けた時の result の値が左辺値になります。
または明示的に return で返した場合もこの値になります。
一方、options で no_result_var を指定すると左辺値は
アクションブロックの最後の文の値になります (Rubyと同じ)。
では以下に文法記述の全体の例をしめします。
rule goal: def ruls source { result = val } def : /* none */ { result = [] } | def startdesig { result[0] = val[1] } | def precrule # これは上の行の続きです。 { result[1] = val[1] } (略) end # endで規則部終了アクション内では、いくつか特別な意味をもった変数が使えます。そのような変数には、 以下のものがあります(この名前は将来変えられるようになるかもしれません)。 かっこの中は、yacc での表記です。
さらに、バージョン 0.10.3 からは埋めこみアクションをサポートしました。 埋めこみアクションはトークン列の途中の好きなところに記述することができます。 以下は埋めこみアクションの例です。
target: A B { puts 'test test' } C D { normal action };このように記述すると A B を検出した時点で puts が実行されます。 また、埋めこみアクションはそれ自体が値を持ちます。つまり、以下の例において
target: A { result = 1 } B { p val[1] };p val[1] は埋めこみアクションの値 1 を表示します。B の値ではありません。
意味的には、埋めこみアクションは空の規則を持つ非終端記号を追加することと 全く同じ働きをします。つまり、上の例は次のコードと全く同じ意味です。
target : A nonterm B { p val[1] }; nonterm : /* 空の規則 */ { result = 1 };
あるトークン上でシフト・還元衝突がおこったとき、そのトークンに 演算子優先順位が設定してあると、衝突を解消できる場合があります。 そのようなものとして特に有名なのは数式の演算子と if...else 構文です。
優先順位で解決できる文法は、うまく文法をくみかえてやれば 優先順位なしでも同じ効果を得ることができます。 しかしたいていの場合は、優先順位を設定して解決するほうが文法が簡単になります。
シフト・還元衝突がおこったとき、Racc はまずその規則に順位が設定されているか調べます。 規則の順位は、その規則で一番うしろにある終端トークンの優先順位です。たとえば、
target: TERM_A nonterm_a TERM_B nonterm_b ;のような規則の順位はTERM_Bの優先順位になります。もしTERM_Bに優先順位が設定されて いなかったら、優先順位で衝突を解決することはできないと判断し、 「Shift/Reduce conflict」を報告します。
演算子優先順位は、つぎのように書きます。
prechigh に近いほうが、順位の「高い」トークンです。上下をまるごとさかさまにして、
preclow...prechigh の順番に書くこともできます。
prechigh nonassoc PLUSPLUS left MULTI DEVIDE left PLUS MINUS right '=' preclowleft などは必ず行の最初の単語でなければいけません。
通常は、還元する規則の最後のトークンが順位を決めますが、 ある規則に限って順位をあげたいときがあります。yacc で言えば %prec です。 たとえば、符号反転のマイナスは引き算のマイナスより順位を高くしないといけません。
prechigh nonassoc UMINUS left '*' '/' left '+' '-' preclow (略) exp: exp '*' exp | exp '-' exp | '-' exp = UMINUS # 順位を上げるこのように記述すると、'-' exp の規則の順位が UMINUS の順位になります。 こうすることで符号反転の '-' は '*' よりも順位が高くなるので、 意図どおりになります。
トークン(終端記号)のつづりを間違えるというのはよくあることですが、
発見するのはなかなか難しいものです。1.1.5 からはトークンを明示的に
宣言することで、宣言にないトークン/宣言にだけあるトークンに対して
警告が出るようになりました。yacc の %token と似ていますが最大の違いは
racc では必須ではなく、しかもエラーにならず警告だけ、という点です。
トークン宣言は以下のように書きます。
token THIS_IS_TOKEN AND_IS_THIS ALSO_THIS_IS AGAIN_AND_AGAINトークンのリストを複数行にわたって書けることに注目してください。 また Racc では一般に「予約語」は行の先頭に来た時だけ予約語とみなされるので prechigh などもシンボルとして使えます。(ただし end だけはだめです)
racc のデフォルトのコマンドラインオプションをファイル中に 記述することができます。
options オプション オプション …現在使えるのは
トークンシンボルを表す値は、デフォルトでは
となっていますが、たとえば他の形式のスキャナがすでに存在する場合などは、 これにあわせなければならず、このままでは不便です。このような場合には、 convert 節を加えることで、トークンシンボルを表す値を変えることができます。 以下がその例です。
convert PLUS 'PlusClass' # --> PlusClass MIN 'MinusClass' # --> MinusClass endデフォルトでは、トークンシンボルPLUSに対してはトークンシンボル値は:PLUSですが、 上のような記述がある場合は、PlusClassになります。 変換後の値は false、nil 以外ならなんでも使えます。
変換後の値として文字列を使うときは、次のように引用符を重ねる必要があります。
convert PLUS '"plus"' # --> "plus" end
また、「'」を使っても生成された Ruby のコード上では「"」になるので注意してください。 バックスラッシュによるクオートは有効ですが、バックスラッシュは消えずにそのまま 残ります。これは仕様です。バグではありません。
PLUS '"plus\n"' # --> "plus\n" MIN "\"minus#{val}\"" # --> \"minus#{val}\"
パーサをつくるためには、どの規則が「最初の」規則か、ということを Racc におしえて やらなければいけません。それを明示的に書くのがスタート規則です。スタート規則は 次のように書きます。
start real_targetstart は行の最初にこなければいけません。
ユーザーコードは、パーサクラスが書きこまれるファイルに、 アクションの他にもコードを含めたい時に使います。このようなものは 書きこまれる場所に応じて三つ存在し、パーサクラスの定義の前が header、クラスの定義中(の冒頭)が inner、定義の後が footer です。 ユーザコードとして書いたものは全く手を加えずにそのまま連結されます。
ユーザーコード部の書式は以下の通りです。
---- ユーザーコードの識別子 ruby の文 ruby の文 ruby の文 ---- 次のユーザーコードの識別子 ruby の文 :行の先頭から四つ以上連続した「- (マイナス)」があるとユーザーコードと みなされます。識別子は一つの単語で、そのあとには「=」以外なら何を 書いてもかまいません。
また、次のような文で外部ファイルをユーザーコードとしてインクルード することもできます。
---- 識別子 = ファイル名 ファイル名 ファイル名 .....以下はこの記述を使った例です。
---- footer = init.rb err.rb run.rb print "this line is added, too\n"ここでは init.rb err.rb run.rb の三つを footer コードとして指定しています。 こうするとまず ---- のある行の下に書いたもの(printのある行など)が連結され、 その後にこれら三つのファイルの中身が連結されます(この順番は 1.2.5 から)。 さきほど ---- のある行の識別子のあとはなにを書いてもいいと言いましたが、 外部ファイルを指定する場合はなにも書くことができません。コメントもだめです。 正確にファイル名だけをならべて書いてください。
またほとんどの人には関係ないことですが、いちおうつけくわえます。 上に書いたような順番からすると、最終的な全体像は次のようになります。
# header # 外部 header class MyParser < Racc::Parser # inner # 外部 inner # パーサコア end # footer # 外部 footerここから、もし header と footer を使ってパーサをモジュールの中に ネストさせていてしかも外部 header がそのモジュールの外で定義 されるべきである場合、問題が起きます。具体的には以下のような場合です。
(mondai.y) class MyParser rule いろいろする end ---- header = mypar.head module MyMod ---- footer end (mypar.head) module MyMod class HelperClass いろいろする end endこの場合、mypar.head が header の module MyMod の後に来てしまうので HelperClass は ::MyMod::MyMod::HelperClass になってしまいます。 これはおそらく意図と違うはずです。
Copyright (c) 1999,2000 Minero Aoki <aamine@dp.u-netsurf.ne.jp>