Racc文法


updated for v0.9.1

これは暫定仕様です。未来には確実に変更されるので気をつけてください。

全体の目次


全体の構造

0.9 になってまた大幅に文法がかわりました。 最も変更が大きいのはアクションです。ダサダサのピリオド方式をやめて { と } で囲むように しました。詳しくは「文法記述」を参照してください。

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

コメント

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

規則部

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

    class クラス名
      文法記述
      [演算子順位]
      [スタート規則]
      [トークンシンボル値定義]
    end
"クラス名"はここで定義するパーサクラスの名前です。これはそのままRubyのクラス名に なるので、大文字ではじまっている必要があります。

文法の記述

racc で生成するパーサが理解できる文法を記述します。 書式は次のようなものです。

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

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

書式は yacc とほぼ同じです。ただし、セミコロンは必須です。アクションがあっても 省略してはいけません。

アクションはイテレータのように、{ } で囲みます。ただしまだ対応が不十分なので、 中では % 文字列やヒアドキュメントは使えません。コメントは # タイプのみ。 正規表現は // タイプのみです。
本当は'}'がはいってなければどれも大丈夫ですが…やらないほうが無難です。

また、アクションは省略してもかまいません。その場合は空文字列が使われます。

アクションからの返り値が左辺の値になります。通常どおりreturnしても 大丈夫ですし、resultに返したい値を代入しておけば、自動的にそれが返り値と なります(その場合はreturnしてはいけません。ただしreturn(result)ならOKです)。

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

rule
  goal: definition ruls source { result = val } ;  # セミコロンを忘れずに

   definition: /* 空配列 */   { result = [] }
     | definition startdesig  { result[0] = val[1] }  # '|'で継続できる
     | definition
           precrule   # これは上の行の続きです。
       {
         result[1] = val[1]
       } ;
(略)
end  # endで規則部終了

アクション内では、いくつか特別な意味をもった変数が使えます。そのような変数には、 以下のものがあります(将来この名前は変えられるようになる予定です)。かっこの中は、 yacc での表記です。

result ($$)
左辺の値。アクション実行のまえには必ずresult = val[0]が実行されます。
val ($1,$2,$3…)
右辺の値の配列。
tok (該当なし)
右辺のトークンシンボルの配列。その内容はデフォルトでは を期待しています。このあと説明するtoken節をつかうと、この値を変更できます。
vstack ($0,$-1,$-2…)
値スタック。
sstack (該当なし)
トークンシンボルスタック。エラー処理のときつかえるかも。
__state__ (該当なし)
LALRステータススタック。LALR状態のID(整数)がはいっています。
LALR状態のIDはracc.rbに-vオプションをつけてコンパイルしたとき出力される 「.output」ファイルに、「state X」のように記入されているものです。 LALRパーサの動作を理解しているひと以外はさわらないほうが無難です。

また、規則中では特別なトークンシンボル「$end」がつかえます。
このシンボルが最後にある規則では、その規則を還元したあとでちょうど入力が終了すれば パース終了になります。Rubyレベルでは、「false」が相当します。

演算子優先順位

あるトークン上でシフト・還元衝突がおこったとき、そのトークンに演算子優先順位が設定して あると、衝突を解消できる場合があります。そのようなものとして特に有名なのは数式の 演算子と (だから演算子優先順位っていうのか (^^;; ) if...else構文です。

優先順位で解決できる文法は、うまく文法をくみかえてやれば、必ず優先順位なしでも同じ 効果を得られます。しかしたいていの場合、優先順位を設定するほうが、文法が簡単になります。

シフト・還元衝突がおこったとき、Raccはまずその規則に順位が設定されているか調べます。 規則の順位は、その規則で一番うしろにある終端トークンの優先順位です。たとえば、

      target: TERM_A non_terma TERM_B non_termc ;
のような規則の順位はTERM_Bの優先順位になります。もしTERM_Bに優先順位が設定されて いなかったら、優先順位で衝突を解決することはできないと判断し、「衝突がある」と 報告します。

演算子優先順位は、つぎのように書きます。
prechighに近いほうが、順位の「高い」トークンです。上下をまるごとさかさまにして、 preclow...prechighの順番に書くこともできます。

    prechigh
      nonassoc '++'
      left     '*' '/'
      left     '+' '-'
      right    '='
    preclow

ここで、rightという予約語は、そのトークンが「右結合」であるということを表しています。 右結合とは、「もし同じ順位のトークンが並んでいたら、右の演算子を先に使う」ということ です。たとえば、上のように順位が設定されているとすると、x = y = z は (x = (y = z)) と 書いたように扱われるということです。同様に、leftは「左結合」です。1 + 2 + 3 は、 ((1 + 2) + 3)です。

nonassocは、「非結合」を表します。x++ ++ のような表現はありえないということを示し、 実際にこのようなトークンの並びがきたときには、パースエラーをだすようになります。

また、通常は還元する規則の、最後のトークンが順位を決めますが、ある規則に限って 順位をあげたいときがあります。yaccで言えば%precです。たとえば、符号反転のマイナスは 引き算のマイナスより順位を高くしないといけません。

    prechigh
      nonassoc UMINUS
      left '*' '/'
      left '+' '-'
    preclow
       :
       :

    exp: exp '*' exp
       | exp '-' exp
       | '-' exp   =   UMINUS    # 順位を上げる

このように記述すると、'-' exp の規則の順位がUMINUSの順位になります。こうすることで、 符号反転の'-'は'*'よりも順位が高くなるので、正常に処理されます。

スタート規則

パーサをつくるためには、どの規則が「最初の」規則か、ということをRaccにおしえて やらなければいけません。それを明示的に書くのがスタート規則です。スタート規則は 次のように書きます。

      start real_target

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

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

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

となっていますが、たとえば他の形式のスキャナがすでに存在する場合などは、 これにあわせなければならず、このままでは不便です。このような場合には、 token節を加えることで、トークンシンボルを表す値を変えることができます。 以下がその例です。

    token
      PLUS 'PlusClass'      # --> PlusClass
      MIN  'MinusClass'     # --> MinusClass
    end

デフォルトでは、トークンシンボルPLUSに対してはトークンシンボル値は:PLUSですが、 上のような記述がある場合は、PlusClassになります。変換後の値はほぼ任意のものを 使えますが、false と nil だけは例外です。

変換後の値として文字列を使うときは、次のように引用符を重ねる必要があることに 注意してください。

    token
      PLUS '"plus"'       # --> "plus"
    end

ちなみに、引用符には「'」だけでなく「"」も使えます。 ただし、どちらを使っても生成された Ruby のコード上では「"」になります。

また、バックスラッシュによるクオートは有効ですが、バックスラッシュは消えずにそのまま 残りますので注意してください。これは「仕様」です。バグではありません。

      PLUS '"plus\n"'          # --> "plus\n"
      MIN  "\"minus#{val}\""   # --> \"minus#{val}\"

ユーザーコード部

ユーザーコード部には、パーサクラスの内部または外部で使用するRubyのコードを書きます。 ここに書いたコードは、Raccオブジェクトのcodeプロパティに「名前=>ソースコード」の ような連想配列(Hash)として格納されます。それをどう使うかは、ドライバによります。

たとえば、添付してあるracc.rbでは、driver prepare inner の 3つを特別な名前として使い、それぞれを生成するファイルの特定の場所に転写しています。

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

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

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

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

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

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

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

---- driver = init.rb err.rb run.rb

print "this line is added, too\n"

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


Copyright(c) 1998-1999 Minero Aoki.