私はコンパイラの概念に取り組んでいますが、少し混乱しています...グーグルで明確な答えを得ることができませんでした。
SLRとLR(0)パーサーは同じですか?そうでない場合、違いは何ですか?
私はコンパイラの概念に取り組んでいますが、少し混乱しています...グーグルで明確な答えを得ることができませんでした。
SLRとLR(0)パーサーは同じですか?そうでない場合、違いは何ですか?
回答:
LR(0)パーサーとSLR(1)パーサーはどちらも、ボトムアップの指向性予測パーサーです。この意味は
LR(0)とSLR(1)はどちらもシフト/リデュースパーサーです。つまり、入力ストリームのトークンをスタックに配置して処理し、各ポイントでトークンをスタックにプッシュするか、一部をリデュースしてシフトします。スタックの上の終端記号と非終端記号のシーケンスを、いくつかの非終端記号に戻します。シフト/リデュースパーサーを使用して任意の文法をボトムアップで解析できることを示すことができますが、そのパーサーは決定論的ではない可能性があります。つまり、パーサーはシフトまたはリダクションのどちらを適用するかを「推測」する必要があり、間違った選択をしたことに気付くためにバックトラックしなければならない場合があります。構築する決定論的シフト/リデュースパーサーがどれほど強力であっても、すべての文法を解析することはできません。
決定論的なshift / reduceパーサーを使用して、処理できない文法を解析すると、shift / reduceの競合、またはreduce / reduceの競合が発生し、パーサーが実行するアクションを判別できない状態になる可能性があります。シフト/削減の競合では、スタックに別のシンボルを追加する必要があるのか、スタックの最上位のシンボルに対して何らかの削減を実行する必要があるのかを判断できません。削減/削減の競合では、パーサーはスタックの最上位のシンボルを非終端記号に置き換える必要があることを認識していますが、どの削減を使用するかはわかりません。
これが長い説明である場合はお詫びしますが、LR(0)とSLR(1)の構文解析の違いに対処できるようにするためにこれが必要です。LR(0)パーサーは、先読みのゼロトークンを使用して実行するアクションを決定するシフト/リデュースパーサーです(したがって、0)。つまり、パーサーのどの構成でも、パーサーは特定のシンボルをシフトするか、特定の縮小を適用するかのいずれかを選択するための明確なアクションを持っている必要があります。選択する選択肢が2つ以上ある場合、パーサーは失敗し、文法はLR(0)ではないと言います。
考えられる2つのLR競合は、shift / reduceとreduce / reduceであることを思い出してください。どちらの場合も、LR(0)オートマトンが実行できるアクションが少なくとも2つあり、どちらを使用するかがわかりません。競合するアクションの少なくとも1つは削減であるため、合理的な攻撃ラインは、パーサーが特定の削減を実行するときに、より注意を払うようにすることです。より具体的には、パーサーが入力の次のトークンを調べて、シフトするか減らすかを決定できると仮定しましょう。パーサーが「理にかなっている」場合にのみ縮小することを許可する場合(「理にかなっている」の定義について)、オートマトンにシフトまたは縮小のいずれかを具体的に選択させることで、競合を排除できる可能性があります。特定のステップ。
SLR(1)( "Simplified LR(1)")では、パーサーは、シフトするか減らすかを決定するときに、先読みの1つのトークンを調べることができます。特に、パーサーがA→wの形式(非終端記号Aおよび文字列wの場合)の何かを削減しようとすると、入力の次のトークンを調べます。そのトークンが何らかの派生で非終端記号Aの後に合法的に現れる可能性がある場合、パーサーは減少します。それ以外の場合は、そうではありません。ここでの直感は、これまでに見たトークンと今後のトークンを考えると、削減を正しく行う方法がないため、削減を試みることが意味をなさない場合があるということです。
LR(0)とSLR(1)の唯一の違いは、競合が発生したときに実行するアクションを決定するのに役立つこの追加機能です。このため、LR(0)パーサーで解析できる文法はすべてSLR(1)パーサーで解析できます。ただし、SLR(1)パーサーは、LR(0)よりも多くの文法を解析できます。
ただし、実際には、SLR(1)は依然としてかなり弱い解析方法です。より一般的には、LALR(1)( "Lookahead LR(1)")パーサーが使用されているのがわかります。それらもLR(0)パーサーで競合を解決しようとすることで機能しますが、競合を解決するために使用するルールはSLR(1)で使用されるルールよりもはるかに正確であるため、はるかに多くの文法がLALR(1)です。 SLR(1)よりも。もう少し具体的に言うと、SLR(1)パーサーは、文法の構造を調べて、シフトするタイミングと削減するタイミングに関する詳細情報を学習することにより、競合を解決しようとします。LALR(1)パーサーは、文法とLR(0)パーサーの両方を調べて、シフトするタイミングと削減するタイミングに関するさらに具体的な情報を取得します。LALR(1)はLR(0)パーサーの構造を調べることができるため、特定の競合がスプリアスである場合をより正確に識別できます。yacc
そして、bison
、デフォルトでは、LALR(1)パーサを作成。
歴史的に、LALR(1)パーサーは通常、はるかに強力なLR(1)パーサーに依存する別の方法で構築されていたため、LALR(1)がそのように記述されていることがよくあります。これを理解するには、LR(1)パーサーについて説明する必要があります。LR(0)パーサーでは、パーサーは、プロダクションの途中にある可能性がある場所を追跡することによって機能します。生産の終わりに達したことがわかると、それは削減しようとすることを知っています。ただし、パーサーは、あるプロダクションの終了時と別のプロダクションの途中にあるかどうかを判断できない場合があります。これにより、シフト/リデュースの競合が発生するか、2つの異なるプロダクションのどちらが終了に到達したか(リデュース/競合を減らす)。LR(0)では、これはすぐに競合につながり、パーサーは失敗します。SLR(1)またはLALR(1)では、
LR(1)パーサーでは、パーサーは動作時に追加情報を追跡します。パーサーが使用されていると信じるプロダクションを追跡することに加えて、そのプロダクションが完了した後に表示される可能性のあるトークンを追跡します。パーサーは、決定を下す必要があるときだけでなく、各ステップでこの情報を追跡するため、LR(1)パーサーは、LR(0)、SLR(1)、またはどのLR(0)よりも大幅に強力で正確です。これまでに説明したLALR(1)パーサー。LR(1)は非常に強力な構文解析手法であり、いくつかのトリッキーな数学を使用して、シフト/リデュースパーサーによって決定論的に解析できる言語には、LR(1)オートマトンで解析できる文法があることを示すことができます。(これはすべての文法を意味するわけではないことに注意してください決定論的に解析できるのはLR(1)です。これは、決定論的に解析できる言語にLR(1)文法があることを示しているだけです。ただし、この能力には代償が伴い、生成されたLR(1)パーサーは、動作するために非常に多くの情報を必要とするため、実際には使用できない可能性があります。たとえば、実際のプログラミング言語用のLR(1)パーサーは、正しく動作するために数十から数百メガバイトの追加情報を必要とする場合があります。このため、LR(1)は通常実際には使用されず、代わりにLALR(1)やSLR(1)などの弱いパーサーが使用されます。
最近では、GLR(0)( "Generalized LR(0)")と呼ばれる新しい解析アルゴリズムが人気を博しています。GLR(0)パーサーは、LR(0)パーサーに表示される競合を解決しようとするのではなく、考えられるすべてのオプションを並行して試行することで機能します。いくつかの巧妙なトリックを使用して、これを多くの文法で非常に効率的に実行することができます。さらに、GLR(0)は、任意のkのLR(k)パーサーで解析できない文法であっても、文脈自由文法を解析できます。他のパーサー(たとえば、EarleyパーサーまたはCYKパーサー)もこれを行うことができますが、GLR(0)は実際には高速である傾向があります。
もっと詳しく知りたい場合は、この夏、コンパイラの入門コースを教え、2週間弱で構文解析のテクニックについて話しました。LR(0)、SLR(1)、およびその他の強力な構文解析手法のより厳密な紹介を取得したい場合は、私の講義スライドと構文解析に関する宿題をお楽しみください。すべてのコース資料は、私の個人サイトで入手できます。
お役に立てれば!
これは私が学んだことです。通常、LR(0)パーサーにはあいまいさがあります。つまり、テーブルの1つのボックス(パーサーを作成するために派生)に複数の値(または)を含めることができます。パーサーは、同じ入力で2つの最終状態になります。したがって、SLRパーサーは、このあいまいさを取り除くために作成されます。それを構築するために、goto状態につながるすべてのプロダクションを見つけ、左側のプロダクションシンボルのfollowを見つけ、followに存在するgoto状態のみを含めます。これは、元の文法を使用して不可能なプロダクションを含めないことを意味します(その状態は次のセットにありません)