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


文法に関する前バージョンとの非互換

全体の構造

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

コメント

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

規則部

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


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

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

また M::C のように「::」を使った名前を使うと、クラス定義を モジュール M の中にネストさせます。つまり class M::C ならば

module M
  class C < Racc::Parser
    いろいろ
  end
end
のように出力します。

さらに、Ruby と同じ構文でスーパークラスを指定できます。 ただしこの指定をするとパーサの動作に重大な影響を与えるので、 特に必要がない限り指定してはいけません。これは将来の拡張の ために用意したもので、現在指定する必然性はあまりありません。

文法の記述

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

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

トークン: トークンの並び アクション
        | トークンの並び アクション
        | トークンの並び アクション
             (必要なだけ同じようにつづける)
アクションは { } で囲みます。アクションでは Ruby の文はほとんど 使えますが、一部だけは非対応です。対応していないものは以下のとおり。 このあたりに関しては完全な対応はまず無理です。あきらめてください。

左辺の値($$)は、オプションによって返し方がかわります。まずデフォルトでは ローカル変数 result (そのデフォルト値は val[0])が 左辺値を表し、アクション ブロックを抜けた時の result の値が左辺値になります。または明示的に return で返した場合もこの値になります。一方、options で no_result_var を指定した 場合、左辺値はアクションブロックの最後の文の値になります (Ruby のメソッドと 同じ)。

どちらの場合でもアクションは省略でき、省略した場合の左辺値は常に val[0] です。

以下に文法記述の全体の例をしめします。

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)
値スタック。Racc コアが使っているオブジェクトがそのまま渡されます。 この変数の意味がわかる人以外は絶対に変更してはいけません。

またアクションの特別な形式に、埋めこみアクションというものがあります。 これはトークン列の途中の好きなところに記述することができます。 以下に埋めこみアクションの例を示します。

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
  nonassoc PLUSPLUS
  left     MULTI DEVIDE
  left     PLUS MINUS
  right    '='
preclow
prechigh に近い行にあるほど優先順位の高いトークンです。上下をまるごと さかさまにして preclow...prechigh の順番に書くこともできます。left などは必ず行の最初になければいけません。

left right nonassoc はそれぞれ「結合性」を表します。結合性によって、 同じ順位の演算子の規則が衝突した場合にシフト還元のどちらをとるかが 決まります。たとえば

a + b + c
(a + b) + c
になるのが左結合(left)です。四則演算は普通これです。一方
a + (b + c)
になるのが右結合(right)です。代入のクオートは普通 right です。 またこのように演算子が重なるのはエラーである場合、非結合(nonassoc)です。 C 言語の ++ や単項のマイナスなどがこれにあたります。

ところで、説明したとおり通常は還元する規則の最後のトークンが順位を 決めるのですが、ある規則に限ってそのトークンとは違う順位にしたいことも あります。例えば符号反転のマイナスは引き算のマイナスより順位を高く しないといけません。このような場合 yacc では %prec を使います。 racc ではイコール記号を使って同じことをできます。

prechigh
  nonassoc UMINUS
  left '*' '/'
  left '+' '-'
preclow
(略)
exp: exp '*' exp
   | exp '-' exp
   | '-' exp     = UMINUS    # ここだけ順位を上げる
このように記述すると、'-' exp の規則の順位が UMINUS の順位になります。 こうすることで符号反転の '-' は '*' よりも順位が高くなるので、 意図どおりになります。

トークン宣言

トークン(終端記号)のつづりを間違えるというのはよくあることですが、 発見するのはなかなか難しいものです。1.1.5 からはトークンを明示的に 宣言することで、宣言にないトークン / 宣言にだけあるトークンに対して 警告が出るようになりました。yacc の %token と似ていますが最大の違いは racc では必須ではなく、しかもエラーにならず警告だけ、という点です。

トークン宣言は以下のように書きます。

token A B C D
        E F G H
トークンのリストを複数行にわたって書けることに注目してください。 racc では一般に「予約語」は行の先頭に来た時だけ予約語とみなされるので prechigh などもシンボルとして使えます。ただし深淵な理由から end だけは どうやっても予約語になってしまいます。

オプション

racc のコマンドラインオプションの一部をファイル中にデフォルト値 として記述することができます。

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

expect

実用になるパーサはたいてい無害な shift/reduce conflict を含みます。 しかし文法ファイルを書いた本人はそれを知っているからいいですが、 ユーザが文法ファイルを処理した時に「conflict」と表示されたら 不安に思うでしょう。そのような場合、以下のように書いておくと shift/reduce conflict のメッセージを抑制できます。

expect 3
この場合 shift/reduce conflict はぴったり三つでなければいけません。 三つでない場合はやはり表示が出ます (ゼロでも出ます)。 また reduce/reduce conflict の表示は抑制できません。

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

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

となっていますが、たとえば他の形式のスキャナがすでに存在する場合などは、 これにあわせなければならず、このままでは不便です。このような場合には、 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 の規則をスタート規則として使います。 省略した場合は、ファイルの最初の規則がスタート規則になります。普通は 最初の規則を一番上にかくほうが書きやすく、わかりやすくなりますから、 この記法はあまりつかう必要はないでしょう。

ユーザーコード部

ユーザーコードは、パーサクラスが書きこまれるファイルに、 アクションの他にもコードを含めたい時に使います。このようなものは 書きこまれる場所に応じて三つ存在し、パーサクラスの定義の前が 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-2001 Minero Aoki <aamine@cd.xdsl.ne.jp>