LR、SLR、およびLALRパーサーの実際の違いは何ですか? SLRとLALRはLRパーサーの一種であることは知っていますが、構文解析テーブルに関する実際の違いは何ですか?
そして、文法がLR、SLR、またはLALRであるかどうかを表示する方法は? LL文法の場合、解析テーブルのセルに複数のプロダクションルールを含めないようにする必要があります。 LALR、SLR、およびLRに類似したルールはありますか?
たとえば、どのように文法を示すことができますか
S --> Aa | bAc | dc | bda
A --> d
lALR(1)はSLR(1)ではありませんか?
EDIT(ybungalobill):LALRとLRの違いについて満足のいく答えが得られませんでした。そのため、LALRのテーブルはサイズは小さくなりますが、LR文法のサブセットのみを認識できます。誰かがLALRとLRの違いについて詳しく説明してください。 LALR(1)とLR(1)は回答に十分です。どちらも1トークン先読みを使用し、bothはテーブル駆動です!どう違うの?
SLR、LALR、およびLRパーサーはすべて、まったく同じテーブル駆動機構を使用して実装できます。
基本的に、解析アルゴリズムは次の入力トークンTを収集し、現在の状態S(および関連する先読み、GOTO、およびリダクションテーブル)を参照して、何をするかを決定します。
それで、もし彼ら全員が同じ機械を使っているなら、何がポイントなのでしょうか?
SLRの価値は、実装がシンプルであることです。ルックアヘッドセットをチェックする可能性のある削減をスキャンする必要はありません。これは最大で1つです。これは、状態からSHIFTが終了しない場合の唯一の実行可能なアクションです。どの削減が適用されるかは、状態に具体的に関連付けられるため、SLR構文解析機構はそれを探す必要がありません。実際には、L(AL)Rパーサーは便利な大きな言語のセットを処理し、アカデミックな演習を除いて誰もSLRを実装しないため、実装する余分な作業がほとんどありません。
LALRとLRの違いは、テーブルgeneratorに関係しています。 LRパーサージェネレーターは、特定の状態とその正確な先読みセットからのすべての可能な削減を追跡します。最終的な状態は、すべての削減がその左コンテキストからの正確な先読みセットに関連付けられている状態になります。これは、かなり大きな状態のセットを構築する傾向があります。 LALRパーサージェネレーターは、縮小用のGOTOテーブルとルックヘッドセットが互換性があり、競合しない場合、状態を結合します。これにより、LRが識別できる特定のシンボルシーケンスを識別できないという代償を払って、かなり少ない数の状態が生成されます。したがって、LRパーサーはLALRパーサーよりも大きな言語セットを解析できますが、非常に大きなパーサーテーブルを使用できます。実際には、ステートマシンのサイズを最適化する価値があるターゲット言語に十分近いLALR文法を見つけることができます。 LRパーサーの方が適している場所は、パーサー外部のアドホックチェックによって処理されます。
そのため、3つすべてが同じ機械を使用しています。 SLRは、ごくわずかな機械を無視できるという意味で「簡単」ですが、それだけの価値はありません。 LRはより広範な言語を解析しますが、状態テーブルはかなり大きくなる傾向があります。そのため、LALRが実用的な選択肢となります。
これをすべて言ったが、 GLRパーサー はより複雑な機構を使用してコンテキストフリー言語を解析できるが、まったく同じテーブル(LALRで使用される小さいバージョンを含む)。これは、GLRがLR、LALR、SLRよりも厳密に強力であることを意味します。ほとんどの場合、標準のBNF文法を記述できれば、GLRはそれに従って解析します。機械の違いは、GOTOテーブルまたは先読みセットの間に競合がある場合、GLRが複数の解析を試行することです。 (GLRがこれを効率的に行う方法は天才です(私のものではありません)が、このSO投稿)には収まりません。
それは私にとって非常に有用な事実です。プログラムアナライザーを作成し、コードトランスフォーマーとパーサーが必要ですが、「面白くない」です。興味深い結果は、解析された結果を使用して行うことなので、解析後の処理に焦点を当てます。 GLRを使用すると、グラマーをハッキングしてLALRの使用可能なフォームに入るのに比べて、実用的なグラマーを比較的簡単に構築できます。これは、言語全体を適切に処理するために文字通り何千ものルールを必要とするC++やFortranなどの非アカデミックな言語に対処しようとするとき、非常に重要です。 LALR(またはLR)の制限を満たします。
一種の有名な例として、C++は解析するのが非常に難しいと考えられています... LALR解析を行う人によって。 C++は、C++リファレンスマニュアルの裏に記載されているほとんどのルールを使用して、GLR機構を使用して簡単に解析できます。 (正確にそのようなパーサーがあり、Vanilla C++だけでなく、さまざまなベンダーの方言も処理します。GLRパーサーIMHOを使用しているため、これは実際にのみ可能です)。
[2011年11月編集:パーサーを拡張して、すべてのC++ 11を処理できるようにしました。 GLRを使用すると、これがはるかに簡単になりました。 2014年8月編集:C++ 17のすべてを処理するようになりました。何も壊れたり悪化したりすることはありません。GLRはまだ猫の鳴き声です。]
LALRパーサーは、LR文法内の同様の状態をマージして、同等のSLR文法とまったく同じサイズのパーサー状態テーブルを生成します。これは通常、純粋なLR解析テーブルよりも1桁小さくなります。ただし、LALRには複雑すぎるLR文法の場合、これらのマージされた状態により、パーサーの競合が発生するか、元のLR文法を完全に認識しないパーサーが生成されます。
ところで、これについては、MLR(k)解析テーブルアルゴリズム ここ でいくつか言及しています。
補遺
簡単な答えは、LALR解析テーブルは小さくなりますが、パーサーの仕組みは同じです。特定のLALR文法は、すべてのLR状態が生成され、多くの冗長な(ほぼ同一の)状態で、はるかに大きな解析テーブルを生成します。
LALRテーブルは、類似した(冗長な)状態がマージされ、個々の状態がエンコードするコンテキスト/先読み情報を効果的に破棄するため、小さくなります。利点は、同じ文法に対してより小さな解析テーブルを取得できることです。
欠点は、すべてのLR文法がLALRテーブルとしてエンコードできるわけではないことです。より複雑な文法では先読みがより複雑になるため、単一のマージ状態ではなく2つ以上の状態になります。
主な違いは、LRテーブルを生成するアルゴリズムは、状態から状態への遷移間でより多くの情報を運ぶのに対し、LALRアルゴリズムはそうしないことです。そのため、LALRアルゴリズムは、特定のマージされた状態を実際に2つ以上の別個の状態のままにする必要があるかどうかを判断できません。
さらに別の答え(YAA)。
SLR(1)、LALR(1)、LR(1)の解析アルゴリズムは、Ira Baxterが言ったように同一です。
ただし、パーサー生成アルゴリズムのため、パーサーテーブルは異なる場合があります。
SLRパーサージェネレーターはLR(0)ステートマシンを作成し、文法から先読みを計算します(FIRSTおよびFOLLOWセット)。これは単純化されたアプローチであり、LR(0)ステートマシンには実際には存在しない競合を報告する場合があります。
LALRパーサージェネレーターはLR(0)ステートマシンを作成し、LR(0)ステートマシンから先読みを計算します(ターミナル遷移を介して)。これは正しいアプローチですが、LR(1)ステートマシンには存在しない競合を時折報告します。
Canonical LRパーサージェネレーターはLR(1)ステートマシンを計算し、先読みは既にLR(1)ステートマシンの一部です。これらのパーサーテーブルは非常に大きくなる可能性があります。
最小LRパーサージェネレーターはLR(1)ステートマシンを計算しますが、プロセス中に互換性のある状態をマージし、最小LR(1)ステートマシンから先読みを計算します。これらのパーサーテーブルは、LALRパーサーテーブルと同じサイズかわずかに大きいため、最適なソリューションを提供します。
LRSTAR 10.でパーサーをLALR(1)、LR(1)、CLR(1)またはLR(*)生成できますC++、文法に必要なもの。 この図を参照してください。これは、LRパーサー間の違いを示しています。
[完全開示:LRSTARは私の製品です]
先読みのないパーサーが、文法のために文字列をうまく解析しているとします。
あなたの与えられた例を使用すると、それは文字列dc
に遭遇します、それは何をしますか? S
はこの文法によって生成される有効な文字列であるため、それをdc
に減らしますか? OR多分、bdc
を解析しようとしていました。
人間は答えが簡単だと知っているので、b
を解析したかどうかを覚えておく必要があります。しかし、コンピュータは愚かです:)
SLR(1)パーサーはLR(0)より先読みを実行するための追加のパワーを持っているため、先読みの量はこの場合に何をすべきかを指示できないことがわかります。代わりに、過去を振り返る必要があります。したがって、正規のLRパーサーが助けになります。過去の文脈を覚えています。
このコンテキストを記憶する方法は、b
に出会うたびに、1つの可能性としてbdc
を読む方向に歩き始めます。そのため、d
を検出すると、すでにパスを歩いているかどうかがわかります。したがって、CLR(1)パーサーは、SLR(1)パーサーができないことを実行できます!
しかし、今では非常に多くのパスを定義する必要があるため、マシンの状態は非常に大きくなります!
したがって、同じように見えるパスをマージしますが、予想どおり、混乱の問題を引き起こす可能性があります。ただし、サイズを縮小するという犠牲を払ってリスクを負うことをいとわない。
これはLALR(1)パーサーです。
次に、アルゴリズム的にそれを行う方法。
上記の言語の構成セットを描画すると、2つの状態でshift-reduceの競合が発生します。それらを削除するには、フォローを見て決定を下すSLR(1)を検討することをお勧めしますが、それでもそれができないことを観察するでしょう。したがって、構成セットを再度描画しますが、今回は、クロージャーを計算するたびに、追加されるプロダクションには厳密なフォローが必要であるという制限があります。これらがどうあるべきかについての教科書を参照してください。
SLRパーサーは、LALR(1)パーサーが認識できる文法の適切なサブセットを認識します。LALR(1)パーサーは、LR(1)パーサーが認識できる適切な文法のサブセットを認識します。
これらはそれぞれステートマシンとして構築され、各ステートは入力を解析する際の文法のプロダクションルール(およびそれぞれの位置)のセットを表します。
Dragon Book SLRではないLALR(1)文法の例は次のとおりです。
S → L = R | R
L → * R | id
R → L
この文法の状態の1つを次に示します。
S → L•= R
R → L•
•
は、可能性のある各プロダクションにおけるパーサーの位置を示します。最後に到達して削減を試みるまで、実際にどのプロダクションにあるかはわかりません。
ここで、パーサーは=
をシフトするか、R → L
を減らすことができます。
SLR(別名LR(0))パーサーは、次の入力シンボルが[set set of R
(つまり、すべての端末のセット) R
に続くことができる文法で)。 =
もこのセットに含まれているため、SLRパーサーはshift-reduceの競合に遭遇します。
ただし、LALR(1)パーサーは、Rのこの特定のプロダクションに続くすべての端末のセットを使用します。これは$
(つまり、入力の終わり)のみです。したがって、競合はありません。
以前のコメンターが指摘したように、LALR(1)パーサーには、SLRパーサーと同じ数の状態があります。先読み伝播アルゴリズムを使用して、対応するLR(1)状態からSLR状態生成に先読みを付加します。結果のLALR(1)パーサーは、LR(1)パーサーには存在しないreduce-reduce競合を引き起こす可能性がありますが、shift-reduce競合を引き起こすことはできません。
あなたの例では、次のLALR(1)状態はSLR実装でshift-reduce競合を引き起こします:
S → b d•a / $
A → d• / c
/
の後の記号は、LALR(1)パーサーの各プロダクションのフォローセットです。 SLRでは、follow(A
)にはa
が含まれますが、これもシフトできます。
SLRとLRで生成されたパーサーテーブルの基本的な違いは、reduceアクションがSLRテーブルのフォローセットに基づいていることです。これは過度に制限的であり、最終的にシフト削減の競合を引き起こします。
一方、LRパーサーは、縮小される非ターミナルに実際に従うことができるターミナルのセットでのみ決定を減らします。この端末のセットは、多くの場合、このような非端末のFollowsセットの適切なサブセットであるため、シフトアクションと競合する可能性が低くなります。
このため、LRパーサーはより強力です。ただし、LR解析テーブルは非常に大きくなる可能性があります。
LALRパーサーは、LR解析テーブルを構築するというアイデアから始まりますが、生成された状態を結合して、テーブルサイズを大幅に小さくします。欠点は、LRテーブルでは回避できなかった一部の文法で競合の可能性がわずかに生じることです。
LALRパーサーはLRパーサーよりも若干強力ですが、SLRパーサーよりも強力です。 YACCおよび他のそのようなパーサージェネレーターは、この理由でLALRを使用する傾向があります。
追伸簡潔にするために、上記のSLR、LALR、およびLRは実際にはSLR(1)、LALR(1)、およびLR(1)を意味するため、1つのトークンの先読みが暗示されます。
1つの簡単な答えは、すべてのLR(1)文法がLALR(1)文法であるということです。 LALR(1)と比較して、LR(1)は、関連付けられた有限状態マシンでより多くの状態(状態の2倍以上)を持っています。そして、それがLALR(1)文法がLR(1)文法よりも構文エラーを検出するために多くのコードを必要とする主な理由です。そして、これらの2つの文法に関して知っておくべきもう1つの重要なことは、LR(1)文法では競合の削減/削減が少ない可能性があるということです。しかし、LALR(1)では、競合を減らす/減らす可能性が高くなります。