Parece que GCC y LLVM-Clang están utilizando analizadores de descenso recursivos escritos a mano , y no análisis de abajo hacia arriba generados por máquina, basados en Bison-Flex.
¿Podría alguien aquí confirmar que este es el caso? Y si es así, ¿por qué los marcos de compilación convencionales usan analizadores escritos a mano?
Actualización : blog interesante sobre este tema aquí
Respuestas:
Si:
GCC usó un analizador yacc (bisonte) una vez, pero fue reemplazado por un analizador de descenso recursivo escrito a mano en algún momento de la serie 3.x: consulte http://gcc.gnu.org/wiki/New_C_Parser para enlaces a envíos de parches relevantes.
Clang también utiliza un analizador de descenso recursivo escrito a mano: consulte la sección "Un analizador unificado único para C, Objective C, C ++ y Objective C ++" cerca del final de http://clang.llvm.org/features.html .
fuente
foo * bar;
podría analizar como una expresión de multiplicación (con el resultado sin usar) o una declaración de una variablebar
con tipo pointer-to-foo
. Cuál es el correcto depende de si untypedef
forfoo
está dentro del alcance en ese momento, lo cual no es algo que pueda determinarse con cualquier cantidad de anticipación. Pero eso solo significa que el analizador sintáctico descendente recursivo necesita algo de maquinaria adicional desagradable para manejar eso.Hay un teorema popular que dice que C es difícil de analizar y C ++ esencialmente imposible.
No es verdad
Lo que es cierto es que C y C ++ son bastante difíciles de analizar utilizando analizadores LALR (1) sin piratear la maquinaria de análisis y enredar los datos de la tabla de símbolos. De hecho, GCC solía analizarlos, usando YACC y hacker adicional como este, y sí, era feo. Ahora GCC usa analizadores escritos a mano, pero aún con el hacker de tabla de símbolos. La gente de Clang nunca intentó utilizar generadores de analizadores automáticos; AFAIK, el analizador de Clang siempre ha sido un descenso recursivo codificado a mano.
Lo que es cierto es que C y C ++ son relativamente fáciles de analizar con analizadores generados automáticamente más fuertes, por ejemplo, analizadores GLR , y no necesita ningún truco. El analizador Elsa C ++ es un ejemplo de esto. Nuestro Front End de C ++ es otro (al igual que todos nuestros front-end de "compiladores", GLR es una tecnología de análisis bastante maravillosa).
Nuestra interfaz de C ++ no es tan rápida como la de GCC, y ciertamente más lenta que la de Elsa; hemos puesto poca energía en ajustarlo con cuidado porque tenemos otros problemas más urgentes (no obstante, se ha utilizado en millones de líneas de código C ++). Es probable que Elsa sea más lenta que GCC simplemente porque es más general. Dadas las velocidades del procesador en estos días, estas diferencias pueden no importar mucho en la práctica.
Pero los "compiladores reales" que se distribuyen ampliamente hoy tienen sus raíces en compiladores de hace 10 o 20 años o más. Entonces, las ineficiencias importaban mucho más, y nadie había oído hablar de los analizadores GLR, por lo que la gente hizo lo que sabía hacer. Clang es ciertamente más reciente, pero los teoremas populares conservan su "capacidad de persuasión" durante mucho tiempo.
Ya no tienes que hacerlo de esa manera. Puede usar GLR y otros analizadores como interfaces de manera muy razonable, con una mejora en la capacidad de mantenimiento del compilador.
Lo que es cierto, es que conseguir una gramática que coincide con el comportamiento de su vecindario amigable compilador es difícil. Si bien prácticamente todos los compiladores de C ++ implementan (la mayoría) del estándar original, también tienden a tener muchas extensiones de esquinas oscuras, por ejemplo, especificaciones de DLL en compiladores de MS, etc. Si tiene un motor de análisis sólido, puede dedicar su tiempo a intentar obtener la gramática final para que coincida con la realidad, en lugar de intentar modificar su gramática para que coincida con las limitaciones de su generador de analizador sintáctico.
EDITAR noviembre de 2012: desde que escribí esta respuesta, hemos mejorado nuestra interfaz de C ++ para manejar C ++ 11 completo, incluidos los dialectos variantes ANSI, GNU y MS. Si bien había muchas cosas adicionales, no tenemos que cambiar nuestro motor de análisis; acabamos de revisar las reglas gramaticales. Nosotros nos tenemos que cambiar el análisis semántico; C ++ 11 es semánticamente muy complicado, y este trabajo ahoga el esfuerzo para hacer que el analizador se ejecute.
EDITAR Febrero de 2015: ... ahora maneja C ++ 14 completo. (Consulte obtener AST legible por humanos a partir del código c ++ para análisis GLR de un simple fragmento de código, y el infame "análisis más molesto" de C ++).
EDITAR Abril de 2017: ahora maneja (borrador) C ++ 17.
fuente
foo * bar;
ambigüedad?El analizador de Clang es un analizador de descenso recursivo escrito a mano, al igual que varios otros interfaces de C y C ++ de código abierto y comerciales.
Clang usa un analizador de descenso recursivo por varias razones:
En general, para un compilador de C ++, simplemente no importa mucho: la parte de análisis de C ++ no es trivial, pero sigue siendo una de las partes más fáciles, por lo que vale la pena mantenerlo simple. El análisis semántico, en particular la búsqueda de nombres, la inicialización, la resolución de sobrecarga y la creación de instancias de plantillas, es un orden de magnitud más complicado que el análisis. Si desea una prueba, consulte la distribución de código y confirmaciones en el componente "Sema" de Clang (para análisis semántico) frente a su componente "Parse" (para análisis).
fuente
El analizador de gcc está escrito a mano. . Sospecho lo mismo para el clang. Probablemente esto se deba a algunas razones:
Probablemente este no sea un caso de síndrome "no inventado aquí", sino más bien en la línea de "no había nada optimizado específicamente para lo que necesitábamos, así que escribimos el nuestro".
fuente
¡Respuestas extrañas ahí!
Las gramáticas C / C ++ no están libres de contexto. Son sensibles al contexto debido a la barra Foo *; ambigüedad. Tenemos que construir una lista de typedefs para saber si Foo es un tipo o no.
Ira Baxter: No veo el sentido de lo de GLR. ¿Por qué construir un árbol de análisis que comprende ambigüedades? Analizar significa resolver ambigüedades, construir el árbol de sintaxis. Resuelve estas ambigüedades en una segunda pasada, por lo que esto no es menos feo. Para mí es mucho más feo ...
Yacc es un generador de analizador sintáctico LR (1) (o LALR (1)), pero se puede modificar fácilmente para que sea sensible al contexto. Y no tiene nada de feo. Yacc / Bison se ha creado para ayudar a analizar el lenguaje C, por lo que probablemente no sea la herramienta más fea para generar un analizador de C ...
Hasta GCC 3.x, el analizador de C es generado por yacc / bison, con la tabla typedefs construida durante el análisis. Con la construcción de tablas typedefs "in parse", la gramática C se vuelve localmente libre de contexto y además "localmente LR (1)".
Ahora, en Gcc 4.x, es un analizador de descenso recursivo. Es exactamente el mismo analizador que en Gcc 3.x, sigue siendo LR (1) y tiene las mismas reglas gramaticales. La diferencia es que el analizador yacc se ha reescrito a mano, el shift / reduce ahora está oculto en la pila de llamadas y no hay "state454: if (nextsym == '(') goto state398" como en gcc 3.x yacc's analizador, por lo que es más fácil parchear, manejar errores e imprimir mensajes más agradables, y realizar algunos de los siguientes pasos de compilación durante el análisis, al precio de un código mucho menos "fácil de leer" para un novato de gcc.
¿Por qué cambiaron de yacc a descenso recursivo? Porque es muy necesario evitar yacc para analizar C ++, y porque GCC sueña con ser un compilador de múltiples lenguajes, es decir, compartir el máximo de código entre los diferentes lenguajes que puede compilar. Esta es la razón por la que C ++ y el analizador de C se escriben de la misma manera.
C ++ es más difícil de analizar que C porque no es LR (1) "localmente" como C, ni siquiera LR (k). Mire
func<4 > 2>
cuál es una función de plantilla instanciada con 4> 2, es decir,func<4 > 2>
debe leerse comofunc<1>
. Esto definitivamente no es LR (1). Consideremos ahora,func<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>
. Aquí es donde un descenso recursivo puede resolver fácilmente la ambigüedad, al precio de algunas llamadas de función más (parse_template_parameter es la función del analizador ambigua. Si parse_template_parameter (17 tokens) falló, intente nuevamente parse_template_parameter (15tokens), parse_template_parameter (13tokens) ... hasta funciona).No sé por qué no sería posible agregar en sub gramáticas recursivas yacc / bison, tal vez este sea el siguiente paso en el desarrollo del analizador gcc / GNU.
fuente
Bison, en particular, no creo que pueda manejar la gramática sin analizar algunas cosas de manera ambigua y hacer una segunda pasada más tarde.
Sé que Happy de Haskell permite analizadores monádicos (es decir, dependientes del estado) que pueden resolver el problema particular con la sintaxis C, pero no conozco generadores de analizadores C que permitan una mónada de estado proporcionada por el usuario.
En teoría, la recuperación de errores sería un punto a favor de un analizador escrito a mano, pero mi experiencia con GCC / Clang ha sido que los mensajes de error no son particularmente buenos.
En cuanto al desempeño, algunas de las afirmaciones parecen infundadas. La generación de una máquina de estado grande usando un generador de analizador debería resultar en algo que es
O(n)
y dudo que el análisis sea el cuello de botella en muchas herramientas.fuente