回答:
高レベルでは、LL解析とLR解析の違いは、LLパーサーが開始記号から始まり、プロダクションを適用してターゲット文字列に到達しようとするのに対し、LRパーサーはターゲット文字列から始まり、先頭に戻って到達しようとすることです。シンボル。
LL解析は、左から右、左端の派生です。つまり、入力シンボルを左から右に検討し、左端の派生を構築しようとします。これは、開始記号から開始し、ターゲット文字列に到達するまで左端の非終端記号を繰り返し展開することによって行われます。LR解析は、左から右、右端の派生です。つまり、左から右にスキャンして、右端の派生を構築しようとします。パーサーは入力の部分文字列を継続的に選択し、それを非終端に戻そうとします。
LL解析中、パーサーは2つのアクションから継続的に選択します。
例として、この文法を考えると:
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つのアクションがあります。
例として、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サイトに構文解析に関する配布資料と講義スライドがあります。あなたがそれが有用だと思うなら、私はそれらのどれかについて詳しく述べていただければ幸いです。
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パーサーが後順トラバーサルを出力することです。
LLはトップダウン方式を使用し、LRはボトムアップ方式を使用します。
プログラミング言語を解析する場合:
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では不自然な方法で文法を記述する必要があります。
左端の派生例: 文脈自由な文法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)