I parser LR non possono gestire regole grammaticali ambigue, in base alla progettazione. (Ha reso la teoria più semplice negli anni '70, quando le idee venivano elaborate).
C e C ++ consentono entrambi la seguente dichiarazione:
x * y ;
Ha due analisi diverse:
- Può essere la dichiarazione di y, come puntatore per digitare x
- Può essere una moltiplicazione di xey, gettando via la risposta.
Ora, potresti pensare che quest'ultimo sia stupido e debba essere ignorato. La maggior parte sarebbe d'accordo con te; tuttavia, ci sono casi in cui potrebbe avere un effetto collaterale (ad esempio, se la moltiplicazione è sovraccarica). ma non è questo il punto. Il punto è che ci sono due analisi diverse, e quindi un programma può significare cose diverse a seconda di come dovrebbe essere analizzato.
Il compilatore deve accettare quello appropriato nelle circostanze appropriate e in assenza di altre informazioni (ad esempio, conoscenza del tipo di x) deve raccogliere entrambi per decidere in seguito cosa fare. Quindi una grammatica deve permetterlo. E questo rende la grammatica ambigua.
Quindi l'analisi pura di LR non può gestirlo. Né molti altri generatori di parser ampiamente disponibili, come Antlr, JavaCC, YACC, o Bison tradizionale, o persino parser in stile PEG, possono essere usati in modo "puro".
Ci sono molti casi più complicati (l'analisi della sintassi del modello richiede un lookahead arbitrario, mentre LALR (k) può guardare avanti alla maggior parte dei token k), ma solo un solo controesempio richiede di abbattere puro l'analisi LR (o gli altri).
La maggior parte dei veri parser C / C ++ gestisce questo esempio usando un qualche tipo di parser deterministico con un hack aggiuntivo: si intrecciano con l'analisi della raccolta di tabelle di simboli ... in modo che quando si incontra "x", il parser sa se x è un tipo oppure no, e può quindi scegliere tra i due potenziali analisi. Ma un parser che fa questo non è privo di contesto e i parser LR (quelli puri, ecc.) Sono (nella migliore delle ipotesi) liberi dal contesto.
Si può imbrogliare e aggiungere controlli semantici di riduzione del tempo per regola nei parser LR per fare questo chiarimento. (Questo codice spesso non è semplice). La maggior parte degli altri tipi di parser ha alcuni mezzi per aggiungere controlli semantici in vari punti dell'analisi, che possono essere usati per fare questo.
E se trucchi abbastanza, puoi far funzionare i parser LR per C e C ++. I ragazzi del GCC lo hanno fatto per un po ', ma hanno rinunciato all'analisi codificata a mano, penso perché volevano una migliore diagnostica degli errori.
C'è un altro approccio, tuttavia, che è bello e pulito e analizza C e C ++ bene senza alcun hackery nella tabella dei simboli: i parser GLR . Questi sono parser senza contesto completo (con lookahead effettivamente infinito). I parser GLR accettano semplicemente entrambe le analisi, producendo un "albero" (in realtà un grafico aciclico diretto che è per lo più simile ad un albero) che rappresenta l'analisi ambigua. Un passaggio post-analisi può risolvere le ambiguità.
Usiamo questa tecnica nei front-end C e C ++ per il nostro Tookit di reingegnerizzazione del software DMS (a giugno 2017 gestiscono il C ++ 17 completo nei dialetti MS e GNU). Sono stati usati per elaborare milioni di linee di grandi sistemi C e C ++, con analisi complete e precise che producono AST con dettagli completi del codice sorgente. (Vedi l'AST per l'analisi più irritante del C ++. )