web-dev-qa-db-ja.com

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

LL解析とLR解析の簡単な例を教えてもらえますか?

209
Creativity2345

大まかに言うと、LL構文解析とLR構文解析の違いは、LL構文解析プログラムは開始記号から開始し、プロダクションを適用してターゲット文字列に到達しようとするのに対して、LR構文解析プログラムはターゲット文字列から開始して開始文字列に戻りますシンボル。

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

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

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

例として、次の文法を考えます:

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

次に、文字列int + int + intを指定すると、LL(2)パーサー(lookaheadの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. Reduce:プロダクションを逆にすることにより、このバッファー内の端末および非端末のコレクションを非端末に戻します。

例として、LR(1)パーサー(lookaheadの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)など)があり、はるかに強力です。また、より複雑になる傾向があり、ほとんどの場合、yaccbisonなどのツールによって生成されます。 LLパーサーにも多くのフレーバーがあります( ANTLR ツールで使用されるLL(*)を含む)が、実際にはLL(1)が最も広く使用されています。

恥知らずのプラグインとして、LLおよびLR解析の詳細を知りたい場合は、コンパイラーコースの指導を終えたばかりで、コースのWebサイトに 解析に関するいくつかの配布資料と講義スライド があります。役に立つと思われる場合は、それらのいずれかについて詳しく説明させていただきます。

445
templatetypedef

Josh Habermanの記事 LLおよびLR Parsing Demystified は、LL解析が ポーランド表記法 に直接対応するのに対し、LRは 逆ポーランド表記法 に対応すると主張しています。 PNとRPNの違いは、方程式の二分木を走査する順序です。

binary tree of an equation

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

Habermanによると、これはLLパーサーとLRパーサーの主な違いを示しています。

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

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

52
msiemens

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

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

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

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を使用して不自然な方法で文法を作成する必要があります。

1
Paul B Mann

左端の派生例:文脈自由な文法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)

0