Nella progettazione del compilatore, perché la ricorsione sinistra dovrebbe essere eliminata nelle grammatiche? Sto leggendo che è perché può causare una ricorsione infinita, ma non è vero anche per una corretta grammatica ricorsiva?
Nella progettazione del compilatore, perché la ricorsione sinistra dovrebbe essere eliminata nelle grammatiche? Sto leggendo che è perché può causare una ricorsione infinita, ma non è vero anche per una corretta grammatica ricorsiva?
Risposte:
Le grammatiche ricorsive a sinistra non sono necessariamente una cosa negativa. Queste grammatiche possono essere facilmente analizzate usando una pila per tenere traccia delle frasi già analizzate, come nel caso del parser LR .
Ricordiamo che una regola ricorsiva a sinistra di una grammatica CF la forma:
con un elemento di V e β un elemento di V ∪ Σ . (Vedi la definizione formale completa per la tupla ( V , Σ , R , S ) lì ).
Ogni volta che un nuovo terminale viene ricevuto dal parser grammaticale (dal lexer), questo terminale viene spinto in cima allo stack: questa operazione è chiamata shift .
Ogni volta che il lato destro di una regola viene associato da un gruppo di elementi consecutivi nella parte superiore dello stack, questo gruppo viene sostituito da un singolo elemento che rappresenta la frase appena trovata. Questa sostituzione si chiama riduzione .
Con le giuste grammatiche ricorsive, lo stack può crescere indefinitamente fino a quando si verifica una riduzione, limitando così in modo piuttosto drammatico le possibilità di analisi. Tuttavia, quelli a sinistra ricorsivi consentiranno al compilatore di generare riduzioni prima (in effetti, il più presto possibile). Vedere la voce di Wikipedia per ulteriori informazioni.
Considera questa regola:
example : 'a' | example 'b' ;
Ora considera un parser LL che cerca di abbinare una stringa non corrispondente come 'b'
a questa regola. Dal momento 'a'
che non corrisponde, proverà ad abbinare example 'b'
. Ma per farlo, deve corrispondere example
... che è quello che stava cercando di fare in primo luogo. Potrebbe rimanere bloccato cercando per sempre di vedere se può corrispondere, perché cerca sempre di abbinare lo stesso flusso di token alla stessa regola.
Per evitarlo, dovresti o analizzare da destra (il che è abbastanza raro, per quanto ho visto, e farebbe invece la giusta ricorsione il problema), limitare artificialmente la quantità di nidificazione consentita, o abbinare un token prima dell'inizio della ricorsione, quindi c'è sempre un caso base (vale a dire, dove tutti i token sono stati consumati e non c'è ancora alcuna corrispondenza completa). Poiché una regola ricorsiva a destra fa già la terza, non ha lo stesso problema.
(So che questa domanda è piuttosto vecchia ormai, ma nel caso in cui altre persone abbiano la stessa domanda ...)
Stai chiedendo nel contesto di parser di discesa ricorsivi? Ad esempio, per la grammatica expr:: = expr + term | term
, perché qualcosa del genere (lasciato ricorsivo):
// expr:: = expr + term
expr() {
expr();
if (token == '+') {
getNextToken();
}
term();
}
è problematico, ma non questo (giusto ricorsivo)?
// expr:: = term + expr
expr() {
term();
if (token == '+') {
getNextToken();
expr();
}
}
Sembra che entrambe le versioni di expr()
call stesse. Ma la differenza importante è il contesto, ovvero il token corrente quando viene effettuata quella chiamata ricorsiva.
Nel caso ricorsivo a sinistra, si expr()
chiama continuamente con lo stesso token e non vengono fatti progressi. Nel caso ricorsivo corretto, consuma parte dell'input nella chiamata a term()
e il token PLUS prima di raggiungere la chiamata expr()
. Quindi, a questo punto, la chiamata ricorsiva può chiamare termine e quindi terminare prima di raggiungere nuovamente il test if.
Ad esempio, considera l'analisi di 2 + 3 + 4. Il parser ricorsivo sinistro chiama expr()
all'infinito mentre è bloccato sul primo token, mentre il parser ricorsivo destro consuma "2 +" prima di chiamare expr()
nuovamente. La seconda chiamata expr()
corrisponde a "3 +" e chiama expr()
con solo i 4 rimasti. Il 4 corrisponde a un termine e l'analisi termina senza ulteriori chiamate a expr()
.
Dal manuale Bison:
"Qualsiasi tipo di sequenza può essere definita utilizzando la ricorsione a sinistra o la ricorsione a destra, ma dovresti sempre usare la ricorsione a sinistra , perché può analizzare una sequenza di qualsiasi numero di elementi con spazio di stack limitato. La ricorsione a destra utilizza lo spazio nello stack Bison in proporzione al numero di elementi nella sequenza, perché tutti gli elementi devono essere spostati sulla pila prima che la regola possa essere applicata anche una sola volta. Vedi Algoritmo del parser del bisonte, per ulteriori spiegazioni di questo. "
http://www.gnu.org/software/bison/manual/html_node/Recursion.html
Quindi dipende dall'algoritmo del parser, ma come indicato in altre risposte, alcuni parser potrebbero semplicemente non funzionare con la ricorsione a sinistra