it-swarm.com.de

Was ist der Unterschied zwischen LL- und LR-Analyse?

Kann mir jemand ein einfaches Beispiel für LL-Parsen im Vergleich zu LR-Parsen geben?

209
Creativity2345

Auf einer hohen Ebene besteht der Unterschied zwischen LL-Parsing und LR-Parsing darin, dass LL-Parser am Startsymbol beginnen und versuchen, Produktionen anzuwenden, um zur Zielzeichenfolge zu gelangen, während LR-Parser an der Zielzeichenfolge beginnen und versuchen, zum Start zurückzukehren Symbol.

Eine LL-Analyse ist eine Ableitung von links nach rechts. Das heißt, wir betrachten die Eingabesymbole von links nach rechts und versuchen, eine Ableitung ganz links zu konstruieren. Beginnen Sie dazu am Startsymbol und erweitern Sie das linke Nichtterminal wiederholt, bis Sie die Zielzeichenfolge erreichen. Eine LR-Analyse ist eine Ableitung von links nach rechts ganz rechts, dh, wir scannen von links nach rechts und versuchen, eine Ableitung ganz rechts zu konstruieren. Der Parser wählt fortlaufend eine Teilzeichenfolge der Eingabe aus und versucht, sie in ein Nichtterminal umzukehren.

Während einer LL-Analyse wählt der Parser kontinuierlich zwischen zwei Aktionen:

  1. Vorhersagen : Wählen Sie auf der Grundlage des linken Nichtterminals und einiger Lookahead-Token aus, welche Produktion angewendet werden soll, um näher an die Eingabezeichenfolge heranzukommen.
  2. Match : Ordnet das am weitesten links stehende erratene Terminalsymbol dem am weitesten links liegenden nicht verbrauchten Eingabesymbol zu.

Als Beispiel für diese Grammatik:

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

Dann gegeben die Zeichenfolge int + int + int, ein LL (2) -Parser (der zwei Lookahead-Token verwendet) würde die Zeichenfolge folgendermaßen analysieren:

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

Beachten Sie, dass wir in jedem Schritt das Symbol ganz links in unserer Produktion betrachten. Wenn es ein Terminal ist, passen wir es an, und wenn es kein Terminal ist, sagen wir voraus, wie es sein wird, indem wir eine der Regeln auswählen.

In einem LR-Parser gibt es zwei Aktionen:

  1. Shift : Fügt das nächste Eingabe-Token zur Berücksichtigung in einen Puffer ein.
  2. Reduzieren : Reduzieren Sie eine Sammlung von Terminals und Nicht-Terminals in diesem Puffer auf ein Nicht-Terminal, indem Sie eine Produktion umkehren.

Beispielsweise kann ein LR (1) -Parser (mit einem Token Lookahead) dieselbe Zeichenfolge wie folgt analysieren:

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

Die beiden von Ihnen erwähnten Parsing-Algorithmen (LL und LR) haben bekanntermaßen unterschiedliche Eigenschaften. LL-Parser lassen sich in der Regel leichter von Hand schreiben, sind jedoch weniger leistungsfähig als LR-Parser und akzeptieren eine viel kleinere Menge von Grammatiken als LR-Parser. LR-Parser gibt es in vielen Geschmacksrichtungen (LR (0), SLR (1), LALR (1), LR (1), IELR (1), GLR (0) usw.) und sie sind weitaus leistungsstärker. Sie sind in der Regel auch komplexer und werden fast immer von Tools wie yacc oder bison generiert. LL-Parser gibt es auch in vielen Varianten (einschließlich LL (*), das vom Tool ANTLR verwendet wird), obwohl LL (1) in der Praxis am häufigsten verwendet wird.

Als unverschämter Plug, wenn Sie mehr über LL- und LR-Parsing erfahren möchten, habe ich gerade einen Compilerkurs abgeschlossen und einige Handouts und Vortragsfolien zum Parsing auf der Kurswebsite. Ich würde mich freuen, auf eines von ihnen einzugehen, wenn Sie denken, dass es nützlich wäre.

445
templatetypedef

Josh Haberman behauptet in seinem Artikel LL- und LR-Analyse entmystifiziert , dass die LL-Analyse direkt der polnischen Notation entspricht, wohingegen LR mgekehrten polnischen Notation entspricht. Der Unterschied zwischen PN und RPN ist die Reihenfolge, in der der Binärbaum der Gleichung durchlaufen wird:

binary tree of an equation

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

Laut Haberman zeigt dies den Hauptunterschied zwischen LL- und LR-Parsern:

Der Hauptunterschied zwischen der Funktionsweise von LL- und LR-Parsern besteht darin, dass ein LL-Parser eine Durchquerung des Analysebaums vorbestellt und ein LR-Parser eine Durchquerung nachbestellt ausgibt.

Für die ausführliche Erklärung, Beispiele und Schlussfolgerungen lesen Sie Habermans Artikel .

52
msiemens

Der LL verwendet Top-Down, während der LR den Bottom-Up-Ansatz verwendet.

Wenn Sie eine Programmiersprache analysieren:

  • Das LL sieht einen Quellcode, der Funktionen enthält, die Ausdruck enthalten.
  • Der LR sieht Ausdruck, der zu Funktionen gehört, was die vollständige Quelle ergibt.
8
betontalpfa

Das Parsen von LL ist im Vergleich zu LR behindert. Hier ist eine Grammatik, die ein Albtraum für einen LL-Parsergenerator ist:

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

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

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

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

FuncName       -> IDENTIFIER                

Arg            -> TypeSpec ArgName         

ArgName        -> IDENTIFIER 

Ein FunctionDef sieht genauso aus wie ein FunctionDecl, bis das ';' oder '{' ist aufgetreten.

Ein LL-Parser kann nicht zwei Regeln gleichzeitig verarbeiten, daher muss er entweder FunctionDef oder FunctionDecl auswählen. Aber um zu wissen, was richtig ist, muss man nach einem ';' oder '{'. Zum Zeitpunkt der Grammatikanalyse scheint der Lookahead (k) unendlich zu sein. Zum Zeitpunkt des Parsens ist es endlich, könnte aber groß sein.

Ein LR-Parser muss nicht vorhergesehen werden, da er zwei Regeln gleichzeitig verarbeiten kann. Daher können LALR (1) -Parser-Generatoren diese Grammatik problemlos verarbeiten.

Mit dem eingegebenen Code:

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

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

}

Ein LR-Parser kann das analysieren

int main (int na, char** arg)

ohne sich darum zu kümmern, welche Regel erkannt wird, bis sie auf ein ';' trifft oder ein '{'.

Ein LL-Parser legt am 'int' auf, weil er wissen muss, welche Regel erkannt wird. Deshalb muss es nach einem ';' oder '{'.

Der andere Albtraum für LL-Parser ist die Rekursion in einer Grammatik. Linke Rekursion ist eine normale Sache in der Grammatik, kein Problem für einen LR-Parser-Generator, aber LL kann damit nicht umgehen.

Sie müssen also Ihre Grammatiken mit LL auf unnatürliche Weise schreiben.

1
Paul B Mann

Beispiel für die Ableitung ganz links: Eine kontextfreie Grammatik G enthält die Produktionen

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

Berechnen Sie den String w = "xcbxbc" mit der Ableitung ganz links.

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


Beispiel für die Ableitung ganz rechts: K → aKK (Regel: 1) A → b (Regel: 2)

Berechnen Sie den String w = "aababbb" mit der Ableitung ganz rechts.

K ⇒ aKK (Regel: 1) ⇒ aKb (Regel: 2) ⇒ aaKKb (Regel: 1) ⇒ aaKaKkb (Regel: 2) ⇒ aaKabbb (Regel: 2) ⇒ aababbb (Regel: 2)

0