tratamiento de errores introduccion

Anuncio
Universidad Nacional del Santa
Curso: Teoría de Compiladores
TRATAMIENTO DE ERRORES
INTRODUCCION
Los errores encontrados en las distintas fases de análisis se envían a un módulo
denominado manejo de errores. En el caso más sencillo puede ser un subprograma al que
se le invoca enviándole el código de error, y que se encarga de escribir un mensaje con
el error correspondiente, y el número de línea donde se ha producido, así como de cortar
el proceso de traducción. Si se desea construir un tratamiento de errores más completo,
por ejemplo detectando todos los errores del programa fuente, el módulo se complica
dado que los analizadores deben proseguir su trabajo con falta de datos.
A continuación se muestra un fragmento de código de un método de la clase Errores
escrito en C++, para el tratamiento de errores sintácticos. En el caso que se presenta
cada vez que se encuentra un error el compilador se detiene, y finaliza el proceso.
Una de las funciones más importantes de un compilador es su respuesta a los errores en
un programa fuente. Los errores pueden ser detectados durante casi cualquier fase de la
compilación. Estos errores estáticos (o de tiempo de compilación) deben ser notificados
por un compilador, y es importante que el compilador sea capaz de generar mensajes de
error significativos y reanudar la compilación después de cada error. Cada fase de un
compilador necesitará una clase ligeramente diferente de manejo de errores. Y, por lo
tanto, un manejador de errores debe contener operaciones diferentes, cada una
apropiada para una fase y situación específica. Es recomendable estudiar las técnicas de
manejo de errores por cada fase.
Docente: Ing. Mirko Manrique Ronceros
~1~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
Una definición de lenguaje por lo general requerirá no solamente que los errores estáticos
sean detectados por un compilador, sino también ciertos errores de ejecución. Esto
requiere que un compilador genere código extra, el cual realizará pruebas de
ejecución apropiadas para garantizar que todos esos errores provocarán un evento
apropiado durante la ejecución. El más simple de tales eventos será detener la ejecución
del programa. Sin embargo, a menudo esto no es adecuado, y una definición de lenguaje
puede requerir la presencia de mecanismos para el manejo de excepciones. Éstos pueden
complicar sustancialmente la administración de un sistema de ejecución, especialmente si
un programa puede continuar ejecutándose desde el punto donde ocurrió el error.
Ejemplo
VAR
ch : CHAR; (* Un identificador no se puede utilizar si *)
ent: INTEGER; (* previamente no se ha definido. *)
...
ch := ent + 1; (* En Pascal no es válido, en C sí. *)
ü Análisis Léxico: Devuelve la secuencia de tokens: id asig id suma numero ptocoma
ü Análisis Sintáctico: Orden de los tokens válido
ü Análisis Semántico: Tipo de variables asignadas incorrecta
TIPOS DE ERRORES
Tipos de errores que suelen ocurrir (dependiendo de la fase):
Léxicos: No concuerda con ninguna ER.
ü Ejemplo: escribir mal una palabra clave
Sintácticos: La estructura que se ha seguido no es correcta.
ü Ejemplo: expresión con paréntesis no emparejados
Semánticos: La estructura está bien pero hay errores de significado
ü Ejemplo: operador y operandos incompatibles.
Lógicos: Los comete el programador
ü Ejemplo: una llamada infinitamente recursiva
Algunos errores se pueden detectar en compilación otros solo en ejecución. El
tratamiento de errores es una parte importante que se suele descuidar
Docente: Ing. Mirko Manrique Ronceros
~2~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
TRATAMIENTO DE LOS ERRORES LEXICOS
Un traductor debe adoptar alguna estrategia para detectar, informar y recuperarse para
seguir analizando hasta el final.
Las respuestas ante el error pueden ser:
ü Inaceptables: Provocadas por fallos del traductor, entrada en lazos infinitos,
producir resultados erróneos, y detectar sólo el primer error y detenerse.
ü Aceptables: Evitarla avalancha de errores (mala recuperación) y, aunque más
complejo, informar y reparar el error de forma automática. La conducta de un
Analizador de Léxico es el de un Autómata finito o “scanner”.
ü Detección del error: El analizador de Léxico detecta un error cuando no existe
transición desde el estado que se encuentra con el símbolo de la entrada. El
símbolo en la entrada no es el esperado.
Los errores léxicos se detectan cuando el analizador léxico intenta reconocer
componentes léxicos y la cadena de caracteres de la entrada no encaja con ningún
patrón. Son situaciones en las que usa un carácter invalido (@,$,",>,...), que no
pertenece al vocabulario del lenguaje de programación, al escribir mal un
identificador, palabra reservada u operador.
Errores léxicos típicos son:
1. Nombre ilegales de identificadores: un nombre contiene caracteres inválidos.
2. Números incorrectos: Un numero contiene caracteres inválidos o no está formado
correctamente, por ejemplo 3,14 en vez de 3.14 o 0.3.14.
3. Errores de ortografía en palabras reservadas: caracteres omitidos, adicionales o
cambiados de sitio, por ejemplo la palabra while en vez de hwile.
4. Fin de archivo: se detecta un fin de archivo a la mitad de un componente léxico.
Los errores léxicos se deben a descuidos del programador. En general, la recuperación de
errores léxicos es sencilla y siempre se traduce en la generación de un error de sintaxis
que será detectado más tarde por el analizador sintáctico cuando el analizador léxico
devuelve un componente léxico que el analizador sintáctico no espera en esa posición.
Los métodos de recuperación de errores léxicos se basan bien en saltarse caracteres en la
entrada hasta que un patrón se ha podido reconocer; o bien usar otros métodos más
sofisticados que incluyen la inserción, borrado, sustitución de un carácter en la entrada o
intercambio de dos caracteres consecutivos. Una buena estrategia para la recuperación de
errores léxicos:
Docente: Ing. Mirko Manrique Ronceros
~3~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
ü Si en el momento de detectar el error ya hemos pasado por algún estado final
ejecutamos la acción correspondiente al último estado final visitado con el lexema
formado hasta que salimos de él; el resto de caracteres leídos se devuelven al
flujo de entrada y se vuelve al estado inicial;
ü Si no hemos pasado por ningún estado final, advertimos que el carácter
encontrado no se esperaba, lo eliminamos y proseguimos con el análisis.
Construcción de un analizador léxico
Los analizadores léxicos pueden construirse:
§
Usando generadores de analizadores léxicos: Es la forma más sencilla pero
el código generado por el analizador léxico es más difícil de mantener y puede
resultar menos eficiente.
§
Escribiendo el analizador léxico en un lenguaje de alto nivel: Permite obtener
analizadores léxicos con más esfuerzo que con el método anterior pero
más eficientes y sencillos de mantener.
TRATAMIENTO DE LOS ERRORES SINTACTICOS
Gran parte de la detección y recuperación se centra en el AS.
Muchos errores de naturaleza sintáctica Recuperación: Al producirse un error el
compilador debe ser capaz de informar del error y seguir compilando. (Ideal)
El manejo de errores de sintaxis es el más complicado desde el punto de vista de la
creación de compiladores. Nos interesa que cuando el compilador encuentre un error, se
recupere y siga buscando errores. Por lo tanto el manejador de errores de un analizador
sintáctico debe tener como objetivos:
ü Indicar los errores de forma clara y precisa. Aclarar el tipo de error y su
localización.
ü Recuperarse del error, para poder seguir examinando la entrada.
ü No ralentizar significativamente la compilación.
Un buen compilador debe hacerse siempre teniendo también en mente los errores que se
pueden producir; con ello se consigue:
ü Simplificar la estructura del compilador.
ü Mejorar la respuesta ante los errores.
Tenemos varias estrategias para corregir errores, una vez detectados:
Docente: Ing. Mirko Manrique Ronceros
~4~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
Ignorar el problema (Panicmode)
Consiste en ignorar el resto de la entrada hasta llegar a una condición de seguridad.
Una condición tal se produce cuando nos encontramos un token especial (por ejemplo
un ‘;’ o un ‘END’). A partir de este punto se sigue analizando normalmente.
Recuperación a nivel de frase
Intenta recuperar el error una vez descubierto. En el caso anterior, por ejemplo, podría
haber sido lo suficientemente inteligente como para insertar el token ‘;’. Hay que tener
cuidado con este método, pues puede dar lugar a recuperaciones infinitas.
Reglas de producción adicionales para el control de errores
La gramática se puede aumentar con las reglas que reconocen los errores más
comunes. En el caso anterior, se podría haber puesto algo como:
sent_erróne a Ú sent_sin_acabar sentencia_acabada ’;’
sentencia_acabada Ú sentencia ‘;’
sent_sin_acabar Ú sentencia
Lo cual nos da mayor control en ciertas circunstancias
Corrección Global
Dada una secuencia completa de tokens a ser reconocida, si hay algún error por el
que no se puede reconocer, consiste en encontrar la secuencia completa más
parecida que sí se pueda reconocer. Es decir, el analizador sintáctico le pide toda
la secuencia de tokens al léxico, y lo que hace es devolver lo más parecido a la cadena
de entrada pero sin errores, así como el árbol que lo reconoce.
Docente: Ing. Mirko Manrique Ronceros
~5~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
TRATAMIENTO ERRORES SEMANTICOS
COMPROBACIÓN DE TIPOS
1. Aspectos generales
Un lenguaje con comprobación fuerte de tipos es capaz de garantizar que
los programas se pueden ejecutar sin errores de tipo, por lo que los
errores de tipo se detectarán siempre en tiempo de compilación.
Como mínimo, ante un error, un comprobador de tipos debe informar de
la naturaleza y posición del error y recuperarse para continuar con la
comprobación del resto del programa a analizar.
Veamos algunas de las operaciones a tener en cuenta en una comprobación
de tipos:
§
Conversión de tipos: A veces es necesario transformar el tipo de una
expresión para utilizar correctamente un operador o para pasar de forma
adecuada un parámetro a una función.
§
Coerción: Es una conversión de tipos que realiza de forma implícita el
propio compilador. Si es el programador el que realiza la conversión se
tratará entonces de una conversión explícita.
§
Sobrecarga de operadores: La sobrecarga se resuelve determinando
el tipo de cada una de las expresiones intervinientes en la sobrecarga.
§
Funciones polimórficas: Son aquellas que trabajan con argumentos cuyo
tipo puede cambiaren distintas llamadas a la función.
2. Especificación de un comprobador de tipos básico
Básicamente se deberán realizar dos tareas:
a) Asignación de tipos: en las declaraciones.
b) Evaluación y comprobación de tipos: En las expresiones y en las
funciones, así como en las sentencias.
Docente: Ing. Mirko Manrique Ronceros
~6~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
Sea la gramática:
Primer paso: Asignación de tipo
Docente: Ing. Mirko Manrique Ronceros
~7~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
Segundo paso: Comprobación de tipo en expresiones
Tercer paso: Comprobación de tipo en sentencias
Docente: Ing. Mirko Manrique Ronceros
~8~
Universidad Nacional del Santa
3. Otras comprobaciones
semánticos
Curso: Teoría de Compiladores
semánticas
y
recuperación
de
errores
Dentro de las comprobaciones estáticas (en el momento de la compilación),
tenemos la detección e información de errores como:
Comprobaciones de tipos: operadores aplicados a operandos incompatibles,
asignación de tipos incompatibles, llamadas a funciones con tipos no
adecuados, etc.
Comprobaciones de flujo de control: las sentencias que hacen que el flujo de
control abandone una construcción debe tener alg ´un lugar a donde transmitir
el control. Por ejemplo: Unbreak debe estar dentro de una proposición while,
for o switch en C.
Comprobaciones de unicidad: situaciones en las que solo se puede definir un
objeto una vez exactamente. Por ejemplo: Un identificador, las etiquetas case
dentro de un switch.
Solo nos hemos centrado en las comprobaciones de tipo. Las otras son en
cierto modo rutinarias y se pueden realizar fácilmente insertando acciones
intercaladas en el código para realizarlas, por eje. Cuando se introduce un
identificador en la Tabla de Símbolos.
Docente: Ing. Mirko Manrique Ronceros
~9~
Descargar