Qual è la differenza tra analisi LL e LR?


Risposte:


484

Ad un livello elevato, la differenza tra analisi LL e analisi LR è che i parser LL iniziano dal simbolo iniziale e provano ad applicare le produzioni per arrivare alla stringa di destinazione, mentre i parser LR iniziano alla stringa di destinazione e tentano di tornare all'inizio simbolo.

Un'analisi LL è una derivazione da sinistra a destra, all'estrema sinistra. Cioè, consideriamo i simboli di input da sinistra a destra e proviamo a costruire una derivazione all'estrema sinistra. Questo viene fatto iniziando dal simbolo iniziale e espandendo ripetutamente il nonterminale più a sinistra fino ad arrivare alla stringa di destinazione. Un'analisi LR è una derivazione da sinistra a destra, all'estrema destra, il che significa che scansioniamo da sinistra a destra e tentiamo di costruire una derivazione all'estrema destra. Il parser seleziona continuamente una sottostringa dell'input e tenta di invertirla in un non terminale.

Durante un'analisi LL, il parser sceglie continuamente tra due azioni:

  1. Previsione : in base al numero non finale più a sinistra e al numero di token lookahead, scegliere quale produzione deve essere applicata per avvicinarsi alla stringa di input.
  2. Abbina : abbina il simbolo del terminale indovinato più a sinistra con il simbolo di input non consumato più a sinistra.

Ad esempio, data questa grammatica:

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

Quindi, data la stringa int + int + int, un parser LL (2) (che utilizza due token di lookahead) analizzerebbe la stringa come segue:

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

Si noti che in ogni passaggio guardiamo il simbolo più a sinistra nella nostra produzione. Se è un terminale, lo abbiniamo e se è un non terminale, prevediamo quale sarà scegliendo una delle regole.

In un parser LR, ci sono due azioni:

  1. Maiusc : aggiungere il token successivo di input a un buffer per considerazione.
  2. Riduci : riduci una raccolta di terminali e non terminali in questo buffer ad alcuni non terminali invertendo una produzione.

Ad esempio, un parser LR (1) (con un token di lookahead) potrebbe analizzare la stessa stringa come segue:

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

I due algoritmi di analisi menzionati (LL e LR) hanno caratteristiche diverse. I parser LL tendono a essere più facili da scrivere a mano, ma sono meno potenti dei parser LR e accettano un set di grammatiche molto più piccolo rispetto ai parser LR. I parser LR sono disponibili in molti gusti (LR (0), SLR (1), LALR (1), LR (1), IELR (1), GLR (0), ecc.) E sono molto più potenti. Inoltre tendono ad avere molto più complessi e sono quasi sempre generati da strumenti come yacco bison. Anche i parser LL sono disponibili in molti gusti (incluso LL (*), che viene utilizzato dallo ANTLRstrumento), sebbene in pratica LL (1) sia il più utilizzato.

Come spina spudorata, se vuoi saperne di più sull'analisi LL e LR, ho appena finito di insegnare un corso di compilatori e ho delle dispense e delle diapositive di lezione sull'analisi sul sito web del corso. Sarei felice di approfondire uno di questi se pensi che sarebbe utile.


40
Le diapositive delle tue lezioni sono fenomenali, facilmente la spiegazione più divertente che io abbia mai visto :) Questo è il genere di cose che in realtà stimola interessi.
kizzx2,

1
Devo commentare anche le diapositive! Passandoli tutti adesso. Aiuta molto! Grazie!
kornfridge,

Mi piacciono molto anche le diapositive. Suppongo che potresti pubblicare la versione non Windows dei file di progetto (e il file scanner.l, per pp2)? :)
Erik P.

1
L'unica cosa che posso contribuire all'eccellente risposta di sintesi di Matt è che qualsiasi grammatica che può essere analizzata da un parser LL (k) (ovvero, guardando in avanti i terminali "k" per decidere sulla prossima azione di analisi) può essere analizzata da un LR ( 1) parser. Questo dà un indizio sull'incredibile potenza dell'analisi LR sull'analisi LL. Fonte: corso di compilazione presso UCSC tenuto dal Dr. F. DeRemer, creatore di parser LALR ().
JoGusto,

1
Ottima risorsa! Grazie per aver fornito anche diapositive, volantini, progetti.
P. Hinker,

58

Josh Haberman nel suo articolo LL e LR Parsing Demysified afferma che l'analisi di LL corrisponde direttamente alla notazione polacca , mentre LR corrisponde alla notazione polacca inversa . La differenza tra PN e RPN è l'ordine di attraversamento dell'albero binario dell'equazione:

albero binario di un'equazione

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

Secondo Haberman, questo illustra la principale differenza tra i parser LL e LR:

La differenza principale tra il funzionamento dei parser LL e LR è che un parser LL genera un attraversamento pre-ordine dell'albero di analisi e un parser LR genera un attraversamento post-ordine.

Per una spiegazione approfondita, esempi e conclusioni consulta l' articolo di Haberman .


9

L'LL utilizza dall'alto verso il basso, mentre l'LR utilizza l'approccio dal basso verso l'alto.

Se analizzi un linguaggio di programmazione:

  • LL vede un codice sorgente, che contiene funzioni, che contiene espressione.
  • L'LR vede l'espressione, che appartiene alle funzioni, che risulta la fonte completa.

6

L'analisi LL è handicappata rispetto a LR. Ecco una grammatica che è un incubo per un generatore di parser 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 

Un FunctionDef si presenta esattamente come un FunctionDecl fino a ";" o "{" è stato rilevato.

Un parser LL non può gestire due regole contemporaneamente, quindi deve scegliere FunctionDef o FunctionDecl. Ma per sapere qual è corretto bisogna cercare un ';' o '{'. Al momento dell'analisi grammaticale, il lookahead (k) sembra essere infinito. Al momento dell'analisi è finito, ma potrebbe essere grande.

Un parser LR non deve guardare avanti, perché può gestire due regole contemporaneamente. Quindi i generatori di parser LALR (1) possono gestire questa grammatica con facilità.

Dato il codice di input:

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

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

}

Un parser LR può analizzare il file

int main (int na, char** arg)

senza preoccuparsi di quale regola venga riconosciuta fino a quando non incontra un ';' o un '{'.

Un parser LL viene bloccato in "int" perché deve sapere quale regola viene riconosciuta. Pertanto deve cercare un ';' o '{'.

L'altro incubo per i parser LL è la ricorsione in una grammatica. La ricorsione a sinistra è una cosa normale nelle grammatiche, nessun problema per un generatore di parser LR, ma LL non può gestirlo.

Quindi devi scrivere le tue grammatiche in modo innaturale con LL.


0

Sinistra Most derivation Esempio: una grammatica G che è senza contesto ha le produzioni

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

Calcola la stringa w = 'xcbxbc' con la maggior parte della derivazione a sinistra.

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


Destra Più derivazione Esempio: K → aKK (Regola: 1) A → b (Regola: 2)

Calcola la stringa w = 'aababbb' con la derivazione più a destra.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.