Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -0,0 +1,99 @@
<h1>Raccコマンドリファレンス</h1>
<p>
racc [-o<var>filename</var>] [--output-file=<var>filename</var>]
[-e<var>rubypath</var>] [--executable=<var>rubypath</var>]
[-v] [--verbose]
[-O<var>filename</var>] [--log-file=<var>filename</var>]
[-g] [--debug]
[-E] [--embedded]
[-F] [--frozen]
[-l] [--no-line-convert]
[-c] [--line-convert-all]
[-a] [--no-omit-actions]
[-C] [--check-only]
[-S] [--output-status]
[--version] [--copyright] [--help] <var>grammarfile</var>
</p>
<dl>
<dt><var>filename</var>
<dd>
Raccの文法ファイルを指定します。拡張子には特に制限はありません。
</dd>
<dt>-o<var>outfile</var>, --output-file=<var>outfile</var>
<dd>
作成するクラスをかきこむファイル名を指定します。デフォルトは<filename>.tab.rbです。
</dd>
<dt>-O<var>filename</var>, --log-file=<var>filename</var>
<dd>
-v オプションをつけた時に生成するログファイルの名前を
<var>filename</var> に変更します。
デフォルトは <var>filename</var>.output です。
</dd>
<dt>-e<var>rubypath</var>, --executable=<var>rubypath</var>
<dd>
実行可能ファイルを生成します。<var>rubypath</var>は Ruby 本体のパスです。
<var>rubypath</var>を単に 'ruby' にした時には Racc が動作している
Ruby のパスを使用します。
</dd>
<dt>-v, --verbose
<dd>
ファイル "filename".output に詳細な解析情報を出力します。
</dd>
<dt>-g, --debug
<dd>
出力するコードにデバッグ用コードを加えます。-g をつけて生成したパーサで
@yydebug を true にセットすると、デバッグ用のコードが出力されます。<br>
-g をつけるだけでは何もおこりませんので注意してください。
</dd>
<dt>-E, --embedded
<dd>
ランタイムルーチンをすべて含んだコードを生成します。
つまり、このオプションをつけて生成したコードは Ruby さえあれば動きます。
</dd>
<dt>-F, --frozen
<dd>
Add frozen_string_literals: true.
</dd>
<dt>-C, --check-only
<dd>
(文法ファイルの) 文法のチェックだけをして終了します。
</dd>
<dt>-S, --output-status
<dd>
進行状況を逐一報告します。
</dd>
<dt>-l, --no-line-convert
<dd>
<p>
Ruby では例外が発生した時のファイル名や行番号を表示してくれますが、
Racc の生成したパーサは、デフォルトではこの場合のファイル名・行番号を
文法ファイルでのものに置きかえます。このフラグはその機能をオフにします。
</p>
<p>
ruby 1.4.3 以前のバージョンではバグのために定数の参照に失敗する
場合があるので、定数参照に関してなにかおかしいことがおこったらこのフラグを
試してみてください。
</p>
</dd>
<dt>-c, --line-convert-all
<dd>
アクションと inner に加え header footer の行番号も変換します。
header と footer がつながっているような場合には使わないでください。
<dt>-a, --no-omit-actions
<dd>
全てのアクションに対応するメソッド定義と呼び出しを行います。
例えアクションが省略されていても空のメソッドを生成します。
</dd>
<dt>--version
<dd>
Racc のバージョンを出力して終了します。
</dd>
<dt>--copyright
<dd>
著作権表示を出力して終了します。
<dt>--help
<dd>
オプションの簡単な説明を出力して終了します。
</dd>
</dl>
+36
View File
@@ -0,0 +1,36 @@
= パーサのデバッグ
ここでは、Racc を使っていくうえで遭遇しそうな問題について書きます。
== 文法ファイルがパースエラーになる
エラーメッセージに出ている行番号のあたりを見て間違いを
探してください。ブロックを閉じる行でエラーになる場合は、
どこかで開き括弧などを増やしてしまっている可能性が高いです。
== なんたら conflict って言われた
一番ありがちで一番面倒な問題は衝突 (conflict) でしょう。
文法中に衝突があると、racc はコンパイル後に
「5 shift/reduce conflict」のようなメッセージを表示します。
-v をつけると出力される .output ファイルからはさらに詳しい情報が得られます。
それをどう使うか、とかそういうことに関しては、それなりの本を読んでください。
とてもここに書けるような単純な話ではありません。
当然ながら『Ruby を 256 倍使うための本 無道編』(青木峰郎著)がお勧めです。
== パーサは問題なく生成できたけど予想どおりに動かない
racc に -g オプションをつけてパーサを出力すると、デバッグ用のコードが
付加されます。ここで、パーサクラスのインスタンス変数 @yydebug を true に
しておいてから do_parse/yyparse を呼ぶと、デバッグ用メッセージが出力
されます。パーサが動作する様子が直接見えますので、完全に現在の状態を
把握できます。これを見てどこがおかしいのかわかったらあとは直すだけ。
== next_token に関して
いまだ自分でも忘れることが多いのが
「送るトークンが尽きたら [false,なにか] を送る」ということです。
ちなみに Racc 0.10.2 以降では一度 [false,なにか] を受け取ったら
それ以上 next_token は呼ばないことが保証されています。
追記: 最近は [false,なにか] ではなく nil でもよいことになった。
+348
View File
@@ -0,0 +1,348 @@
= 規則ファイル文法リファレンス
== 文法に関する前バージョンとの非互換
* (1.2.5) ユーザーコードを連結する時、外部ファイルよりも
埋めこんであるコードを先に連結します。
* (1.1.6) 新しいディレクティブ options が追加されました。
* (1.1.5) 予約語 token の意味が変更になりました。
* (0.14) ルールの最後のセミコロンが省略可能になりました。
また、token prechigh などが予約語でなくなりました。
* (10.2) prepare が header に driver が footer になりました。
今はそのままでも使えますが、2.0 からは対応しません。
* (0.10) class に対応する end がなくなりました。
* (0.9) ダサダサのピリオド方式をやめて { と } で囲むようにしました。
== 全体の構造
トップレベルは、規則部とユーザーコード部に分けられます。
ユーザーコード部はクラス定義の後に来なければいけません。
=== コメント
文法ファイルには、一部例外を除いて、ほとんどどこにでもコメントを
書くことができます。コメントは、Rubyの #.....(行末) スタイルと、
Cの /*......*/ スタイルを使うことができます。
=== 規則部
規則部は以下のような形をしています。
--
class クラス名 [< スーパークラス]
[演算子順位]
[トークン宣言]
[オプション]
[expect]
[トークンシンボル値おきかえ]
[スタート規則]
rule
文法記述
--
"クラス名"はここで定義するパーサクラスの名前です。
これはそのままRubyのクラス名になります。
また M::C のように「::」を使った名前を使うと、クラス定義を
モジュール M の中にネストさせます。つまり class M::C ならば
--
module M
class C < Racc::Parser
いろいろ
end
end
--
のように出力します。
さらに、Ruby と同じ構文でスーパークラスを指定できます。
ただしこの指定をするとパーサの動作に重大な影響を与えるので、
特に必要がない限り指定してはいけません。これは将来の拡張の
ために用意したもので、現在指定する必然性はあまりありません。
=== 文法の記述
racc で生成するパーサが理解できる文法を記述します。
文法は、予約語 rule と end の間に、以下のような書式で書きます。
--
トークン: トークンの並び アクション
トークン: トークンの並び アクション
| トークンの並び アクション
| トークンの並び アクション
(必要なだけ同じようにつづける)
--
アクションは { } で囲みます。アクションでは Ruby の文はほとんど
使えますが、一部だけは非対応です。対応していないものは以下のとおり。
* ヒアドキュメント
* =begin ... =end 型コメント
* スペースで始まる正規表現
* ごくまれに % の演算。普通に演算子のまわりにスペースを入れていれば問題なし
このあたりに関しては完全な対応はまず無理です。あきらめてください。
左辺の値($$)は、オプションによって返し方がかわります。まずデフォルトでは
ローカル変数 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]
}
(略)
--
アクション内では特別な意味をもった変数がいくつか使えます。
そのような変数を以下に示します。括弧の中は yacc での表記です。
* result ($$)
左辺の値。初期値は val[0] です。
* val ($1,$2,$3…)
右辺の記号の値の配列。Ruby の配列なので当然インデックスはゼロから始まります。
この配列は毎回作られるので自由に変更したり捨てたりして構いません。
* _values (...,$-2,$-1,$0)
値スタック。Racc コアが使っているオブジェクトがそのまま渡されます。
この変数の意味がわかる人以外は<em>絶対に</em>変更してはいけません。
またアクションの特別な形式に、埋めこみアクションというものがあります。
これはトークン列の途中の好きなところに記述することができます。
以下に埋めこみアクションの例を示します。
--
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 DIVIDE
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 の表示は抑制できません。
=== トークンシンボル値の変更
トークンシンボルを表す値は、デフォルトでは
* 文法中、引用符でかこまれていないもの (RULEとかXENDとか)
→その名前の文字列を intern して得られるシンボル (1.4 では Fixnum)
* 引用符でかこまれているもの(':'とか'.'とか)
→その文字列そのまま
となっていますが、たとえば他の形式のスキャナがすでに存在する場合などは、
これにあわせなければならず、このままでは不便です。このような場合には、
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 の文
:
--
行の先頭から四つ以上連続した「-」(マイナス)があるとユーザーコードと
みなされます。識別子は一つの単語で、そのあとには「=」以外なら何を
書いてもかまいません。
+10
View File
@@ -0,0 +1,10 @@
<h1>Racc ユーザマニュアル</h1>
<p>バージョン 1.4 対応</p>
<ul>
<li><a href="usage.html">Racc の使い方</a>
<li><a href="command.html">racc コマンドリファレンス</a>
<li><a href="grammar.html">規則ファイル文法リファレンス</a>
<li><a href="parser.html">Parser クラスリファレンス</a>
<li><a href="debug.html">パーサのデバッグ</a>
<li><a href="NEWS.html">リリースノート</a>
</ul>
+125
View File
@@ -0,0 +1,125 @@
= class Racc::Parser
Racc の生成するパーサはすべて Racc::Parser クラスを継承します。
Racc::Parser クラスにはパース中に使用するメソッドがいくつかあり、
そのようなメソッドをオーバーロードすると、パーサを初期化したり
することができます。
== Super Class
Object
== Constants
プリフィクス "Racc_" がついた定数はパーサの予約定数です。
そのような定数は使わないでください。動作不可能になります。
== Instance Methods
ここに載っているもののほか、プリフィクス "racc_" および "_racc_" が
ついたメソッドはパーサの予約名です。そのようなメソッドは使わないで
ください。
: do_parse -> Object
パースを開始します。
また、トークンが必要になった時は #next_token を呼び出します。
--
# Example
---- inner
def parse
@q = [[1,1],
[2,2],
[3,3],
[false, '$']]
do_parse
end
def next_token
@q.shift
end
--
: next_token -> [Symbol, Object]
[abstract method]
パーサが次のトークンを読みこむ時に使います。
[記号, その値] の形式の配列を返してください。
記号はデフォルトでは
* 文法中、引用符でかこまれていないもの
→ その名前の文字列のシンボル (例えば :ATOM )
* 引用符でかこまれているもの<br>
→ その文字列そのまま (例えば '=' )
で表します。これを変更する方法については、
文法リファレンスを参照してください。
また、もう送るシンボルがなくなったときには
[false, なにか] または nil を返してください。
このメソッドは抽象メソッドなので、#do_parse を使う場合は
必ずパーサクラス中で再定義する必要があります。
定義しないままパースを始めると例外 NotImplementedError が
発生します。
: yyparse( receiver, method_id )
パースを開始します。このメソッドでは始めてトークンが
必要になった時点で receiver に対して method_id メソッドを
呼び出してトークンを得ます。
receiver の method_id メソッドはトークンを yield しなければ
なりません。形式は #next_token と同じで [記号, 値] です。
つまり、receiver の method_id メソッドの概形は以下のように
なるはずです。
--
def method_id
until end_of_file
:
yield 記号, 値
:
end
end
--
少し注意が必要なのは、method_id が呼び出されるのは始めて
トークンが必要になった時点であるということです。method_id
メソッドが呼び出されたときは既にパースが進行中なので、
アクション中で使う変数を method_id の冒頭で初期化すると
まず失敗します。
トークンの終端を示す [false, なにか] を渡したらそれ以上は
yield しないでください。その場合には例外が発生します。
最後に、method_id メソッドからは必ず yield してください。
しない場合は何が起きるかわかりません。
: on_error( error_token_id, error_value, value_stack )
パーサコアが文法エラーを検出すると呼び出します (yacc の yyerror)。
エラーメッセージを出すなり、例外を発生するなりしてください。
このメソッドから正常に戻った場合、パーサはエラー回復モード
に移行します。
error_token_id はパースエラーを起こした記号の内部表現 (整数) です。
#token_to_str で文法ファイル上の文字列表現に直せます。
error_value はその値です。
value_stack はエラーの時点での値スタックです。
value_stack を変更してはいけません。
on_error のデフォルトの実装は例外 ParseError を発生します。
: token_to_str( t ) -> String
Racc トークンの内部表現 (整数)
を文法ファイル上の記号表現の文字列に変換します。
t が整数でない場合は TypeError を発生します。
t が範囲外の整数だった場合は nil を返します。
: yyerror
エラー回復モードに入ります。このとき #on_error は呼ばれません。
アクション以外からは呼び出さないでください。
: yyerrok
エラー回復モードから復帰します。
アクション以外からは呼び出さないでください。
: yyaccept
すぐに値スタックの先頭の値を返して #do_parse、#yyparse を抜けます。
+414
View File
@@ -0,0 +1,414 @@
<h1>Racc の使い方</h1>
<p>
Racc は文法規則から Ruby で書かれたパーサを生成するパーサジェネレータです。
パーサ生成アルゴリズムには yacc などと同じ LALR(1) を使用しています。
</p>
<p>
yacc を知っている人は記述法の違いだけわかれば使えると思います。
yacc を知らない人は
拙著『Ruby を 256 倍使うための本 無道編』(青木峰郎著、ASCII)
などを一読していただくのがよいかと思います。
他の UNIX コマンドなどとは異なり、
いきなり使うだけで Racc を理解するのはかなり困難です。
</p>
<h2>Racc とはなにか</h2>
<p>
Racc は文法を処理するツールです。
文字列はただの文字の列で、コンピュータにとっては意味を持ちません。
しかし人間はその文字の列の中になにか意味を見出すことができます。
コンピュータにもそのようなことを、部分的にでも、させられたら便利でしょう。
Racc はその手伝いをしてくれます。完全な自動化ではありませんが、
人間が全部やるよりも遥かに簡単になります。
</p>
<p>
Racc が自動化してくれる部分とは、文字列の含む「構造」の処理です。
たとえば Ruby の if 文を考えてみると、次のように定式化できます。
</p>
<pre>
if 条件式 [then]
文
:
[elsif 条件式 [then]
文
:]
[else
文
:]
end
</pre>
<p>
if 文では if という単語が最初になくてはならず、
elsif 節は else 節より前になくてはいけません。
このような配置の関係 (構造) が、Racc が処理する対象です。
</p>
<p>
一方、Racc で処理できないのはどういうことでしょうか。それは、たとえば
if の条件式にあたる部分が「なんであるか」ということです。つまり、条件
式が if の条件だということです。これは、こっちで条件として扱うコードを
書いてやらないといけません。
</p>
<p>
と言っても、わかりにくいでしょう。こういう抽象的なものは実際にいじって
みるのが一番です。
</p>
<h2>実際の話</h2>
<p>
実際に Racc をどのように使うかという話をします。Racc には独自のソース
コードみたいなものがあって、この中に処理したい「構造」を記述しておきま
す。このソースファイルを「文法ファイル」と呼ぶことにしましょう。この文
法ファイルの名前が parse.y と仮定すると、コマンドラインから以下のよう
に打ちこめば、その構造を処理するためのクラスを含んだファイルが得られま
す。
</p>
<pre>
$ racc parse.y
</pre>
<p>
生成されるファイルはデフォルトでは "ファイル名.tab.rb" です。他の名前
にしたいなら、-o オプションで変更できます。
</p>
<pre>
$ racc parse.y -o myparser.rb
</pre>
<p>
このようにして作ったクラス、またはそのような処理を担当するパート、
のことはパーサ (parser) と呼ぶことになっています。解析するヤツ、
というくらいに適当にとらえてください。
</p>
<h2>文法ファイルを書く</h2>
<p>
Racc は文法ファイルから Ruby のクラスを生成するツールだと言いました。
そのクラスは全て Racc::Parser の下位クラスで、名前は文法ファイル中で
指定します。以下、ここに書くべきことが「なんなのか」を説明します。
ここでは内容に重点を置くので、文法ファイル自体の文法の詳細は
<a href="grammar.html">文法リファレンス</a>を見てください。
</p>
<h3>文法</h3>
<p>
まずは、全体の概形です。
</p>
<pre>
class MyParser
rule
if_stmt: IF expr then stmt_list elsif else END
then : THEN
|
elsif :
| ELSIF stmt_list
else :
| ELSE stmt_list
expr : NUMBER
| IDENT
| STRING
stmt_list : ふにゃふにゃ
end
</pre>
<p>
Ruby スクリプトのように class でパーサクラス名を指定し、rule ... end
の間にパーサに解析させたい文法を記述します。
</p>
<p>
文法は、記号の並びでもって表します。rule ... end の間にあるコロンとバー
以外のもの、if_stmt IF expr then などが全て「記号」です。そしてコロン
が日本語で言う「〜は××だ」の「は」みたいなもんで、その左の記号が右の
記号の列と同じものを指す、というふうに定義します。また、バーは「または」
を意味します。それと、単純にコロンの左の記号のことを左辺、右を右辺とも
言います。以下はこちらのほうを使って説明しましょう。
</p>
<p>
少し注意が必要な点を述べます。まず、then の、バーのあとの定義 (規則) を
見てください。ここには何も書いていないので、これはその通り「無」であっ
てもいい、ということを表しています。つまり、then は記号 THEN 一個か、
またはなにもなし(省略する)でよい、ということです。記号 then は実際の
Ruby のソースコードにある then とは切り離して考えましょう
(それは実は大文字の記号 THEN が表しています)。
</p>
<p>
さて、そろそろ「記号」というものがなんなのか書きましょう。
ただし順番に話をしないといけないので、まずは聞いていてください。
この文章の最初に、パーサとは文字の列から構造を見出す部分だと言いました。
しかし文字の列からいきなり構造を探すのは面倒なので、実際にはまず
文字の列を単語の列に分割します。その時点でスペースやコメントは捨てて
しまい、以降は純粋にプログラムの一部をなす部分だけを相手にします。
たとえば文字列の入力が次のようだったとすると、
</p>
<pre>
if flag then # item found.
puts 'ok'
end
</pre>
<p>
単語の列は次のようになります。
</p>
<pre>
if flag then puts 'ok' end
</pre>
<p>
ここで、工夫が必要です。どうやら flag はローカル変数名だと思われますが、
変数名というのは他にもいろいろあります。しかし名前が i だろうが a だろ
うが vvvvvvvvvvvv だろうが、「構造」は同じです。つまり同じ扱いをされる
べきです。変数 a を書ける場所なら b も書けなくてはいけません。だったら
一時的に同じ名前で読んでもいいじゃん。ということで、この単語の列を以下
のように読みかえましょう。
</p>
<pre>
IF IDENT THEN IDENT STRING END
</pre>
<p>
これが「記号」の列です。パーサではこの記号列のほうを扱い、構造を見付け
ていきます。
</p>
<p>
さらに記号について見ていきましょう。
記号は二種類に分けられます。「左辺にある記号」と「ない記号」です。
左辺にある記号は「非終端」記号と言います。ないほうは「終端」記号と
言います。最初の例では終端記号はすべて大文字、非終端記号は小文字で
書いてあるので、もう一度戻って例の文法を見てください。
</p>
<p>
なぜこの区分が重要かと言うと、入力の記号列はすべて終端記号だからです。
一方、非終端記号はパーサの中でだけ、終端記号の列から「作りだす」ことに
よって始めて存在します。例えば次の規則をもう一度見てください。
</p>
<pre>
expr : NUMBER
| IDENT
| STRING
</pre>
<p>
expr は NUMBER か IDENT か STRING だと言っています。逆に言うと、
IDENT は expr に「なることができます」。文法上 expr が存在できる
場所に IDENT が来ると、それは expr になります。例えば if の条件式の
部分は expr ですから、ここに IDENT があると expr になります。その
ように文法的に「大きい」記号を作っていって、最終的に一個になると、
その入力は文法を満たしていることになります。実際にさっきの入力で
試してみましょう。入力はこうでした。
</p>
<pre>
IF IDENT THEN IDENT STRING END
</pre>
<p>
まず、IDENT が expr になります。
</p>
<pre>
IF expr THEN IDENT STRING END
</pre>
<p>
次に THEN が then になります。
</p>
<pre>
IF expr then IDENT STRING END
</pre>
<p>
IDENT STRING がメソッドコールになります。この定義はさきほどの例には
ないですが、実は省略されているんだと考えてください。そしていろいろな
過程を経て、最終的には stmt_list (文のリスト)になります。
</p>
<pre>
IF expr then stmt_list END
</pre>
<p>
elsif と else は省略できる、つまり無から生成できます。
</p>
<pre>
IF expr then stmt_list elsif else END
</pre>
<p>
最後に if_stmt を作ります。
</p>
<pre>
if_stmt
</pre>
<p>
ということでひとつになりました。
つまりこの入力は文法的に正しいということがわかりました。
</p>
<h3>アクション</h3>
<p>
ここまでで入力の文法が正しいかどうかを確認する方法はわかりましたが、
これだけではなんにもなりません。最初に説明したように、ここまででは
構造が見えただけで、プログラムは「意味」を理解できません。そしてその
部分は Racc では自動処理できないので、人間が書く、とも言いました。
それを書くのが以下に説明する「アクション」という部分です。
</p>
<p>
前項で、記号の列がだんだんと大きな単位にまとめられていく過程を見ました。
そのまとめる時に、同時になにかをやらせることができます。それが
アクションです。アクションは、文法ファイルで以下のように書きます。
</p>
<pre>
class MyParser
rule
if_stmt: IF expr then stmt_list elsif else END
{ puts 'if_stmt found' }
then : THEN
{ puts 'then found' }
|
{ puts 'then is omitted' }
elsif :
{ puts 'elsif is omitted' }
| ELSIF stmt_list
{ puts 'elsif found' }
else :
{ puts 'else omitted' }
| ELSE stmt_list
{ puts 'else found' }
expr : NUMBER
{ puts 'expr found (NUMBER)' }
| IDENT
{ puts 'expr found (IDENT)' }
| STRING
{ puts 'expr found (STRING)' }
stmt_list : ふにゃふにゃ
end
</pre>
<p>
見てのとおり、規則のあとに { と } で囲んで書きます。
アクションにはだいたい好きなように Ruby スクリプトが書けます。
</p>
<p>
(この節、未完)
</p>
<hr>
<p>
yacc での <code>$$</code> は Racc ではローカル変数 <code>result</code>
で、<code>$1,$2...</code> は配列 <var>val</var>です。
<code>result</code> は <code>val[0]</code> ($1) の値に初期化され、
アクションを抜けたときの <code>result</code> の値が左辺値になります。
Racc ではアクション中の <code>return</code> はアクションから抜けるだけで、
パース自体は終わりません。アクション中からパースを終了するには、
メソッド <code>yyaccept</code> を使ってください。
</p>
<p>
演算子の優先順位、スタートルールなどの yacc の一般的な機能も用意されて
います。ただしこちらも少し文法が違います。
</p>
<p>
yacc では生成されたコードに直接転写されるコードがありました。
Racc でも同じように、ユーザ指定のコードが書けます。
Racc ではクラスを生成するので、クラス定義の前/中/後の三個所があります。
Racc ではそれを上から順番に header inner footer と呼んでいます。
</p>
<h3>ユーザが用意すべきコード</h3>
<p>
パースのエントリポイントとなるメソッドは二つあります。ひとつは
<code>do_parse</code>で、こちらはトークンを
<code>Parser#next_token</code> から得ます。もうひとつは
<code>yyparse</code> で、こちらはスキャナから <code>yield</code> され
ることによってトークンを得ます。ユーザ側ではこのどちらか(両方でもいい
けど)を起動する簡単なメソッドを inner に書いてください。これらメソッド
の引数など、詳しいことはリファレンスを見てください。
</p>
<ul>
<li><a href="parser.html#Racc%3a%3aParser-do_parse">do_parse</a>
<li><a href="parser.html#Racc%3a%3aParser-yyparse">yyparse</a>
</ul>
<p>
どちらのメソッドにも共通なのはトークンの形式です。必ずトークンシンボル
とその値の二要素を持つ配列を返すようにします。またスキャンが終了して、
もう送るものがない場合は <code>[false,<var>なにか</var>]</code> を返し
てください。これは一回返せば十分です (逆に、<code>yyparse</code> を使
う場合は二回以上 <code>yield</code> してはいけない)。
</p>
<p>
パーサは別に文字列処理にだけ使われるものではありませんが、実際問題とし
て、パーサを作る場面ではたいてい文字列のスキャナとセットで使うことが多
いでしょう。Ruby ならスキャナくらい楽勝で作れますが、高速なスキャナと
なると実は難しかったりします。そこで高速なスキャナを作成するためのライ
ブラリも作っています。詳しくは
<a href="#WritingScanner">「スキャナを作る」の項</a>を見てください。
</p>
<p>
Racc には error トークンを使ったエラー回復機能もあります。yacc の
<code>yyerror()</code> は Racc では
<a href="parser.html#Racc%3a%3aParser-on_error"><code>Racc::Parser#on_error</code></a>
で、エラーが起きたトークンとその値、値スタック、の三つの引数をとります。
<code>on_error</code> のデフォルトの実装は例外
<code>Racc::ParseError</code> を発生します。
</p>
<p>
ユーザがアクション中でパースエラーを発見した場合は、メソッド
<a href="parser.html#Racc%3a%3aParser-yyerror"><code>yyerror</code></a>
を呼べばパーサがエラー回復モードに入ります。
ただしこのとき <code>on_error</code>は呼ばれません。
</p>
<h3>パーサを生成する</h3>
<p>
これだけあればだいたい書けると思います。あとは、最初に示した方法で文法
ファイルを処理し、Ruby スクリプトを得ます。
</p>
<p>
うまくいけばいいのですが、大きいものだと最初からはうまくいかないでしょ
う。racc に -g オプションをつけてコンパイルし、@yydebug を true にする
とデバッグ用の出力が得られます。デバッグ出力はパーサの @racc_debug_out
に出力されます(デフォルトは stderr)。また、racc に -v オプションをつけ
ると、状態遷移表を読みやすい形で出力したファイル(*.output)が得られます。
どちらもデバッグの参考になるでしょう。
</p>
<h2>作ったパーサを配布する</h2>
<p>
Racc の生成したパーサは動作時にランタイムルーチンが必要です。
具体的には parser.rb と cparse.so です。
ただし cparse.so は単にパースを高速化するためのライブラリなので
必須ではありません。なくても動きます。
</p>
<p>
まず Ruby 1.8.0 以降にはこのランタイムが標準添付されているので、
Ruby 1.8 がある環境ならばランタイムについて考慮する必要はありません。
Racc 1.4.x のランタイムと Ruby 1.8 に添付されているランタイムは
完全互換です。
</p>
<p>
問題は Ruby 1.8 を仮定できない場合です。
Racc をユーザみんなにインストールしてもらうのも一つの手ですが、
これでは不親切です。そこでRacc では回避策を用意しました。
</p>
<p>
racc に -E オプションをつけてコンパイルすると、
パーサと racc/parser.rb を合体したファイルを出力できます。
これならばファイルは一つだけなので簡単に扱えます。
racc/parser.rb は擬似的に require したような扱いになるので、
この形式のパーサが複数あったとしてもクラスやメソッドが衝突することもありません。
ただし -E を使った場合は cparse.so が使えませんので、
必然的にパーサの速度は落ちます。
</p>
<h2><a name="WritingScanner">おまけ: スキャナを書く</a></h2>
<p>
パーサを使うときは、たいてい文字列をトークンに切りわけてくれるスキャナ
が必要になります。しかし実は Ruby は文字列の最初からトークンに切りわけ
ていくという作業があまり得意ではありません。
正確に言うと、簡単にできるのですが、それなりのオーバーヘッドがかかります。
</p>
<p>
そのオーバーヘッドを回避しつつ、
手軽にスキャナを作れるように strscan というパッケージを作りました。
Ruby 1.8 以降には標準添付されていますし、
<a href="http://i.loveruby.net/ja/">筆者のホームページ</a>には
単体パッケージがあります。
</p>