規則ファイル文法リファレンス


全体の構造

トップレベルは、規則部とユーザーコード部に分けられます。 ユーザーコード部はクラス定義の後に来なければいけません。

コメント

文法ファイルには、一部例外を除いて、ほとんどどこにでもコメントを 書くことができます。コメントは、Rubyの #.....(行末) スタイルと、 Cの /*......*/ スタイルを使うことができます。

規則部

規則部は以下のような形をしています。


    class クラス名
      [演算子順位]
      [トークン宣言]
      [オプション]
      [トークンシンボル値おきかえ]
      [スタート規則]
    rule
      文法記述
    end

"クラス名"はここで定義するパーサクラスの名前です。 これはそのままRubyのクラス名になります。

文法の記述

racc で生成するパーサが理解できる文法を記述します。 文法は、予約語 rule と end の間に、以下のような書式で書きます。


      トークン: トークンの並び アクション

      トークン: トークンの並び アクション
              | トークンの並び アクション
              | トークンの並び アクション
                  (必要なだけ同じようにつづける)

アクションは { } で囲みます。ただしまだ対応が不十分なので、 中ではヒアドキュメントが使えません。コメントは # タイプのみです。 (本当は'}'がはいってなければどれも大丈夫ですが、やらないほうが無難)。
また、アクションは省略できます。 省略すると、デフォルトの val[0] が返り値になります。 ちなみに、省略するほうがやや高速です。

左辺の値($$)は、オプションによって返し方がかわります。 まずデフォルトではローカル変数 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 での表記です。
result ($$)
左辺の値。初期値は val[0] です。
val ($1,$2,$3…)
右辺の値の配列。Rubyの配列なので当然インデックスはゼロから始まります。 この配列は毎回作られるので変更しても構いません。
_values (...,$-2,$-1,$0)
値スタック。 この変数の意味がわかる人以外は絶対に変更してはいけません。

さらに、バージョン 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    '='
    preclow

left などは必ず行の最初の単語でなければいけません。
left right nonassoc はそれぞれ「右結合」「左結合」「結合しない」をあらわします。

通常は、還元する規則の最後のトークンが順位を決めますが、 ある規則に限って順位をあげたいときがあります。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 オプション オプション …

現在使えるのは
omit_action_call
空のアクション呼び出しを省略するか
result_var
変数 result を使うか
です。それぞれ no_ を頭につけることで意味を反転できます。

トークンシンボル値の変更

トークンシンボルを表す値は、デフォルトでは

となっていますが、たとえば他の形式のスキャナがすでに存在する場合などは、 これにあわせなければならず、このままでは不便です。このような場合には、 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_target

start は行の最初にこなければいけません。
このように書くと、最初にある real_target の規則をスタート規則として使います。 省略した場合は、最初の規則がスタート規則になります。 普通は、最初の規則を一番上にかくほうが書きやすく、わかりやすくなりますから、 この記法はあまりつかう必要はないでしょう。

ユーザーコード部

ユーザーコード部には、パーサクラスの内部または外部で使用するRubyのコードを書きます。 racc コマンドでは、header inner footer の 3つをコードの名前として使い、それぞれを生成するファイルの特定の場所に転写しています。

ユーザーコード部の書式は以下の通りです。


---- ユーザーコードの識別子
  rubyの文
  rubyの文
  rubyの文

---- 次のユーザーコードの識別子
  rubyの文
     :

行の先頭から4つ以上連続した「-」があるとユーザーコードとみなされます。 識別子は一つの単語で、そのあとには「=」以外なにを書いてもかまいません。

また、次のような文で外部ファイルをユーザーコードとしてインクルードすることもできます。


---- 識別子 = ファイル名 ファイル名 ファイル名 .....

このように書くと、すべてのファイルの内容をその順番につなげたものが そのユーザーコードになります。次はその例です。

---- footer = init.rb err.rb run.rb

print "this line is added, too\n"

ここでは init.rb err.rb run.rb のみっつを footer コードとして指定しています。 さらに、---- のある行の下に書いたもの(printのある行など)がつづけて加えられます。 さきほど ---- のある行の識別子のあとはなにを書いてもいいと言いましたが、 ファイルを記述する場合はなにも書くことができません。コメントなどもだめです。 正確にファイル名だけをならべて書いてください。 この制限はもちろん「対応するのがめんどくさかったから」です ^^;;;

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