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


Antworten:


483

Auf hoher 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, wieder am Start anzukommen Symbol.

Eine LL-Analyse ist eine Ableitung von links nach rechts ganz links. Das heißt, wir betrachten die Eingabesymbole von links nach rechts und versuchen, eine Ableitung ganz links zu konstruieren. Dies geschieht, indem Sie am Startsymbol beginnen und das Nichtterminal ganz links wiederholt erweitern, bis wir zur Zielzeichenfolge gelangen. Eine LR-Analyse ist eine Ableitung von links nach rechts ganz rechts. Dies bedeutet, dass wir von links nach rechts scannen und versuchen, eine Ableitung ganz rechts zu erstellen. Der Parser wählt kontinuierlich eine Teilzeichenfolge der Eingabe aus und versucht, sie auf ein Nichtterminal zurückzusetzen.

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

  1. Vorhersagen : Wählen Sie anhand des Nicht-Terminals ganz links und einiger Lookahead-Token aus, welche Produktion angewendet werden soll, um näher an die Eingabezeichenfolge heranzukommen.
  2. Übereinstimmung : Ordnen Sie das am weitesten links erratene Terminalsymbol dem am weitesten links liegenden nicht verbrauchten Symbol der Eingabe zu.

Als Beispiel für diese Grammatik:

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

Dann würde int + int + intein LL (2) -Parser (der zwei Lookahead-Token verwendet) die Zeichenfolge wie folgt 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 sich um ein Terminal handelt, passen wir es an, und wenn es sich um ein Nicht-Terminal handelt, sagen wir voraus, wie es aussehen wird, indem wir eine der Regeln auswählen.

In einem LR-Parser gibt es zwei Aktionen:

  1. Shift : Fügen Sie 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 durch Umkehren einer Produktion auf ein Nicht-Terminal.

Beispielsweise könnte 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

Es ist bekannt, dass die beiden von Ihnen erwähnten Parsing-Algorithmen (LL und LR) unterschiedliche Eigenschaften aufweisen. LL-Parser sind in der Regel einfacher von Hand zu schreiben, aber sie sind weniger leistungsfähig als LR-Parser und akzeptieren einen viel kleineren Satz 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 sind weitaus leistungsfähiger. Sie sind in der Regel auch viel komplexer und werden fast immer von Tools wie yaccoder generiert bison. LL-Parser gibt es auch in vielen Geschmacksrichtungen (einschließlich LL (*), das vom ANTLRTool verwendet wird), obwohl LL (1) in der Praxis am häufigsten verwendet wird.

Als schamloser Plug, wenn Sie mehr über das Parsen von LL und LR erfahren möchten, habe ich gerade einen Compilerkurs unterrichtet und auf der Kurswebsite einige Handouts und Vorlesungsfolien zum Parsen bereitgestellt. Ich würde gerne auf einen von ihnen näher eingehen, wenn Sie denken, dass es nützlich wäre.


40
Ihre Vorlesungsfolien sind phänomenal, leicht die lustigste Erklärung, die ich je gesehen habe :) So etwas weckt tatsächlich Interessen.
kizzx2

1
Ich muss auch die Folien kommentieren! Ich gehe jetzt alle durch. Hilft sehr! Vielen Dank!
Kornfridge

Ich genieße die Folien auch sehr. Ich nehme nicht an, dass Sie die Nicht-Windows-Version der Projektdateien (und die Datei scanner.l für pp2) veröffentlichen könnten. :)
Erik P.

1
Das einzige, was ich zu Matts hervorragender zusammenfassender Antwort beitragen kann, ist, dass jede Grammatik, die von einem LL (k) -Parser analysiert werden kann (dh mit Blick auf "k" -Terminals, um über die nächste Analyseaktion zu entscheiden), von einem LR analysiert werden kann ( 1) Parser. Dies gibt einen Hinweis auf die unglaubliche Kraft der LR-Analyse gegenüber der LL-Analyse. Quelle: Compilerkurs an der UCSC, unterrichtet von Dr. F. DeRemer, dem Erfinder von LALR () -Parsern.
JoGusto

1
Ausgezeichnete Ressource! Vielen Dank für die Bereitstellung von Folien, Handouts und Projekten.
P. Hinker

58

Josh Haberman behauptet in seinem Artikel LL und LR Parsing Demystified , dass LL Parsing direkt der polnischen Notation entspricht , während LR der umgekehrten polnischen Notation entspricht . Der Unterschied zwischen PN und RPN ist die Reihenfolge des Durchlaufens des Binärbaums der Gleichung:

binärer Baum einer Gleichung

+ 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 vor der Bestellung und ein LR-Parser eine Durchquerung nach der Bestellung ausgibt.

Eine ausführliche Erklärung, Beispiele und Schlussfolgerungen finden Sie in Habermans Artikel .


9

Das LL verwendet Top-Down, während das 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 einen Ausdruck, der zu Funktionen gehört und die vollständige Quelle ergibt.

6

Die LL-Analyse ist im Vergleich zu LR behindert. Hier ist eine Grammatik, die für einen LL-Parser-Generator ein Albtraum 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 bis zum ';' genau wie ein FunctionDecl aus oder '{' wird angetroffen.

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 es nach einem ';' suchen. oder '{'. Zur Zeit der Grammatikanalyse scheint der Lookahead (k) unendlich zu sein. Zur Analysezeit ist es endlich, kann aber groß sein.

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

Angesichts des Eingabecodes:

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 ';' oder ein '{'.

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

Der andere Albtraum für LL-Parser ist die Rekursion in einer Grammatik. Linke Rekursion ist eine normale Sache in Grammatiken, 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.


0

Ableitung ganz links Beispiel: 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: 1) ⇒ aaKaKbb (Regel: 2) ⇒ aaKabbb (Regel: 2) ⇒ aababbb (Regel: 2)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.