私は最近(言語/文脈自由文法用の)パーサーがどのように機能するかを自分自身に教えようとしています。私は特にLL(k)文法に注意を集中しており、2つの主要なアルゴリズムは LL parser (スタック/解析テーブルを使用)および 再帰降下パーサー =(単純に再帰を使用)。
私が見る限り、再帰降下アルゴリズムはすべてのLL(k)文法で動作し、LLパーサーはすべてのLL(k)文法で動作します。ただし、再帰的降下パーサーは、LLパーサーよりも明らかに簡単に実装できます(LLパーサーがLRパーサーよりも単純であるように)。
だから私の質問は、どちらかのアルゴリズムを使用するときに遭遇する可能性のある利点/問題は何ですか?同じ文法のセットで機能し、実装するのが難しいのに、なぜ再帰降下よりもLLを選ぶのでしょうか?
LLは通常、再帰下降よりも効率的な解析手法です。実際、単純な再帰下降パーサーは、実際にはO(k ^ n)(wherenは、最悪の場合の入力サイズです。メモ化( Packrat パーサーを生成する)などの一部の手法は、これを改善し、パーサーが受け入れる文法のクラスを拡張できますが、常にスペースのトレードオフがあります。 LLパーサーは(私の知る限り)常に線形時間です。
反対に、再帰下降パーサーはLLよりも大きなクラスの文法を処理できるという直感が正しいです。再帰下降は、LL(*)(つまり、unlimited先読み)のすべての文法と、あいまいな文法の小さなセットを処理できます。これは、再帰降下が実際には直接エンコードされたPEGの実装、または Parser Expression Grammar(s) であるためです。具体的には、選言演算子(a | b
)は可換ではありません。つまり、a | b
はb | a
と等しくありません。再帰下降パーサーは、各代替を順番に試行します。そのため、a
が入力に一致する場合、b
が入力に一致するであっても成功します。これにより、ぶら下がりelse
問題のような古典的な「最長一致」あいまいさを、選言を正しく順序付けるだけで処理できます。
以上のことから、再帰的降下を使用してLL(k)パーサーを実装し、線形時間で実行することが可能ですpossible。これは基本的に予測セットをインライン化することにより行われ、各解析ルーチンは一定の時間で与えられた入力に対して適切な生成を決定します。残念ながら、このような手法は、クラス全体の文法の処理を排除します。予測的な解析に入ると、else
のぶら下がりのような問題は、それほど簡単に解決できなくなります。
LLが再帰下降よりも選択される理由については、主に効率と保守性の問題です。再帰下降パーサーは実装が著しく簡単ですが、それらが表す文法が宣言的な形式では存在しないため、通常は保守が困難です。ほとんどの非自明なパーサーのユースケースは、ANTLRやBisonなどのパーサージェネレーターを採用しています。このようなツールを使用すると、アルゴリズムが直接エンコードされた再帰下降型かテーブル駆動型LL(k)かは問題になりません。
興味深いことに、 recursive-ascent も検討する価値があります。これは、再帰下降の方法の後に直接エンコードされた解析アルゴリズムですが、LALR文法を処理できます。また、 parser combinators を掘り下げます。これは、再帰下降パーサーを一緒に構成する機能的な方法です。