Los analizadores LR no pueden manejar reglas gramaticales ambiguas, por diseño. (Facilitó la teoría en la década de 1970, cuando las ideas se estaban elaborando).
C y C ++ permiten la siguiente declaración:
x * y ;
Tiene dos análisis diferentes:
- Puede ser la declaración de y, como puntero para escribir x
- Puede ser una multiplicación de x e y, tirando la respuesta.
Ahora, podrías pensar que este último es estúpido y debería ser ignorado. La mayoría estaría de acuerdo contigo; sin embargo, hay casos en los que podría tener un efecto secundario (por ejemplo, si la multiplicación está sobrecargada). Pero ese no es el punto. El punto es que hay son dos análisis sintácticos diferentes, y por lo tanto un programa puede significar cosas diferentes dependiendo de cómo esto debería haber sido analizado.
El compilador debe aceptar el apropiado bajo las circunstancias apropiadas, y en ausencia de cualquier otra información (por ejemplo, conocimiento del tipo de x) debe recopilar ambos para decidir más adelante qué hacer. Por lo tanto, una gramática debe permitir esto. Y eso hace que la gramática sea ambigua.
Por lo tanto, el análisis LR puro no puede manejar esto. Tampoco pueden utilizarse de manera "pura" muchos otros generadores de analizadores disponibles, como Antlr, JavaCC, YACC o Bison tradicional, o incluso analizadores de estilo PEG.
Hay muchos casos más complicados (la sintaxis de la plantilla de análisis requiere una búsqueda arbitraria anticipada, mientras que LALR (k) puede mirar hacia adelante en la mayoría de los tokens k), pero solo se necesita un contraejemplo para derribar el análisis LR puro (o los otros).
La mayoría de los analizadores reales de C / C ++ manejan este ejemplo utilizando algún tipo de analizador determinista con un truco adicional: entrelazan el análisis con la colección de tablas de símbolos ... de modo que cuando se encuentra "x", el analizador sabe si x es un tipo o no, y así puede elegir entre los dos posibles análisis. Pero un analizador que hace esto no está libre de contexto, y los analizadores LR (los puros, etc.) están (en el mejor de los casos) libres de contexto.
Uno puede hacer trampa y agregar controles semánticos de tiempo de reducción por regla en los analizadores LR para hacer esta desambiguación. (Este código a menudo no es simple). La mayoría de los otros tipos de analizadores tienen algunos medios para agregar comprobaciones semánticas en varios puntos del análisis, que pueden usarse para hacer esto.
Y si hace trampa lo suficiente, puede hacer que los analizadores LR funcionen para C y C ++. Los chicos de GCC lo hicieron por un tiempo, pero lo abandonaron por el análisis codificado a mano, creo que porque querían mejores diagnósticos de errores.
Sin embargo, hay otro enfoque, que es agradable y limpio y analiza C y C ++ muy bien sin ninguna piratería de la tabla de símbolos: analizadores GLR . Estos son analizadores completos sin contexto (que tienen efectivamente anticipación infinita). Los analizadores GLR simplemente aceptan ambos analizadores , produciendo un "árbol" (en realidad, un gráfico acíclico dirigido que es principalmente un árbol) que representa el análisis ambiguo. Un pase posterior al análisis puede resolver las ambigüedades.
Utilizamos esta técnica en las interfaces C y C ++ para nuestro token de reingeniería de software DMS (a partir de junio de 2017, estos manejan C ++ 17 completo en dialectos MS y GNU). Se han utilizado para procesar millones de líneas de grandes sistemas C y C ++, con análisis completos y precisos que producen AST con detalles completos del código fuente. (Vea el AST para el análisis más irritante de C ++ ) .