He leído varios artículos, artículos y la sección 4.1.4, capítulo 4 de Compiladores: Principios, Técnicas y Herramientas (2ª Edición) (también conocido como "El Libro del Dragón") que tratan sobre el tema de la recuperación de errores del compilador sintáctico. Sin embargo, después de experimentar con varios compiladores modernos, he visto que también se recuperan de errores semánticos , así como de errores sintácticos.
Entiendo bastante bien los algoritmos y técnicas detrás de los compiladores que se recuperan de errores relacionados sintácticamente, sin embargo, no entiendo exactamente cómo un compilador puede recuperarse de un error semántico.
Actualmente estoy usando una ligera variación del patrón de visitante para generar código a partir de mi árbol de sintaxis abstracta. Considere mi compilador compilando las siguientes expresiones:
1 / (2 * (3 + "4"))
El compilador generaría el siguiente árbol de sintaxis abstracta:
op(/)
|
-------
/ \
int(1) op(*)
|
-------
/ \
int(2) op(+)
|
-------
/ \
int(3) str(4)
La fase de generación de código luego usaría el patrón de visitante para recorrer recursivamente el árbol de sintaxis abstracta y realizar la verificación de tipo. El árbol de sintaxis abstracta se atravesaría hasta que el compilador llegara a la parte más interna de la expresión; (3 + "4")
. Luego, el compilador verifica cada lado de las expresiones y ve que no son semánticamente equivalentes. El compilador genera un error de tipo. Aquí es donde radica el problema. ¿Qué debería hacer ahora el compilador ?
Para que el compilador se recupere de este error y continúe verificando el tipo de las partes externas de las expresiones, tendría que devolver algún tipo ( int
o str
) de la evaluación de la parte más interna de la expresión, a la siguiente parte más interna de la expresión. Pero simplemente no tiene un tipo para devolver . Como se produjo un error de tipo, no se dedujo ningún tipo.
Una posible solución que he postulado es que si se produce un error de tipo, se debe generar un error, y un valor especial que significa que se produjo un error de tipo, debe devolverse a las llamadas transversales de árbol de sintaxis abstracta anteriores. Si las llamadas transversales anteriores encuentran este valor, saben que se produjo un error de tipo más profundo en el árbol de sintaxis abstracta y deben evitar intentar deducir un tipo. Si bien este método parece funcionar, parece ser muy ineficiente. Si la parte más interna de una expresión está profunda en el árbol de sintaxis abstracta, entonces el compilador tendrá que hacer muchas llamadas recursivas solo para darse cuenta de que no se puede hacer un trabajo real, y simplemente regresar de cada una.
Se utiliza el método que describí anteriormente (lo dudo). Si es así, ¿no es eficiente? Si no, ¿cuáles son exactamente los métodos utilizados cuando los compiladores se recuperan de los errores semánticos?
fuente
Respuestas:
Su idea propuesta es esencialmente correcta.
La clave es que el tipo de nodo AST se calcula solo una vez y luego se almacena. Cada vez que se necesita el tipo nuevamente, simplemente recupera el tipo almacenado. Si la resolución termina en un error, se almacena un tipo de error en su lugar.
fuente
Un enfoque interesante es tener un tipo especial para errores. Cuando se encuentra un error de este tipo, se registra un diagnóstico y el tipo de error se devuelve como el tipo de la expresión. Este tipo de error tiene algunas propiedades interesantes:
Con esta combinación, puede compilar correctamente el código que contiene errores de tipo, y mientras ese código no se use realmente, no se producirá ningún error de tiempo de ejecución. Esto puede ser útil, por ejemplo, para permitirle ejecutar pruebas unitarias para partes del código que no se vean afectadas.
fuente
Si hay un error semántico, se emite un mensaje de error de compilación que indica que se ha enviado al usuario.
Una vez hecho esto, está bien abortar la compilación ya que el programa de entrada está en error; no es un programa legal en el lenguaje, por lo que simplemente puede ser rechazado.
Sin embargo, eso es bastante duro, por lo que hay alternativas más suaves. Anule cualquier generación de código y generación de archivos de salida, sin embargo, continúe buscando algo para buscar más errores.
Por ejemplo, simplemente puede abortar cualquier análisis de tipo adicional para el árbol de expresión actual y continuar procesando expresiones de declaraciones posteriores.
fuente
Supongamos que su idioma permite agregar enteros y permite la concatenación de cadenas con el
+
operador.Como
int + string
no está permitido, la evaluación del+
resultado dará como resultado un error. El compilador podría simplemente regresarerror
como el tipo. O podría ser más inteligente, ya queint + int -> int
ystring + string -> string
están permitidos, podría devolver "error, podría ser int o string".Luego viene el
*
operador, y asumiremos que soloint + int
está permitido. Luego, el compilador puede decidir que+
se suponía que debía regresarint
, y el tipo devuelto para*
entonces seríaint
, sin ningún mensaje de error.fuente