Está en la página 1de 13

1.

7 Manejo de errores semánticos


Un error semántico se produce cuando la sintaxis del código es correcta, pero la
semántica o significado no es el que se pretendía. La construcción obedece las reglas del
lenguaje, y por ello el compilador o intérprete no detectan los errores semánticos. Los
compiladores e intérpretes sólo se ocupan de la estructura del código que se escribe, y no
de su significado.
Un error semántico puede hacer que el programa termine de forma anormal, con o sin un
mensaje de error. Hablando en términos coloquiales, puede hacer que el equipo se quede
"colgado".
Bueno esto quiere decir que los Errores semánticos son más que nada errores que
cometen las personas, al momento de trabajar ya que la computadora no tiene
pensamiento propio solo acata las reglas por las que fue diseño y su protocolo. El
computador trabaja revisando la sintaxis y la estructura del programa mas no la lógica.
¿Cómo manejar errores?

Un compilador es un sistema que en la mayoría de los casos tiene que manejar una
entrada incorrecta. Sobre todo en las primeras etapas de la creación de un programa, es
probable que el compilador se utilizará para efectuar las características que debería
proporcionar un buen sistema de edición dirigido por la sintaxis, es decir, para determinar
si las variables han sido declaradas antes de usarla, o si faltan corchetes o algo así. Por lo
tanto, el manejo de errores es parte importante de un compilador y el escritor del
compilador siempre debe tener esto presente durante su diseño.

Hay que señalar que los posibles errores ya deben estar considerados al diseñar un
lenguaje de programación. Por ejemplo, considerar si cada proposición del lenguaje de
programación comienza con una palabra clave diferente (excepto la proposición de
asignación, por supuesto). Sin embargo, es indispensable lo siguiente:

1. El compilador debe ser capaz de detectar errores en la entrada;


2. El compilador debe recuperarse de los errores sin perder demasiada información;
3. Y sobre todo, el compilador debe producir un mensaje de error que permita al
programador encontrar y corregir fácilmente los elementos (sintácticamente)
incorrectos de su programa.

Los mensajes de error de la forma

*** Error 111 ***

*** Ocurrió un error ***

*** Falta declaración ***

*** Falta delimitador ***


no son útiles para el programador y no deben presentarse en un ambiente de compilación
amigable y bien diseñado. Por ejemplo, el mensaje de error ‘Falta declaración’ podría
reemplazarse por

*** No se ha declarado la variable Nombre ***

o en el caso del delimitador omitido se puede especificar cuál es el delimitador esperado.


Además de estos mensajes de error informativos, es deseable que el compilador
produzca una lista con el código fuente e indique en ese listado dónde han ocurrido los
errores.

No obstante, antes de considerar el manejo de errores en el análisis léxico y sintáctico,


hay que caracterizar y clasificar los errores posibles (Sec. 6.1). Esta clasificación nos
mostrará que un compilador no puede detectar todos los tipos de errores.

Clasificación de Errores

Durante un proceso de resolución de problemas existen varias formas en que pueden


surgir errores, las cuales se reflejan en el código fuente del programa. Desde el punto de
vista del compilador, los errores se pueden dividir en dos categorías:

1. Errores visibles y Errores invisibles

Los errores invisibles en un programa son aquellos que no puede detectar el compilador,
ya que no son el resultado de un uso incorrecto del lenguaje de programación, sino de
decisiones erróneas durante el proceso de especificación o de la mala formulación de
algoritmos. Por ejemplo, si se escribe

a : = b + c ; en lugar de a : = b * c ;

el error no podrá ser detectado por el compilador ni por el sistema de ejecución. Estos
errores lógicos no afectan la validez del programa en cuanto a su corrección sintáctica.
Son objeto de técnicas formales de verificación de programas que no se consideran aquí.
Para conocer más sobre la verificación de programas, consulte, por ejemplo, [LOEC 87].

Los errores visibles, a diferencia de los errores lógico, pueden ser detectados por el
compilador o al menos por el sistema de ejecución. Estos errores se pueden caracterizar
de la siguiente manera:

1. Errores de ortografía y
2. Errores que ocurren por omitir requisitos formales del lenguaje de
programación.

Estos errores se presentará porque los programadores no tienen el cuidado suficiente al


programador. Los errores del segundo tipo también pueden ocurrir porque el programador
no comprende a la perfección el lenguaje que se utiliza o porque suele escribir sus
programas en otro lenguaje y, por tanto, emplea las construcciones de dicho lenguaje
(estos problemas pueden presentarse al usar a la vez lenguajes de programación como
PASCAL y MODULA-2, por ejemplo).

Clasificación de Ocurrencias

Por lo regular, los errores visibles o detectables por el compilador se dividen en tres
clases, dependiendo de la fase del compilador en el cual se detectan:

1. Errores Léxicos;
2. Errores Sintácticos;
3. Errores Semánticos;

Por ejemplo, un error léxico puede ocasionarse por usar un carácter inválido (uno que no
pertenezca al vocabulario del lenguaje de programación) o por tratar de reconocer una
constante que produce un desbordamiento.

Un error de sintaxis se detecta cuando el analizador sintáctico espera un símbolo que no


corresponde al que se acaba de leer. Los analizadores sintácticos LL y LR tienen la
ventaja de que pueden detectar errores sintácticos lo más pronto posible, es decir, se
genera un mensaje de error en cuanto el símbolo analizado no sigue la secuencia de los
símbolos analizados hasta ese momento.

Los errores semánticos corresponden a la semántica del lenguaje de programación, la


cual normalmente no está descrita por la gramática. Los errores semánticos más
comunes son la omisión de declaraciones.

Además de estas tres clases de errores, hay otros que serán detectados por el sistema de
ejecución porque el compilador ha proporcionado el código generado con ciertas acciones
para estos casos.

1. Un Error de Ejecución

típico ocurre cuando el índice de una matriz no es un elemento del subintervalo


especificado o por intentar una división entre cero. En tales situaciones, se informa
del error y se detiene la ejecución del programa.

Clasificación Estadística

Ripley y Druseikis muestran resultados interesantes sobre el análisis estadístico


de los errores de sintaxis en [RIPL 78]. Ellos investigaron los errores que cometen
los programadores de PASCAL y analizaron los resultados en relación con las
estrategias de recuperación. El resultado principal del estudio fue que los errores
de sintaxis suelen ser muy simples y que, por lo general, sólo ocurre un error por
frase. En el resumen siguiente se describen de manera general los resultados del
estudio:

2. Al menos el 40% de los programas compilados eran sintáctica o semánticamente


incorrectos.
3. Un 80% de las proposiciones incorrectas sólo tenían un error.
4. El 13% de las proposiciones incorrectas tenían dos errores, menos del 3% tenían
tres errores y el resto tenían cuatro o más errores por proposición.

5. En aproximadamente la mitad de los errores de componentes léxicos olvidados, el


elemento que faltaba era ":", mientras que omitir el "END" final ocupaba el
segundo lugar, con un 10.5%.
6. En un 13% de los errores de componentes léxico incorrecto se escribió "," en lugar
de ";" y en más del 9% de los casos se escribió ":=" en lugar de "=".

Los errores que ocurren pueden clasificarse en cuatro categorías:

1. Errores de puntuación,
2. Errores de operadores y operandos,
3. Errores de palabras clave y
4. Otros tipos de errores.

La distribución estadística de estas cuatro categorías aparece en la figura 6.1.

Efectos de los Errores

La detección de un error en el código fuente ocasiona ciertas reacciones del compilador.


El comportamiento de un compilador en el caso de que el código fuente contenga un error
puede tener varias facetas:

1. El proceso de compilación de detiene al ocurrir el error y el compilador debe


informar del error.
2. El proceso de compilación continúa cuando ocurre el error y se informa de éste en
un archivo de listado.
3. El compilador no reconoce el error y por tanto no advierte al programador.

La última situación nunca debe presentarse en un buen sistema de compilación; es decir,


el compilador debe ser capaz de detectar todos los errores visibles.

La detención del proceso de compilación al detectar el primer error es la forma más


simple de satisfacer el requisito de que una compilación siempre debe terminar, sin
importar cuál sea la entrada [BRIN 85]. Sin embargo, este comportamiento también es el
peor en un ambiente amigable para el usuario, ya que una compilación puede tardar
varios minutos. Por lo tanto, el programador espera que el sistema de compilación detecte
todos los errores posibles en el mismo proceso de compilación.

Entonces, en general, el compilador debe recuperarse de un error para poder revisar el


código fuente en busca de otros errores. No obstante, hay que observar que cualquier
"reparación" efectuada por el compilador tiene el propósito único de continuar la búsqueda
de otros errores, no de corregir el código fuente. No hay reglas generales bien definidas
acerca de cómo recuperarse de un error, por lo cual el proceso de recuperación debe
hacerse en hipótesis acerca de los errores. La carencia de tales reglas se debe al hecho
de que el proceso de recuperación siempre depende del lenguaje.

Manejo de Errores en el Análisis Léxico

Los errores léxicos se detectan cuando el analizador léxico intenta reconocer


componentes léxicos en el código fuente. Los errores léxicos típicos son:

1. Nombres ilegales de identificadores: un nombre contiene caracteres inválidos;


2. Números inválidos: un número contiene caracteres inválidos (por ejemplo; 2,13 en
lugar de 2.13), no está formando correctamente (por ejemplo, 0.1.33), o es
demasiado grande y por tanto produce un desbordamiento;
3. Cadenas incorrectas de caracteres: una cadena de caracteres es demasiado larga
(probablemente por la omisión de comillas que cierran);
4. Errores de ortografía en palabras reservadas: caracteres omitidos, adicionales,
incorrectos o mezclados;
5. Etiquetas ilegales: una etiqueta es demasiado larga o contiene caracteres
inválidos;
6. Fin de archivo: se detecta un fin de archivo a la mitad de un componente léxico.

La mayoría de los errores léxicos se deben a descuidos del programador. En general, la


recuperación de los errores léxicos es relativamente sencilla.

Si un nombre, un número o una etiqueta contiene un carácter inválido, se elimina el


carácter y continúa el análisis en el siguiente carácter; en otras palabras, el analizador
léxico comienza a reconocer el siguiente componente léxico. El efecto es la generación de
un error de sintaxis que será detectado por el analizador sintáctico. Este método también
puede aplicarse a números mal formados.

Las secuencias de caracteres como 12AB pueden ocurrir si falta un operador (el caso
menos probable) o cuando se han tecleado mal ciertos caracteres. Es imposible que el
analizador léxico pueda decidir si esta secuencia es un identificador ilegal o u número
ilegal. En tales casos, el analizador léxico puede saltarse la cadena completa o intentar
dividir las secuencias ilegales en secuencias legales más cortas. Independientemente de
cuál sea la decisión , la consecuencia será un error de sintaxis.

La detección de cadenas demasiado margas no es muy complicada, incluso si faltan las


comillas que cierran, porque por lo general no está permitido que las cadenas pasen de
una línea a la siguiente. Si faltan las comillas que cierran, puede usarse el carácter de fin
de línea como el fin de cadena y reanudar el análisis léxico en la línea siguiente. Esta
reparación quizás produzca errores adicionales. En cualquier caso, el programador debe
ser informado por medio de un mensaje de error.

Un caso similar a la falta de comillas que cierran en una cadena, es la falta de un símbolo
de terminación de comentario. Como por lo regular está permitido que los comentario
abarquen varias líneas, no podrá detectarse la falta del símbolo que cierra el comentario
hasta que el analizador léxico llegue al final del archivo o al símbolo de fin de otro
comentario (si no se permiten comentarios anidados).

Si se sabe que el siguiente componente léxico debe ser una palabra reservada, es posible
corregir una palabra reservada mal escrita. Esto se hace mediante funciones de
corrección de errores, bien conocidas en los sistemas de lenguajes naturales, o
simplemente aplicando una función de distancia métrica entre la secuencia de entrada y el
conjunto de palabras reservadas.

Por último, el proceso de compilación puede terminar si se detecta un fin de archivo


dentro de un componente léxico.

Manejo de Errores en el Análisis Sintáctico

El analizador sintáctico detecta un error de sintaxis cuando el analizador léxico


proporciona el siguiente símbolo y éste es incompatible con el estado actual del
analizador sintáctico. Los errores sintácticos típicos son:

1. Paréntesis o corchetes omitidos, por ejemplo, x : = y * (1 + z;


2. Operadores u operando omitidos, por ejemplo, x : = y (1 + z );
3. Delimitadores omitidos, por ejemplo, x : = y + 1 IF a THEN y : = z.

No hay estrategias de recuperación de errores cuya validez sea general, y la mayoría de


las estrategias conocidas son heurísticas, ya que se basan en suposiciones acerca de
cómo pueden ocurrir los errores y lo que probablemente quiso decir el programador con
una determinada construcción. Sin embargo, hay algunas estrategias que gozan de
amplia aceptación:

1. Recuperación de emergencia (o en modo pánico): Al detectar un error, el


analizador sintáctico salta todos los símbolos de entrada hasta encontrar un
símbolo que pertenezca a un conjunto previamente definido de símbolos de
sincronización. Estos símbolos de sincronización son el punto y como, el
símbolo end o cualquier palabra clave que pueda ser el inicio de una proposición
nueva, por ejemplo. Es fácil implantar la recuperación de emergencia, pero sólo
reconoce un error por proporción. Esto no necesariamente es una desventaja, ya
que no es muy probable que ocurran varios errores en la misma proposición
(véase [IPL 78], por ejemplo). Esta suposición es un ejemplo típico del carácter
heurístico de esta estrategia.
2. Recuperación por inserción, borrado y reemplazo: éste también es un método fácil
de implantar y funciona bien en ciertos casos de error. Usemos como ejemplo una
declaración de variable en PASCAL . cuando una coma va seguida por dos
puntos, en lugar de un nombre de variable, es posible eliminar esta coma. En
forma similar, se puede insertar un punto y coma omitido o reemplazar un punto y
coma por una coma en una lista de parámetros.
3. Recuperación por expansión de gramática: De acuerdo con [RIPL 78], el 60% de
los errores en los programas fuente son errores de puntuación, por ejemplo, la
escritura de un punto y coma en lugar de una coma, o viceversa. Una forma de
recuperarse de estos errores es legalizarlos en ciertos casos, introduciendo lo que
llamaremos producciones de error en la gramática del lenguaje de programación.
La expansión de la gramática con estas producciones no quiere decir que ciertos
errores no serán detectados, ya que pueden incluirse acciones para informar de su
detección.

La recuperación de emergencia es la estrategia que se encontrará en la mayoría de los


compiladores, pero la legalización de ciertos errores mediante la definición de una
gramática aumentada es una técnica que se emplea con frecuencia. No obstante, hay que
expandir la gramática con mucho cuidado para asegurarse de que no cambien el tipo y las
características de la gramática.

Los errores de sintaxis se detectan cuando el analizador sintáctico espera un símbolo que
no concuerda con el símbolo que está analizando, a . En los analizadores sintácticos LL,
los errores de sintaxis se detectan cuando a y el no terminal que están en la cima de la
pila nos llevan a un índice de una posición vacía de la tabla de análisis sintáctico. En los
analizadores sintácticos LR, los errores de sintaxis se detectan cuando hay un índice a
una posición vacía de la tabla, o sea, cuando no se especifica ninguna transición al
analizar á en el estado actual (véase Cap. 4). Sin embargo, si se emplea una gramática
aumentada con producciones de error adicionales, no sólo se detectarán errores por
medio de los índices a posiciones vacías de la tabla de análisis sintáctico.

Errores Semánticos

Los errores que puede detectar el analizador sintáctico son aquellos que violan las reglas
de una gramática independiente del contexto. Ya hemos mencionado que algunas de las
características de un lenguaje de programación no pueden enunciarse con reglas
independientes del contexto, ya que dependen de él; por ejemplo, la restricción de que los
identificadores deben declararse previamente. Por lo tanto, los principales errores
semánticos son:

1. Identificadores no definidos;
2. Operadores y operandos incompatibles.

Es mucho más difícil introducir métodos formales para la recuperación de errores


semánticos que para la recuperación de errores sintácticos, ya que a menudo la
recuperación de errores semánticos es ad hoc. No obstante, puede requerirse que, por lo
menos, el error semántico sea informado al programador, que se le ignore y que, por
tanto, se suprimirá la generación de código.

Sin embargo, la mayoría de los errores semánticos pueden ser detectados mediante la
revisión de la tabla de símbolos, suponiendo un tipo que se base en el contexto donde
ocurra o un tipo universal que permita al identificador ser un operando de cualquier
operador del lenguaje. Al hacerlo, evitamos la producción de un mensaje de error cada
vez que se use la variable no definida. Si el tipo de un operando no concuerda con los
requisitos de tipo del operador, también es conveniente reemplazar el operando con una
variable ficticia de tipo universal.
Recuperación de Errores PL/0

A continuación ejemplificaremos algunos de los métodos antes mencionados para la


recuperación de errores sintácticos. Para ellos expandiremos fragmentos del programa
del analizador sintáctico descendente recursivo de PL/0 que vimos en el capítulo 4.

Recuperación de Emergencia

La idea del análisis sintáctico descendente recursivo es que un problema de análisis


sintáctico se divida en subproblemas que se resuelven en forma recursiva. Ahora bien, la
ocurrencia de un error en un subproblema significa que no sólo hay que informar del error
al procedimiento que llama. Mas bien, hay que garantizar que el procedimiento del
subproblema se recupere del error de modo que el procedimiento invocador pueda
continuar con el proceso de análisis sintáctico, es decir, que termine de forma normal.

Por ello, además de generar un mensaje de error, hay que ir saltándose la entrada hasta
llegar a un símbolo de sincronización. Esto implica que cada procedimiento de un
analizador sintáctico descendente recursivo debe conocer cuáles son los símbolos

PROCEDURE Prueba(Siguiente, detención: conjsím; n:

INTEGER);

(*siguiente, detención: símbolos de sincronización*)

(*n: número de error *)

VAR símsinc : conjsím;

BEJÍN

IF NOT (símbolo IN siguiente) THEN

Error (n);

Símsinc := siguiente + detención;

WHILE NOT (símbolo IN símsinc) DO Leer_Símbolo END

END

END Prueba;

Figura 6.2 Procedimiento para revisar y saltar símbolos

PROCEDURE Expresión (siguiente: conjsím);


VAR ADDoSUB: símbolos;

PROCEDURE Término (siguiente: conjsím);

VAR MULoDIV:símbolos;

PROCEDURE Factor (siguiente: conjsím);

VAR i: INTEGER;

BEGÍN (*Factor*)

Prueba (iniciofact, siguiente, ...);

WHILE símbolo IN iniciofact DO

...

Prueba (siguiente, [pareni], ...)

END

END Factor;

BEGÍN (*Término*)

Factor (siguiente + [por, diagonal]);

WHILE símbolo IN [por, diagonal]) DO

MULoDIV := símbolo; Leer_Símbolo;

Factor (siguiente + [por, diagonal]);

...

END

END Término;

BEGÍN (*Expresión*)

...

END Expresión;

Figura 6.3 Uso del procedimiento de prueba


válidos que le pueden seguir. Para evitar el salto descontrolado de símbolos, se aumentan
los conjuntos de símbolos de detención adicionales que indiquen las construcciones que
no deben saltarse. Los símbolos siguientes y los símbolos de detención forman, en
conjunto, los símbolos de sincronización.

En la caso de la implantación, esto quiere decir que cada procedimiento de análisis


sintáctico consta de un parámetro que especifica el conjunto de los símbolos válidos que
siguen. La prueba para los símbolos de sincronización puede efectuarse fácilmente con el
procedimiento presentado en la figura 6.2. este procedimiento prueba si un símbolo
siguiente es legal. En caos de un símbolo ilegal, se genera un mensaje de error y se
saltan los símbolos de entrada hasta detectar un símbolo de sincronización. Este
procedimiento de prueba será invocado al final de cada procedimiento para verificar que
le símbolo siguiente sea válido, pero también puede emplearse al iniciar un procedimiento
de análisis sintáctico para verificar si el símbolo de entrada actual es un símbolo inicial
permitido. El uso del procedimiento de prueba se ilustra en la figura 6.3 para el análisis
sintáctico de expresiones aritméticas (donde ‘iniciofact’ indica los símbolos iniciales
permitidos para ‘Factor’).

Expansión de Gramática

Como ya mencionamos, es un hecho bien conocido que los errores de puntuación son
muy comunes. Por ejemplo, consideremos las constantes PL/0 que se separan por
comas; un error frecuente en el cual podríamos pensar sería el uso de un punto y coma
en lugar de la coma. Sabiendo esto, la estructura sintáctica de las declaraciones de
constantes puede modificarse de manera que se permita usar coma y punto y coma,
como se muestra en la figura 6.4.

La declaración modificada de constantes de la figura 6.4 legaliza el error que acabamos


de describir. El diagrama sintáctico de la figura 6.4 puede entonces traducirse al
fragmento de programa de la figura 6.5, mediante las técnicas presentadas en el capítulo
4.

IF símbolo = símconst THEN

Leer_Símbolo;
REPEAT

Declaración_const;

WHILE símbolo = coma DO

Leer_Símbolo; Declaración_const

END;

IF símbolo = puntocoma THEN Leer_Símbolo

ELSE Error(...) END;

UNTIL (símbolo <> ident);

END;

Figura 6.5 Código modificado para el análisis des de constantes

El fragmento del programa de la figura 6.5 permite la separación de constantes con


comas o puntos y coma sin producir mensajes de error. Además de esta legalización, se
aceptará la omisión de la coma y el punto y coma; sin embargo, en este caso sí se
produce un mensaje de error. Es obvio que de esta misma forma podemos expandir la
sintaxis de las declaraciones de variables para permitir la separación con puntos y coma o
incluso con espacios (véase Fig. 6.6).

IF símbolo = símvar THEN

Ler_Símbolo;

REPEAT

Declaración_var;

WHILE símbolo = coma DO

Leer_Símbolo; Declaración_var

END;

IF símbolo = puntocoma THEN Leer_Símbolo

ELSE Error (...) END;


UNTIL (símbolo <> ident);

END;

Figura 6.6 Código modificado para el análisis de declaraciones de variables

En forma análoga, puede permitirse la omisión del punto y coma entre dos proposiciones.
Esto muestra en el fragmento de programa de la figura 6.7, donde ‘síminicioprop’ es el
conjunto de símbolos iniciales de la proposiciones.

IF símbolo = símbegin THEN

Leer_Símbolo;

REPEAT

Proposición (siguiente + [puntocoma, símend]);

WHILE símbolo = puntocoma DO

Leer_Símbolo;

Proposición (siguiente + [puntocoma, símed]);

END

UNTIL NOT (símbolo IN síminicioprop);

IF símbolo = símed THEN

Leer_símbollo

ELSE Error(...) END;

END;
REFERENCIA:
Alonso, L. A. (2019). 1.7 Manejo de Errores Semánticos. Studocu. Recuperado de:
https://www.studocu.com/es-mx/document/instituto-tecnologico-de-
oaxaca/lenguajes-y-automatas-ii/apuntes/1-7-manejo-de-errores-
semanticos/8456915/view

También podría gustarte