Qualcuno può darmi un semplice esempio di analisi LL contro analisi LR?
Qualcuno può darmi un semplice esempio di analisi LL contro analisi LR?
Risposte:
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:
Ad esempio, data questa grammatica:
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:
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 yacc
o bison
. Anche i parser LL sono disponibili in molti gusti (incluso LL (*), che viene utilizzato dallo ANTLR
strumento), 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.
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:
+ 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 .
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:
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.
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)