LL解析とLR解析の違いは何ですか?


回答:


483

高レベルでは、LL解析とLR解析の違いは、LLパーサーが開始記号から始まり、プロダクションを適用してターゲット文字列に到達しようとするのに対し、LRパーサーはターゲット文字列から始まり、先頭に戻って到達しようとすることです。シンボル。

LL解析は、左から右、左端の派生です。つまり、入力シンボルを左から右に検討し、左端の派生を構築しようとします。これは、開始記号から開始し、ターゲット文字列に到達するまで左端の非終端記号を繰り返し展開することによって行われます。LR解析は、左から右、右端の派生です。つまり、左から右にスキャンして、右端の派生を構築しようとします。パーサーは入力の部分文字列を継続的に選択し、それを非終端に戻そうとします。

LL解析中、パーサーは2つのアクションから継続的に選択します。

  1. 予測:左端の非終端記号といくつかの先読みトークンに基づいて、入力文字列に近づくために適用するプロダクションを選択します。
  2. 一致:左端の推測された終端記号を入力の左端の未使用記号と一致させます。

例として、この文法を考えると:

  • S→E
  • E→T + E
  • E→T
  • T→ int

次に、文字列を指定するint + int + intと、LL(2)パーサー(先読みの2つのトークンを使用)は、次のように文字列を解析します。

Production       Input              Action
---------------------------------------------------------
S                int + int + int    Predict S -> E
E                int + int + int    Predict E -> T + E
T + E            int + int + int    Predict T -> int
int + E          int + int + int    Match int
+ E              + int + int        Match +
E                int + int          Predict E -> T + E
T + E            int + int          Predict T -> int
int + E          int + int          Match int
+ E              + int              Match +
E                int                Predict E -> T
T                int                Predict T -> int
int              int                Match int
                                    Accept

各ステップで、プロダクションの左端のシンボルを確認することに注意してください。端末の場合は照合し、非端末の場合は、ルールの1つを選択して、端末がどうなるかを予測します。

LRパーサーには、2つのアクションがあります。

  1. Shift:検討のために、入力の次のトークンをバッファに追加します。
  2. 削減:プロダクションを元に戻すことにより、このバッファー内の端末と非端末のコレクションを一部の非端末に戻します。

例として、LR(1)パーサー(先読みのトークンが1つ)は、次のように同じ文字列を解析します。

Workspace        Input              Action
---------------------------------------------------------
                 int + int + int    Shift
int              + int + int        Reduce T -> int
T                + int + int        Shift
T +              int + int          Shift
T + int          + int              Reduce T -> int
T + T            + int              Shift
T + T +          int                Shift
T + T + int                         Reduce T -> int
T + T + T                           Reduce E -> T
T + T + E                           Reduce E -> T + E
T + E                               Reduce E -> T + E
E                                   Reduce S -> E
S                                   Accept

あなたが言及した2つの解析アルゴリズム(LLとLR)は、異なる特性を持つことがわかっています。LLパーサーは手動で書く方が簡単な傾向がありますが、LRパーサーよりも強力ではなく、LRパーサーよりもはるかに少ない文法のセットを受け入れます。LRパーサーには多くの種類(LR(0)、SLR(1)、LALR(1)、LR(1)、IELR(1)、GLR(0)など)があり、はるかに強力です。また、これらははるかに複雑になる傾向があり、ほとんどの場合、yaccまたはのようなツールによって生成されますbison。LLパーサーには多くの種類(ANTLRツールで使用されるLL(*)を含む)もありますが、実際にはLL(1)が最も広く使用されています。

恥知らずなプラグインとして、LLとLRの構文解析について詳しく知りたい場合は、コンパイラーコースの指導を終えたばかりで、コースのWebサイトに構文解析に関する配布資料と講義スライドがあります。あなたがそれが有用だと思うなら、私はそれらのどれかについて詳しく述べていただければ幸いです。


40
あなたの講義のスライドは驚異的で、簡単に私が見た中で最も楽しい説明です:)これは実際に興味を起こさせるようなものです。
kizzx2

1
スライドにもコメントしなきゃ!今それらすべてを通過します。とても役に立ちます!ありがとう!
kornfridge 2013年

スライドも本当に楽しんでいます。Windows以外のバージョンのプロジェクトファイル(およびpp2のscanner.lファイル)を投稿できると思いませんか?:)
エリックP.

1
Mattの優れた要約回答に貢献できることの1つは、LL(k)パーサーで解析できるすべての文法(つまり、「k」端子を先読みして次の解析アクションを決定する)は、LR( 1)パーサー。これは、LL解析に対するLR解析の信じられないほどの力のヒントを与えます。出典:LALR()パーサーの作成者であるDr. F. DeRemerが教えるUCSCのコンパイラコース。
JoGusto 2015

1
優れたリソース!スライド、配布資料、プロジェクトも提供していただきありがとうございます。
P.ヒンカー

58

Josh Habermanは、彼の記事LL and LR Parsing Demystifiedで、LL解析はポーランド記法に直接対応するのに対し、LRは逆ポーランド記法に対応すると主張しています。PNとRPNの違いは、方程式のバイナリツリーをトラバースする順序です。

方程式の二分木

+ 1 * 2 3  // Polish (prefix) expression; pre-order traversal.
1 2 3 * +  // Reverse Polish (postfix) expression; post-order traversal.

ハーバーマンによれば、これはLLパーサーとLRパーサーの主な違いを示しています:

LLパーサーとLRパーサーの動作の主な違いは、LLパーサーが解析ツリーの前順トラバーサルを出力し、LRパーサーが後順トラバーサルを出力することです。

詳細な説明、例、および結論については、ハーバーマンの記事をご覧ください


9

LLはトップダウン方式を使用し、LRはボトムアップ方式を使用します。

プログラミング言語を解析する場合:

  • LLは、式を含む関数を含むソースコードを参照します。
  • LRは、関数に属する式を参照して、完全なソースを生成します。

6

LRと比較すると、LL解析はハンディキャップを持っています。以下は、LLパーサージェネレーターの悪夢である文法です。

Goal           -> (FunctionDef | FunctionDecl)* <eof>                  

FunctionDef    -> TypeSpec FuncName '(' [Arg/','+] ')' '{' '}'       

FunctionDecl   -> TypeSpec FuncName '(' [Arg/','+] ')' ';'            

TypeSpec       -> int        
               -> char '*' '*'                
               -> long                 
               -> short                   

FuncName       -> IDENTIFIER                

Arg            -> TypeSpec ArgName         

ArgName        -> IDENTIFIER 

FunctionDefは、 ';'まではFunctionDeclとまったく同じように見えます。または「{」が検出されました。

LLパーサーは2つのルールを同時に処理できないため、FunctionDefまたはFunctionDeclを選択する必要があります。しかし、どちらが正しいかを知るには、「;」を探す必要があります。または「{」。文法分析時には、先読み(k)は無限に見える。解析時には有限ですが、大きくなる可能性があります。

LRパーサーは2つのルールを同時に処理できるため、先読みする必要はありません。したがって、LALR(1)パーサージェネレーターは、この文法を簡単に処理できます。

入力コードを考える:

int main (int na, char** arg); 

int main (int na, char** arg) 
{

}

LRパーサーは、

int main (int na, char** arg)

「;」に遭遇するまで認識されているルールを気にすることなく または「{」。

認識されているルールを知る必要があるため、LLパーサーは「int」でハングアップします。したがって、「;」を探す必要があります。または「{」。

LLパーサーのもう1つの悪夢は、文法の中に再帰を残すことです。左再帰は文法では通常のことであり、LRパーサージェネレーターでは問題ありませんが、LLでは処理できません。

したがって、LLでは不自然な方法で文法を記述する必要があります。


0

左端の派生例: 文脈自由な文法Gは生成物を持っています

z→xXY(ルール:1)X→Ybx(ルール:2)Y→bY(ルール:3)Y→c(ルール:4)

文字列w = 'xcbxbc'を左端の微分で計算します。

z⇒xXY(ルール:1)⇒xYbxY(ルール:2)⇒xcbxY(ルール:4)⇒xcbxbY(ルール:3)⇒xcbxbc(ルール:4)


最も右側の導出例: K→aKK(ルール:1)A→b(ルール:2)

文字列w = 'aababbb'を最も適切な導出で計算します。

K⇒aKK(ルール:1)⇒aKb(ルール:2)⇒aaKKb(ルール:1)⇒aaKaKKb(ルール:1)⇒aaKaKbb(ルール:2)⇒aaKabbb(ルール:2)⇒aababbb(ルール:2)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.