I parser SLR, LALR e LR possono essere implementati utilizzando esattamente lo stesso macchinario azionato da tabella.
Fondamentalmente, l'algoritmo di analisi raccoglie il successivo token di input T e consulta lo stato corrente S (e le tabelle di lookahead, GOTO e riduzione associate) per decidere cosa fare:
- SHIFT: se la tabella corrente dice di SHIFT sul token T, la coppia (S, T) viene inserita nello stack di analisi, lo stato viene modificato in base a ciò che la tabella GOTO dice per il token corrente (ad esempio, GOTO (T) ), viene recuperato un altro token di input T 'e il processo si ripete
- RIDUZIONE: ogni stato ha 0, 1 o molte possibili riduzioni che potrebbero verificarsi nello stato. Se il parser è LR o LALR, il token viene verificato rispetto ai set di lookahead per tutte le riduzioni valide per lo stato. Se il token corrisponde a un set di lookahead per una riduzione per la regola grammaticale G = R1 R2 .. Rn, si verifica una riduzione dello stack e uno spostamento: viene chiamata l'azione semantica per G, lo stack viene estratto n (da Rn) volte, la coppia ( S, G) viene inserito nello stack, il nuovo stato S 'è impostato su GOTO (G) e il ciclo si ripete con lo stesso token T. Se il parser è un parser SLR, c'è al massimo una regola di riduzione per il stato e quindi l'azione di riduzione può essere eseguita alla cieca senza cercare di vedere quale riduzione si applica. E 'utile per un parser SLR di sapere se v'èuna riduzione o meno; questo è facile da capire se ogni stato registra esplicitamente il numero di riduzioni ad esso associate, e quel conteggio è comunque necessario per le versioni L (AL) R in pratica.
- ERRORE: se né SHIFT né REDUCE è possibile, viene dichiarato un errore di sintassi.
Quindi, se usano tutti lo stesso macchinario, qual è il punto?
Il valore presunto in SLR è la sua semplicità di implementazione; non è necessario scorrere le possibili riduzioni controllando i set di lookahead perché ce n'è al massimo uno, e questa è l'unica azione praticabile se non ci sono uscite SHIFT dallo stato. Quale riduzione si applica può essere attribuita specificamente allo stato, quindi il macchinario di analisi SLR non deve cercarla. In pratica, i parser L (AL) R gestiscono un insieme di linguaggi utilmente più ampio, ed è così poco lavoro extra da implementare che nessuno implementa SLR se non come esercizio accademico.
La differenza tra LALR e LR ha a che fare con il generatore di tabelle. I generatori di parser LR tengono traccia di tutte le possibili riduzioni da stati specifici e del loro preciso set di lookahead; si finisce con stati in cui ogni riduzione è associata al suo esatto lookahead impostato dal contesto sinistro. Questo tende a costruire insiemi di stati piuttosto ampi. I generatori di parser LALR sono disposti a combinare stati se le tabelle GOTO e le serie di lookhead per le riduzioni sono compatibili e non sono in conflitto; questo produce un numero di stati notevolmente inferiore, al prezzo di non essere in grado di distinguere certe sequenze di simboli che LR può distinguere. Quindi, i parser LR possono analizzare un insieme più ampio di linguaggi rispetto ai parser LALR, ma hanno tabelle di parser molto più grandi. In pratica, si possono trovare grammatiche LALR che sono abbastanza vicine ai linguaggi di destinazione che vale la pena ottimizzare la dimensione della macchina a stati;
Quindi: tutti e tre usano lo stesso macchinario. La reflex è "facile" nel senso che puoi ignorare un po 'della macchina ma non ne vale la pena. LR analizza un insieme più ampio di linguaggi, ma le tabelle di stato tendono ad essere piuttosto grandi. Ciò lascia LALR come scelta pratica.
Detto questo, vale la pena sapere che i parser GLR possono analizzare qualsiasi linguaggio senza contesto, usando macchinari più complicati ma esattamente le stesse tabelle (inclusa la versione più piccola usata da LALR). Ciò significa che GLR è strettamente più potente di LR, LALR e SLR; più o meno se puoi scrivere una grammatica BNF standard, GLR la analizzerà in base ad essa. La differenza nel meccanismo è che GLR è disposto a provare più analisi in caso di conflitti tra la tabella GOTO e / o gli insiemi di lookahead. (Il modo in cui GLR lo fa in modo efficiente è puro genio [non mio] ma non si adatterà a questo post SO).
Questo per me è un fatto estremamente utile. Costruisco analizzatori di programma e trasformatori e parser di codice sono necessari ma "poco interessanti"; il lavoro interessante è quello che fai con il risultato analizzato e quindi l'attenzione è concentrata sul lavoro di post-analisi. Usare GLR significa che posso costruire relativamente facilmente grammatiche funzionanti, rispetto all'hacking di una grammatica per entrare nella forma utilizzabile LALR. Questo è molto importante quando si cerca di trattare con linguaggi non accademici come C ++ o Fortran, dove hai letteralmente bisogno di migliaia di regole per gestire bene l'intera lingua e non vuoi passare la vita cercando di hackerare le regole grammaticali per soddisfare i limiti di LALR (o anche LR).
Come una sorta di famoso esempio, il C ++ è considerato estremamente difficile da analizzare ... dai ragazzi che eseguono l'analisi LALR. C ++ è semplice da analizzare usando i macchinari GLR usando praticamente le regole fornite nel retro del manuale di riferimento di C ++. (Ho proprio un analizzatore di questo tipo e gestisce non solo vanilla C ++, ma anche una varietà di dialetti del fornitore. Questo è possibile solo in pratica perché stiamo usando un parser GLR, IMHO).
[EDIT novembre 2011: abbiamo esteso il nostro parser per gestire tutto il C ++ 11. GLR lo ha reso molto più facile da fare. EDIT Agosto 2014: ora gestisce tutto il C ++ 17. Niente si è rotto o è peggiorato, GLR è ancora il miagolio del gatto.]